use subtle::ConstantTimeEq as _;
use zeroize::{Zeroize as _, Zeroizing};
use super::der::{self, Reader, Writer};
use super::jwk_build;
use super::pkcs8;
use super::spki;
use super::types::{AlgId, Pkcs8Parts, SpkiParts};
use crate::error::{Error, Result};
use crate::jwk::{Jwk, OkpCurve, OkpKey};
const KEY_LEN: usize = 32;
fn curve(alg: AlgId) -> Result<OkpCurve> {
match alg {
AlgId::Ed25519 => Ok(OkpCurve::Ed25519),
AlgId::X25519 => Ok(OkpCurve::X25519),
AlgId::Ec(_) | AlgId::RsaEncryption => Err(Error::InvalidKey("the key is not an OKP key")),
}
}
fn alg_id(crv: OkpCurve) -> AlgId {
match crv {
OkpCurve::Ed25519 => AlgId::Ed25519,
OkpCurve::X25519 => AlgId::X25519,
}
}
const PKCS8_V1: u8 = 0;
fn curve_private_key(private_key: &[u8]) -> Result<Zeroizing<[u8; KEY_LEN]>> {
let mut reader = Reader::new(private_key)?;
let key = reader.octet_string()?;
reader.finish()?;
if key.len() != KEY_LEN {
return Err(Error::InvalidKey("CurvePrivateKey is not 32 octets"));
}
let mut d = Zeroizing::new([0u8; KEY_LEN]);
d.copy_from_slice(key);
Ok(d)
}
fn ed25519_public(d: &[u8; KEY_LEN]) -> [u8; KEY_LEN] {
ed25519_dalek::SigningKey::from_bytes(d)
.verifying_key()
.to_bytes()
}
pub(super) fn x25519_public(d: &[u8; KEY_LEN]) -> [u8; KEY_LEN] {
let mut copy = *d;
x25519_public_from_copy(&mut copy)
}
fn x25519_public_from_copy(copy: &mut [u8; KEY_LEN]) -> [u8; KEY_LEN] {
let secret = x25519_dalek::StaticSecret::from(*copy);
copy.zeroize();
x25519_dalek::PublicKey::from(&secret).to_bytes()
}
pub(crate) fn from_pkcs8(parts: Pkcs8Parts<'_>) -> Result<Jwk> {
let crv = curve(parts.alg)?;
let d = curve_private_key(parts.private_key)?;
let embedded: Option<[u8; KEY_LEN]> = parts
.public_key
.map(|key| {
key.try_into()
.map_err(|_| Error::InvalidKey("PKCS#8 publicKey is not 32 octets"))
})
.transpose()?;
let x = match crv {
OkpCurve::Ed25519 => embedded.unwrap_or_else(|| ed25519_public(&d)),
OkpCurve::X25519 => {
let derived = x25519_public(&d);
if embedded.is_some_and(|embedded| !bool::from(derived[..].ct_eq(&embedded[..]))) {
return Err(Error::InvalidKey(
"X25519 PKCS#8 publicKey does not match the private key",
));
}
derived
}
};
jwk_build::okp_jwk(crv, &x, Some(d.as_slice()), None)
}
pub(crate) fn to_pkcs8(key: &OkpKey) -> Result<Zeroizing<Vec<u8>>> {
let d = key.d().ok_or(Error::InvalidKey("private key required"))?;
let mut writer = Writer::new(der::encoded_len(d.len()))?;
writer.element(der::OCTET_STRING, d)?;
let curve_private_key = writer.finish()?;
pkcs8::encode(&Pkcs8Parts {
version: PKCS8_V1,
alg: alg_id(key.crv()),
private_key: &curve_private_key,
public_key: None,
})
}
pub(crate) fn from_spki(parts: SpkiParts<'_>) -> Result<Jwk> {
let crv = curve(parts.alg)?;
if parts.subject_public_key.len() != KEY_LEN {
return Err(Error::InvalidKey("OKP subjectPublicKey is not 32 octets"));
}
jwk_build::okp_jwk(crv, parts.subject_public_key, None, None)
}
pub(crate) fn to_spki(key: &OkpKey) -> Result<Zeroizing<Vec<u8>>> {
spki::encode(&SpkiParts {
alg: alg_id(key.crv()),
subject_public_key: key.x(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::jwk::{EcCurve, KeyMaterial};
use crate::keys::der::fixtures;
use crate::keys::pem;
use crate::keys::types::Label;
fn jwk_fixture(dir: &str, file: &str) -> Jwk {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/data/keys")
.join(dir)
.join(file);
let bytes = std::fs::read(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
Jwk::from_json(&bytes).unwrap_or_else(|e| panic!("{file}: {e:?}"))
}
fn assert_same_key(actual: &Jwk, expected: &Jwk, what: &str) {
assert_eq!(*actual.to_json(), *expected.to_json(), "{what}");
}
fn okp(jwk: &Jwk) -> &OkpKey {
let KeyMaterial::Okp(key) = jwk.material() else {
panic!("an OKP key")
};
key
}
fn import_pkcs8(der: &[u8]) -> Result<Jwk> {
from_pkcs8(pkcs8::decode(der)?)
}
fn import_spki(der: &[u8]) -> Result<Jwk> {
from_spki(spki::decode(der)?)
}
#[test]
fn rfc_spki_vectors_import_and_export_byte_for_byte() {
let names = fixtures::files("rfc", |name| {
(name.starts_with("rfc8037-") || name.starts_with("rfc8410-")) && name.contains("spki")
});
assert_eq!(names.len(), 7, "{names:?}");
for name in names {
let der = fixtures::load("rfc", &name);
let stem = name.rsplit_once('.').unwrap().0;
let jwk = import_spki(&der).unwrap_or_else(|e| panic!("{name}: {e:?}"));
assert!(jwk.is_public(), "{name}");
assert_same_key(
&jwk,
&jwk_fixture("rfc", &format!("{stem}.jwk.json")),
&name,
);
assert_eq!(*to_spki(okp(&jwk)).unwrap(), der, "{name}");
}
}
#[test]
fn rfc8410_10_1_public_key() {
let der = fixtures::load("rfc", "rfc8410-10.1-ed25519-spki.pem");
let jwk = import_spki(&der).unwrap();
let expected = Jwk::from_json(
br#"{"kty":"OKP","crv":"Ed25519","x":"Gb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE"}"#,
)
.unwrap();
assert_same_key(&jwk, &expected, "RFC 8410 §10.1");
}
#[test]
fn rfc_pkcs8_vectors_import_and_export_byte_for_byte() {
let names = fixtures::files("rfc", |name| {
(name.starts_with("rfc8037-") || name.starts_with("rfc8410-")) && name.contains("pkcs8")
});
assert_eq!(names.len(), 6, "{names:?}");
for name in names {
let der = fixtures::load("rfc", &name);
let stem = name.rsplit_once('.').unwrap().0;
let jwk = import_pkcs8(&der).unwrap_or_else(|e| panic!("{name}: {e:?}"));
assert!(!jwk.is_public(), "{name}");
assert_same_key(
&jwk,
&jwk_fixture("rfc", &format!("{stem}.jwk.json")),
&name,
);
let exported = if name.contains("-v2-") {
fixtures::load("rfc", "rfc8410-10.3-ed25519-pkcs8-v1.pem")
} else {
der
};
assert_eq!(*to_pkcs8(okp(&jwk)).unwrap(), exported, "{name}");
}
}
#[test]
fn rfc8410_10_3_v1_and_v2_are_one_key() {
let v1 = fixtures::load("rfc", "rfc8410-10.3-ed25519-pkcs8-v1.pem");
let v2 = fixtures::load("rfc", "rfc8410-10.3-ed25519-pkcs8-v2-attr-pub.pem");
assert_eq!(pkcs8::decode(&v2).unwrap().version, 1);
let from_v1 = import_pkcs8(&v1).unwrap();
let from_v2 = import_pkcs8(&v2).unwrap();
assert_same_key(&from_v1, &from_v2, "v1 and v2");
assert_same_key(
&from_v1,
&jwk_fixture("rfc", "rfc8410-10.3-ed25519-pkcs8-v1.jwk.json"),
"§10.3",
);
assert_eq!(*to_pkcs8(okp(&from_v2)).unwrap(), v1);
assert_eq!(
*to_spki(okp(&from_v2)).unwrap(),
fixtures::load("rfc", "rfc8410-10.1-ed25519-spki.pem")
);
}
#[test]
fn openssl_keys_round_trip_byte_identical() {
let names = fixtures::files("openssl", |name| {
name.starts_with("ed25519-") || name.starts_with("x25519-")
});
assert_eq!(names.len(), 8, "{names:?}");
for name in names {
let der = fixtures::load("openssl", &name);
let stem = name.rsplit_once('.').unwrap().0;
let (jwk, exported, label) = if name.contains("pkcs8") {
let jwk = import_pkcs8(&der).unwrap_or_else(|e| panic!("{name}: {e:?}"));
let exported = to_pkcs8(okp(&jwk)).unwrap();
(jwk, exported, Label::PrivateKey)
} else {
let jwk = import_spki(&der).unwrap_or_else(|e| panic!("{name}: {e:?}"));
let exported = to_spki(okp(&jwk)).unwrap();
(jwk, exported, Label::PublicKey)
};
assert_same_key(
&jwk,
&jwk_fixture("openssl/expected", &format!("{stem}.jwk.json")),
&name,
);
assert_eq!(*exported, der, "{name}");
if name.ends_with(".pem") {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/data/keys/openssl")
.join(&name);
let text = std::fs::read_to_string(path).unwrap();
assert_eq!(*pem::encode(label, &exported).unwrap(), text, "{name}");
}
}
}
#[test]
fn okp_pkcs8_negative_vectors_fail_with_the_manifest_code() {
let vectors: Vec<_> = fixtures::negatives()
.into_iter()
.filter(|n| n.id.starts_with("N2"))
.collect();
let ids: Vec<&str> = vectors.iter().map(|n| n.id.as_str()).collect();
assert_eq!(
ids,
["N20", "N21", "N22", "N23", "N24", "N25", "N26", "N27"]
);
for vector in vectors {
let err = import_pkcs8(&fixtures::load("negative", &vector.file)).unwrap_err();
assert_eq!(err.code(), vector.expect, "{}: {err:?}", vector.id);
}
let reason = |id: &str| import_pkcs8(&fixtures::negative(id)).unwrap_err();
assert_eq!(
reason("N25"),
Error::InvalidKey("CurvePrivateKey is not 32 octets")
);
assert!(
matches!(reason("N26"), Error::InvalidKey(detail) if detail.contains("tag")),
"{:?}",
reason("N26")
);
assert!(
matches!(reason("N27"), Error::InvalidKey(detail) if detail.starts_with("Ed25519")),
"{:?}",
reason("N27")
);
}
#[test]
fn ed448_x448_and_encrypted_keys_are_unsupported() {
let names = fixtures::files("openssl", |name| {
name.starts_with("reject-ed448-")
|| name.starts_with("reject-x448-")
|| name.starts_with("reject-ed25519-")
});
assert_eq!(names.len(), 4, "{names:?}");
for name in names {
let der = fixtures::load("openssl", &name);
let result = if name.contains("spki") {
import_spki(&der)
} else {
import_pkcs8(&der)
};
assert_eq!(result.unwrap_err(), Error::UnsupportedAlgorithm, "{name}");
}
}
#[test]
fn spki_keys_that_are_not_32_octets_are_rejected() {
for alg in [AlgId::Ed25519, AlgId::X25519] {
for len in [0, 1, 31, 33, 57, 64] {
let key = vec![0x09; len];
let der = spki::encode(&SpkiParts {
alg,
subject_public_key: &key,
})
.unwrap();
assert_eq!(
import_spki(&der).unwrap_err(),
Error::InvalidKey("OKP subjectPublicKey is not 32 octets"),
"{alg:?} {len}"
);
}
}
}
fn x25519_v2(public_key: &[u8]) -> Zeroizing<Vec<u8>> {
let v1 = fixtures::load("rfc", "rfc8037-a6-x25519-pkcs8.der");
let parts = pkcs8::decode(&v1).unwrap();
pkcs8::encode(&Pkcs8Parts {
version: 1,
public_key: Some(public_key),
..parts
})
.unwrap()
}
#[test]
fn x25519_v2_public_key_must_be_the_key_of_d() {
let jwk = jwk_fixture("rfc", "rfc8037-a6-x25519-pkcs8.jwk.json");
let alice = okp(&jwk).x().to_vec();
let accepted = import_pkcs8(&x25519_v2(&alice)).unwrap();
assert_same_key(&accepted, &jwk, "v2 with Alice's public key");
assert_eq!(
*to_pkcs8(okp(&accepted)).unwrap(),
fixtures::load("rfc", "rfc8037-a6-x25519-pkcs8.der")
);
let bob = okp(&jwk_fixture("rfc", "rfc8037-a6-x25519-bob-spki.jwk.json"))
.x()
.to_vec();
let mut flipped = alice.clone();
flipped[0] ^= 0x01;
let mut high_bit = alice.clone();
high_bit[31] |= 0x80;
assert_ne!(high_bit, alice);
for (what, key) in [("Bob's", bob), ("flipped", flipped), ("high bit", high_bit)] {
assert_eq!(
import_pkcs8(&x25519_v2(&key)).unwrap_err(),
Error::InvalidKey("X25519 PKCS#8 publicKey does not match the private key"),
"{what}"
);
}
}
#[test]
fn v2_public_keys_that_are_not_32_octets_are_rejected() {
let ed25519 = fixtures::load("rfc", "rfc8037-a1-ed25519-pkcs8.der");
for len in [0, 31, 33, 64] {
let key = vec![0x5A; len];
let ed = pkcs8::encode(&Pkcs8Parts {
version: 1,
public_key: Some(&key),
..pkcs8::decode(&ed25519).unwrap()
})
.unwrap();
for der in [&ed[..], &x25519_v2(&key)[..]] {
assert_eq!(
import_pkcs8(der).unwrap_err(),
Error::InvalidKey("PKCS#8 publicKey is not 32 octets"),
"{len}"
);
}
}
}
#[test]
fn malformed_curve_private_keys_are_rejected() {
let a1 = fixtures::load("rfc", "rfc8037-a1-ed25519-pkcs8.der");
let parts = pkcs8::decode(&a1).unwrap();
let seed = &parts.private_key[2..];
let mut trailing = parts.private_key.to_vec();
trailing.extend_from_slice(&[0x05, 0x00]);
let mut long = vec![0x04, 0x21];
long.extend_from_slice(seed);
long.push(0x00);
let cases: [(&str, &[u8]); 5] = [
("empty", &[]),
("bare seed", seed),
("trailing NULL", &trailing),
("33 octets", &long),
("empty OCTET STRING", &[0x04, 0x00]),
];
for (what, private_key) in cases {
let err = from_pkcs8(Pkcs8Parts {
private_key,
..parts
})
.unwrap_err();
assert!(matches!(err, Error::InvalidKey(_)), "{what}: {err:?}");
}
}
#[test]
fn non_okp_containers_are_rejected() {
let a1 = fixtures::load("rfc", "rfc8037-a1-ed25519-pkcs8.der");
let parts = pkcs8::decode(&a1).unwrap();
let spki_der = fixtures::load("rfc", "rfc8037-a2-ed25519-spki.der");
let public = spki::decode(&spki_der).unwrap();
for alg in [AlgId::Ec(EcCurve::P256), AlgId::RsaEncryption] {
let not_okp = Error::InvalidKey("the key is not an OKP key");
assert_eq!(
from_pkcs8(Pkcs8Parts { alg, ..parts }).unwrap_err(),
not_okp
);
assert_eq!(from_spki(SpkiParts { alg, ..public }).unwrap_err(), not_okp);
}
}
#[test]
fn a_public_key_has_no_pkcs8_export() {
let jwk = jwk_fixture("rfc", "rfc8037-a2-ed25519-spki.jwk.json");
assert_eq!(
to_pkcs8(okp(&jwk)).unwrap_err(),
Error::InvalidKey("private key required")
);
}
#[test]
fn ed25519_v2_public_key_of_another_key_is_rejected() {
let v2 = fixtures::load("rfc", "rfc8410-10.3-ed25519-pkcs8-v2-attr-pub.pem");
let other = okp(&jwk_fixture("rfc", "rfc8037-a2-ed25519-spki.jwk.json"))
.x()
.to_vec();
let der = pkcs8::encode(&Pkcs8Parts {
public_key: Some(&other),
..pkcs8::decode(&v2).unwrap()
})
.unwrap();
assert_eq!(
import_pkcs8(&der).unwrap_err(),
Error::InvalidKey("Ed25519 private key d does not match x")
);
}
const MIXED_ORDER: &str = "Fd6zBynrPkZciPEbV9DI026w9nDDsi0vgOpgSe6ywjs";
#[test]
fn ed25519_spki_with_a_torsion_component_is_rejected() {
let key = crate::util::b64url_decode(MIXED_ORDER).unwrap();
let spki_of = |alg| {
spki::encode(&SpkiParts {
alg,
subject_public_key: &key,
})
.unwrap()
};
assert_eq!(
import_spki(&spki_of(AlgId::Ed25519)).unwrap_err(),
Error::InvalidKey("Ed25519 public key is invalid")
);
let x25519 = import_spki(&spki_of(AlgId::X25519)).unwrap();
assert_eq!(okp(&x25519).crv(), OkpCurve::X25519);
assert_eq!(okp(&x25519).x(), key.as_slice());
}
fn relabel(der: &[u8], from: &[u8], to: &[u8]) -> Vec<u8> {
assert_eq!(from.len(), to.len());
let at = der
.windows(from.len())
.position(|window| window == from)
.expect("the template is in the DER");
let mut out = der.to_vec();
out[at..at + to.len()].copy_from_slice(to);
out
}
#[test]
fn swapped_algorithm_identifiers_change_crv_and_x() {
use crate::keys::oid::{ALG_ED25519, ALG_X25519};
let cases = [
(
"rfc8037-a1-ed25519-pkcs8.der",
OkpCurve::Ed25519,
ALG_ED25519,
ALG_X25519,
),
(
"rfc8037-a6-x25519-pkcs8.der",
OkpCurve::X25519,
ALG_X25519,
ALG_ED25519,
),
];
for (name, crv, from, to) in cases {
let der = fixtures::load("rfc", name);
let original = import_pkcs8(&der).unwrap();
assert_eq!(okp(&original).crv(), crv, "{name}");
let swapped = import_pkcs8(&relabel(&der, from, to)).unwrap();
let (original, swapped) = (okp(&original), okp(&swapped));
assert_ne!(swapped.crv(), crv, "{name}");
assert_eq!(swapped.d(), original.d(), "{name}");
assert_ne!(swapped.x(), original.x(), "{name}");
let d: &[u8; KEY_LEN] = swapped.d().unwrap().try_into().unwrap();
let expected = match swapped.crv() {
OkpCurve::Ed25519 => ed25519_public(d),
OkpCurve::X25519 => x25519_public(d),
};
assert_eq!(swapped.x(), expected, "{name}");
}
let v2 = fixtures::load("rfc", "rfc8410-10.3-ed25519-pkcs8-v2-attr-pub.pem");
assert_eq!(
import_pkcs8(&relabel(&v2, ALG_ED25519, ALG_X25519)).unwrap_err(),
Error::InvalidKey("X25519 PKCS#8 publicKey does not match the private key")
);
let ed = fixtures::load("rfc", "rfc8037-a2-ed25519-spki.der");
let as_x25519 = import_spki(&relabel(&ed, ALG_ED25519, ALG_X25519)).unwrap();
assert_eq!(okp(&as_x25519).crv(), OkpCurve::X25519);
let x = fixtures::load("rfc", "rfc8037-a6-x25519-spki.der");
assert_eq!(
import_spki(&relabel(&x, ALG_X25519, ALG_ED25519)).unwrap_err(),
Error::InvalidKey("Ed25519 public key is invalid")
);
}
const ALICE_D: [u8; KEY_LEN] = [
0x77, 0x07, 0x6d, 0x0a, 0x73, 0x18, 0xa5, 0x7d, 0x3c, 0x16, 0xc1, 0x72, 0x51, 0xb2, 0x66,
0x45, 0xdf, 0x4c, 0x2f, 0x87, 0xeb, 0xc0, 0x99, 0x2a, 0xb1, 0x77, 0xfb, 0xa5, 0x1d, 0xb9,
0x2c, 0x2a,
];
const ALICE_X: [u8; KEY_LEN] = [
0x85, 0x20, 0xf0, 0x09, 0x89, 0x30, 0xa7, 0x54, 0x74, 0x8b, 0x7d, 0xdc, 0xb4, 0x3e, 0xf7,
0x5a, 0x0d, 0xbf, 0x3a, 0x0d, 0x26, 0x38, 0x1a, 0xf4, 0xeb, 0xa4, 0xa9, 0x8e, 0xaa, 0x9b,
0x4e, 0x6a,
];
#[test]
fn x25519_public_wipes_its_copy_of_the_scalar() {
let mut copy = ALICE_D;
assert_eq!(x25519_public_from_copy(&mut copy), ALICE_X);
assert_eq!(copy, [0u8; KEY_LEN]);
let d = ALICE_D;
assert_eq!(x25519_public(&d), ALICE_X);
assert_eq!(d, ALICE_D);
for d in [[0u8; KEY_LEN], [0xff; KEY_LEN], ALICE_X] {
let oracle = x25519_dalek::x25519(d, x25519_dalek::X25519_BASEPOINT_BYTES);
assert_eq!(x25519_public(&d), oracle);
}
}
#[test]
fn the_private_key_is_held_in_a_zeroizing_array() {
let der = fixtures::load("rfc", "rfc8037-a1-ed25519-pkcs8.der");
let parts = pkcs8::decode(&der).unwrap();
let d: Zeroizing<[u8; KEY_LEN]> = curve_private_key(parts.private_key).unwrap();
assert_eq!(d.as_slice(), &parts.private_key[2..]);
}
#[test]
fn errors_carry_no_key_bytes() {
use crate::keys::oid::ALG_ED25519;
const MARKER: &[u8; KEY_LEN] = b"POLYJOSE-MARKER-0123456789-ABCDE";
let ed25519_other = okp(&jwk_fixture("rfc", "rfc8037-a2-ed25519-spki.jwk.json"))
.x()
.to_vec();
let curve_private_key = |key: &[u8]| {
let mut out = vec![0x04, u8::try_from(key.len()).unwrap()];
out.extend_from_slice(key);
out
};
let pkcs8_of = |alg, private: &[u8], public: Option<&[u8]>| {
let private_key = curve_private_key(private);
pkcs8::encode(&Pkcs8Parts {
version: u8::from(public.is_some()),
alg,
private_key: &private_key,
public_key: public,
})
.unwrap()
};
let spki_of = |key: &[u8]| {
spki::encode(&SpkiParts {
alg: AlgId::Ed25519,
subject_public_key: key,
})
.unwrap()
};
let mut long = MARKER.to_vec();
long.push(b'!');
let errors = [
import_pkcs8(&pkcs8_of(AlgId::X25519, MARKER, Some(MARKER))).unwrap_err(),
import_pkcs8(&pkcs8_of(AlgId::Ed25519, MARKER, Some(&ed25519_other))).unwrap_err(),
import_pkcs8(&pkcs8_of(AlgId::Ed25519, MARKER, Some(MARKER))).unwrap_err(),
import_pkcs8(&pkcs8_of(AlgId::Ed25519, &MARKER[..31], None)).unwrap_err(),
import_pkcs8(&pkcs8_of(AlgId::X25519, MARKER, Some(&long))).unwrap_err(),
import_spki(&spki_of(MARKER)).unwrap_err(),
import_spki(&spki_of(&long)).unwrap_err(),
import_pkcs8(&relabel(
&pkcs8_of(AlgId::Ed25519, MARKER, None),
ALG_ED25519,
&[0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x71],
))
.unwrap_err(),
];
let hex = |bytes: &[u8]| -> String { bytes.iter().map(|b| format!("{b:02x}")).collect() };
let forbidden = [
"MARKER".to_owned(),
hex(&MARKER[..8]),
hex(&MARKER[..8]).to_uppercase(),
crate::util::b64url_encode(&MARKER[..9]),
format!("{:?}", &MARKER[..4]),
];
for err in errors {
for text in [format!("{err}"), format!("{err:?}")] {
for needle in &forbidden {
assert!(!text.contains(needle.as_str()), "{text} contains {needle}");
}
}
}
}
mod properties {
use proptest::prelude::*;
use super::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn every_key_round_trips(d in any::<[u8; KEY_LEN]>()) {
for (crv, oid_last) in [(OkpCurve::Ed25519, 0x70), (OkpCurve::X25519, 0x6E)] {
let x = match crv {
OkpCurve::Ed25519 => ed25519_public(&d),
OkpCurve::X25519 => x25519_public(&d),
};
let jwk = jwk_build::okp_jwk(crv, &x, Some(&d), None).unwrap();
let key = okp(&jwk);
let pkcs8_der = to_pkcs8(key).unwrap();
let mut expected = vec![
0x30, 0x2E, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65,
oid_last, 0x04, 0x22, 0x04, 0x20,
];
expected.extend_from_slice(&d);
prop_assert_eq!(&pkcs8_der[..], &expected[..]);
let back = import_pkcs8(&pkcs8_der).unwrap();
prop_assert_eq!(&*back.to_json(), &*jwk.to_json());
let v2 = pkcs8::encode(&Pkcs8Parts {
version: 1,
public_key: Some(&x),
..pkcs8::decode(&pkcs8_der).unwrap()
})
.unwrap();
let back = import_pkcs8(&v2).unwrap();
prop_assert_eq!(&*back.to_json(), &*jwk.to_json());
let spki_der = to_spki(key).unwrap();
let mut expected = vec![
0x30, 0x2A, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, oid_last, 0x03, 0x21,
0x00,
];
expected.extend_from_slice(&x);
prop_assert_eq!(&spki_der[..], &expected[..]);
let public = import_spki(&spki_der).unwrap();
prop_assert_eq!(&*public.to_json(), &*jwk.to_public().unwrap().to_json());
}
}
}
}
}