diff --git a/Cargo.toml b/Cargo.toml index 57f0bff82..9371a9c32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -229,6 +229,7 @@ include = [ "include/GFp/mem.h", "include/GFp/type_check.h", "examples/checkdigest.rs", + "tests/ecdsa_from_pkcs8_tests.txt", "tests/ecdsa_tests.rs", "tests/ecdsa_verify_asn1_tests.txt", "tests/ecdsa_verify_fixed_tests.txt", diff --git a/src/data/alg-ecPublicKey-P256.der b/src/data/alg-ecPublicKey-P256.der new file mode 100644 index 000000000..d49c30da3 --- /dev/null +++ b/src/data/alg-ecPublicKey-P256.der @@ -0,0 +1 @@ +*†HÎ=*†HÎ= \ No newline at end of file diff --git a/src/data/alg-ecPublicKey-P384.der b/src/data/alg-ecPublicKey-P384.der new file mode 100644 index 000000000..8b24916ca Binary files /dev/null and b/src/data/alg-ecPublicKey-P384.der differ diff --git a/src/ec/ec.rs b/src/ec/ec.rs index 2811bd8ec..136664590 100644 --- a/src/ec/ec.rs +++ b/src/ec/ec.rs @@ -47,6 +47,11 @@ pub enum CurveID { P384, } +pub struct KeyPair { + pub private_key: PrivateKey, + pub public_key: [u8; PUBLIC_KEY_MAX_LEN], +} + pub struct PrivateKey { bytes: [u8; SCALAR_MAX_BYTES], } diff --git a/src/ec/suite_b/ecdsa.rs b/src/ec/suite_b/ecdsa.rs index 6d2d83acd..1ae848fe8 100644 --- a/src/ec/suite_b/ecdsa.rs +++ b/src/ec/suite_b/ecdsa.rs @@ -15,13 +15,20 @@ //! ECDSA Signatures using the P-256 and P-384 curves. use arithmetic::montgomery::*; -use {der, digest, error, private, signature}; +use {der, digest, ec, error, private, signature}; use super::verify_jacobian_point_is_on_the_curve; use super::ops::*; use super::public_key::*; use untrusted; -/// An ECDSA signature verification algorithm. +/// An ECDSA signing algorithm. +pub struct ECDSASigningAlgorithm { + curve: &'static ec::Curve, + key_alg_id: &'static [u8], +} + + +/// An ECDSA verification algorithm. pub struct ECDSAVerificationAlgorithm { ops: &'static PublicScalarOps, digest_alg: &'static digest::Algorithm, @@ -132,6 +139,48 @@ impl signature::VerificationAlgorithm for ECDSAVerificationAlgorithm { impl private::Private for ECDSAVerificationAlgorithm {} +/// An ECDSA key pair, used for signing. +pub struct ECDSAKeyPair { + #[allow(dead_code)] // XXX: Temporary, since signing isn't implemented yet. + key_pair: ec::KeyPair, + + #[allow(dead_code)] // XXX: Temporary, since signing isn't implemented yet. + alg: &'static ECDSASigningAlgorithm, +} + +impl<'a> ECDSAKeyPair { + /// Constructs an ECDSA key pair by parsing an unencrypted PKCS#8 v1 + /// id-ecPublicKey `ECPrivateKey` key. + /// + /// The input must be in PKCS#8 v1 format. It must contain the public key in + /// the `ECPrivateKey` structure; `from_pkcs8()` will verify that the public + /// key and the private key are consistent with each other. The algorithm + /// identifier must identify the curve by name; it must not use an + /// "explicit" encoding of the curve. + 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)); + Ok(ECDSAKeyPair { key_pair, alg }) + } + + /// Constructs an ECDSA key pair directly from the big-endian-encoded + /// private key and public key bytes. + /// + /// This is intended for use by code that deserializes key pairs. It is + /// recommended to use `ECDSAKeyPair::from_pkcs8()` (with a PKCS#8-encoded + /// key) instead. + pub fn from_private_key_and_public_key(alg: &'static ECDSASigningAlgorithm, + private_key: untrusted::Input, + public_key: untrusted::Input) + -> Result { + let key_pair = try!(ec::suite_b::key_pair_from_bytes( + alg.curve, private_key, public_key)); + Ok(ECDSAKeyPair { key_pair, alg }) + } +} + fn split_rs_fixed<'a>( ops: &'static ScalarOps, input: &mut untrusted::Reader<'a>) -> Result<(untrusted::Input<'a>, untrusted::Input<'a>), @@ -209,6 +258,17 @@ fn twin_mul(ops: &PrivateKeyOps, g_scalar: &Scalar, p_scalar: &Scalar, } +/// Signing of fixed-length (PKCS#11 style) ECDSA signatures using the +/// P-256 curve and SHA-256. +/// +/// See "`ECDSA_*_FIXED` Details" in `ring::signature`'s module-level +/// documentation for more details. +pub static ECDSA_P256_SHA256_FIXED_SIGNING: ECDSASigningAlgorithm = + ECDSASigningAlgorithm { + curve: &ec::suite_b::curve::P256, + key_alg_id: EC_PUBLIC_KEY_P256, +}; + /// Verification of fixed-length (PKCS#11 style) ECDSA signatures using the /// P-256 curve and SHA-256. /// @@ -221,6 +281,17 @@ pub static ECDSA_P256_SHA256_FIXED: ECDSAVerificationAlgorithm = split_rs: split_rs_fixed, }; +/// Signing of fixed-length (PKCS#11 style) ECDSA signatures using the +/// P-256 curve and SHA-256. +/// +/// See "`ECDSA_*_FIXED` Details" in `ring::signature`'s module-level +/// documentation for more details. +pub static ECDSA_P384_SHA384_FIXED_SIGNING: ECDSASigningAlgorithm = + ECDSASigningAlgorithm { + curve: &ec::suite_b::curve::P384, + key_alg_id: EC_PUBLIC_KEY_P384, +}; + /// Verification of fixed-length (PKCS#11 style) ECDSA signatures using the /// P-384 curve and SHA-384. /// @@ -233,6 +304,17 @@ pub static ECDSA_P384_SHA384_FIXED: ECDSAVerificationAlgorithm = split_rs: split_rs_fixed, }; +/// Signing of fixed-length (PKCS#11 style) ECDSA signatures using the +/// P-256 curve and SHA-256. +/// +/// See "`ECDSA_*_ASN1` Details" in `ring::signature`'s module-level +/// documentation for more details. +pub static ECDSA_P256_SHA256_ASN1_SIGNING: ECDSASigningAlgorithm = + ECDSASigningAlgorithm { + curve: &ec::suite_b::curve::P256, + key_alg_id: EC_PUBLIC_KEY_P256, +}; + /// Verification of ASN.1 DER-encoded ECDSA signatures using the P-256 curve /// and SHA-256. /// @@ -279,6 +361,17 @@ pub static ECDSA_P384_SHA256_ASN1: ECDSAVerificationAlgorithm = split_rs: split_rs_asn1, }; +/// Signing of fixed-length (PKCS#11 style) ECDSA signatures using the +/// P-384 curve and SHA-384. +/// +/// See "`ECDSA_*_ASN1` Details" in `ring::signature`'s module-level +/// documentation for more details. +pub static ECDSA_P384_SHA384_ASN1_SIGNING: ECDSASigningAlgorithm = + ECDSASigningAlgorithm { + curve: &ec::suite_b::curve::P384, + key_alg_id: EC_PUBLIC_KEY_P384, +}; + /// Verification of ASN.1 DER-encoded ECDSA signatures using the P-384 curve /// and SHA-384. /// @@ -291,6 +384,11 @@ 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"); + #[cfg(test)] mod tests { diff --git a/src/ec/suite_b/suite_b.rs b/src/ec/suite_b/suite_b.rs index 42d6adfd9..0f3c2950c 100644 --- a/src/ec/suite_b/suite_b.rs +++ b/src/ec/suite_b/suite_b.rs @@ -15,8 +15,9 @@ //! Elliptic curve operations on P-256 & P-384. use arithmetic::montgomery::*; -use error; +use {der, ec, error, pkcs8}; use self::ops::*; +use untrusted; // NIST SP 800-56A Step 3: "If q is an odd prime p, verify that @@ -152,6 +153,59 @@ fn verify_affine_point_is_on_the_curve_scaled( Ok(()) } +pub fn key_pair_from_pkcs8(curve: &ec::Curve, key_alg_id: &[u8], + input: untrusted::Input) + -> Result { + let (ec_private_key, _) = try!(pkcs8::unwrap_key( + pkcs8::Version::V1Only, input, key_alg_id)); + let (private_key, public_key) = try!(ec_private_key.read_all( + error::Unspecified, |input| { + // https://tools.ietf.org/html/rfc5915#section-3 + der::nested(input, der::Tag::Sequence, error::Unspecified, |input| { + let version = try!(der::small_nonnegative_integer(input)); + if version != 1 { + return Err(error::Unspecified); + } + + let private_key = try!(der::expect_tag_and_get_value( + input, der::Tag::OctetString)); + + // [0] parameters. TODO: support this. + + // [1] publicKey. The RFC says it is optional, but we require it + // to be present. + let public_key = try!(der::nested( + input, der::Tag::ContextSpecificConstructed1, + error::Unspecified, |input| { + der::bit_string_with_no_unused_bits(input) + })); + + Ok((private_key, public_key)) + }) + })); + key_pair_from_bytes(curve, private_key, public_key) +} + +pub fn key_pair_from_bytes(curve: &ec::Curve, + private_key_bytes: untrusted::Input, + public_key_bytes: untrusted::Input) + -> Result { + let private_key = try!(ec::PrivateKey::from_bytes(curve, private_key_bytes)); + + let mut public_key_check = [0; ec::PUBLIC_KEY_MAX_LEN]; + { // Borrow `public_key_check`. + let public_key_check = &mut public_key_check[..curve.public_key_len]; + try!((curve.public_from_private)(public_key_check, &private_key)); + if public_key_bytes != &*public_key_check { + return Err(error::Unspecified); + } + } + + Ok(ec::KeyPair { + private_key: private_key, + public_key: public_key_check, + }) +} pub mod curve; pub mod ecdsa; diff --git a/src/pkcs8.rs b/src/pkcs8.rs index 46092f8dc..351e2c064 100644 --- a/src/pkcs8.rs +++ b/src/pkcs8.rs @@ -20,7 +20,7 @@ use {der, error}; use untrusted; pub enum Version { - #[cfg(feature = "rsa_signing")] V1Only, + V1Only, V1OrV2, V2Only, } @@ -45,7 +45,7 @@ pub fn unwrap_key<'a>(version: Version, input: untrusted::Input<'a>, // in v1 form, so reject v2 and any later form. let require_public_key = match (try!(der::small_nonnegative_integer(input)), version) { - #[cfg(feature = "rsa_signing")] (0, Version::V1Only) => false, + (0, Version::V1Only) => false, (0, Version::V1OrV2) => false, (1, Version::V1OrV2) | (1, Version::V2Only) => true, diff --git a/src/signature.rs b/src/signature.rs index 1b7904964..abe7f19ea 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -258,13 +258,16 @@ use {error, init, private}; use untrusted; pub use ec::suite_b::ecdsa::{ + ECDSAKeyPair, ECDSAVerificationAlgorithm, ECDSA_P256_SHA256_ASN1, ECDSA_P256_SHA256_FIXED, + ECDSA_P256_SHA256_ASN1_SIGNING, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P256_SHA384_ASN1, ECDSA_P384_SHA256_ASN1, ECDSA_P384_SHA384_ASN1, ECDSA_P384_SHA384_FIXED, + ECDSA_P384_SHA384_ASN1_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING, }; pub use ec::curve25519::ed25519::{ diff --git a/tests/ecdsa_from_pkcs8_tests.txt b/tests/ecdsa_from_pkcs8_tests.txt new file mode 100644 index 000000000..3e788871c --- /dev/null +++ b/tests/ecdsa_from_pkcs8_tests.txt @@ -0,0 +1,25 @@ +Curve = P-256 +Input = 308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420090460075f15d2a256248000fb02d83ad77593dde4ae59fc5e96142dffb2bd07a14403420004cf0d13a3a7577231ea1b66cf4021cd54f21f4ac4f5f2fdd28e05bc7d2bd099d1374cd08d2ef654d6f04498db462f73e0282058dd661a4c9b0437af3f7af6e724 + +Curve = P-384 +Input = 3081b6020100301006072a8648ce3d020106052b8104002204819e30819b0201010430fc0603810412769beeabbf97ce9764e104bca45b3b7428006fb42d1fa69a344bf475ce17bf06daf553c4eccffcfecc26a1640362000417e425506a81d85e607a3caeaccbe6cc7ef58b559115b9867175ef9911f66ea77eb5b7f43e42f3129a1fe2841f6717ed4fc02bf8cfe2d10cac06a150dcba7ae9f035ec9b6b034a4ddc554da7c2da4719a1d990097fbb451a3ea1e664fc444cfa + +# The curve P-256 is encoded explicitly, instead of as a reference to a named curve. +Curve = P-256 +Input = 308201220201010420c6c1aada15b07661f8142c6caf0fdb241aff2efe46c0938b74f2bcc53052b077a081fa3081f7020101302c06072a8648ce3d0101022100ffffffff00000001000000000000000000000000ffffffffffffffffffffffff305b0420ffffffff00000001000000000000000000000000fffffffffffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b031500c49d360886e704936a6678e1139d26b7819f7e900441046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5022100ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551020101 +Error = DECODE_ERROR + +# A valid PKCS#8 P-256 private key, but without the public key in the ECPrivateKey. +Curve = P-256 +Input = 308141020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420090460075f15d2a256248000fb02d83ad77593dde4ae59fc5e96142dffb2bd07 +Error = DECODE_ERROR + +# The AlgorithmIdentifier is ecPublicKey w/ P-256, but it's an RSAPrivateKey. +Curve = P-256 +Input = 308181020100300d06092a864886f70d0101010500046d306b0201010420090460075f15d2a256248000fb02d83ad77593dde4ae59fc5e96142dffb2bd07a14403420004cf0d13a3a7577231ea1b66cf4021cd54f21f4ac4f5f2fdd28e05bc7d2bd099d1374cd08d2ef654d6f04498db462f73e0282058dd661a4c9b0437af3f7af6e724 +Error = DECODE_ERROR + +# A P-256 ECPrivateKey, but the AlgorithmIdentifier is The AlgorithmIdentifier is rsaEncryption. +Curve = P-256 +Input = 308181020100300d06092a864886f70d0101010500046d306b0201010420090460075f15d2a256248000fb02d83ad77593dde4ae59fc5e96142dffb2bd07a14403420004cf0d13a3a7577231ea1b66cf4021cd54f21f4ac4f5f2fdd28e05bc7d2bd099d1374cd08d2ef654d6f04498db462f73e0282058dd661a4c9b0437af3f7af6e724 +Error = DECODE_ERROR diff --git a/tests/ecdsa_tests.rs b/tests/ecdsa_tests.rs index 95c45de26..1f8bbefba 100644 --- a/tests/ecdsa_tests.rs +++ b/tests/ecdsa_tests.rs @@ -17,6 +17,45 @@ extern crate untrusted; use ring::{signature, test}; +#[test] +fn ecdsa_from_pkcs8_test() { + test::from_file("tests/ecdsa_from_pkcs8_tests.txt", |section, test_case| { + assert_eq!(section, ""); + + let curve_name = test_case.consume_string("Curve"); + let ((this_fixed, this_asn1), (other_fixed, other_asn1)) = + match curve_name.as_str() { + "P-256" => ((&signature::ECDSA_P256_SHA256_FIXED_SIGNING, + &signature::ECDSA_P256_SHA256_ASN1_SIGNING), + (&signature::ECDSA_P384_SHA384_FIXED_SIGNING, + &signature::ECDSA_P384_SHA384_ASN1_SIGNING)), + "P-384" => ((&signature::ECDSA_P384_SHA384_FIXED_SIGNING, + &signature::ECDSA_P384_SHA384_ASN1_SIGNING), + (&signature::ECDSA_P256_SHA256_FIXED_SIGNING, + &signature::ECDSA_P256_SHA256_ASN1_SIGNING)), + _ => unreachable!(), + }; + + let input = test_case.consume_bytes("Input"); + let input = untrusted::Input::from(&input); + + let error = test_case.consume_optional_string("Error"); + + assert_eq!( + signature::ECDSAKeyPair::from_pkcs8(this_fixed, input).is_ok(), + error.is_none()); + assert_eq!( + signature::ECDSAKeyPair::from_pkcs8(this_asn1, input).is_ok(), + error.is_none()); + assert!( + signature::ECDSAKeyPair::from_pkcs8(other_fixed, input).is_err()); + assert!( + signature::ECDSAKeyPair::from_pkcs8(other_asn1, input).is_err()); + + Ok(()) + }); +} + #[test] fn signature_ecdsa_verify_asn1_test() { test::from_file("tests/ecdsa_verify_asn1_tests.txt", |section, test_case| { @@ -77,7 +116,8 @@ fn signature_ecdsa_verify_fixed_test() { ("P-256", "SHA256") => &signature::ECDSA_P256_SHA256_FIXED, ("P-384", "SHA384") => &signature::ECDSA_P384_SHA384_FIXED, _ => { - panic!("Unsupported curve+digest: {}+{}", curve_name, digest_name); + panic!("Unsupported curve+digest: {}+{}", curve_name, + digest_name); } };