diff --git a/Cargo.lock b/Cargo.lock index b4ec0d7..973cc9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64ct" version = "1.6.0" @@ -132,6 +138,18 @@ dependencies = [ "libc", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -165,6 +183,8 @@ version = "0.1.0" dependencies = [ "cryptoki", "der", + "ecdsa", + "p256", "rsa", "serial_test", "sha1", @@ -203,6 +223,21 @@ dependencies = [ "block-buffer", "const-oid", "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", ] [[package]] @@ -211,6 +246,26 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "errno" version = "0.3.4" @@ -232,6 +287,16 @@ dependencies = [ "libc", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -240,6 +305,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -259,12 +325,32 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.5" @@ -418,6 +504,18 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.11.2" @@ -501,6 +599,15 @@ dependencies = [ "syn 2.0.37", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.67" @@ -610,6 +717,16 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rsa" version = "0.9.4" @@ -664,6 +781,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "secrecy" version = "0.8.0" @@ -975,9 +1106,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] diff --git a/cryptoki-rustcrypto/Cargo.toml b/cryptoki-rustcrypto/Cargo.toml index dc35c4d..4484956 100644 --- a/cryptoki-rustcrypto/Cargo.toml +++ b/cryptoki-rustcrypto/Cargo.toml @@ -8,10 +8,13 @@ readme = "README.md" keywords = ["pkcs11", "cryptoki", "hsm"] categories = ["cryptography", "hardware-support"] license = "Apache-2.0" +repository = "https://github.com/parallaxsecond/rust-cryptoki" [dependencies] cryptoki = { path = "../cryptoki", version = "0.6.1" } der = "0.7.8" +ecdsa = "0.16.9" +p256 = { version = "0.13.2", features = ["pkcs8"] } rsa = "0.9" signature = { version = "2.2.0", features = ["digest"] } sha1 = { version = "0.10", features = ["oid"] } diff --git a/cryptoki-rustcrypto/src/ecdsa.rs b/cryptoki-rustcrypto/src/ecdsa.rs new file mode 100644 index 0000000..a2b64a2 --- /dev/null +++ b/cryptoki-rustcrypto/src/ecdsa.rs @@ -0,0 +1,181 @@ +use cryptoki::{ + mechanism::Mechanism, + object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle}, + session::Session, +}; +use der::{ + asn1::{ObjectIdentifier, OctetStringRef}, + oid::AssociatedOid, + AnyRef, Decode, Encode, +}; +use ecdsa::{ + elliptic_curve::{ + generic_array::ArrayLength, + sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}, + AffinePoint, CurveArithmetic, FieldBytesSize, PublicKey, + }, + hazmat::DigestPrimitive, + PrimeCurve, Signature, VerifyingKey, +}; +use signature::digest::Digest; +use spki::{ + AlgorithmIdentifier, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier, + SignatureAlgorithmIdentifier, +}; +use std::{convert::TryFrom, ops::Add}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Cryptoki error: {0}")] + Cryptoki(#[from] cryptoki::error::Error), + + #[error("Private key missing attribute: {0}")] + MissingAttribute(AttributeType), + + #[error("Elliptic curve error: {0}")] + Ecdsa(#[from] ecdsa::elliptic_curve::Error), +} + +pub trait SignAlgorithm: PrimeCurve + CurveArithmetic + AssociatedOid + DigestPrimitive { + fn sign_mechanism() -> Mechanism<'static>; +} + +impl SignAlgorithm for p256::NistP256 { + fn sign_mechanism() -> Mechanism<'static> { + Mechanism::Ecdsa + } +} + +pub struct Signer { + session: Session, + _public_key: ObjectHandle, + private_key: ObjectHandle, + verifying_key: VerifyingKey, +} + +impl Signer +where + FieldBytesSize: ModulusSize, + AffinePoint: FromEncodedPoint + ToEncodedPoint, +{ + pub fn new(session: Session, label: &[u8]) -> Result { + // First we'll lookup a private key with that label. + let template = vec![ + Attribute::Token(true), + Attribute::Private(true), + Attribute::Label(label.to_vec()), + Attribute::Class(ObjectClass::PRIVATE_KEY), + Attribute::KeyType(KeyType::EC), + Attribute::EcParams(C::OID.to_der().unwrap()), + Attribute::Sign(true), + ]; + + let private_key = session.find_objects(&template)?.remove(0); + let attribute_pk = session.get_attributes(private_key, &[AttributeType::Id])?; + + // Second we'll lookup a public key with the same label/ec params/ec point + let mut template = vec![ + Attribute::Private(false), + Attribute::Label(label.to_vec()), + Attribute::Class(ObjectClass::PUBLIC_KEY), + Attribute::KeyType(KeyType::EC), + Attribute::EcParams(C::OID.to_der().unwrap()), + ]; + let mut id = None; + for attribute in attribute_pk { + match attribute { + Attribute::Id(i) if id.is_none() => { + template.push(Attribute::Id(i.clone())); + id = Some(i); + } + _ => {} + } + } + + let public_key = session.find_objects(&template)?.remove(0); + let attribute_pk = session.get_attributes(public_key, &[AttributeType::EcPoint])?; + + let mut ec_point = None; + for attribute in attribute_pk { + match attribute { + Attribute::EcPoint(p) if ec_point.is_none() => { + ec_point = Some(p); + } + _ => {} + } + } + + let ec_point = ec_point.ok_or(Error::MissingAttribute(AttributeType::EcPoint))?; + + // documented as "DER-encoding of ANSI X9.62 ECPoint value Q" + // https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.1/os/pkcs11-spec-v3.1-os.html#_Toc111203418 + // https://www.rfc-editor.org/rfc/rfc5480#section-2.2 + let ec_point = OctetStringRef::from_der(&ec_point).unwrap(); + let public = PublicKey::::from_sec1_bytes(ec_point.as_bytes())?; + let verifying_key = public.into(); + + Ok(Self { + session, + private_key, + _public_key: public_key, + verifying_key, + }) + } + + pub fn into_session(self) -> Session { + self.session + } +} + +impl AssociatedAlgorithmIdentifier for Signer +where + C: AssociatedOid, +{ + type Params = ObjectIdentifier; + + const ALGORITHM_IDENTIFIER: AlgorithmIdentifier = + PublicKey::::ALGORITHM_IDENTIFIER; +} + +impl signature::Keypair for Signer { + type VerifyingKey = VerifyingKey; + + fn verifying_key(&self) -> Self::VerifyingKey { + self.verifying_key.clone() + } +} + +impl signature::Signer> for Signer +where + <::FieldBytesSize as Add>::Output: ArrayLength, +{ + fn try_sign(&self, msg: &[u8]) -> Result, signature::Error> { + println!("try sign"); + + let msg = C::Digest::digest(msg); + + let bytes = self + .session + .sign(&C::sign_mechanism(), self.private_key, &msg) + .map_err(Error::Cryptoki) + .map_err(Box::new) + .map_err(signature::Error::from_source)?; + + let signature = Signature::try_from(bytes.as_slice())?; + + Ok(signature) + } +} + +impl SignatureAlgorithmIdentifier for Signer +where + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + Signature: AssociatedAlgorithmIdentifier>, +{ + type Params = AnyRef<'static>; + + const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = + Signature::::ALGORITHM_IDENTIFIER; +} diff --git a/cryptoki-rustcrypto/src/lib.rs b/cryptoki-rustcrypto/src/lib.rs index 5ba72a6..3d02de0 100644 --- a/cryptoki-rustcrypto/src/lib.rs +++ b/cryptoki-rustcrypto/src/lib.rs @@ -1 +1,2 @@ +pub mod ecdsa; pub mod rsa; diff --git a/cryptoki-rustcrypto/tests/ecdsa.rs b/cryptoki-rustcrypto/tests/ecdsa.rs new file mode 100644 index 0000000..8691ae6 --- /dev/null +++ b/cryptoki-rustcrypto/tests/ecdsa.rs @@ -0,0 +1,83 @@ +// Copyright 2021 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 +mod common; + +use crate::common::USER_PIN; +use common::init_pins; +use cryptoki::{ + mechanism::Mechanism, + object::{Attribute, KeyType}, + session::UserType, + types::AuthPin, +}; +use cryptoki_rustcrypto::ecdsa; +use der::Encode; +use p256::pkcs8::AssociatedOid; +use serial_test::serial; +use signature::{Keypair, Signer, Verifier}; +use testresult::TestResult; + +#[test] +#[serial] +fn sign_verify() -> TestResult { + let (pkcs11, slot) = init_pins(); + + // open a session + let session = pkcs11.open_rw_session(slot)?; + + // log in the session + session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?; + + // get mechanism + let mechanism = Mechanism::EccKeyPairGen; + + let secp256r1_oid: Vec = p256::NistP256::OID.to_der().unwrap(); + println!("oid: {:x?}", secp256r1_oid); + + let label = b"demo-signer"; + + // pub key template + let pub_key_template = vec![ + Attribute::Token(true), + Attribute::Private(false), + Attribute::KeyType(KeyType::EC), + Attribute::Verify(true), + Attribute::EcParams(secp256r1_oid.clone()), + Attribute::Label(label.to_vec()), + ]; + + // priv key template + let priv_key_template = vec![ + Attribute::Token(true), + Attribute::Private(true), + //Attribute::KeyType(KeyType::EC), + //Attribute::EcParams(secp256r1_oid), + //Attribute::Sensitive(true), + //Attribute::Extractable(false), + Attribute::Sign(true), + Attribute::Label(label.to_vec()), + ]; + + // generate a key pair + let (public, private) = + session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?; + + // data to sign + let data = [0xFF, 0x55, 0xDD]; + + let signer = + ecdsa::Signer::::new(session, label).expect("Lookup keys from HSM"); + + let signature = signer.sign(&data); + + let verifying_key = signer.verifying_key(); + verifying_key.verify(&data, &signature)?; + + let session = signer.into_session(); + + // delete keys + session.destroy_object(public)?; + session.destroy_object(private)?; + + Ok(()) +}