Add Ed25519 PKCS#8 support.
This commit is contained in:
parent
743cefca51
commit
eacd3b8fa0
@ -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;
|
||||
|
BIN
src/ec/curve25519/ed25519_pkcs8_v2_template.der
Normal file
BIN
src/ec/curve25519/ed25519_pkcs8_v2_template.der
Normal file
Binary file not shown.
@ -153,7 +153,6 @@ mod init;
|
||||
mod limb;
|
||||
pub mod pbkdf2;
|
||||
|
||||
#[cfg(feature = "rsa_signing")]
|
||||
mod pkcs8;
|
||||
|
||||
mod poly1305;
|
||||
|
52
src/pkcs8.rs
52
src/pkcs8.rs
@ -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))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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"))]
|
||||
|
1
tests/ed25519_test_private_key.bin
Normal file
1
tests/ed25519_test_private_key.bin
Normal file
@ -0,0 +1 @@
|
||||
<EFBFBD>a±<EFBFBD>ïýZ`º„Jô’ì,ÄDIÅi{2ip;¬®`
|
2
tests/ed25519_test_public_key.bin
Normal file
2
tests/ed25519_test_public_key.bin
Normal file
@ -0,0 +1,2 @@
|
||||
×Z<EFBFBD>‚±
|
||||
·ŐKţÓÉd:áróÚ¦#%Żh÷Q
|
@ -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
Loading…
x
Reference in New Issue
Block a user