Add Ed25519 PKCS#8 support.

This commit is contained in:
Brian Smith 2017-05-07 09:22:03 -10:00
parent 743cefca51
commit eacd3b8fa0
10 changed files with 699 additions and 606 deletions

View File

@ -14,7 +14,7 @@
//! EdDSA Signatures. //! EdDSA Signatures.
use {digest, error, private, rand, signature}; use {digest, error, pkcs8, private, rand, signature};
use super::ops::*; use super::ops::*;
use untrusted; use untrusted;
@ -33,54 +33,95 @@ pub struct Ed25519KeyPair {
public_key: PublicKey, public_key: PublicKey,
} }
/// The raw bytes of the Ed25519 key pair, for serialization.
pub struct Ed25519KeyPairBytes {
/// Private key (seed) bytes.
pub private_key: [u8; SEED_LEN],
/// Public key bytes.
pub public_key: [u8; PUBLIC_KEY_LEN],
}
impl<'a> Ed25519KeyPair { impl<'a> Ed25519KeyPair {
/// Generates a new random key pair. There is no way to extract the private /// Generates a new random key pair. There is no way to extract the private
/// key bytes to save them. If you need to save the private key bytes for /// key bytes to save them. If you need to save the private key bytes for
/// future use then use `generate_serializable()` instead. /// future use then use `generate_serializable()` instead.
pub fn generate(rng: &rand::SecureRandom) pub fn generate(rng: &rand::SecureRandom)
-> Result<Ed25519KeyPair, error::Unspecified> { -> Result<Ed25519KeyPair, error::Unspecified> {
Ed25519KeyPair::generate_serializable(rng).map(|(key_pair, _)| key_pair)
}
/// Generates a new key pair and returns the key pair as both an
/// `Ed25519KeyPair` and a `Ed25519KeyPairBytes`. There is no way to
/// extract the private key bytes from an `Ed25519KeyPair`, so extracting
/// the values from the `Ed25519KeyPairBytes` is the only way to get them.
pub fn generate_serializable(rng: &rand::SecureRandom)
-> Result<(Ed25519KeyPair, Ed25519KeyPairBytes),
error::Unspecified> {
let mut seed = [0u8; SEED_LEN]; let mut seed = [0u8; SEED_LEN];
try!(rng.fill(&mut seed)); try!(rng.fill(&mut seed));
Ok(Ed25519KeyPair::from_seed_(&seed))
let key_pair = Self::from_seed_(&seed);
let bytes = Ed25519KeyPairBytes {
private_key: seed,
public_key: key_pair.public_key,
};
Ok((key_pair, bytes))
} }
/// Copies key data from the given slices to create a new key pair. The /// Generates a new key pair and returns the key pair serialized as a
/// first slice must hold the private key and the second slice must hold /// PKCS#8 document.
/// the public key. Both slices must contain 32 little-endian-encoded
/// bytes.
/// ///
/// This is intended for use by code that deserializes key pairs. /// The PKCS#8 document will be a v2 `OneAsymmetricKey` with the public key,
/// as described in [RFC 5958 Section 2]. See also
/// https://tools.ietf.org/html/draft-ietf-curdle-pkix-04.
/// ///
/// The private and public keys will be verified to be consistent. This /// [RFC 5958 Section 2]: https://tools.ietf.org/html/rfc5958#section-2
/// helps protect, for example, against the accidental swapping of the pub fn generate_pkcs8(rng: &rand::SecureRandom)
/// public and private components of the key pair. This also detects -> Result<[u8; ED25519_PKCS8_V2_LEN], error::Unspecified> {
/// corruption that might have occurred during storage of the key pair. let mut seed = [0u8; SEED_LEN];
try!(rng.fill(&mut seed));
let key_pair = Ed25519KeyPair::from_seed_(&seed);
let mut bytes = [0; ED25519_PKCS8_V2_LEN];
let (before_seed, after_seed) =
PKCS8_TEMPLATE.split_at(PKCS8_SEED_INDEX);
bytes[..PKCS8_SEED_INDEX].copy_from_slice(before_seed);
bytes[PKCS8_SEED_INDEX..(PKCS8_SEED_INDEX + SEED_LEN)]
.copy_from_slice(&seed);
bytes[(PKCS8_SEED_INDEX + SEED_LEN)..PKCS8_PUBLIC_KEY_INDEX]
.copy_from_slice(after_seed);
bytes[PKCS8_PUBLIC_KEY_INDEX..]
.copy_from_slice(key_pair.public_key_bytes());
Ok(bytes)
}
/// Parses a PKCS#8 v2 Ed25519 private key.
///
/// The input must be in PKCS#8 v2 format, and in particular it must contain
/// the public key in addition to the private key. `from_pkcs8()` will
/// verify that the public key and the private key are consistent with each
/// other.
///
/// If you need to parse PKCS#8 v1 files (without the public key) then use
/// `Ed25519KeyPair::from_pkcs8_maybe_unchecked()` instead.
pub fn from_pkcs8(input: untrusted::Input)
-> Result<Ed25519KeyPair, error::Unspecified> {
let (seed, public_key) =
try!(pkcs8::unwrap_key(pkcs8::Version::V2Only, input,
ed25519_alg_id()));
Self::from_seed_and_public_key(seed, public_key.unwrap())
}
/// Parses a PKCS#8 v1 or v2 Ed25519 private key.
///
/// It is recommended to use `Ed25519KeyPair::from_pkcs8()`, which accepts
/// only PKCS#8 v2 files that contain the public key.
/// `from_pkcs8_maybe_unchecked()` parses PKCS#2 files exactly like
/// `from_pkcs8()`. It also accepts v1 files. PKCS#8 v1 files do not contain
/// the public key, so when a v1 file is parsed the public key will be
/// computed from the private key, and there will be no consistency check
/// between the public key and the private key.
///
/// PKCS#8 v2 files are parsed exactly like `Ed25519KeyPair::from_pkcs8()`.
pub fn from_pkcs8_maybe_unchecked(input: untrusted::Input)
-> Result<Ed25519KeyPair, error::Unspecified> {
let (seed, public_key) =
try!(pkcs8::unwrap_key(pkcs8::Version::V1OrV2, input,
ed25519_alg_id()));
if let Some(public_key) = public_key {
Self::from_seed_and_public_key(seed, public_key)
} else {
Self::from_seed_unchecked(seed)
}
}
/// Constructs a Ed25519 key pair from the private key seed `seed` and its
/// public key `public_key`.
///
/// It is recommended to use `Ed25519KeysiPair::from_pkcs8()` instead.
///
/// The private and public keys will be verified to be consistent with each
/// other. This helps avoid misuse of the key (e.g. accidentally swapping
/// the private key and public key, or using the wrong private key for the
/// public key). This also detects any corruption of the public or private
/// key.
pub fn from_seed_and_public_key(seed: untrusted::Input, pub fn from_seed_and_public_key(seed: untrusted::Input,
public_key: untrusted::Input) public_key: untrusted::Input)
-> Result<Ed25519KeyPair, error::Unspecified> { -> Result<Ed25519KeyPair, error::Unspecified> {
@ -265,3 +306,16 @@ const SIGNATURE_LEN: usize = ELEM_LEN + SCALAR_LEN;
type Seed = [u8; SEED_LEN]; type Seed = [u8; SEED_LEN];
const SEED_LEN: usize = 32; const SEED_LEN: usize = 32;
const PKCS8_TEMPLATE: &[u8] = include_bytes!("ed25519_pkcs8_v2_template.der");
const PKCS8_SEED_INDEX: usize = 0x0e;
const PKCS8_PUBLIC_KEY_INDEX: usize = 0x33;
#[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()`
/// may generate files of a different length in the future.
pub const ED25519_PKCS8_V2_LEN: usize = 0x53;

Binary file not shown.

View File

@ -153,7 +153,6 @@ mod init;
mod limb; mod limb;
pub mod pbkdf2; pub mod pbkdf2;
#[cfg(feature = "rsa_signing")]
mod pkcs8; mod pkcs8;
mod poly1305; mod poly1305;

View File

@ -12,29 +12,45 @@
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//! PKCS#8 is specified in [RFC 5958].
//!
//! [RFC 5958]: https://tools.ietf.org/html/rfc5958.
use {der, error}; use {der, error};
use untrusted; use untrusted;
/// Parses an unencrypted PKCS#8 privatg key, verifies that it is the right type pub enum Version {
/// of key, and returns the key value. `alg_id` must be the encoded value (not #[cfg(feature = "rsa_signing")] V1Only,
/// including the outermost `SEQUENCE` tag and length) of the V1OrV2,
/// `AlgorithmIdentifier` that identifies the key type. The result will be an V2Only,
/// encoded `RSAPrivateKey` or `ECPrivateKey` or similar. }
/// 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]. Only v1 keys are supported, as none of /// `alg_id` must be the encoded value (not including the outermost `SEQUENCE`
/// the algorithms we support require v2 support. /// tag and length) of the `AlgorithmIdentifier` that identifies the key type.
/// The result will be an encoded `RSAPrivateKey` or `ECPrivateKey` or similar.
///
/// PKCS#8 is specified in [RFC 5958].
/// ///
/// [RFC 5958]: https://tools.ietf.org/html/rfc5958. /// [RFC 5958]: https://tools.ietf.org/html/rfc5958.
pub fn unwrap_key<'a>(input: untrusted::Input<'a>, alg_id: &[u8]) pub fn unwrap_key<'a>(version: Version, input: untrusted::Input<'a>,
-> Result<untrusted::Input<'a>, error::Unspecified> { alg_id: &[u8])
-> Result<(untrusted::Input<'a>, Option<untrusted::Input<'a>>),
error::Unspecified> {
input.read_all(error::Unspecified, |input| { input.read_all(error::Unspecified, |input| {
der::nested(input, der::Tag::Sequence, error::Unspecified, |input| { der::nested(input, der::Tag::Sequence, error::Unspecified, |input| {
// Currently we only support algorithms that should only be encoded // Currently we only support algorithms that should only be encoded
// in v1 form, so reject v2 and any later form. // in v1 form, so reject v2 and any later form.
let version = try!(der::small_nonnegative_integer(input)); let require_public_key =
if version != 0 { match (try!(der::small_nonnegative_integer(input)), version) {
return Err(error::Unspecified); #[cfg(feature = "rsa_signing")] (0, Version::V1Only) => false,
} (0, Version::V1OrV2) => false,
(1, Version::V1OrV2) |
(1, Version::V2Only) => true,
_ => { return Err(error::Unspecified); }
};
let actual_alg_id = let actual_alg_id =
try!(der::expect_tag_and_get_value(input, der::Tag::Sequence)); try!(der::expect_tag_and_get_value(input, der::Tag::Sequence));
@ -51,7 +67,15 @@ pub fn unwrap_key<'a>(input: untrusted::Input<'a>, alg_id: &[u8])
der::Tag::ContextSpecificConstructed0)); der::Tag::ContextSpecificConstructed0));
} }
Ok(private_key) let public_key = if require_public_key {
Some(try!(der::nested(
input, der::Tag::ContextSpecificConstructed1,
error::Unspecified, der::bit_string_with_no_unused_bits)))
} else {
None
};
Ok((private_key, public_key))
}) })
}) })
} }

