From eb3843e4ddc065e8b3e8868f58abfb0b1bca4035 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Fri, 12 May 2017 14:12:53 -1000 Subject: [PATCH] Add PKCS#8 generation to `ECDSAKeyPair`. --- Cargo.toml | 2 + src/data/alg-ecPublicKey-P256.der | 1 - src/data/alg-ecPublicKey-P384.der | Bin 16 -> 0 bytes src/ec/curve25519/ed25519.rs | 18 ++-- src/ec/ec.rs | 11 +++ .../ecPublicKey_p256_pkcs8_v1_template.der | Bin 0 -> 41 bytes .../ecPublicKey_p384_pkcs8_v1_template.der | Bin 0 -> 40 bytes src/ec/suite_b/ecdsa.rs | 53 +++++++++--- src/ec/suite_b/suite_b.rs | 4 +- src/pkcs8.rs | 77 ++++++++++++++++-- src/rsa/signing.rs | 4 +- src/signature.rs | 2 + tests/ecdsa_tests.rs | 23 +++++- 13 files changed, 160 insertions(+), 35 deletions(-) delete mode 100644 src/data/alg-ecPublicKey-P256.der delete mode 100644 src/data/alg-ecPublicKey-P384.der create mode 100644 src/ec/suite_b/ecPublicKey_p256_pkcs8_v1_template.der create mode 100644 src/ec/suite_b/ecPublicKey_p384_pkcs8_v1_template.der diff --git a/Cargo.toml b/Cargo.toml index 9371a9c32..7e7198a58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,8 @@ include = [ "src/ec/suite_b/ecdh.rs", "src/ec/suite_b/ecdsa.rs", "src/ec/suite_b/ecdsa_digest_scalar_tests.txt", + "src/ec/suite_b/ecPublicKey_p256_pkcs8_v1_template.der", + "src/ec/suite_b/ecPublicKey_p384_pkcs8_v1_template.der", "src/ec/suite_b/ops/elem.rs", "src/ec/suite_b/ops/ops.rs", "src/ec/suite_b/ops/p256.rs", diff --git a/src/data/alg-ecPublicKey-P256.der b/src/data/alg-ecPublicKey-P256.der deleted file mode 100644 index d49c30da3..000000000 --- a/src/data/alg-ecPublicKey-P256.der +++ /dev/null @@ -1 +0,0 @@ -*†HÎ=*†HÎ= \ No newline at end of file diff --git a/src/data/alg-ecPublicKey-P384.der b/src/data/alg-ecPublicKey-P384.der deleted file mode 100644 index 8b24916caf9bfaeaecb98407738772aa659fc65e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16 XcmZQ$*J|@PXUoLM#;V=O!k`2I8~OtA diff --git a/src/ec/curve25519/ed25519.rs b/src/ec/curve25519/ed25519.rs index 26c4130a5..dc7dcd38b 100644 --- a/src/ec/curve25519/ed25519.rs +++ b/src/ec/curve25519/ed25519.rs @@ -14,6 +14,7 @@ //! EdDSA Signatures. +use core; use {der, digest, error, pkcs8, private, rand, signature, signature_impl}; use super::ops::*; use untrusted; @@ -57,9 +58,10 @@ impl<'a> Ed25519KeyPair { let mut seed = [0u8; SEED_LEN]; try!(rng.fill(&mut seed)); let key_pair = Ed25519KeyPair::from_seed_(&seed); + // TODO: Replace this with `wrap_key()` and return a `PKCS8Document`. let mut bytes = [0; ED25519_PKCS8_V2_LEN]; - pkcs8::wrap_key(PKCS8_TEMPLATE, PKCS8_SEED_INDEX, &seed[..], - key_pair.public_key_bytes(), &mut bytes[..]); + pkcs8::wrap_key_(&PKCS8_TEMPLATE, &seed[..], key_pair.public_key_bytes(), + &mut bytes[..]); Ok(bytes) } @@ -211,7 +213,7 @@ fn unwrap_pkcs8(version: pkcs8::Version, input: untrusted::Input) -> Result<(untrusted::Input, Option), error::Unspecified> { let (private_key, public_key) = - try!(pkcs8::unwrap_key(version, input, ed25519_alg_id())); + try!(pkcs8::unwrap_key(&PKCS8_TEMPLATE, version, input)); let private_key = try!(private_key.read_all(error::Unspecified, |input| { der::expect_tag_and_get_value(input, der::Tag::OctetString) })); @@ -308,15 +310,15 @@ const SIGNATURE_LEN: usize = ELEM_LEN + SCALAR_LEN; type Seed = [u8; SEED_LEN]; const SEED_LEN: usize = 32; -const PKCS8_TEMPLATE: &[u8] = include_bytes!("ed25519_pkcs8_v2_template.der"); -const PKCS8_SEED_INDEX: usize = 0x10; +static PKCS8_TEMPLATE: pkcs8::Template = pkcs8::Template { + bytes: include_bytes!("ed25519_pkcs8_v2_template.der"), + alg_id_range: core::ops::Range { start: 7, end: 12 }, + private_key_index: 0x10, +}; /// The length of an Ed25519 public key. pub const ED25519_PUBLIC_KEY_LEN: usize = PUBLIC_KEY_LEN; -#[inline] -fn ed25519_alg_id() -> &'static [u8] { &PKCS8_TEMPLATE[7..12] } - /// The length of a Ed25519 PKCS#8 (v2) private key generated by /// `Ed25519KeyPair::generate_pkcs8()`. Ed25519 PKCS#8 files generated by other /// software may have different lengths, and `Ed25519KeyPair::generate_pkcs8()` diff --git a/src/ec/ec.rs b/src/ec/ec.rs index 136664590..d58575727 100644 --- a/src/ec/ec.rs +++ b/src/ec/ec.rs @@ -101,6 +101,17 @@ pub const SCALAR_MAX_BYTES: usize = ELEM_MAX_BYTES; /// The maximum length, in bytes, of an encoded public key. pub const PUBLIC_KEY_MAX_LEN: usize = 1 + (2 * ELEM_MAX_BYTES); +/// The maximum length of a PKCS#8 documents generated by *ring* for ECC keys. +/// +/// This is NOT the maximum length of a PKCS#8 document that can be consumed by +/// `pkcs8::unwrap_key()`. +/// +/// `40` is the length of the P-384 template. It is actually one byte shorter +/// than the P-256 template, but the private key and the public key are much +/// longer. +pub const PKCS8_DOCUMENT_MAX_LEN: usize = + 40 + SCALAR_MAX_BYTES + PUBLIC_KEY_MAX_LEN; + #[path = "curve25519/curve25519.rs"] pub mod curve25519; diff --git a/src/ec/suite_b/ecPublicKey_p256_pkcs8_v1_template.der b/src/ec/suite_b/ecPublicKey_p256_pkcs8_v1_template.der new file mode 100644 index 0000000000000000000000000000000000000000..d579082387e02f393f114bb8e0bcdb6bacd2d835 GIT binary patch literal 41 tcmXqLY-eI*Fc4;A*J|@PXUoLM#sOw9GqSVf8e}suGO{QvbYXU4006QS2n+xK literal 0 HcmV?d00001 diff --git a/src/ec/suite_b/ecPublicKey_p384_pkcs8_v1_template.der b/src/ec/suite_b/ecPublicKey_p384_pkcs8_v1_template.der new file mode 100644 index 0000000000000000000000000000000000000000..76cc36d403744ed0a6e94414c806d54187a49147 GIT binary patch literal 40 vcmXqL+{VPnU?9N8uGQvo&X$Rhja9pmg+Ym>ah^frY$irV7K4Q;%t;IYu$Bm{ literal 0 HcmV?d00001 diff --git a/src/ec/suite_b/ecdsa.rs b/src/ec/suite_b/ecdsa.rs index 1ae848fe8..60992468e 100644 --- a/src/ec/suite_b/ecdsa.rs +++ b/src/ec/suite_b/ecdsa.rs @@ -15,7 +15,8 @@ //! ECDSA Signatures using the P-256 and P-384 curves. use arithmetic::montgomery::*; -use {der, digest, ec, error, private, signature}; +use core; +use {der, digest, ec, error, pkcs8, private, rand, signature}; use super::verify_jacobian_point_is_on_the_curve; use super::ops::*; use super::public_key::*; @@ -24,7 +25,7 @@ use untrusted; /// An ECDSA signing algorithm. pub struct ECDSASigningAlgorithm { curve: &'static ec::Curve, - key_alg_id: &'static [u8], + pkcs8_template: &'static pkcs8::Template, } @@ -149,6 +150,28 @@ pub struct ECDSAKeyPair { } impl<'a> ECDSAKeyPair { + /// Generates a new key pair and returns the key pair serialized as a + /// PKCS#8 document. + /// + /// The PKCS#8 document will be a v1 `OneAsymmetricKey` with the public key + /// included in the `ECPrivateKey` structure, as described in + /// [RFC 5958 Section 2] and [RFC 5915]. The `ECPrivateKey` structure will + /// not have a `parameters` field so the generated key is compatible with + /// PKCS#11. + /// + /// [RFC 5915]: https://tools.ietf.org/html/rfc5915 + /// [RFC 5958 Section 2]: https://tools.ietf.org/html/rfc5958#section-2 + pub fn generate_pkcs8(alg: &'static ECDSASigningAlgorithm, + rng: &rand::SecureRandom) + -> Result { + let private_key = try!(ec::PrivateKey::generate(alg.curve, rng)); + let mut public_key_bytes = [0; ec::PUBLIC_KEY_MAX_LEN]; + let public_key_bytes = &mut public_key_bytes[..alg.curve.public_key_len]; + try!((alg.curve.public_from_private)(public_key_bytes, &private_key)); + Ok(pkcs8::wrap_key(&alg.pkcs8_template, private_key.bytes(alg.curve), + public_key_bytes)) + } + /// Constructs an ECDSA key pair by parsing an unencrypted PKCS#8 v1 /// id-ecPublicKey `ECPrivateKey` key. /// @@ -160,8 +183,8 @@ impl<'a> ECDSAKeyPair { pub fn from_pkcs8(alg: &'static ECDSASigningAlgorithm, input: untrusted::Input) -> Result { - let key_pair = try!(ec::suite_b::key_pair_from_pkcs8( - alg.curve, alg.key_alg_id, input)); + let key_pair = try!(ec::suite_b::key_pair_from_pkcs8(alg.curve, + alg.pkcs8_template, input)); Ok(ECDSAKeyPair { key_pair, alg }) } @@ -266,7 +289,7 @@ fn twin_mul(ops: &PrivateKeyOps, g_scalar: &Scalar, p_scalar: &Scalar, pub static ECDSA_P256_SHA256_FIXED_SIGNING: ECDSASigningAlgorithm = ECDSASigningAlgorithm { curve: &ec::suite_b::curve::P256, - key_alg_id: EC_PUBLIC_KEY_P256, + pkcs8_template: &EC_PUBLIC_KEY_P256_PKCS8_V1_TEMPLATE, }; /// Verification of fixed-length (PKCS#11 style) ECDSA signatures using the @@ -289,7 +312,7 @@ pub static ECDSA_P256_SHA256_FIXED: ECDSAVerificationAlgorithm = pub static ECDSA_P384_SHA384_FIXED_SIGNING: ECDSASigningAlgorithm = ECDSASigningAlgorithm { curve: &ec::suite_b::curve::P384, - key_alg_id: EC_PUBLIC_KEY_P384, + pkcs8_template: &EC_PUBLIC_KEY_P384_PKCS8_V1_TEMPLATE, }; /// Verification of fixed-length (PKCS#11 style) ECDSA signatures using the @@ -312,7 +335,7 @@ pub static ECDSA_P384_SHA384_FIXED: ECDSAVerificationAlgorithm = pub static ECDSA_P256_SHA256_ASN1_SIGNING: ECDSASigningAlgorithm = ECDSASigningAlgorithm { curve: &ec::suite_b::curve::P256, - key_alg_id: EC_PUBLIC_KEY_P256, + pkcs8_template: &EC_PUBLIC_KEY_P256_PKCS8_V1_TEMPLATE, }; /// Verification of ASN.1 DER-encoded ECDSA signatures using the P-256 curve @@ -369,7 +392,7 @@ pub static ECDSA_P384_SHA256_ASN1: ECDSAVerificationAlgorithm = pub static ECDSA_P384_SHA384_ASN1_SIGNING: ECDSASigningAlgorithm = ECDSASigningAlgorithm { curve: &ec::suite_b::curve::P384, - key_alg_id: EC_PUBLIC_KEY_P384, + pkcs8_template: &EC_PUBLIC_KEY_P384_PKCS8_V1_TEMPLATE, }; /// Verification of ASN.1 DER-encoded ECDSA signatures using the P-384 curve @@ -384,11 +407,17 @@ pub static ECDSA_P384_SHA384_ASN1: ECDSAVerificationAlgorithm = split_rs: split_rs_asn1, }; -const EC_PUBLIC_KEY_P256: &'static [u8] = - include_bytes!("../../data/alg-ecPublicKey-P256.der"); -const EC_PUBLIC_KEY_P384: &'static [u8] = - include_bytes!("../../data/alg-ecPublicKey-P384.der"); +static EC_PUBLIC_KEY_P256_PKCS8_V1_TEMPLATE: pkcs8::Template = pkcs8::Template { + bytes: include_bytes ! ("ecPublicKey_p256_pkcs8_v1_template.der"), + alg_id_range: core::ops::Range { start: 8, end: 27 }, + private_key_index: 0x24, +}; +static EC_PUBLIC_KEY_P384_PKCS8_V1_TEMPLATE: pkcs8::Template = pkcs8::Template { + bytes: include_bytes!("ecPublicKey_p384_pkcs8_v1_template.der"), + alg_id_range: core::ops::Range { start: 8, end: 24 }, + private_key_index: 0x23, +}; #[cfg(test)] mod tests { diff --git a/src/ec/suite_b/suite_b.rs b/src/ec/suite_b/suite_b.rs index 0f3c2950c..08a684d39 100644 --- a/src/ec/suite_b/suite_b.rs +++ b/src/ec/suite_b/suite_b.rs @@ -153,11 +153,11 @@ fn verify_affine_point_is_on_the_curve_scaled( Ok(()) } -pub fn key_pair_from_pkcs8(curve: &ec::Curve, key_alg_id: &[u8], +pub fn key_pair_from_pkcs8(curve: &ec::Curve, template: &pkcs8::Template, input: untrusted::Input) -> Result { let (ec_private_key, _) = try!(pkcs8::unwrap_key( - pkcs8::Version::V1Only, input, key_alg_id)); + template, pkcs8::Version::V1Only, input)); let (private_key, public_key) = try!(ec_private_key.read_all( error::Unspecified, |input| { // https://tools.ietf.org/html/rfc5915#section-3 diff --git a/src/pkcs8.rs b/src/pkcs8.rs index 351e2c064..d98da991e 100644 --- a/src/pkcs8.rs +++ b/src/pkcs8.rs @@ -16,7 +16,8 @@ //! //! [RFC 5958]: https://tools.ietf.org/html/rfc5958. -use {der, error}; +use core; +use {der, ec, error}; use untrusted; pub enum Version { @@ -25,6 +26,43 @@ pub enum Version { V2Only, } +/// A template for constructing PKCS#8 documents. +/// +/// Note that this only works for ECC. +pub struct Template { + pub bytes: &'static [u8], + + // The range within `bytes` that holds the value (not including the tag and + // length) for use in the PKCS#8 document's privateKeyAlgorithm field. + pub alg_id_range: core::ops::Range, + + // `bytes` will be split into two parts at `private_key_index`, where the + // first part is written before the private key and the second part is + // written after the private key. The public key is written after the second + // part. + pub private_key_index: usize, +} + +impl Template { + #[inline] + fn alg_id_value(&self) -> &[u8] { + &self.bytes[self.alg_id_range.start..self.alg_id_range.end] + } +} + +/// Parses an unencrypted PKCS#8 private key, verifies that it is the right type +/// of key, and returns the key value. +/// +/// PKCS#8 is specified in [RFC 5958]. +/// +/// [RFC 5958]: https://tools.ietf.org/html/rfc5958. +pub fn unwrap_key<'a>(template: &Template, version: Version, + input: untrusted::Input<'a>) + -> Result<(untrusted::Input<'a>, Option>), + error::Unspecified> { + unwrap_key_(template.alg_id_value(), version, input) +} + /// Parses an unencrypted PKCS#8 private key, verifies that it is the right type /// of key, and returns the key value. /// @@ -35,8 +73,8 @@ pub enum Version { /// PKCS#8 is specified in [RFC 5958]. /// /// [RFC 5958]: https://tools.ietf.org/html/rfc5958. -pub fn unwrap_key<'a>(version: Version, input: untrusted::Input<'a>, - alg_id: &[u8]) +pub fn unwrap_key_<'a>(alg_id: &[u8], version: Version, + input: untrusted::Input<'a>) -> Result<(untrusted::Input<'a>, Option>), error::Unspecified> { input.read_all(error::Unspecified, |input| { @@ -80,15 +118,36 @@ pub fn unwrap_key<'a>(version: Version, input: untrusted::Input<'a>, }) } +/// A generated PKCS#8 document. +pub struct PKCS8Document { + bytes: [u8; ec::PKCS8_DOCUMENT_MAX_LEN], + len: usize, +} + +impl AsRef<[u8]> for PKCS8Document { + #[inline] + fn as_ref(&self) -> &[u8] { &self.bytes[..self.len] } +} + +pub fn wrap_key(template: &Template, private_key: &[u8], public_key: &[u8]) + -> PKCS8Document { + let mut result = PKCS8Document { + bytes: [0; ec::PKCS8_DOCUMENT_MAX_LEN], + len: template.bytes.len() + private_key.len() + public_key.len(), + }; + wrap_key_(template, private_key, public_key, &mut result.bytes[..result.len]); + result +} + /// Formats a private key "prefix||private_key||middle||public_key" where /// `template` is "prefix||middle" split at position `private_key_index`. -pub fn wrap_key(template: &[u8], private_key_index: usize, private_key: &[u8], - public_key: &[u8], bytes: &mut [u8]) { +pub fn wrap_key_(template: &Template, private_key: &[u8], public_key: &[u8], + bytes: &mut [u8]) { let (before_private_key, after_private_key) = - template.split_at(private_key_index); - let private_key_end_index = private_key_index + private_key.len(); - bytes[..private_key_index].copy_from_slice(before_private_key); - bytes[private_key_index..private_key_end_index] + template.bytes.split_at(template.private_key_index); + let private_key_end_index = template.private_key_index + private_key.len(); + bytes[..template.private_key_index].copy_from_slice(before_private_key); + bytes[template.private_key_index..private_key_end_index] .copy_from_slice(&private_key); bytes[private_key_end_index.. (private_key_end_index + after_private_key.len())] diff --git a/src/rsa/signing.rs b/src/rsa/signing.rs index d5dd7d38b..e934d9cfc 100644 --- a/src/rsa/signing.rs +++ b/src/rsa/signing.rs @@ -142,8 +142,8 @@ impl RSAKeyPair { -> Result { const RSA_ENCRYPTION: &'static [u8] = include_bytes!("../data/alg-rsa-encryption.der"); - let (der, _) = try!(pkcs8::unwrap_key(pkcs8::Version::V1Only, input, - &RSA_ENCRYPTION)); + let (der, _) = try!(pkcs8::unwrap_key_(&RSA_ENCRYPTION, + pkcs8::Version::V1Only, input)); Self::from_der(der) } diff --git a/src/signature.rs b/src/signature.rs index abe7f19ea..9f3065b5f 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -280,6 +280,8 @@ pub use ec::curve25519::ed25519::{ ED25519_PUBLIC_KEY_LEN, }; +pub use pkcs8::PKCS8Document; + #[cfg(all(feature = "rsa_signing", feature = "use_heap"))] pub use rsa::signing::{RSAKeyPair, RSASigningState}; diff --git a/tests/ecdsa_tests.rs b/tests/ecdsa_tests.rs index 1f8bbefba..ba0ee20dd 100644 --- a/tests/ecdsa_tests.rs +++ b/tests/ecdsa_tests.rs @@ -15,7 +15,7 @@ extern crate ring; extern crate untrusted; -use ring::{signature, test}; +use ring::{rand, signature, test}; #[test] fn ecdsa_from_pkcs8_test() { @@ -56,6 +56,27 @@ fn ecdsa_from_pkcs8_test() { }); } +// Verify that, at least, we generate PKCS#8 documents that we can read. +#[test] +fn ecdsa_generate_pkcs8_test() { + let rng = rand::SystemRandom::new(); + + for alg in &[&signature::ECDSA_P256_SHA256_ASN1_SIGNING, + &signature::ECDSA_P256_SHA256_FIXED_SIGNING, + &signature::ECDSA_P384_SHA384_ASN1_SIGNING, + &signature::ECDSA_P384_SHA384_FIXED_SIGNING] { + let pkcs8 = signature::ECDSAKeyPair::generate_pkcs8(alg, &rng).unwrap(); + println!(); + for b in pkcs8.as_ref() { + print!("{:02x}", *b); + } + println!(); + println!(); + let _ = signature::ECDSAKeyPair::from_pkcs8( + alg, untrusted::Input::from(pkcs8.as_ref())).unwrap(); + } +} + #[test] fn signature_ecdsa_verify_asn1_test() { test::from_file("tests/ecdsa_verify_asn1_tests.txt", |section, test_case| {