fix(native-certgen): address Phase 0-1 validation findings
EC keygen now returns proper SPKI DER instead of raw point bytes. RSA keygen uses caller-supplied exponent via new_with_exp() and validates key size to 2048/3072/4096. Keybox parser extracts leaf subject DN (not issuer). Added AttestKey=7 to KeyPurpose. Realigned error variants with spec.
This commit is contained in:
+21
-19
@@ -2,33 +2,41 @@ use std::fmt;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CertGenError {
|
pub enum CertGenError {
|
||||||
|
Jni(String),
|
||||||
|
NullParam(&'static str),
|
||||||
|
UnsupportedAlgorithm(i32),
|
||||||
|
UnsupportedEcCurve(i32),
|
||||||
KeyGenFailed(String),
|
KeyGenFailed(String),
|
||||||
CertBuildFailed(String),
|
CertBuildFailed(String),
|
||||||
AttestationEncodeFailed(String),
|
|
||||||
KeyboxParseFailed(String),
|
KeyboxParseFailed(String),
|
||||||
JniError(String),
|
AttestationBuildFailed(String),
|
||||||
|
DerError(der::Error),
|
||||||
|
RcgenError(rcgen::Error),
|
||||||
|
EmptyKeyboxChain,
|
||||||
|
ChallengeTooLong(usize),
|
||||||
InvalidParameter(String),
|
InvalidParameter(String),
|
||||||
UnsupportedAlgorithm(i32),
|
|
||||||
UnsupportedCurve(i32),
|
|
||||||
SigningFailed(String),
|
SigningFailed(String),
|
||||||
SerializationFailed(String),
|
SerializationFailed(String),
|
||||||
InternalError(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for CertGenError {
|
impl fmt::Display for CertGenError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Jni(msg) => write!(f, "JNI error: {}", msg),
|
||||||
|
Self::NullParam(name) => write!(f, "null required parameter: {}", name),
|
||||||
|
Self::UnsupportedAlgorithm(v) => write!(f, "unsupported algorithm: {}", v),
|
||||||
|
Self::UnsupportedEcCurve(v) => write!(f, "unsupported EC curve: {}", v),
|
||||||
Self::KeyGenFailed(msg) => write!(f, "key generation failed: {}", msg),
|
Self::KeyGenFailed(msg) => write!(f, "key generation failed: {}", msg),
|
||||||
Self::CertBuildFailed(msg) => write!(f, "certificate build failed: {}", msg),
|
Self::CertBuildFailed(msg) => write!(f, "certificate build failed: {}", msg),
|
||||||
Self::AttestationEncodeFailed(msg) => write!(f, "attestation encode failed: {}", msg),
|
|
||||||
Self::KeyboxParseFailed(msg) => write!(f, "keybox parse failed: {}", msg),
|
Self::KeyboxParseFailed(msg) => write!(f, "keybox parse failed: {}", msg),
|
||||||
Self::JniError(msg) => write!(f, "JNI error: {}", msg),
|
Self::AttestationBuildFailed(msg) => write!(f, "attestation build failed: {}", msg),
|
||||||
|
Self::DerError(e) => write!(f, "DER error: {}", e),
|
||||||
|
Self::RcgenError(e) => write!(f, "rcgen error: {}", e),
|
||||||
|
Self::EmptyKeyboxChain => write!(f, "keybox certificate chain is empty"),
|
||||||
|
Self::ChallengeTooLong(len) => write!(f, "attestation challenge too long: {} bytes (max 128)", len),
|
||||||
Self::InvalidParameter(msg) => write!(f, "invalid parameter: {}", msg),
|
Self::InvalidParameter(msg) => write!(f, "invalid parameter: {}", msg),
|
||||||
Self::UnsupportedAlgorithm(v) => write!(f, "unsupported algorithm: {}", v),
|
|
||||||
Self::UnsupportedCurve(v) => write!(f, "unsupported EC curve: {}", v),
|
|
||||||
Self::SigningFailed(msg) => write!(f, "signing failed: {}", msg),
|
Self::SigningFailed(msg) => write!(f, "signing failed: {}", msg),
|
||||||
Self::SerializationFailed(msg) => write!(f, "serialization failed: {}", msg),
|
Self::SerializationFailed(msg) => write!(f, "serialization failed: {}", msg),
|
||||||
Self::InternalError(msg) => write!(f, "internal error: {}", msg),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,13 +45,13 @@ impl std::error::Error for CertGenError {}
|
|||||||
|
|
||||||
impl From<jni::errors::Error> for CertGenError {
|
impl From<jni::errors::Error> for CertGenError {
|
||||||
fn from(e: jni::errors::Error) -> Self {
|
fn from(e: jni::errors::Error) -> Self {
|
||||||
Self::JniError(e.to_string())
|
Self::Jni(e.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<der::Error> for CertGenError {
|
impl From<der::Error> for CertGenError {
|
||||||
fn from(e: der::Error) -> Self {
|
fn from(e: der::Error) -> Self {
|
||||||
Self::SerializationFailed(e.to_string())
|
Self::DerError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,13 +75,7 @@ impl From<rsa::Error> for CertGenError {
|
|||||||
|
|
||||||
impl From<rcgen::Error> for CertGenError {
|
impl From<rcgen::Error> for CertGenError {
|
||||||
fn from(e: rcgen::Error) -> Self {
|
fn from(e: rcgen::Error) -> Self {
|
||||||
Self::CertBuildFailed(e.to_string())
|
Self::RcgenError(e)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<anyhow::Error> for CertGenError {
|
|
||||||
fn from(e: anyhow::Error) -> Self {
|
|
||||||
Self::InternalError(e.to_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ pub fn parse_keybox(cert_chain_bytes: &[u8], private_key_bytes: &[u8]) -> Result
|
|||||||
let leaf = Certificate::from_der(&certs[0])
|
let leaf = Certificate::from_der(&certs[0])
|
||||||
.map_err(|e| CertGenError::KeyboxParseFailed(format!("leaf cert parse: {e}")))?;
|
.map_err(|e| CertGenError::KeyboxParseFailed(format!("leaf cert parse: {e}")))?;
|
||||||
|
|
||||||
let issuer_dn_der = leaf.tbs_certificate.issuer.to_der()
|
let issuer_dn_der = leaf.tbs_certificate.subject.to_der()
|
||||||
.map_err(|e| CertGenError::KeyboxParseFailed(format!("issuer DN encode: {e}")))?;
|
.map_err(|e| CertGenError::KeyboxParseFailed(format!("subject DN encode: {e}")))?;
|
||||||
|
|
||||||
let not_after = leaf.tbs_certificate.validity.not_after;
|
let not_after = leaf.tbs_certificate.validity.not_after;
|
||||||
let leaf_not_after = not_after.to_unix_duration().as_secs() as i64;
|
let leaf_not_after = not_after.to_unix_duration().as_secs() as i64;
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
use crate::error::{CertGenError, Result};
|
use crate::error::{CertGenError, Result};
|
||||||
use crate::types::{Algorithm, EcCurve, GeneratedKeyPair};
|
use crate::types::{Algorithm, EcCurve, GeneratedKeyPair};
|
||||||
|
|
||||||
pub fn generate_key_pair(algorithm: Algorithm, key_size: u32, ec_curve: Option<EcCurve>) -> Result<GeneratedKeyPair> {
|
pub fn generate_key_pair(
|
||||||
|
algorithm: Algorithm,
|
||||||
|
key_size: u32,
|
||||||
|
ec_curve: Option<EcCurve>,
|
||||||
|
rsa_public_exponent: u64,
|
||||||
|
) -> Result<GeneratedKeyPair> {
|
||||||
match algorithm {
|
match algorithm {
|
||||||
Algorithm::Ec => {
|
Algorithm::Ec => {
|
||||||
let curve = ec_curve.ok_or_else(|| CertGenError::InvalidParameter("ec_curve required for EC".into()))?;
|
let curve = ec_curve.ok_or_else(|| CertGenError::InvalidParameter("ec_curve required for EC".into()))?;
|
||||||
generate_ec_key_pair(curve)
|
generate_ec_key_pair(curve)
|
||||||
}
|
}
|
||||||
Algorithm::Rsa => generate_rsa_key_pair(key_size),
|
Algorithm::Rsa => generate_rsa_key_pair(key_size, rsa_public_exponent),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,25 +22,99 @@ fn generate_ec_key_pair(curve: EcCurve) -> Result<GeneratedKeyPair> {
|
|||||||
let alg = match curve {
|
let alg = match curve {
|
||||||
EcCurve::P256 => &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING,
|
EcCurve::P256 => &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING,
|
||||||
EcCurve::P384 => &ring::signature::ECDSA_P384_SHA384_ASN1_SIGNING,
|
EcCurve::P384 => &ring::signature::ECDSA_P384_SHA384_ASN1_SIGNING,
|
||||||
_ => return Err(CertGenError::UnsupportedCurve(curve as i32)),
|
_ => return Err(CertGenError::UnsupportedEcCurve(curve as i32)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let rng = ring::rand::SystemRandom::new();
|
let rng = ring::rand::SystemRandom::new();
|
||||||
let pkcs8_doc = ring::signature::EcdsaKeyPair::generate_pkcs8(alg, &rng)?;
|
let pkcs8_doc = ring::signature::EcdsaKeyPair::generate_pkcs8(alg, &rng)?;
|
||||||
let key_pair = ring::signature::EcdsaKeyPair::from_pkcs8(alg, pkcs8_doc.as_ref(), &rng)?;
|
let key_pair = ring::signature::EcdsaKeyPair::from_pkcs8(alg, pkcs8_doc.as_ref(), &rng)?;
|
||||||
|
|
||||||
|
let raw_point = key_pair.public_key().as_ref();
|
||||||
|
let spki = build_ec_spki(curve, raw_point)?;
|
||||||
|
|
||||||
Ok(GeneratedKeyPair {
|
Ok(GeneratedKeyPair {
|
||||||
private_key_pkcs8: pkcs8_doc.as_ref().to_vec(),
|
private_key_pkcs8: pkcs8_doc.as_ref().to_vec(),
|
||||||
public_key_spki: key_pair.public_key().as_ref().to_vec(),
|
public_key_spki: spki,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_rsa_key_pair(key_size: u32) -> Result<GeneratedKeyPair> {
|
/// Build SubjectPublicKeyInfo DER from a raw EC uncompressed point.
|
||||||
|
fn build_ec_spki(curve: EcCurve, raw_point: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
// SPKI = SEQUENCE { AlgorithmIdentifier, BIT STRING(public key) }
|
||||||
|
// AlgorithmIdentifier = SEQUENCE { OID(ecPublicKey), OID(curve) }
|
||||||
|
//
|
||||||
|
// DER-encode manually — the prefix is fixed per curve, only the point varies.
|
||||||
|
|
||||||
|
// OID 1.2.840.10045.2.1 (id-ecPublicKey)
|
||||||
|
const EC_PUBLIC_KEY_OID: &[u8] = &[0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
|
||||||
|
|
||||||
|
let curve_oid: &[u8] = match curve {
|
||||||
|
// OID 1.2.840.10045.3.1.7 (prime256v1 / P-256)
|
||||||
|
EcCurve::P256 => &[0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07],
|
||||||
|
// OID 1.3.132.0.34 (secp384r1 / P-384)
|
||||||
|
EcCurve::P384 => &[0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22],
|
||||||
|
_ => return Err(CertGenError::UnsupportedEcCurve(curve as i32)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let alg_id_seq = der_sequence(EC_PUBLIC_KEY_OID, curve_oid);
|
||||||
|
|
||||||
|
// BIT STRING: 0x03, length, 0x00 (unused bits), raw_point
|
||||||
|
let bit_string_content_len = 1 + raw_point.len(); // 0x00 byte + point
|
||||||
|
let mut bit_string = vec![0x03];
|
||||||
|
encode_der_length(&mut bit_string, bit_string_content_len);
|
||||||
|
bit_string.push(0x00); // zero unused bits
|
||||||
|
bit_string.extend_from_slice(raw_point);
|
||||||
|
|
||||||
|
// Outer SEQUENCE
|
||||||
|
let inner_len = alg_id_seq.len() + bit_string.len();
|
||||||
|
let mut spki = vec![0x30];
|
||||||
|
encode_der_length(&mut spki, inner_len);
|
||||||
|
spki.extend_from_slice(&alg_id_seq);
|
||||||
|
spki.extend_from_slice(&bit_string);
|
||||||
|
|
||||||
|
Ok(spki)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn der_sequence(a: &[u8], b: &[u8]) -> Vec<u8> {
|
||||||
|
let content_len = a.len() + b.len();
|
||||||
|
let mut seq = vec![0x30];
|
||||||
|
encode_der_length(&mut seq, content_len);
|
||||||
|
seq.extend_from_slice(a);
|
||||||
|
seq.extend_from_slice(b);
|
||||||
|
seq
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_der_length(buf: &mut Vec<u8>, len: usize) {
|
||||||
|
if len < 0x80 {
|
||||||
|
buf.push(len as u8);
|
||||||
|
} else if len < 0x100 {
|
||||||
|
buf.push(0x81);
|
||||||
|
buf.push(len as u8);
|
||||||
|
} else {
|
||||||
|
buf.push(0x82);
|
||||||
|
buf.push((len >> 8) as u8);
|
||||||
|
buf.push(len as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_rsa_key_pair(key_size: u32, rsa_public_exponent: u64) -> Result<GeneratedKeyPair> {
|
||||||
use pkcs8::EncodePrivateKey;
|
use pkcs8::EncodePrivateKey;
|
||||||
use rsa::pkcs8::EncodePublicKey;
|
use rsa::pkcs8::EncodePublicKey;
|
||||||
|
|
||||||
|
if !matches!(key_size, 2048 | 3072 | 4096) {
|
||||||
|
return Err(CertGenError::InvalidParameter(
|
||||||
|
format!("RSA key size must be 2048, 3072, or 4096; got {key_size}")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let exp = if rsa_public_exponent == 0 {
|
||||||
|
rsa::BigUint::from(65537u64)
|
||||||
|
} else {
|
||||||
|
rsa::BigUint::from(rsa_public_exponent)
|
||||||
|
};
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let private_key = rsa::RsaPrivateKey::new(&mut rng, key_size as usize)
|
let private_key = rsa::RsaPrivateKey::new_with_exp(&mut rng, key_size as usize, &exp)
|
||||||
.map_err(|e| CertGenError::KeyGenFailed(e.to_string()))?;
|
.map_err(|e| CertGenError::KeyGenFailed(e.to_string()))?;
|
||||||
|
|
||||||
let pkcs8_der = private_key.to_pkcs8_der()
|
let pkcs8_der = private_key.to_pkcs8_der()
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
mod error;
|
mod error;
|
||||||
mod types;
|
mod types;
|
||||||
mod keygen;
|
mod keygen;
|
||||||
// mod attestation; // Phase 2
|
|
||||||
pub mod keybox;
|
pub mod keybox;
|
||||||
// mod certbuilder; // Phase 3
|
|
||||||
// mod logging; // Phase 3
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ impl TryFrom<i32> for EcCurve {
|
|||||||
2 => Ok(Self::P384),
|
2 => Ok(Self::P384),
|
||||||
3 => Ok(Self::P521),
|
3 => Ok(Self::P521),
|
||||||
4 => Ok(Self::Curve25519),
|
4 => Ok(Self::Curve25519),
|
||||||
_ => Err(CertGenError::UnsupportedCurve(value)),
|
_ => Err(CertGenError::UnsupportedEcCurve(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,7 @@ pub enum KeyPurpose {
|
|||||||
Verify = 3,
|
Verify = 3,
|
||||||
WrapKey = 5,
|
WrapKey = 5,
|
||||||
AgreeKey = 6,
|
AgreeKey = 6,
|
||||||
|
AttestKey = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|||||||
Reference in New Issue
Block a user