View File

@ -142,7 +142,8 @@ impl RSAKeyPair {
-> Result<RSAKeyPair, error::Unspecified> { -> Result<RSAKeyPair, error::Unspecified> {
const RSA_ENCRYPTION: &'static [u8] = const RSA_ENCRYPTION: &'static [u8] =
include_bytes!("../data/alg-rsa-encryption.der"); include_bytes!("../data/alg-rsa-encryption.der");
let der = try!(pkcs8::unwrap_key(input, &RSA_ENCRYPTION)); let (der, _) = try!(pkcs8::unwrap_key(pkcs8::Version::V1Only, input,
&RSA_ENCRYPTION));
Self::from_der(der) Self::from_der(der)
} }

View File

@ -138,30 +138,25 @@
//! use ring::{rand, signature}; //! use ring::{rand, signature};
//! //!
//! # fn sign_and_verify_ed25519() -> Result<(), ring::error::Unspecified> { //! # fn sign_and_verify_ed25519() -> Result<(), ring::error::Unspecified> {
//! // Generate a key pair. //! // Generate a key pair in PKCS#8 (v2) format.
//! let rng = rand::SystemRandom::new(); //! let rng = rand::SystemRandom::new();
//! let (generated, generated_bytes) = //! let pkcs8_bytes = try!(signature::Ed25519KeyPair::generate_pkcs8(&rng));
//! try!(signature::Ed25519KeyPair::generate_serializable(&rng));
//! //!
//! // Normally after generating the key pair, the application would extract //! // Normally the application would store the PKCS#8 file persistently. Later
//! // the private and public components and store them persistently for future //! // it would read the PKCS#8 file from persistent storage to use it.
//! // use.
//! //!
//! // Normally the application would later deserialize the private and public //! let key_pair =
//! // key from storage and then create an `Ed25519KeyPair` from the //! try!(signature::Ed25519KeyPair::from_pkcs8(
//! // deserialized bytes. //! untrusted::Input::from(&pkcs8_bytes)));
//! let key_pair = try!(signature::Ed25519KeyPair::from_seed_and_public_key(
//! untrusted::Input::from(&generated_bytes.private_key),
//! untrusted::Input::from(&generated_bytes.public_key)));
//! //!
//! // Sign the message "hello, world". //! // Sign the message "hello, world".
//! const MESSAGE: &'static [u8] = b"hello, world"; //! const MESSAGE: &'static [u8] = b"hello, world";
//! let sig = key_pair.sign(MESSAGE); //! let sig = key_pair.sign(MESSAGE);
//! //!
//! // Normally an application would extract the bytes of the signature and send //! // Normally an application would extract the bytes of the signature and
//! // them in a protocol message to the peer(s). Here we just use the public //! // send them in a protocol message to the peer(s). Here we just get the
//! // key from the private key we just generated. //! // public key key directly from the key pair.
//! let peer_public_key_bytes = &generated_bytes.public_key; //! let peer_public_key_bytes = key_pair.public_key_bytes();
//! let sig_bytes = sig.as_ref(); //! let sig_bytes = sig.as_ref();
//! //!
//! // Verify the signature of the message using the public key. Normally the //! // Verify the signature of the message using the public key. Normally the
@ -278,7 +273,7 @@ pub use ec::curve25519::ed25519::{
ED25519, ED25519,
Ed25519KeyPair, Ed25519KeyPair,
Ed25519KeyPairBytes ED25519_PKCS8_V2_LEN,
}; };
#[cfg(all(feature = "rsa_signing", feature = "use_heap"))] #[cfg(all(feature = "rsa_signing", feature = "use_heap"))]

