From 8e4ebfec1aca1198d2fab0c5f249f2142a6bdcf9 Mon Sep 17 00:00:00 2001 From: eschorn1 Date: Thu, 14 Mar 2024 13:35:26 -0500 Subject: [PATCH] rust ffi --- .gitignore | 7 ++- Cargo.toml | 2 + ffi/Cargo.toml | 21 +++++++++ ffi/README.md | 12 +++++ ffi/fips205.h | 50 +++++++++++++++++++++ ffi/src/lib.rs | 104 +++++++++++++++++++++++++++++++++++++++++++ ffi/tests/Makefile | 19 ++++++++ ffi/tests/baseline.c | 42 +++++++++++++++++ 8 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 ffi/Cargo.toml create mode 100644 ffi/README.md create mode 100644 ffi/fips205.h create mode 100644 ffi/src/lib.rs create mode 100644 ffi/tests/Makefile create mode 100644 ffi/tests/baseline.c diff --git a/.gitignore b/.gitignore index 4e0dc68..c64351b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,11 @@ -/target -/Cargo.lock +**/target /.idea **/artifacts **/corpus **/target /fuzz/.gitignore **/Cargo.lock +**/package-lock.json +**/node_modules +**/pkg +**/dist diff --git a/Cargo.toml b/Cargo.toml index 96f3cae..b07c6dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,5 @@ +workspace = { members = ['ffi'], exclude = ["dudect", "ct_cm4"] } + [package] name = "fips205" version = "0.1.2" diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml new file mode 100644 index 0000000..7588648 --- /dev/null +++ b/ffi/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "fips205-ffi" +version = "0.1.2" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "C shared library exposing FIPS 205 (draft): Stateless Hash-Based Digital Signature Standard" +authors = ["Eric Schorn "] +documentation = "https://docs.rs/fips205" +categories = ["cryptography", "no-std"] +repository = "https://github.com/integritychain/fips205" +keywords = ["FIPS", "FIPS205", "hash", "signature"] +rust-version = "1.70" + +[lib] +crate-type = ["staticlib", "cdylib"] +bench = false +name = "fips205" + +[dependencies.fips205] +path = ".." +version = "0.1.2" \ No newline at end of file diff --git a/ffi/README.md b/ffi/README.md new file mode 100644 index 0000000..8660f90 --- /dev/null +++ b/ffi/README.md @@ -0,0 +1,12 @@ +FIPS 205 FFI + +Thank you to Daniel Kahn Gillmor for the FIPS 203 example. + +Currently only implemented for `slh_dsa_sha2_128f` until A) more testing has been developed, B) better integration with build flow. + +~~~ +$ cd ffi # here +$ cargo build +$ cd tests +$ make +~~~ \ No newline at end of file diff --git a/ffi/fips205.h b/ffi/fips205.h new file mode 100644 index 0000000..8f8b38f --- /dev/null +++ b/ffi/fips205.h @@ -0,0 +1,50 @@ +#ifndef FIPS205_FIPS205_H +#define FIPS205_FIPS205_H + +#include + +typedef uint8_t slh_dsa_err; + +const slh_dsa_err SLH_DSA_OK = 0; +const slh_dsa_err SLH_DSA_NULL_PTR_ERROR = 1; +const slh_dsa_err SLH_DSA_SERIALIZATION_ERROR = 2; +const slh_dsa_err SLH_DSA_DESERIALIZATION_ERROR = 3; +const slh_dsa_err SLH_DSA_KEYGEN_ERROR = 4; +const slh_dsa_err SLH_DSA_SIGN_ERROR = 5; +const slh_dsa_err SLH_DSA_VERIFY_ERROR = 6; + +typedef struct slh_dsa_sha2_128f_public_key { + uint8_t data[32]; +} slh_dsa_sha2_128f_public_key; + +typedef struct slh_dsa_sha2_128f_private_key { + uint8_t data[64]; +} slh_dsa_sha2_128f_private_key; + +typedef struct slh_dsa_sha2_128f_signature { + uint8_t data[17088]; +} slh_dsa_sha2_128f_signature; + + +#ifdef __cplusplus +extern "C" { +#endif + +slh_dsa_err slh_dsa_sha2_128f_keygen(slh_dsa_sha2_128f_public_key *public_out, + slh_dsa_sha2_128f_private_key *private_out); + +slh_dsa_err slh_dsa_sha2_128f_sign(const uint8_t *message_buf, + int message_length, + const slh_dsa_sha2_128f_private_key *private, + slh_dsa_sha2_128f_signature *signature_out); + +slh_dsa_err slh_dsa_sha2_128f_verify(const uint8_t *message_buf, + int message_length, + const slh_dsa_sha2_128f_public_key *public, + const slh_dsa_sha2_128f_signature *signature_out); + + +#ifdef __cplusplus +} // extern "C" +#endif +#endif //FIPS205_FIPS205_H diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs new file mode 100644 index 0000000..d877051 --- /dev/null +++ b/ffi/src/lib.rs @@ -0,0 +1,104 @@ +use std::convert::TryInto; +use fips205; +use fips205::traits::{KeyGen, SerDes, Signer, Verifier}; + +use std::os::raw::c_int; + +pub const SLH_DSA_OK: u8 = 0; +pub const SLH_DSA_NULL_PTR_ERROR: u8 = 1; +pub const SLH_DSA_SERIALIZATION_ERROR: u8 = 2; +pub const SLH_DSA_DESERIALIZATION_ERROR: u8 = 3; +pub const SLH_DSA_KEYGEN_ERROR: u8 = 4; +pub const SLH_DSA_SIGN_ERROR: u8 = 5; +pub const SLH_DSA_VERIFY_ERROR: u8 = 6; + +#[repr(C)] +pub struct slh_dsa_message { + data: [u8], +} + + +// slh_dsa_sha2_128f + +#[repr(C)] +pub struct slh_dsa_sha2_128f_private_key { + data: [u8; fips205::slh_dsa_sha2_128f::SK_LEN], +} + +#[repr(C)] +pub struct slh_dsa_sha2_128f_public_key { + data: [u8; fips205::slh_dsa_sha2_128f::PK_LEN], +} + +#[repr(C)] +pub struct slh_dsa_sha2_128f_signature { + data: [u8; fips205::slh_dsa_sha2_128f::SIG_LEN], +} + + +#[no_mangle] +pub extern "C" fn slh_dsa_sha2_128f_keygen( + public_out: Option<&mut slh_dsa_sha2_128f_public_key>, + private_out: Option<&mut slh_dsa_sha2_128f_private_key>, +) -> u8 { + //use fips205::traits::{KeyGen, SerDes}; + + let (Some(public_out), Some(private_out)) = (public_out, private_out) else { + return SLH_DSA_NULL_PTR_ERROR; + }; + let Ok((pk, sk)) = fips205::slh_dsa_sha2_128f::KG::try_keygen_vt() else { + return SLH_DSA_KEYGEN_ERROR; + }; + + public_out.data = pk.into_bytes(); + private_out.data = sk.into_bytes(); + return SLH_DSA_OK; +} + + +#[no_mangle] +pub extern "C" fn slh_dsa_sha2_128f_sign( + message_buf: *const u8, + message_len: c_int, + private_key: Option<&mut slh_dsa_sha2_128f_private_key>, + signature_out: Option<&mut slh_dsa_sha2_128f_signature>, +) -> u8 { + let (Some(private_key), Some(signature_out)) = (private_key, signature_out) else { + return SLH_DSA_NULL_PTR_ERROR; + }; + + if message_buf.is_null() {return SLH_DSA_NULL_PTR_ERROR}; + + let message = unsafe { std::slice::from_raw_parts(message_buf, message_len.try_into().unwrap()) }; + + let Ok(sk) = fips205::slh_dsa_sha2_128f::PrivateKey::try_from_bytes(&private_key.data) else { + return SLH_DSA_DESERIALIZATION_ERROR; + }; + let Ok(sig) = sk.try_sign_ct(&message, true) else { + return SLH_DSA_SIGN_ERROR; + }; + signature_out.data = sig; + return SLH_DSA_OK; +} + +#[no_mangle] +pub extern "C" fn slh_dsa_sha2_128f_verify( + message_buf: *const u8, + message_len: c_int, + public_key: Option<&mut slh_dsa_sha2_128f_public_key>, + signature: Option<&mut slh_dsa_sha2_128f_signature>, +) -> u8 { + let (Some(public_key), Some(signature)) = (public_key, signature) else { + return SLH_DSA_NULL_PTR_ERROR; + }; + let message = unsafe { std::slice::from_raw_parts(message_buf, message_len.try_into().unwrap()) }; + + let Ok(sk) = fips205::slh_dsa_sha2_128f::PublicKey::try_from_bytes(&public_key.data) else { + return SLH_DSA_DESERIALIZATION_ERROR; + }; + let res = sk.try_verify_vt(&message, &signature.data); + + if res.is_ok() && res.unwrap() { SLH_DSA_OK } else { SLH_DSA_VERIFY_ERROR } +} + + diff --git a/ffi/tests/Makefile b/ffi/tests/Makefile new file mode 100644 index 0000000..d5a8564 --- /dev/null +++ b/ffi/tests/Makefile @@ -0,0 +1,19 @@ +SO_LOCATION = ../../target/debug +FEATURES = slh_dsa_sha2_128f +FRAMES = public_key private_key signature sign verify keygen + +BASELINES=$(foreach sz, $(FEATURES), baseline-$(sz)) +CHECKS=$(foreach sz, $(FEATURES), runtest-$(sz)) + +check: $(CHECKS) + +runtest-%: baseline-% + LD_LIBRARY_PATH=$(SO_LOCATION) ./$< + +baseline-%: baseline.c ../fips205.h + $(CC) -o $@ -g -D SLHDSA=$* $(foreach v, $(FRAMES),-D SLHDSA_$(v)=$*_$(v)) -Werror -Wall -pedantic -L $(SO_LOCATION) $< -Wall -lfips205 + +clean: + rm -f $(BASELINES) + +.PHONY: clean check \ No newline at end of file diff --git a/ffi/tests/baseline.c b/ffi/tests/baseline.c new file mode 100644 index 0000000..204c2b5 --- /dev/null +++ b/ffi/tests/baseline.c @@ -0,0 +1,42 @@ +#include +#include "../fips205.h" + + +int main(int argc, const char **argv) { + SLHDSA_public_key public; + SLHDSA_private_key private; + SLHDSA_signature sig; + uint8_t message[] = { 0x00, 0x11, 0x22 }; + + // Happy path + if (SLHDSA_keygen(&public, &private)) return 1; + if (SLHDSA_sign(message, sizeof message, &private, &sig)) return 1; + if (SLHDSA_verify(message, sizeof message, &public, &sig)) return 1; + + // Verify should fail now + message[0] = 99; + if (SLHDSA_verify(message, sizeof message, &public, &sig) != SLH_DSA_VERIFY_ERROR) return 1; + + // Null parameters + if (SLHDSA_keygen(&public, NULL) != SLH_DSA_NULL_PTR_ERROR) { + fprintf (stderr, "keygen should have failed with NULL private key\n"); + return 1; + } + if (SLHDSA_keygen(NULL, &private) != SLH_DSA_NULL_PTR_ERROR) { + fprintf (stderr, "keygen should have failed with NULL public key\n"); + return 1; + } + + if (SLHDSA_sign(NULL, sizeof message, &private, &sig) != SLH_DSA_NULL_PTR_ERROR) { + fprintf (stderr, "sign should have failed with NULL message\n"); + return 1; + } + if (SLHDSA_sign(message, sizeof message, NULL, &sig) != SLH_DSA_NULL_PTR_ERROR) { + fprintf (stderr, "sign should have failed with NULL private key\n"); + return 1; + } + if (SLHDSA_sign(message, sizeof message, &private, NULL) != SLH_DSA_NULL_PTR_ERROR) { + fprintf (stderr, "sign should have failed with NULL signature\n"); + return 1; + } +} \ No newline at end of file