use std::fmt;
use std::sync::Arc;
use elliptic_curve::{
Generate,
pkcs8::{EncodePrivateKey, EncodePublicKey, LineEnding},
};
use rand_chacha10::ChaCha20Rng;
use rand_core10::SeedableRng;
use uselesskey_core::negative::CorruptPem;
use uselesskey_core::sink::TempArtifact;
use uselesskey_core::{Error, Factory};
use uselesskey_core_keypair_material::Pkcs8SpkiKeyMaterial;
use crate::EcdsaSpec;
pub const DOMAIN_ECDSA_KEYPAIR: &str = "uselesskey:ecdsa:keypair";
#[derive(Clone)]
pub struct EcdsaKeyPair {
factory: Factory,
label: String,
spec: EcdsaSpec,
inner: Arc<Inner>,
}
struct Inner {
#[allow(dead_code, reason = "consumed only when the `jwk` feature is enabled")]
spec: EcdsaSpec,
material: Pkcs8SpkiKeyMaterial,
#[cfg_attr(not(feature = "jwk"), allow(dead_code))]
public_key_bytes: Vec<u8>,
#[cfg_attr(not(feature = "jwk"), allow(dead_code))]
private_key_bytes: Vec<u8>,
}
impl fmt::Debug for EcdsaKeyPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EcdsaKeyPair")
.field("label", &self.label)
.field("spec", &self.spec)
.finish_non_exhaustive()
}
}
pub trait EcdsaFactoryExt {
fn ecdsa(&self, label: impl AsRef<str>, spec: EcdsaSpec) -> EcdsaKeyPair;
}
impl EcdsaFactoryExt for Factory {
fn ecdsa(&self, label: impl AsRef<str>, spec: EcdsaSpec) -> EcdsaKeyPair {
EcdsaKeyPair::new(self.clone(), label.as_ref(), spec)
}
}
impl EcdsaKeyPair {
fn new(factory: Factory, label: &str, spec: EcdsaSpec) -> Self {
let inner = load_inner(&factory, label, spec, "good");
Self {
factory,
label: label.to_string(),
spec,
inner,
}
}
fn load_variant(&self, variant: &str) -> Arc<Inner> {
load_inner(&self.factory, &self.label, self.spec, variant)
}
pub fn spec(&self) -> EcdsaSpec {
self.spec
}
pub fn label(&self) -> &str {
&self.label
}
pub fn private_key_pkcs8_der(&self) -> &[u8] {
self.inner.material.private_key_pkcs8_der()
}
pub fn private_key_pkcs8_pem(&self) -> &str {
self.inner.material.private_key_pkcs8_pem()
}
pub fn public_key_spki_der(&self) -> &[u8] {
self.inner.material.public_key_spki_der()
}
pub fn public_key_spki_pem(&self) -> &str {
self.inner.material.public_key_spki_pem()
}
pub fn write_private_key_pkcs8_pem(&self) -> Result<TempArtifact, Error> {
self.inner.material.write_private_key_pkcs8_pem()
}
pub fn write_public_key_spki_pem(&self) -> Result<TempArtifact, Error> {
self.inner.material.write_public_key_spki_pem()
}
pub fn private_key_pkcs8_pem_corrupt(&self, how: CorruptPem) -> String {
self.inner.material.private_key_pkcs8_pem_corrupt(how)
}
pub fn private_key_pkcs8_pem_corrupt_deterministic(&self, variant: &str) -> String {
self.inner
.material
.private_key_pkcs8_pem_corrupt_deterministic(variant)
}
pub fn private_key_pkcs8_der_truncated(&self, len: usize) -> Vec<u8> {
self.inner.material.private_key_pkcs8_der_truncated(len)
}
pub fn private_key_pkcs8_der_corrupt_deterministic(&self, variant: &str) -> Vec<u8> {
self.inner
.material
.private_key_pkcs8_der_corrupt_deterministic(variant)
}
pub fn mismatched_public_key_spki_der(&self) -> Vec<u8> {
let other = self.load_variant("mismatch");
other.material.public_key_spki_der().to_vec()
}
#[cfg(feature = "jwk")]
pub fn kid(&self) -> String {
self.inner.material.kid()
}
#[cfg(feature = "jwk")]
pub fn public_key_jwk(&self) -> uselesskey_jwk::PublicJwk {
self.public_jwk()
}
#[cfg(feature = "jwk")]
pub fn public_jwk(&self) -> uselesskey_jwk::PublicJwk {
use base64::Engine as _;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use uselesskey_jwk::{EcPublicJwk, PublicJwk};
let bytes = &self.inner.public_key_bytes;
assert_eq!(bytes[0], 0x04, "expected uncompressed point");
let coord_len = self.spec.coordinate_len_bytes();
assert_eq!(
bytes.len(),
1 + (coord_len * 2),
"unexpected EC point length for {:?}",
self.spec
);
let x = &bytes[1..1 + coord_len];
let y = &bytes[1 + coord_len..];
PublicJwk::Ec(EcPublicJwk {
kty: "EC",
use_: "sig",
alg: self.spec.alg_name(),
crv: self.spec.curve_name(),
kid: self.kid(),
x: URL_SAFE_NO_PAD.encode(x),
y: URL_SAFE_NO_PAD.encode(y),
})
}
#[cfg(feature = "jwk")]
pub fn private_key_jwk(&self) -> uselesskey_jwk::PrivateJwk {
use base64::Engine as _;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use uselesskey_jwk::{EcPrivateJwk, PrivateJwk};
let bytes = &self.inner.public_key_bytes;
assert_eq!(bytes[0], 0x04, "expected uncompressed point");
let coord_len = self.spec.coordinate_len_bytes();
assert_eq!(
bytes.len(),
1 + (coord_len * 2),
"unexpected EC point length for {:?}",
self.spec
);
let x = &bytes[1..1 + coord_len];
let y = &bytes[1 + coord_len..];
PrivateJwk::Ec(EcPrivateJwk {
kty: "EC",
use_: "sig",
alg: self.spec.alg_name(),
crv: self.spec.curve_name(),
kid: self.kid(),
x: URL_SAFE_NO_PAD.encode(x),
y: URL_SAFE_NO_PAD.encode(y),
d: URL_SAFE_NO_PAD.encode(&self.inner.private_key_bytes),
})
}
#[cfg(feature = "jwk")]
pub fn public_jwks(&self) -> uselesskey_jwk::Jwks {
use uselesskey_jwk::JwksBuilder;
let mut builder = JwksBuilder::new();
builder.push_public(self.public_jwk());
builder.build()
}
#[cfg(feature = "jwk")]
pub fn public_jwk_json(&self) -> serde_json::Value {
self.public_jwk().to_value()
}
#[cfg(feature = "jwk")]
pub fn public_jwks_json(&self) -> serde_json::Value {
self.public_jwks().to_value()
}
#[cfg(feature = "jwk")]
pub fn private_key_jwk_json(&self) -> serde_json::Value {
self.private_key_jwk().to_value()
}
}
fn load_inner(factory: &Factory, label: &str, spec: EcdsaSpec, variant: &str) -> Arc<Inner> {
let spec_bytes = spec.stable_bytes();
factory.get_or_init(DOMAIN_ECDSA_KEYPAIR, label, &spec_bytes, variant, |seed| {
let mut rng = ChaCha20Rng::from_seed(*seed.bytes());
match spec {
EcdsaSpec::Es256 => generate_p256(spec, &mut rng),
EcdsaSpec::Es384 => generate_p384(spec, &mut rng),
}
})
}
fn generate_p256(spec: EcdsaSpec, rng: &mut impl rand_core10::CryptoRng) -> Inner {
use p256::ecdsa::SigningKey;
let signing_key =
SigningKey::try_generate_from_rng(rng).expect("failed to generate deterministic P-256 key");
let verifying_key = signing_key.verifying_key();
let pkcs8_der_doc = signing_key
.to_pkcs8_der()
.expect("failed to encode P-256 private key as PKCS#8 DER");
let pkcs8_der: Arc<[u8]> = Arc::from(pkcs8_der_doc.as_bytes());
let pkcs8_pem = signing_key
.to_pkcs8_pem(LineEnding::LF)
.expect("failed to encode P-256 private key as PKCS#8 PEM")
.to_string();
let spki_der_doc = verifying_key
.to_public_key_der()
.expect("failed to encode P-256 public key as SPKI DER");
let spki_der: Arc<[u8]> = Arc::from(spki_der_doc.as_bytes());
let spki_pem = verifying_key
.to_public_key_pem(LineEnding::LF)
.expect("failed to encode P-256 public key as SPKI PEM");
let point = verifying_key.to_sec1_point(false);
let public_key_bytes = point.as_bytes().to_vec();
let private_key_bytes = signing_key.to_bytes().to_vec();
let material = Pkcs8SpkiKeyMaterial::new(pkcs8_der, pkcs8_pem, spki_der, spki_pem);
Inner {
spec,
material,
public_key_bytes,
private_key_bytes,
}
}
fn generate_p384(spec: EcdsaSpec, rng: &mut impl rand_core10::CryptoRng) -> Inner {
use p384::ecdsa::SigningKey;
let signing_key =
SigningKey::try_generate_from_rng(rng).expect("failed to generate deterministic P-384 key");
let verifying_key = signing_key.verifying_key();
let pkcs8_der_doc = signing_key
.to_pkcs8_der()
.expect("failed to encode P-384 private key as PKCS#8 DER");
let pkcs8_der: Arc<[u8]> = Arc::from(pkcs8_der_doc.as_bytes());
let pkcs8_pem = signing_key
.to_pkcs8_pem(LineEnding::LF)
.expect("failed to encode P-384 private key as PKCS#8 PEM")
.to_string();
let spki_der_doc = verifying_key
.to_public_key_der()
.expect("failed to encode P-384 public key as SPKI DER");
let spki_der: Arc<[u8]> = Arc::from(spki_der_doc.as_bytes());
let spki_pem = verifying_key
.to_public_key_pem(LineEnding::LF)
.expect("failed to encode P-384 public key as SPKI PEM");
let point = verifying_key.to_sec1_point(false);
let public_key_bytes = point.as_bytes().to_vec();
let private_key_bytes = signing_key.to_bytes().to_vec();
let material = Pkcs8SpkiKeyMaterial::new(pkcs8_der, pkcs8_pem, spki_der, spki_pem);
Inner {
spec,
material,
public_key_bytes,
private_key_bytes,
}
}