View File

@ -0,0 +1 @@
<EFBFBD><EFBFBD>ïýZ`º„Jôì,ÄDIÅi{2ip;¬®`

View File

@ -0,0 +1,2 @@
×Z<EFBFBD>‚±
·ŐKţÓÉd:áróÚ¦#%ŻQ

View File

@ -15,7 +15,7 @@
extern crate ring; extern crate ring;
extern crate untrusted; extern crate untrusted;
use ring::{rand, signature, test}; use ring::{signature, test};
use signature::Ed25519KeyPair; use signature::Ed25519KeyPair;
/// Test vectors from BoringSSL. /// Test vectors from BoringSSL.
@ -23,51 +23,67 @@ use signature::Ed25519KeyPair;
fn test_signature_ed25519() { fn test_signature_ed25519() {
test::from_file("tests/ed25519_tests.txt", |section, test_case| { test::from_file("tests/ed25519_tests.txt", |section, test_case| {
assert_eq!(section, ""); assert_eq!(section, "");
let private_key = test_case.consume_bytes("PRIV"); let seed = test_case.consume_bytes("SEED");
assert_eq!(64, private_key.len()); assert_eq!(32, seed.len());
let seed = untrusted::Input::from(&seed);
let public_key = test_case.consume_bytes("PUB"); let public_key = test_case.consume_bytes("PUB");
assert_eq!(32, public_key.len()); assert_eq!(32, public_key.len());
let public_key = untrusted::Input::from(&public_key);
let msg = test_case.consume_bytes("MESSAGE"); let msg = test_case.consume_bytes("MESSAGE");
let expected_sig = test_case.consume_bytes("SIG"); let expected_sig = test_case.consume_bytes("SIG");
let key_pair = {
Ed25519KeyPair::from_seed_and_public_key( let key_pair = Ed25519KeyPair::from_seed_and_public_key(
untrusted::Input::from(&private_key[..32]), seed, public_key).unwrap();
untrusted::Input::from(&public_key)).unwrap(); let actual_sig = key_pair.sign(&msg);
assert_eq!(&expected_sig[..], actual_sig.as_ref());
}
// Test PKCS#8 generation, parsing, and private-to-public calculations.
let rng = test::rand::FixedSliceRandom {
bytes: seed.as_slice_less_safe()
};
let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
let key_pair = Ed25519KeyPair::from_pkcs8(
untrusted::Input::from(&pkcs8)).unwrap();
assert_eq!(public_key, key_pair.public_key_bytes());
// Test Signature generation.
let actual_sig = key_pair.sign(&msg); let actual_sig = key_pair.sign(&msg);
assert_eq!(&expected_sig[..], actual_sig.as_ref()); assert_eq!(&expected_sig[..], actual_sig.as_ref());
let public_key = untrusted::Input::from(&public_key); // Test Signature verification.
let msg = untrusted::Input::from(&msg); assert!(signature::verify(
let expected_sig = untrusted::Input::from(&expected_sig); &signature::ED25519, public_key, untrusted::Input::from(&msg),
untrusted::Input::from(&expected_sig)).is_ok());
assert!(signature::verify(&signature::ED25519, public_key, msg,
expected_sig).is_ok());
Ok(()) Ok(())
}); });
} }
#[test] #[test]
fn test_ed25519_from_seed_and_public_key_misuse() { fn test_ed25519_from_seed_and_public_key_misuse() {
let rng = rand::SystemRandom::new(); const PRIVATE_KEY: &[u8] = include_bytes!("ed25519_test_private_key.bin");
let (_, bytes) = Ed25519KeyPair::generate_serializable(&rng).unwrap(); const PUBLIC_KEY: &[u8] = include_bytes!("ed25519_test_public_key.bin");
assert!(Ed25519KeyPair::from_seed_and_public_key( assert!(Ed25519KeyPair::from_seed_and_public_key(
untrusted::Input::from(&bytes.private_key), untrusted::Input::from(PRIVATE_KEY),
untrusted::Input::from(&bytes.public_key)).is_ok()); untrusted::Input::from(PUBLIC_KEY)).is_ok());
// Truncated private key. // Truncated private key.
assert!(Ed25519KeyPair::from_seed_and_public_key( assert!(Ed25519KeyPair::from_seed_and_public_key(
untrusted::Input::from(&bytes.private_key[..31]), untrusted::Input::from(&PRIVATE_KEY[..31]),
untrusted::Input::from(&bytes.public_key)).is_err()); untrusted::Input::from(PUBLIC_KEY)).is_err());
// Truncated public key. // Truncated public key.
assert!(Ed25519KeyPair::from_seed_and_public_key( assert!(Ed25519KeyPair::from_seed_and_public_key(
untrusted::Input::from(&bytes.private_key), untrusted::Input::from(PRIVATE_KEY),
untrusted::Input::from(&bytes.public_key[..31])).is_err()); untrusted::Input::from(&PUBLIC_KEY[..31])).is_err());
// Swapped public and private key. // Swapped public and private key.
assert!(Ed25519KeyPair::from_seed_and_public_key( assert!(Ed25519KeyPair::from_seed_and_public_key(
untrusted::Input::from(&bytes.public_key), untrusted::Input::from(PUBLIC_KEY),
untrusted::Input::from(&bytes.private_key)).is_err()); untrusted::Input::from(PRIVATE_KEY)).is_err());
} }

File diff suppressed because it is too large Load Diff