fix(attestation): reject oversized challenges and rewrite cert DER encoding
DuckDetector flagged two issues: 1. Oversized challenge accepted, 256-byte attestation challenge should return INVALID_INPUT_LENGTH (-21) like real KeyMint. Added early check in handleGenerateKey before any path decision. 2. Issuer/subject chain mismatch, rcgen's HashMap loses DN attribute ordering and converts PrintableString to UTF8String, producing different DER bytes. Replaced rcgen with manual DER assembly that injects raw keybox issuer_dn_der bytes directly. Verified on device: TX_ID 315 rejects 256-byte challenge, TX_ID 501 generates valid 4-cert chain with correct issuer linkage.
This commit is contained in:
@@ -12,6 +12,17 @@ data class KeyIdentifier(val uid: Int, val alias: String)
|
|||||||
/** A collection of utility functions to support binder interception. */
|
/** A collection of utility functions to support binder interception. */
|
||||||
object InterceptorUtils {
|
object InterceptorUtils {
|
||||||
|
|
||||||
|
private const val EX_SERVICE_SPECIFIC = -8
|
||||||
|
|
||||||
|
fun createErrorReply(errorCode: Int): BinderInterceptor.TransactionResult.OverrideReply {
|
||||||
|
val parcel = Parcel.obtain().apply {
|
||||||
|
writeInt(EX_SERVICE_SPECIFIC)
|
||||||
|
writeInt(errorCode)
|
||||||
|
writeString(null)
|
||||||
|
}
|
||||||
|
return BinderInterceptor.TransactionResult.OverrideReply(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses reflection to get the integer transaction code for a given method name from a Stub
|
* Uses reflection to get the integer transaction code for a given method name from a Stub
|
||||||
* class. This is necessary for older Android versions where codes are not public constants.
|
* class. This is necessary for older Android versions where codes are not public constants.
|
||||||
|
|||||||
+9
@@ -18,6 +18,7 @@ import java.security.spec.PKCS8EncodedKeySpec
|
|||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
import org.matrix.TEESimulator.attestation.AttestationBuilder
|
||||||
|
import org.matrix.TEESimulator.attestation.AttestationConstants
|
||||||
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
import org.matrix.TEESimulator.attestation.AttestationPatcher
|
||||||
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
import org.matrix.TEESimulator.attestation.KeyMintAttestation
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
@@ -246,6 +247,13 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
)
|
)
|
||||||
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
val params = data.createTypedArray(KeyParameter.CREATOR)!!
|
||||||
val parsedParams = KeyMintAttestation(params)
|
val parsedParams = KeyMintAttestation(params)
|
||||||
|
|
||||||
|
val challenge = parsedParams.attestationChallenge
|
||||||
|
if (challenge != null && challenge.size > AttestationConstants.CHALLENGE_LENGTH_LIMIT) {
|
||||||
|
SystemLogger.warning("[TX_ID: $txId] Rejecting oversized attestation challenge: ${challenge.size} bytes (max ${AttestationConstants.CHALLENGE_LENGTH_LIMIT})")
|
||||||
|
return InterceptorUtils.createErrorReply(KEYMINT_INVALID_INPUT_LENGTH)
|
||||||
|
}
|
||||||
|
|
||||||
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
val keyId = KeyIdentifier(callingUid, keyDescriptor.alias)
|
||||||
val isAttestKeyRequest = parsedParams.isAttestKey()
|
val isAttestKeyRequest = parsedParams.isAttestKey()
|
||||||
|
|
||||||
@@ -506,6 +514,7 @@ class KeyMintSecurityLevelInterceptor(
|
|||||||
// Maximum alias length to prevent binder buffer exhaustion (Issue #109)
|
// Maximum alias length to prevent binder buffer exhaustion (Issue #109)
|
||||||
// Binder buffer is ~1MB; 256KB provides 4x safety margin for transaction overhead
|
// Binder buffer is ~1MB; 256KB provides 4x safety margin for transaction overhead
|
||||||
private const val MAX_ALIAS_LENGTH = 256 * 1024
|
private const val MAX_ALIAS_LENGTH = 256 * 1024
|
||||||
|
private const val KEYMINT_INVALID_INPUT_LENGTH = -21
|
||||||
private const val MAX_CONCURRENT_HW_KEYGEN_PER_UID = 2
|
private const val MAX_CONCURRENT_HW_KEYGEN_PER_UID = 2
|
||||||
// Sliding window: max hardware keygen permits per UID within the burst window
|
// Sliding window: max hardware keygen permits per UID within the burst window
|
||||||
private const val MAX_HW_KEYGEN_PER_WINDOW = 2
|
private const val MAX_HW_KEYGEN_PER_WINDOW = 2
|
||||||
|
|||||||
Generated
+1166
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,6 @@ ring = "0.17.14"
|
|||||||
rsa = { version = "0.9", features = ["sha2"] }
|
rsa = { version = "0.9", features = ["sha2"] }
|
||||||
pkcs8 = { version = "0.10", features = ["alloc"] }
|
pkcs8 = { version = "0.10", features = ["alloc"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rcgen = { version = "0.13.2", default-features = false, features = ["ring"] }
|
|
||||||
der = { version = "0.7.10", features = ["alloc", "oid"] }
|
der = { version = "0.7.10", features = ["alloc", "oid"] }
|
||||||
const-oid = "0.9.6"
|
const-oid = "0.9.6"
|
||||||
x509-cert = { version = "0.2.5", features = ["pem"] }
|
x509-cert = { version = "0.2.5", features = ["pem"] }
|
||||||
|
|||||||
+463
-179
@@ -1,37 +1,29 @@
|
|||||||
use crate::error::{CertGenError, Result};
|
use crate::error::{CertGenError, Result};
|
||||||
use crate::keybox::ParsedKeybox;
|
use crate::keybox::ParsedKeybox;
|
||||||
use crate::types::{CertGenParams, GeneratedKeyPair};
|
use crate::types::{Algorithm, CertGenParams, GeneratedKeyPair};
|
||||||
|
|
||||||
use rcgen::{
|
|
||||||
BasicConstraints, CertificateParams, CustomExtension, DistinguishedName, DnType, IsCa,
|
|
||||||
KeyPair, KeyUsagePurpose, SerialNumber,
|
|
||||||
};
|
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
const ATTESTATION_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 11129, 2, 1, 17];
|
const ATTESTATION_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 11129, 2, 1, 17];
|
||||||
|
|
||||||
|
// Signature algorithm OIDs
|
||||||
|
const OID_SHA256_WITH_ECDSA: &[u64] = &[1, 2, 840, 10045, 4, 3, 2];
|
||||||
|
const OID_SHA384_WITH_ECDSA: &[u64] = &[1, 2, 840, 10045, 4, 3, 3];
|
||||||
|
const OID_SHA256_WITH_RSA: &[u64] = &[1, 2, 840, 113549, 1, 1, 11];
|
||||||
|
|
||||||
|
// Extension OIDs
|
||||||
|
const OID_KEY_USAGE: &[u64] = &[2, 5, 29, 15];
|
||||||
|
|
||||||
pub fn build_certificate_chain(
|
pub fn build_certificate_chain(
|
||||||
key_pair: &GeneratedKeyPair,
|
key_pair: &GeneratedKeyPair,
|
||||||
attestation_ext_der: &[u8],
|
attestation_ext_der: &[u8],
|
||||||
keybox: &ParsedKeybox,
|
keybox: &ParsedKeybox,
|
||||||
params: &CertGenParams,
|
params: &CertGenParams,
|
||||||
) -> Result<Vec<Vec<u8>>> {
|
) -> Result<Vec<Vec<u8>>> {
|
||||||
let issuer_key = KeyPair::try_from(keybox.signing_key_der.as_slice())
|
let leaf_der = build_leaf_cert(key_pair, attestation_ext_der, keybox, params)?;
|
||||||
.map_err(|e| CertGenError::CertBuildFailed(format!("keybox key parse: {e}")))?;
|
|
||||||
|
|
||||||
let issuer_cert = build_issuer_cert(&issuer_key, &keybox.issuer_dn_der)?;
|
|
||||||
|
|
||||||
let subject_key = KeyPair::try_from(key_pair.private_key_pkcs8.as_slice())
|
|
||||||
.map_err(|e| CertGenError::CertBuildFailed(format!("subject key parse: {e}")))?;
|
|
||||||
|
|
||||||
let leaf_params = build_leaf_params(attestation_ext_der, keybox, params)?;
|
|
||||||
|
|
||||||
let leaf_cert = leaf_params
|
|
||||||
.signed_by(&subject_key, &issuer_cert, &issuer_key)
|
|
||||||
.map_err(|e| CertGenError::CertBuildFailed(format!("signing: {e}")))?;
|
|
||||||
|
|
||||||
let mut chain = Vec::with_capacity(1 + keybox.cert_chain_ders.len());
|
let mut chain = Vec::with_capacity(1 + keybox.cert_chain_ders.len());
|
||||||
chain.push(leaf_cert.der().to_vec());
|
chain.push(leaf_der);
|
||||||
for cert_der in &keybox.cert_chain_ders {
|
for cert_der in &keybox.cert_chain_ders {
|
||||||
chain.push(cert_der.clone());
|
chain.push(cert_der.clone());
|
||||||
}
|
}
|
||||||
@@ -39,201 +31,493 @@ pub fn build_certificate_chain(
|
|||||||
Ok(chain)
|
Ok(chain)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_issuer_cert(
|
fn build_leaf_cert(
|
||||||
issuer_key: &KeyPair,
|
key_pair: &GeneratedKeyPair,
|
||||||
issuer_dn_der: &[u8],
|
|
||||||
) -> Result<rcgen::Certificate> {
|
|
||||||
let mut issuer_params = CertificateParams::default();
|
|
||||||
issuer_params.distinguished_name = parse_dn_from_der(issuer_dn_der)?;
|
|
||||||
issuer_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
|
|
||||||
// Suppress AKI/SKI generation — we only need this cert as a signing vehicle
|
|
||||||
issuer_params.key_identifier_method = rcgen::KeyIdMethod::PreSpecified(vec![]);
|
|
||||||
|
|
||||||
issuer_params
|
|
||||||
.self_signed(issuer_key)
|
|
||||||
.map_err(|e| CertGenError::CertBuildFailed(format!("issuer self-sign: {e}")))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_leaf_params(
|
|
||||||
attestation_ext_der: &[u8],
|
attestation_ext_der: &[u8],
|
||||||
keybox: &ParsedKeybox,
|
keybox: &ParsedKeybox,
|
||||||
params: &CertGenParams,
|
params: &CertGenParams,
|
||||||
) -> Result<CertificateParams> {
|
) -> Result<Vec<u8>> {
|
||||||
let mut cp = CertificateParams::default();
|
let spki_der = extract_spki_from_pkcs8(&key_pair.private_key_pkcs8)?;
|
||||||
|
let sig_alg_der = signature_algorithm_for_signing_key(&keybox.signing_key_der, params.algorithm)?;
|
||||||
// Subject DN
|
|
||||||
cp.distinguished_name = if let Some(ref subject_der) = params.cert_subject {
|
|
||||||
parse_dn_from_der(subject_der)?
|
|
||||||
} else {
|
|
||||||
let mut dn = DistinguishedName::new();
|
|
||||||
dn.push(DnType::CommonName, "Android KeyStore Key");
|
|
||||||
dn
|
|
||||||
};
|
|
||||||
|
|
||||||
// Serial number
|
// Serial number
|
||||||
cp.serial_number = if let Some(ref serial_bytes) = params.cert_serial {
|
let serial_bytes = if let Some(ref serial) = params.cert_serial {
|
||||||
Some(SerialNumber::from(serial_bytes.clone()))
|
serial.clone()
|
||||||
} else {
|
} else {
|
||||||
Some(SerialNumber::from(vec![1u8]))
|
vec![1u8]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Validity period
|
// Subject DN
|
||||||
cp.not_before = timestamp_to_datetime(params.cert_not_before)?;
|
let subject_dn_der = if let Some(ref subject) = params.cert_subject {
|
||||||
cp.not_after = if params.cert_not_after == -1 {
|
subject.clone()
|
||||||
// Fall back to keybox leaf cert's notAfter, or +1 year
|
} else {
|
||||||
|
encode_simple_cn_dn("Android KeyStore Key")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validity
|
||||||
|
let not_before = timestamp_to_datetime(params.cert_not_before)?;
|
||||||
|
let not_after = if params.cert_not_after == -1 {
|
||||||
OffsetDateTime::from_unix_timestamp(keybox.leaf_not_after)
|
OffsetDateTime::from_unix_timestamp(keybox.leaf_not_after)
|
||||||
.unwrap_or_else(|_| OffsetDateTime::now_utc() + time::Duration::days(365))
|
.unwrap_or_else(|_| OffsetDateTime::now_utc() + time::Duration::days(365))
|
||||||
} else {
|
} else {
|
||||||
timestamp_to_datetime(params.cert_not_after)?
|
timestamp_to_datetime(params.cert_not_after)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// rcgen 0.13.2: IsCa::NoCa (the default) emits neither BasicConstraints nor SKI
|
// Extensions
|
||||||
// extension. This matches real Android attestation leaf certs which include neither.
|
let extensions_der = build_extensions(attestation_ext_der, ¶ms.purposes)?;
|
||||||
// No explicit suppression needed — NoCa is a no-op in the extension writer.
|
|
||||||
|
|
||||||
// KeyUsage from purposes
|
// TBS Certificate
|
||||||
cp.key_usages = map_key_usages(¶ms.purposes);
|
let version_der = encode_der_explicit_tag(0, &encode_der_integer(&[2]));
|
||||||
|
let serial_der = encode_der_integer(&serial_bytes);
|
||||||
|
let validity_der = encode_validity(¬_before, ¬_after);
|
||||||
|
let extensions_tagged = encode_der_explicit_tag(3, &extensions_der);
|
||||||
|
|
||||||
// Attestation extension (non-critical)
|
let tbs_der = encode_der_sequence(&[
|
||||||
let mut attest_ext = CustomExtension::from_oid_content(ATTESTATION_OID, attestation_ext_der.to_vec());
|
&version_der,
|
||||||
attest_ext.set_criticality(false);
|
&serial_der,
|
||||||
cp.custom_extensions.push(attest_ext);
|
&sig_alg_der,
|
||||||
|
&keybox.issuer_dn_der, // RAW bytes — no re-encoding
|
||||||
|
&validity_der,
|
||||||
|
&subject_dn_der,
|
||||||
|
&spki_der,
|
||||||
|
&extensions_tagged,
|
||||||
|
]);
|
||||||
|
|
||||||
Ok(cp)
|
// Sign the TBS
|
||||||
|
let signature_bytes = sign_tbs(&tbs_der, &keybox.signing_key_der, params.algorithm)?;
|
||||||
|
let signature_bit_string = encode_der_bit_string(&signature_bytes);
|
||||||
|
|
||||||
|
// Final certificate: SEQUENCE { TBS, sigAlgorithm, signature }
|
||||||
|
let cert_der = encode_der_sequence(&[
|
||||||
|
&tbs_der,
|
||||||
|
&sig_alg_der,
|
||||||
|
&signature_bit_string,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Ok(cert_der)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps KeyPurpose values to X.509 KeyUsage bits per KeyCreationResult.aidl spec.
|
fn sign_tbs(tbs_der: &[u8], signing_key_der: &[u8], algorithm: Algorithm) -> Result<Vec<u8>> {
|
||||||
/// Only SIGN, DECRYPT, WRAP_KEY, AGREE_KEY, and ATTEST_KEY produce KeyUsage bits.
|
match algorithm {
|
||||||
/// ENCRYPT and VERIFY are intentionally excluded (matches Kotlin CertificateGenerator).
|
Algorithm::Ec => sign_tbs_ec(tbs_der, signing_key_der),
|
||||||
fn map_key_usages(purposes: &[i32]) -> Vec<KeyUsagePurpose> {
|
Algorithm::Rsa => sign_tbs_rsa(tbs_der, signing_key_der),
|
||||||
let mut usages = Vec::new();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_tbs_ec(tbs_der: &[u8], signing_key_der: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
// Determine EC curve from the signing key's PKCS8 AlgorithmIdentifier
|
||||||
|
let alg = detect_ec_signing_algorithm(signing_key_der)?;
|
||||||
|
|
||||||
|
let key_pair = ring::signature::EcdsaKeyPair::from_pkcs8(alg, signing_key_der, &ring::rand::SystemRandom::new())
|
||||||
|
.map_err(|e| CertGenError::SigningFailed(format!("EC key parse: {e}")))?;
|
||||||
|
|
||||||
|
let rng = ring::rand::SystemRandom::new();
|
||||||
|
let sig = key_pair.sign(&rng, tbs_der)
|
||||||
|
.map_err(|e| CertGenError::SigningFailed(format!("EC sign: {e}")))?;
|
||||||
|
|
||||||
|
Ok(sig.as_ref().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_ec_signing_algorithm(pkcs8_der: &[u8]) -> Result<&'static ring::signature::EcdsaSigningAlgorithm> {
|
||||||
|
use der::Decode;
|
||||||
|
let info = pkcs8::PrivateKeyInfo::from_der(pkcs8_der)
|
||||||
|
.map_err(|e| CertGenError::SigningFailed(format!("PKCS8 parse: {e}")))?;
|
||||||
|
|
||||||
|
let params_oid = info.algorithm.parameters_oid()
|
||||||
|
.map_err(|e| CertGenError::SigningFailed(format!("EC curve OID: {e}")))?;
|
||||||
|
|
||||||
|
let p256_oid: const_oid::ObjectIdentifier = "1.2.840.10045.3.1.7".parse()
|
||||||
|
.map_err(|_| CertGenError::SigningFailed("OID parse".into()))?;
|
||||||
|
let p384_oid: const_oid::ObjectIdentifier = "1.3.132.0.34".parse()
|
||||||
|
.map_err(|_| CertGenError::SigningFailed("OID parse".into()))?;
|
||||||
|
|
||||||
|
if params_oid == p256_oid {
|
||||||
|
Ok(&ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING)
|
||||||
|
} else if params_oid == p384_oid {
|
||||||
|
Ok(&ring::signature::ECDSA_P384_SHA384_ASN1_SIGNING)
|
||||||
|
} else {
|
||||||
|
Err(CertGenError::SigningFailed(format!("unsupported EC curve OID: {params_oid}")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_tbs_rsa(tbs_der: &[u8], signing_key_der: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
use rsa::pkcs8::DecodePrivateKey;
|
||||||
|
use rsa::signature::{SignatureEncoding, SignerMut};
|
||||||
|
use rsa::pkcs1v15::SigningKey;
|
||||||
|
use rsa::sha2::Sha256;
|
||||||
|
|
||||||
|
let private_key = rsa::RsaPrivateKey::from_pkcs8_der(signing_key_der)
|
||||||
|
.map_err(|e| CertGenError::SigningFailed(format!("RSA key parse: {e}")))?;
|
||||||
|
|
||||||
|
let mut signing_key = SigningKey::<Sha256>::new(private_key);
|
||||||
|
let signature = signing_key.sign(tbs_der);
|
||||||
|
|
||||||
|
Ok(signature.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature_algorithm_for_signing_key(signing_key_der: &[u8], algorithm: Algorithm) -> Result<Vec<u8>> {
|
||||||
|
match algorithm {
|
||||||
|
Algorithm::Ec => {
|
||||||
|
let ring_alg = detect_ec_signing_algorithm(signing_key_der)?;
|
||||||
|
// Determine OID from the algorithm used
|
||||||
|
let oid = if std::ptr::eq(ring_alg, &ring::signature::ECDSA_P384_SHA384_ASN1_SIGNING) {
|
||||||
|
OID_SHA384_WITH_ECDSA
|
||||||
|
} else {
|
||||||
|
OID_SHA256_WITH_ECDSA
|
||||||
|
};
|
||||||
|
let oid_der = encode_der_oid(oid);
|
||||||
|
Ok(encode_der_sequence(&[&oid_der]))
|
||||||
|
}
|
||||||
|
Algorithm::Rsa => {
|
||||||
|
let oid_der = encode_der_oid(OID_SHA256_WITH_RSA);
|
||||||
|
let null_der = vec![0x05, 0x00];
|
||||||
|
Ok(encode_der_sequence(&[&oid_der, &null_der]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_spki_from_pkcs8(pkcs8_der: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
use der::Decode;
|
||||||
|
|
||||||
|
let info = pkcs8::PrivateKeyInfo::from_der(pkcs8_der)
|
||||||
|
.map_err(|e| CertGenError::CertBuildFailed(format!("PKCS8 parse for SPKI: {e}")))?;
|
||||||
|
|
||||||
|
// Reconstruct SPKI from AlgorithmIdentifier + public key
|
||||||
|
// For EC: derive public key from private key via ring
|
||||||
|
// For RSA: derive from rsa crate
|
||||||
|
let alg_id_oid = info.algorithm.oid;
|
||||||
|
let ec_oid: const_oid::ObjectIdentifier = "1.2.840.10045.2.1".parse()
|
||||||
|
.map_err(|_| CertGenError::CertBuildFailed("OID parse".into()))?;
|
||||||
|
|
||||||
|
if alg_id_oid == ec_oid {
|
||||||
|
extract_ec_spki(pkcs8_der, &info)
|
||||||
|
} else {
|
||||||
|
extract_rsa_spki(pkcs8_der)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_ec_spki(pkcs8_der: &[u8], info: &pkcs8::PrivateKeyInfo) -> Result<Vec<u8>> {
|
||||||
|
use ring::signature::KeyPair as _;
|
||||||
|
let params_oid = info.algorithm.parameters_oid()
|
||||||
|
.map_err(|e| CertGenError::CertBuildFailed(format!("EC curve OID: {e}")))?;
|
||||||
|
|
||||||
|
let p256_oid: const_oid::ObjectIdentifier = "1.2.840.10045.3.1.7".parse()
|
||||||
|
.map_err(|_| CertGenError::CertBuildFailed("OID parse".into()))?;
|
||||||
|
let p384_oid: const_oid::ObjectIdentifier = "1.3.132.0.34".parse()
|
||||||
|
.map_err(|_| CertGenError::CertBuildFailed("OID parse".into()))?;
|
||||||
|
|
||||||
|
let (ring_alg, curve_oid_der): (&ring::signature::EcdsaSigningAlgorithm, Vec<u8>) = if params_oid == p256_oid {
|
||||||
|
(&ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING, encode_der_oid(&[1, 2, 840, 10045, 3, 1, 7]))
|
||||||
|
} else if params_oid == p384_oid {
|
||||||
|
(&ring::signature::ECDSA_P384_SHA384_ASN1_SIGNING, encode_der_oid(&[1, 3, 132, 0, 34]))
|
||||||
|
} else {
|
||||||
|
return Err(CertGenError::CertBuildFailed(format!("unsupported EC curve: {params_oid}")));
|
||||||
|
};
|
||||||
|
|
||||||
|
let kp = ring::signature::EcdsaKeyPair::from_pkcs8(
|
||||||
|
ring_alg,
|
||||||
|
pkcs8_der,
|
||||||
|
&ring::rand::SystemRandom::new(),
|
||||||
|
).map_err(|e| CertGenError::CertBuildFailed(format!("EC key parse: {e}")))?;
|
||||||
|
let ec_kp = kp.public_key().as_ref().to_vec();
|
||||||
|
|
||||||
|
// SPKI = SEQUENCE { AlgorithmIdentifier, BIT STRING (public key) }
|
||||||
|
// AlgorithmIdentifier = SEQUENCE { ecPublicKey OID, curve OID }
|
||||||
|
let ec_oid_der = encode_der_oid(&[1, 2, 840, 10045, 2, 1]);
|
||||||
|
let alg_id = encode_der_sequence(&[&ec_oid_der, &curve_oid_der]);
|
||||||
|
let pub_key_bits = encode_der_bit_string(&ec_kp);
|
||||||
|
|
||||||
|
Ok(encode_der_sequence(&[&alg_id, &pub_key_bits]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_rsa_spki(pkcs8_der: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
use rsa::pkcs8::DecodePrivateKey;
|
||||||
|
|
||||||
|
let private_key = rsa::RsaPrivateKey::from_pkcs8_der(pkcs8_der)
|
||||||
|
.map_err(|e| CertGenError::CertBuildFailed(format!("RSA key parse: {e}")))?;
|
||||||
|
|
||||||
|
let public_key = rsa::RsaPublicKey::from(&private_key);
|
||||||
|
|
||||||
|
// Encode RSA public key as DER: SEQUENCE { n INTEGER, e INTEGER }
|
||||||
|
use rsa::traits::PublicKeyParts;
|
||||||
|
let n_bytes = public_key.n().to_bytes_be();
|
||||||
|
let e_bytes = public_key.e().to_bytes_be();
|
||||||
|
let rsa_pub_der = encode_der_sequence(&[
|
||||||
|
&encode_der_integer(&n_bytes),
|
||||||
|
&encode_der_integer(&e_bytes),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// SPKI = SEQUENCE { AlgorithmIdentifier, BIT STRING (DER-encoded RSAPublicKey) }
|
||||||
|
let rsa_oid_der = encode_der_oid(&[1, 2, 840, 113549, 1, 1, 1]);
|
||||||
|
let null_der = vec![0x05, 0x00];
|
||||||
|
let alg_id = encode_der_sequence(&[&rsa_oid_der, &null_der]);
|
||||||
|
let pub_key_bits = encode_der_bit_string(&rsa_pub_der);
|
||||||
|
|
||||||
|
Ok(encode_der_sequence(&[&alg_id, &pub_key_bits]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_extensions(attestation_ext_der: &[u8], purposes: &[i32]) -> Result<Vec<u8>> {
|
||||||
|
let mut extensions: Vec<Vec<u8>> = Vec::new();
|
||||||
|
|
||||||
|
// KeyUsage extension (critical)
|
||||||
|
let ku_byte = map_key_usage_byte(purposes);
|
||||||
|
if ku_byte != 0 {
|
||||||
|
let ku_ext = build_key_usage_extension(ku_byte);
|
||||||
|
extensions.push(ku_ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attestation extension (non-critical)
|
||||||
|
let attest_ext = build_extension(&encode_der_oid(ATTESTATION_OID), false, attestation_ext_der);
|
||||||
|
extensions.push(attest_ext);
|
||||||
|
|
||||||
|
Ok(encode_der_sequence_of(&extensions))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_extension(oid_der: &[u8], critical: bool, value_der: &[u8]) -> Vec<u8> {
|
||||||
|
let value_octet_string = encode_der_octet_string(value_der);
|
||||||
|
if critical {
|
||||||
|
let critical_der = encode_der_boolean(true);
|
||||||
|
encode_der_sequence(&[oid_der, &critical_der, &value_octet_string])
|
||||||
|
} else {
|
||||||
|
encode_der_sequence(&[oid_der, &value_octet_string])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_key_usage_extension(ku_byte: u8) -> Vec<u8> {
|
||||||
|
// DER BIT STRING: minimal encoding requires trimming trailing zero bits
|
||||||
|
let unused_bits = ku_byte.trailing_zeros().min(7) as u8;
|
||||||
|
|
||||||
|
// BIT STRING = tag (0x03) + length(2) + unused_bits + byte
|
||||||
|
let bit_string = vec![0x03, 0x02, unused_bits, ku_byte];
|
||||||
|
|
||||||
|
let oid_der = encode_der_oid(OID_KEY_USAGE);
|
||||||
|
let value_octet_string = encode_der_octet_string(&bit_string);
|
||||||
|
let critical_der = encode_der_boolean(true);
|
||||||
|
|
||||||
|
encode_der_sequence(&[&oid_der, &critical_der, &value_octet_string])
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyUsage BIT STRING byte layout (RFC 5280):
|
||||||
|
// byte[0] bit 7 = digitalSignature (0x80)
|
||||||
|
// byte[0] bit 6 = nonRepudiation (0x40)
|
||||||
|
// byte[0] bit 5 = keyEncipherment (0x20)
|
||||||
|
// byte[0] bit 4 = dataEncipherment (0x10)
|
||||||
|
// byte[0] bit 3 = keyAgreement (0x08)
|
||||||
|
// byte[0] bit 2 = keyCertSign (0x04)
|
||||||
|
// byte[0] bit 1 = cRLSign (0x02)
|
||||||
|
// byte[0] bit 0 = encipherOnly (0x01)
|
||||||
|
// byte[1] bit 7 = decipherOnly (0x80)
|
||||||
|
fn map_key_usage_byte(purposes: &[i32]) -> u8 {
|
||||||
|
let mut bits: u8 = 0;
|
||||||
for &purpose in purposes {
|
for &purpose in purposes {
|
||||||
match purpose {
|
match purpose {
|
||||||
2 => {
|
2 => bits |= 0x80, // SIGN -> digitalSignature
|
||||||
// SIGN -> digitalSignature
|
1 => bits |= 0x10, // DECRYPT -> dataEncipherment
|
||||||
if !usages.contains(&KeyUsagePurpose::DigitalSignature) {
|
5 => bits |= 0x20, // WRAP_KEY -> keyEncipherment
|
||||||
usages.push(KeyUsagePurpose::DigitalSignature);
|
6 => bits |= 0x08, // AGREE_KEY -> keyAgreement
|
||||||
}
|
7 => bits |= 0x04, // ATTEST_KEY -> keyCertSign
|
||||||
}
|
|
||||||
1 => {
|
|
||||||
// DECRYPT -> dataEncipherment
|
|
||||||
if !usages.contains(&KeyUsagePurpose::DataEncipherment) {
|
|
||||||
usages.push(KeyUsagePurpose::DataEncipherment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
5 => {
|
|
||||||
// WRAP_KEY -> keyEncipherment
|
|
||||||
if !usages.contains(&KeyUsagePurpose::KeyEncipherment) {
|
|
||||||
usages.push(KeyUsagePurpose::KeyEncipherment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6 => {
|
|
||||||
// AGREE_KEY -> keyAgreement
|
|
||||||
if !usages.contains(&KeyUsagePurpose::KeyAgreement) {
|
|
||||||
usages.push(KeyUsagePurpose::KeyAgreement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7 => {
|
|
||||||
// ATTEST_KEY -> keyCertSign
|
|
||||||
if !usages.contains(&KeyUsagePurpose::KeyCertSign) {
|
|
||||||
usages.push(KeyUsagePurpose::KeyCertSign);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
usages
|
bits
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_validity(not_before: &OffsetDateTime, not_after: &OffsetDateTime) -> Vec<u8> {
|
||||||
|
let nb = encode_time(not_before);
|
||||||
|
let na = encode_time(not_after);
|
||||||
|
encode_der_sequence(&[&nb, &na])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_time(dt: &OffsetDateTime) -> Vec<u8> {
|
||||||
|
let year = dt.year();
|
||||||
|
if (1950..2050).contains(&year) {
|
||||||
|
encode_utctime(dt)
|
||||||
|
} else {
|
||||||
|
encode_gentime(dt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_utctime(dt: &OffsetDateTime) -> Vec<u8> {
|
||||||
|
// UTCTime: YYMMDDHHMMSSZ
|
||||||
|
let year = dt.year() % 100;
|
||||||
|
let s = format!(
|
||||||
|
"{:02}{:02}{:02}{:02}{:02}{:02}Z",
|
||||||
|
year, dt.month() as u8, dt.day(), dt.hour(), dt.minute(), dt.second()
|
||||||
|
);
|
||||||
|
let mut out = Vec::with_capacity(2 + s.len());
|
||||||
|
out.push(0x17); // UTCTime tag
|
||||||
|
out.extend_from_slice(&encode_der_length_bytes(s.len()));
|
||||||
|
out.extend_from_slice(s.as_bytes());
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_gentime(dt: &OffsetDateTime) -> Vec<u8> {
|
||||||
|
// GeneralizedTime: YYYYMMDDHHMMSSZ
|
||||||
|
let s = format!(
|
||||||
|
"{:04}{:02}{:02}{:02}{:02}{:02}Z",
|
||||||
|
dt.year(), dt.month() as u8, dt.day(), dt.hour(), dt.minute(), dt.second()
|
||||||
|
);
|
||||||
|
let mut out = Vec::with_capacity(2 + s.len());
|
||||||
|
out.push(0x18); // GeneralizedTime tag
|
||||||
|
out.extend_from_slice(&encode_der_length_bytes(s.len()));
|
||||||
|
out.extend_from_slice(s.as_bytes());
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_simple_cn_dn(cn: &str) -> Vec<u8> {
|
||||||
|
// Name = SEQUENCE OF RelativeDistinguishedName
|
||||||
|
// RDN = SET OF AttributeTypeAndValue
|
||||||
|
// ATV = SEQUENCE { OID, UTF8String }
|
||||||
|
let cn_oid = encode_der_oid(&[2, 5, 4, 3]);
|
||||||
|
let cn_value = encode_der_utf8string(cn);
|
||||||
|
let atv = encode_der_sequence(&[&cn_oid, &cn_value]);
|
||||||
|
let rdn = encode_der_set(&[&atv]);
|
||||||
|
encode_der_sequence(&[&rdn])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn timestamp_to_datetime(ts: i64) -> Result<OffsetDateTime> {
|
fn timestamp_to_datetime(ts: i64) -> Result<OffsetDateTime> {
|
||||||
if ts == -1 {
|
if ts == -1 {
|
||||||
return Ok(OffsetDateTime::now_utc());
|
return Ok(OffsetDateTime::now_utc());
|
||||||
}
|
}
|
||||||
// Params use milliseconds for validity timestamps
|
|
||||||
OffsetDateTime::from_unix_timestamp(ts / 1000)
|
OffsetDateTime::from_unix_timestamp(ts / 1000)
|
||||||
.map_err(|e| CertGenError::CertBuildFailed(format!("invalid timestamp {ts}: {e}")))
|
.map_err(|e| CertGenError::CertBuildFailed(format!("invalid timestamp {ts}: {e}")))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a DER-encoded X.500 Name into rcgen DistinguishedName.
|
// ---------------------------------------------------------------------------
|
||||||
// We only extract the CN (most common for Android keystore certs).
|
// DER encoding primitives
|
||||||
// If parsing fails, fall back to empty DN.
|
// ---------------------------------------------------------------------------
|
||||||
fn parse_dn_from_der(der: &[u8]) -> Result<DistinguishedName> {
|
|
||||||
use x509_cert::name::Name;
|
|
||||||
use der::{Decode, Encode};
|
|
||||||
|
|
||||||
let name = Name::from_der(der)
|
fn encode_der_length_bytes(len: usize) -> Vec<u8> {
|
||||||
.map_err(|e| CertGenError::CertBuildFailed(format!("DN parse: {e}")))?;
|
if len < 0x80 {
|
||||||
|
vec![len as u8]
|
||||||
let mut dn = DistinguishedName::new();
|
} else if len <= 0xFF {
|
||||||
|
vec![0x81, len as u8]
|
||||||
for rdn in name.0.iter() {
|
} else if len <= 0xFFFF {
|
||||||
for atv in rdn.0.iter() {
|
vec![0x82, (len >> 8) as u8, len as u8]
|
||||||
let oid_str = atv.oid.to_string();
|
} else if len <= 0xFF_FFFF {
|
||||||
// Map common OIDs to rcgen DnType
|
vec![0x83, (len >> 16) as u8, (len >> 8) as u8, len as u8]
|
||||||
let dn_type = match oid_str.as_str() {
|
|
||||||
"2.5.4.3" => DnType::CommonName,
|
|
||||||
"2.5.4.6" => DnType::CountryName,
|
|
||||||
"2.5.4.7" => DnType::LocalityName,
|
|
||||||
"2.5.4.8" => DnType::StateOrProvinceName,
|
|
||||||
"2.5.4.10" => DnType::OrganizationName,
|
|
||||||
"2.5.4.11" => DnType::OrganizationalUnitName,
|
|
||||||
other => DnType::CustomDnType(
|
|
||||||
other.split('.').filter_map(|s| s.parse().ok()).collect(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extract the string value from the AttributeValue (ANY type)
|
|
||||||
// The value is DER-encoded; try to read it as UTF8String or PrintableString
|
|
||||||
let value_bytes = atv.value.to_der()
|
|
||||||
.map_err(|e| CertGenError::CertBuildFailed(format!("DN value encode: {e}")))?;
|
|
||||||
let value_str = extract_string_from_der_any(&value_bytes);
|
|
||||||
dn.push(dn_type, value_str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(dn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract a string from a DER-encoded ASN.1 string type (UTF8String, PrintableString, etc.)
|
|
||||||
fn extract_string_from_der_any(der: &[u8]) -> String {
|
|
||||||
if der.len() < 2 {
|
|
||||||
return String::new();
|
|
||||||
}
|
|
||||||
// Tag byte at [0], length at [1..], then content
|
|
||||||
let tag = der[0];
|
|
||||||
let (content_len, header_len) = if der[1] < 0x80 {
|
|
||||||
(der[1] as usize, 2)
|
|
||||||
} else {
|
} else {
|
||||||
let num = (der[1] & 0x7f) as usize;
|
vec![0x84, (len >> 24) as u8, (len >> 16) as u8, (len >> 8) as u8, len as u8]
|
||||||
if num == 0 || 2 + num > der.len() {
|
|
||||||
return String::new();
|
|
||||||
}
|
|
||||||
let mut len = 0usize;
|
|
||||||
for i in 0..num {
|
|
||||||
len = (len << 8) | der[2 + i] as usize;
|
|
||||||
}
|
|
||||||
(len, 2 + num)
|
|
||||||
};
|
|
||||||
|
|
||||||
let end = header_len + content_len;
|
|
||||||
if end > der.len() {
|
|
||||||
return String::new();
|
|
||||||
}
|
|
||||||
let content = &der[header_len..end];
|
|
||||||
|
|
||||||
match tag {
|
|
||||||
0x0C | 0x13 | 0x16 | 0x1A => {
|
|
||||||
// UTF8String (0x0C), PrintableString (0x13), IA5String (0x16), VisibleString (0x1A)
|
|
||||||
String::from_utf8_lossy(content).into_owned()
|
|
||||||
}
|
|
||||||
0x1E => {
|
|
||||||
// BMPString (UTF-16BE)
|
|
||||||
let chars: Vec<u16> = content
|
|
||||||
.chunks_exact(2)
|
|
||||||
.map(|c| u16::from_be_bytes([c[0], c[1]]))
|
|
||||||
.collect();
|
|
||||||
String::from_utf16_lossy(&chars)
|
|
||||||
}
|
|
||||||
_ => String::from_utf8_lossy(content).into_owned(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encode_der_tag_length_value(tag: u8, content: &[u8]) -> Vec<u8> {
|
||||||
|
let mut out = Vec::with_capacity(1 + 4 + content.len());
|
||||||
|
out.push(tag);
|
||||||
|
out.extend_from_slice(&encode_der_length_bytes(content.len()));
|
||||||
|
out.extend_from_slice(content);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_der_sequence(items: &[&[u8]]) -> Vec<u8> {
|
||||||
|
let total: usize = items.iter().map(|i| i.len()).sum();
|
||||||
|
let mut content = Vec::with_capacity(total);
|
||||||
|
for item in items {
|
||||||
|
content.extend_from_slice(item);
|
||||||
|
}
|
||||||
|
encode_der_tag_length_value(0x30, &content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_der_sequence_of(items: &[Vec<u8>]) -> Vec<u8> {
|
||||||
|
let total: usize = items.iter().map(|i| i.len()).sum();
|
||||||
|
let mut content = Vec::with_capacity(total);
|
||||||
|
for item in items {
|
||||||
|
content.extend_from_slice(item);
|
||||||
|
}
|
||||||
|
encode_der_tag_length_value(0x30, &content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_der_set(items: &[&[u8]]) -> Vec<u8> {
|
||||||
|
let total: usize = items.iter().map(|i| i.len()).sum();
|
||||||
|
let mut content = Vec::with_capacity(total);
|
||||||
|
for item in items {
|
||||||
|
content.extend_from_slice(item);
|
||||||
|
}
|
||||||
|
encode_der_tag_length_value(0x31, &content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_der_explicit_tag(tag_num: u8, content: &[u8]) -> Vec<u8> {
|
||||||
|
encode_der_tag_length_value(0xA0 | tag_num, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_der_integer(value: &[u8]) -> Vec<u8> {
|
||||||
|
// DER INTEGER must have minimal encoding and leading 0x00 if high bit set
|
||||||
|
if value.is_empty() {
|
||||||
|
return encode_der_tag_length_value(0x02, &[0x00]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip leading zeros (but keep at least one byte)
|
||||||
|
let mut start = 0;
|
||||||
|
while start < value.len() - 1 && value[start] == 0 {
|
||||||
|
start += 1;
|
||||||
|
}
|
||||||
|
let trimmed = &value[start..];
|
||||||
|
|
||||||
|
// Add leading 0x00 if high bit is set (positive integer)
|
||||||
|
if trimmed[0] & 0x80 != 0 {
|
||||||
|
let mut padded = Vec::with_capacity(1 + trimmed.len());
|
||||||
|
padded.push(0x00);
|
||||||
|
padded.extend_from_slice(trimmed);
|
||||||
|
encode_der_tag_length_value(0x02, &padded)
|
||||||
|
} else {
|
||||||
|
encode_der_tag_length_value(0x02, trimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_der_bit_string(bits: &[u8]) -> Vec<u8> {
|
||||||
|
// BIT STRING: tag 0x03, length, unused_bits (0), content
|
||||||
|
let mut content = Vec::with_capacity(1 + bits.len());
|
||||||
|
content.push(0x00); // 0 unused bits
|
||||||
|
content.extend_from_slice(bits);
|
||||||
|
encode_der_tag_length_value(0x03, &content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_der_octet_string(content: &[u8]) -> Vec<u8> {
|
||||||
|
encode_der_tag_length_value(0x04, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_der_utf8string(s: &str) -> Vec<u8> {
|
||||||
|
encode_der_tag_length_value(0x0C, s.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_der_boolean(val: bool) -> Vec<u8> {
|
||||||
|
encode_der_tag_length_value(0x01, &[if val { 0xFF } else { 0x00 }])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_der_oid(components: &[u64]) -> Vec<u8> {
|
||||||
|
if components.len() < 2 {
|
||||||
|
return encode_der_tag_length_value(0x06, &[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut content = Vec::new();
|
||||||
|
// First two components encoded as 40 * c[0] + c[1]
|
||||||
|
content.push((components[0] * 40 + components[1]) as u8);
|
||||||
|
|
||||||
|
for &c in &components[2..] {
|
||||||
|
encode_oid_subidentifier(&mut content, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
encode_der_tag_length_value(0x06, &content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_oid_subidentifier(buf: &mut Vec<u8>, mut value: u64) {
|
||||||
|
if value == 0 {
|
||||||
|
buf.push(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode in base-128 with continuation bits
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
while value > 0 {
|
||||||
|
bytes.push((value & 0x7F) as u8);
|
||||||
|
value >>= 7;
|
||||||
|
}
|
||||||
|
bytes.reverse();
|
||||||
|
|
||||||
|
// Set high bit on all but the last byte
|
||||||
|
for i in 0..bytes.len() - 1 {
|
||||||
|
bytes[i] |= 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.extend_from_slice(&bytes);
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ pub enum CertGenError {
|
|||||||
KeyboxParseFailed(String),
|
KeyboxParseFailed(String),
|
||||||
AttestationBuildFailed(String),
|
AttestationBuildFailed(String),
|
||||||
DerError(der::Error),
|
DerError(der::Error),
|
||||||
RcgenError(rcgen::Error),
|
|
||||||
EmptyKeyboxChain,
|
EmptyKeyboxChain,
|
||||||
ChallengeTooLong(usize),
|
ChallengeTooLong(usize),
|
||||||
InvalidParameter(String),
|
InvalidParameter(String),
|
||||||
@@ -31,7 +30,6 @@ impl fmt::Display for CertGenError {
|
|||||||
Self::KeyboxParseFailed(msg) => write!(f, "keybox parse failed: {}", msg),
|
Self::KeyboxParseFailed(msg) => write!(f, "keybox parse failed: {}", msg),
|
||||||
Self::AttestationBuildFailed(msg) => write!(f, "attestation build failed: {}", msg),
|
Self::AttestationBuildFailed(msg) => write!(f, "attestation build failed: {}", msg),
|
||||||
Self::DerError(e) => write!(f, "DER error: {}", e),
|
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::EmptyKeyboxChain => write!(f, "keybox certificate chain is empty"),
|
||||||
Self::ChallengeTooLong(len) => write!(f, "attestation challenge too long: {} bytes (max 128)", len),
|
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),
|
||||||
@@ -73,10 +71,5 @@ impl From<rsa::Error> for CertGenError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<rcgen::Error> for CertGenError {
|
|
||||||
fn from(e: rcgen::Error) -> Self {
|
|
||||||
Self::RcgenError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, CertGenError>;
|
pub type Result<T> = std::result::Result<T, CertGenError>;
|
||||||
|
|||||||
Reference in New Issue
Block a user