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.
use {digest, error, private, rand, signature};
use {digest, error, pkcs8, private, rand, signature};
use super::ops::*;
use untrusted;
@ -33,54 +33,95 @@ pub struct Ed25519KeyPair {
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 {
/// 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
/// future use then use `generate_serializable()` instead.
pub fn generate(rng: &rand::SecureRandom)
-> 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];
try!(rng.fill(&mut seed));
let key_pair = Self::from_seed_(&seed);
let bytes = Ed25519KeyPairBytes {
private_key: seed,
public_key: key_pair.public_key,
};
Ok((key_pair, bytes))
Ok(Ed25519KeyPair::from_seed_(&seed))
}
/// Copies key data from the given slices to create a new key pair. The
/// first slice must hold the private key and the second slice must hold
/// the public key. Both slices must contain 32 little-endian-encoded
/// bytes.
/// Generates a new key pair and returns the key pair serialized as a
/// PKCS#8 document.
///
/// 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
/// helps protect, for example, against the accidental swapping of the
/// public and private components of the key pair. This also detects
/// corruption that might have occurred during storage of the key pair.
/// [RFC 5958 Section 2]: https://tools.ietf.org/html/rfc5958#section-2
pub fn generate_pkcs8(rng: &rand::SecureRandom)
-> Result<[u8; ED25519_PKCS8_V2_LEN], error::Unspecified> {
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,
public_key: untrusted::Input)
-> Result<Ed25519KeyPair, error::Unspecified> {
@ -265,3 +306,16 @@ 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 = 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;
pub mod pbkdf2;
#[cfg(feature = "rsa_signing")]
mod pkcs8;
mod poly1305;

View File

@ -12,29 +12,45 @@
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// 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 untrusted;
/// Parses an unencrypted PKCS#8 privatg key, verifies that it is the right type
/// of key, and returns the key value. `alg_id` must be the encoded value (not
/// including the outermost `SEQUENCE` tag and length) of the
/// `AlgorithmIdentifier` that identifies the key type. The result will be an
/// encoded `RSAPrivateKey` or `ECPrivateKey` or similar.
pub enum Version {
#[cfg(feature = "rsa_signing")] V1Only,
V1OrV2,
V2Only,
}
/// 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
/// the algorithms we support require v2 support.
/// `alg_id` must be the encoded value (not including the outermost `SEQUENCE`
/// 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.
pub fn unwrap_key<'a>(input: untrusted::Input<'a>, alg_id: &[u8])
-> Result<untrusted::Input<'a>, error::Unspecified> {
pub fn unwrap_key<'a>(version: Version, input: untrusted::Input<'a>,
alg_id: &[u8])
-> Result<(untrusted::Input<'a>, Option<untrusted::Input<'a>>),
error::Unspecified> {
input.read_all(error::Unspecified, |input| {
der::nested(input, der::Tag::Sequence, error::Unspecified, |input| {
// Currently we only support algorithms that should only be encoded
// in v1 form, so reject v2 and any later form.
let version = try!(der::small_nonnegative_integer(input));
if version != 0 {
return Err(error::Unspecified);
}
let require_public_key =
match (try!(der::small_nonnegative_integer(input)), version) {
#[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 =
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));
}
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> {
const RSA_ENCRYPTION: &'static [u8] =
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)
}

View File

@ -138,30 +138,25 @@
//! use ring::{rand, signature};
//!
//! # 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 (generated, generated_bytes) =
//! try!(signature::Ed25519KeyPair::generate_serializable(&rng));
//! let pkcs8_bytes = try!(signature::Ed25519KeyPair::generate_pkcs8(&rng));
//!
//! // Normally after generating the key pair, the application would extract
//! // the private and public components and store them persistently for future
//! // use.
//! // Normally the application would store the PKCS#8 file persistently. Later
//! // it would read the PKCS#8 file from persistent storage to use it.
//!
//! // Normally the application would later deserialize the private and public
//! // key from storage and then create an `Ed25519KeyPair` from the
//! // deserialized 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)));
//! let key_pair =
//! try!(signature::Ed25519KeyPair::from_pkcs8(
//! untrusted::Input::from(&pkcs8_bytes)));
//!
//! // Sign the message "hello, world".
//! const MESSAGE: &'static [u8] = b"hello, world";
//! let sig = key_pair.sign(MESSAGE);
//!
//! // Normally an application would extract the bytes of the signature and send
//! // them in a protocol message to the peer(s). Here we just use the public
//! // key from the private key we just generated.
//! let peer_public_key_bytes = &generated_bytes.public_key;
//! // Normally an application would extract the bytes of the signature and
//! // send them in a protocol message to the peer(s). Here we just get the
//! // public key key directly from the key pair.
//! let peer_public_key_bytes = key_pair.public_key_bytes();
//! let sig_bytes = sig.as_ref();
//!
//! // Verify the signature of the message using the public key. Normally the
@ -278,7 +273,7 @@ pub use ec::curve25519::ed25519::{
ED25519,
Ed25519KeyPair,
Ed25519KeyPairBytes
ED25519_PKCS8_V2_LEN,
};
#[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 untrusted;
use ring::{rand, signature, test};
use ring::{signature, test};
use signature::Ed25519KeyPair;
/// Test vectors from BoringSSL.
@ -23,51 +23,67 @@ use signature::Ed25519KeyPair;
fn test_signature_ed25519() {
test::from_file("tests/ed25519_tests.txt", |section, test_case| {
assert_eq!(section, "");
let private_key = test_case.consume_bytes("PRIV");
assert_eq!(64, private_key.len());
let seed = test_case.consume_bytes("SEED");
assert_eq!(32, seed.len());
let seed = untrusted::Input::from(&seed);
let public_key = test_case.consume_bytes("PUB");
assert_eq!(32, public_key.len());
let public_key = untrusted::Input::from(&public_key);
let msg = test_case.consume_bytes("MESSAGE");
let expected_sig = test_case.consume_bytes("SIG");
let key_pair =
Ed25519KeyPair::from_seed_and_public_key(
untrusted::Input::from(&private_key[..32]),
untrusted::Input::from(&public_key)).unwrap();
{
let key_pair = Ed25519KeyPair::from_seed_and_public_key(
seed, 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);
assert_eq!(&expected_sig[..], actual_sig.as_ref());
let public_key = untrusted::Input::from(&public_key);
let msg = untrusted::Input::from(&msg);
let expected_sig = untrusted::Input::from(&expected_sig);
assert!(signature::verify(&signature::ED25519, public_key, msg,
expected_sig).is_ok());
// Test Signature verification.
assert!(signature::verify(
&signature::ED25519, public_key, untrusted::Input::from(&msg),
untrusted::Input::from(&expected_sig)).is_ok());
Ok(())
});
}
#[test]
fn test_ed25519_from_seed_and_public_key_misuse() {
let rng = rand::SystemRandom::new();
let (_, bytes) = Ed25519KeyPair::generate_serializable(&rng).unwrap();
const PRIVATE_KEY: &[u8] = include_bytes!("ed25519_test_private_key.bin");
const PUBLIC_KEY: &[u8] = include_bytes!("ed25519_test_public_key.bin");
assert!(Ed25519KeyPair::from_seed_and_public_key(
untrusted::Input::from(&bytes.private_key),
untrusted::Input::from(&bytes.public_key)).is_ok());
untrusted::Input::from(PRIVATE_KEY),
untrusted::Input::from(PUBLIC_KEY)).is_ok());
// Truncated private key.
assert!(Ed25519KeyPair::from_seed_and_public_key(
untrusted::Input::from(&bytes.private_key[..31]),
untrusted::Input::from(&bytes.public_key)).is_err());
untrusted::Input::from(&PRIVATE_KEY[..31]),
untrusted::Input::from(PUBLIC_KEY)).is_err());
// Truncated public key.
assert!(Ed25519KeyPair::from_seed_and_public_key(
untrusted::Input::from(&bytes.private_key),
untrusted::Input::from(&bytes.public_key[..31])).is_err());
untrusted::Input::from(PRIVATE_KEY),
untrusted::Input::from(&PUBLIC_KEY[..31])).is_err());
// Swapped public and private key.
assert!(Ed25519KeyPair::from_seed_and_public_key(
untrusted::Input::from(&bytes.public_key),
untrusted::Input::from(&bytes.private_key)).is_err());
untrusted::Input::from(PUBLIC_KEY),
untrusted::Input::from(PRIVATE_KEY)).is_err());
}

File diff suppressed because it is too large Load Diff