|
@@ -1,29 +1,43 @@
|
|
|
//! Code for converting [Writecap]s to and from the X.509 certificate format.
|
|
|
+//! TODO: Improve the efficiency and quality of this code by moving to a different library for
|
|
|
+//! handling X.509 certificates.
|
|
|
|
|
|
use crate::{
|
|
|
bterr,
|
|
|
- crypto::{AsymKeyPub, HashKind, RsaSsaPss, Sha2_256, Sha2_512, Sign},
|
|
|
- Principaled, Result, Writecap,
|
|
|
+ crypto::{AsymKeyPub, HashKind, KeyLen, RsaSsaPss, Sha2_256, Sha2_512, Sign, Signature},
|
|
|
+ BlockPath, Epoch, Principal, Principaled, Result, Writecap, WritecapBody,
|
|
|
};
|
|
|
use bcder::{
|
|
|
- decode::{BytesSource, Constructed},
|
|
|
+ decode::{BytesSource, Constructed, DecodeError, SliceSource},
|
|
|
encode::{PrimitiveContent, Values},
|
|
|
- BitString, Captured, Ia5String, Integer, Mode, OctetString, Oid, Tag,
|
|
|
+ BitString, Captured, Integer, Mode, OctetString, Oid, Tag, Utf8String,
|
|
|
};
|
|
|
use bytes::{BufMut, Bytes, BytesMut};
|
|
|
use chrono::{offset::Utc, TimeZone};
|
|
|
+use std::ops::Deref;
|
|
|
use x509_certificate::{
|
|
|
asn1time::{Time, UtcTime},
|
|
|
certificate::X509Certificate,
|
|
|
- rfc3280::Name,
|
|
|
+ rfc3280::{AttributeValue, Name},
|
|
|
rfc5280::{
|
|
|
AlgorithmIdentifier, AlgorithmParameter, Certificate, CertificateSerialNumber, Extension,
|
|
|
Extensions, SubjectPublicKeyInfo, TbsCertificate, Validity, Version,
|
|
|
},
|
|
|
};
|
|
|
+
|
|
|
mod private {
|
|
|
use super::*;
|
|
|
|
|
|
+ fn oid(slice: &'static [u8]) -> Oid {
|
|
|
+ Oid(Bytes::from(slice))
|
|
|
+ }
|
|
|
+
|
|
|
+ macro_rules! bit_string {
|
|
|
+ ($bytes:expr) => {
|
|
|
+ BitString::new(0, Bytes::from($bytes))
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
impl Sha2_256 {
|
|
|
// The DER encoding of the OID 2.16.840.1.101.3.4.2.1
|
|
|
const OID: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01];
|
|
@@ -41,44 +55,30 @@ mod private {
|
|
|
HashKind::Sha2_512 => Sha2_512::OID,
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ fn from_oid(slice: &[u8]) -> Result<Self> {
|
|
|
+ if slice == Sha2_256::OID {
|
|
|
+ Ok(Self::Sha2_256)
|
|
|
+ } else if slice == Sha2_512::OID {
|
|
|
+ Ok(Self::Sha2_512)
|
|
|
+ } else {
|
|
|
+ Err(bterr!("unrecognized OID"))
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// The DER encoding of 1.2.840.113549.1.1.8 (id-mgf1 in RFC 4055)
|
|
|
const MGF1_OID: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x08];
|
|
|
|
|
|
- struct NullTermOid {
|
|
|
- oid: Oid,
|
|
|
- }
|
|
|
-
|
|
|
- impl NullTermOid {
|
|
|
- fn new(oid: Oid) -> Self {
|
|
|
- Self { oid }
|
|
|
- }
|
|
|
-
|
|
|
- fn encode_ref(&self) -> impl Values + '_ {
|
|
|
- self.encode_ref_as(Tag::SEQUENCE)
|
|
|
- }
|
|
|
-
|
|
|
- fn encode_ref_as(&self, tag: Tag) -> impl Values + '_ {
|
|
|
- bcder::encode::sequence_as(
|
|
|
- tag,
|
|
|
- (
|
|
|
- self.oid.encode_ref(),
|
|
|
- bcder::encode::Constructed::new(Tag::NULL, bcder::encode::Nothing),
|
|
|
- ),
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- struct AlgoId {
|
|
|
+ struct SingleParamAlgoId {
|
|
|
algorithm: Oid,
|
|
|
- parameters: NullTermOid,
|
|
|
+ parameters: Oid,
|
|
|
}
|
|
|
|
|
|
- impl AlgoId {
|
|
|
+ impl SingleParamAlgoId {
|
|
|
fn new(hash_kind: HashKind) -> Self {
|
|
|
let algorithm = Oid(Bytes::from(MGF1_OID));
|
|
|
- let parameters = NullTermOid::new(Oid(Bytes::from(hash_kind.oid())));
|
|
|
+ let parameters = Oid(Bytes::from(hash_kind.oid()));
|
|
|
Self {
|
|
|
algorithm,
|
|
|
parameters,
|
|
@@ -95,19 +95,30 @@ mod private {
|
|
|
(self.algorithm.encode_ref(), self.parameters.encode_ref()),
|
|
|
)
|
|
|
}
|
|
|
+
|
|
|
+ fn take_from<S: bcder::decode::Source>(
|
|
|
+ cons: &mut Constructed<'_, S>,
|
|
|
+ ) -> std::result::Result<Self, DecodeError<S::Error>> {
|
|
|
+ cons.take_sequence(|cons| {
|
|
|
+ Ok(Self {
|
|
|
+ algorithm: Oid::take_from(cons)?,
|
|
|
+ parameters: Oid::take_from(cons)?,
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
struct RsaSsaPssParams {
|
|
|
- hash_algorithm: NullTermOid,
|
|
|
- mask_gen_algorithm: AlgoId,
|
|
|
+ hash_algorithm: Oid,
|
|
|
+ mask_gen_algorithm: SingleParamAlgoId,
|
|
|
salt_length: Integer,
|
|
|
trailer_field: Option<Integer>,
|
|
|
}
|
|
|
|
|
|
impl RsaSsaPssParams {
|
|
|
fn new(hash_kind: HashKind) -> Self {
|
|
|
- let hash_algorithm = NullTermOid::new(Oid(Bytes::from(hash_kind.oid())));
|
|
|
- let mask_gen_algorithm = AlgoId::new(hash_kind);
|
|
|
+ let hash_algorithm = Oid(Bytes::from(hash_kind.oid()));
|
|
|
+ let mask_gen_algorithm = SingleParamAlgoId::new(hash_kind);
|
|
|
let salt_length = Integer::from(hash_kind.len() as u64);
|
|
|
Self {
|
|
|
hash_algorithm,
|
|
@@ -135,6 +146,25 @@ mod private {
|
|
|
),
|
|
|
)
|
|
|
}
|
|
|
+
|
|
|
+ fn take_from<S: bcder::decode::Source>(
|
|
|
+ cons: &mut Constructed<'_, S>,
|
|
|
+ ) -> std::result::Result<Option<Self>, DecodeError<S::Error>> {
|
|
|
+ let option = cons.take_opt_value_if(Tag::SEQUENCE, |content| {
|
|
|
+ let cons = content.as_constructed()?;
|
|
|
+ Ok(RsaSsaPssParams {
|
|
|
+ hash_algorithm: cons.take_constructed_if(Tag::CTX_0, Oid::take_from)?,
|
|
|
+ mask_gen_algorithm: cons
|
|
|
+ .take_constructed_if(Tag::CTX_1, SingleParamAlgoId::take_from)?,
|
|
|
+ salt_length: cons.take_constructed_if(Tag::CTX_2, Integer::take_from)?,
|
|
|
+ trailer_field: cons.take_opt_constructed_if(Tag::CTX_3, Integer::take_from)?,
|
|
|
+ })
|
|
|
+ })?;
|
|
|
+ if option.is_none() {
|
|
|
+ cons.take_null()?;
|
|
|
+ }
|
|
|
+ Ok(option)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl RsaSsaPss {
|
|
@@ -146,7 +176,7 @@ mod private {
|
|
|
/// The OID 1.2.840.113549.1.1.1 (RSA-ES)
|
|
|
const RSA_ES_OID: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01];
|
|
|
|
|
|
- const fn oid(&self) -> &'static [u8] {
|
|
|
+ const fn oid() -> &'static [u8] {
|
|
|
if Self::USE_PSS_OID {
|
|
|
Self::RSA_PSS_OID
|
|
|
} else {
|
|
@@ -164,12 +194,33 @@ mod private {
|
|
|
Ok(None)
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ fn from_params(sig_octet_len: u32, params: Option<&[u8]>) -> Result<Self> {
|
|
|
+ let key_bits = KeyLen::try_from(sig_octet_len)?;
|
|
|
+ let hash_kind = match params {
|
|
|
+ Some(params) => {
|
|
|
+ let source = SliceSource::new(params);
|
|
|
+ let params =
|
|
|
+ Constructed::decode(source, Mode::Der, RsaSsaPssParams::take_from)?;
|
|
|
+ if let Some(params) = params {
|
|
|
+ HashKind::from_oid(params.hash_algorithm.as_ref())?
|
|
|
+ } else {
|
|
|
+ HashKind::default()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ None => HashKind::default(),
|
|
|
+ };
|
|
|
+ Ok(Self {
|
|
|
+ key_bits,
|
|
|
+ hash_kind,
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl Sign {
|
|
|
const fn oid(&self) -> &'static [u8] {
|
|
|
match self {
|
|
|
- Sign::RsaSsaPss(inner) => inner.oid(),
|
|
|
+ Sign::RsaSsaPss(..) => RsaSsaPss::oid(),
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -178,36 +229,43 @@ mod private {
|
|
|
Sign::RsaSsaPss(inner) => inner.params(),
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- fn scheme_to_algo_id(scheme: &Sign) -> Result<AlgorithmIdentifier> {
|
|
|
- Ok(AlgorithmIdentifier {
|
|
|
- algorithm: oid(scheme.oid()),
|
|
|
- parameters: scheme.params()?,
|
|
|
- })
|
|
|
- }
|
|
|
+ fn from_der(sig_octet_len: u32, oid: &[u8], params: Option<&[u8]>) -> Result<Sign> {
|
|
|
+ if oid == RsaSsaPss::oid() {
|
|
|
+ Ok(Sign::RsaSsaPss(RsaSsaPss::from_params(
|
|
|
+ sig_octet_len,
|
|
|
+ params,
|
|
|
+ )?))
|
|
|
+ } else {
|
|
|
+ Err(bterr!("OID does not match a Sign variant"))
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- fn oid(slice: &'static [u8]) -> Oid {
|
|
|
- Oid(Bytes::from(slice))
|
|
|
- }
|
|
|
+ fn to_algo_id(self) -> Result<AlgorithmIdentifier> {
|
|
|
+ Ok(AlgorithmIdentifier {
|
|
|
+ algorithm: oid(self.oid()),
|
|
|
+ parameters: self.params()?,
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
- macro_rules! bit_string {
|
|
|
- ($bytes:expr) => {
|
|
|
- BitString::new(0, Bytes::from($bytes))
|
|
|
- };
|
|
|
+ fn from_algo_id(sig_octet_len: u32, algo_id: &AlgorithmIdentifier) -> Result<Sign> {
|
|
|
+ let params = algo_id.parameters.as_ref().map(|e| e.as_ref());
|
|
|
+ Sign::from_der(sig_octet_len, algo_id.algorithm.as_ref(), params)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- /// OID 2.5.29.17 (Subject Alternative Name)
|
|
|
- const SAN_OID: &[u8] = &[0x55, 0x1D, 0x11];
|
|
|
-
|
|
|
struct SubjectAltName {
|
|
|
- issued_to: Ia5String,
|
|
|
+ block_path: Utf8String,
|
|
|
}
|
|
|
|
|
|
impl SubjectAltName {
|
|
|
- fn new(issued_to: String) -> Result<Self> {
|
|
|
- let issued_to = Ia5String::from_string(issued_to).map_err(|err| bterr!("{:?}", err))?;
|
|
|
- Ok(Self { issued_to })
|
|
|
+ /// OID 2.5.29.17 (Subject Alternative Name)
|
|
|
+ const OID: &[u8] = &[0x55, 0x1D, 0x11];
|
|
|
+
|
|
|
+ fn new(block_path: &BlockPath) -> Result<Self> {
|
|
|
+ let block_path = Utf8String::from_string(block_path.to_string())
|
|
|
+ .map_err(|err| bterr!("{:?}", err))?;
|
|
|
+ Ok(Self { block_path })
|
|
|
}
|
|
|
|
|
|
fn encode(&self) -> impl Values + '_ {
|
|
@@ -215,20 +273,35 @@ mod private {
|
|
|
}
|
|
|
|
|
|
fn encode_as(&self, tag: Tag) -> impl Values + '_ {
|
|
|
- bcder::encode::sequence_as(tag, self.issued_to.encode_ref())
|
|
|
+ bcder::encode::sequence_as(tag, self.block_path.encode_ref())
|
|
|
+ }
|
|
|
+
|
|
|
+ fn take_from<S: bcder::decode::Source>(
|
|
|
+ cons: &mut Constructed<'_, S>,
|
|
|
+ ) -> std::result::Result<Self, DecodeError<S::Error>> {
|
|
|
+ cons.take_sequence(|cons| {
|
|
|
+ Ok(Self {
|
|
|
+ block_path: Utf8String::take_from(cons)?,
|
|
|
+ })
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
fn encode_der(&self) -> Result<Bytes> {
|
|
|
let mut writer = BytesMut::new().writer();
|
|
|
- self.encode().write_encoded(bcder::Mode::Der, &mut writer)?;
|
|
|
+ self.encode().write_encoded(Mode::Der, &mut writer)?;
|
|
|
Ok(writer.into_inner().into())
|
|
|
}
|
|
|
+
|
|
|
+ fn decode_der<B: AsRef<[u8]>>(bytes: B) -> Result<Self> {
|
|
|
+ let source = SliceSource::new(bytes.as_ref());
|
|
|
+ Constructed::decode(source, Mode::Der, Self::take_from).map_err(|err| err.into())
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl AsymKeyPub<Sign> {
|
|
|
fn subject_public_key_info(&self) -> Result<SubjectPublicKeyInfo> {
|
|
|
Ok(SubjectPublicKeyInfo {
|
|
|
- algorithm: scheme_to_algo_id(&self.scheme)?,
|
|
|
+ algorithm: self.scheme.to_algo_id()?,
|
|
|
subject_public_key: self.to_bit_string()?,
|
|
|
})
|
|
|
}
|
|
@@ -240,6 +313,16 @@ mod private {
|
|
|
Ok(spki.subject_public_key)
|
|
|
}
|
|
|
|
|
|
+ fn from_subject_public_key_info(
|
|
|
+ sig_octet_len: u32,
|
|
|
+ spki: &SubjectPublicKeyInfo,
|
|
|
+ ) -> Result<Self> {
|
|
|
+ let scheme = Sign::from_algo_id(sig_octet_len, &spki.algorithm)?;
|
|
|
+ let mut der = Vec::new();
|
|
|
+ spki.encode_ref().write_encoded(Mode::Der, &mut der)?;
|
|
|
+ AsymKeyPub::new(scheme, der.as_slice())
|
|
|
+ }
|
|
|
+
|
|
|
pub fn to_der(&self) -> Result<Vec<u8>> {
|
|
|
let spki = self.subject_public_key_info()?;
|
|
|
let mut vec = Vec::new();
|
|
@@ -248,19 +331,64 @@ mod private {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- impl Writecap {
|
|
|
- pub fn to_cert_chain(
|
|
|
- &self,
|
|
|
- subject_key: &AsymKeyPub<Sign>,
|
|
|
- ) -> Result<Vec<X509Certificate>> {
|
|
|
- let mut chain = match self.next.as_ref() {
|
|
|
- Some(next) => next.as_ref().to_cert_chain(&self.body.signing_key)?,
|
|
|
- None => Vec::new(),
|
|
|
- };
|
|
|
+ trait NameExt {
|
|
|
+ fn try_get_common_name(&self) -> Result<&AttributeValue>;
|
|
|
+ }
|
|
|
+
|
|
|
+ impl NameExt for Name {
|
|
|
+ fn try_get_common_name(&self) -> Result<&AttributeValue> {
|
|
|
+ Ok(&self
|
|
|
+ .iter_common_name()
|
|
|
+ .next()
|
|
|
+ .ok_or_else(|| bterr!("no CommonName component in Name"))?
|
|
|
+ .value)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ trait TryAsStr {
|
|
|
+ fn try_as_str(&self) -> Result<&str>;
|
|
|
+ }
|
|
|
+
|
|
|
+ impl<T: ?Sized + AsRef<[u8]>> TryAsStr for T {
|
|
|
+ fn try_as_str(&self) -> Result<&str> {
|
|
|
+ std::str::from_utf8(self.as_ref()).map_err(|err| err.into())
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ trait TimeExt {
|
|
|
+ fn try_to_epoch(&self) -> Result<Epoch>;
|
|
|
+ }
|
|
|
+
|
|
|
+ impl TimeExt for Time {
|
|
|
+ fn try_to_epoch(&self) -> Result<Epoch> {
|
|
|
+ match self {
|
|
|
+ Self::UtcTime(time) => Ok(Epoch::from_value(time.timestamp() as u64)),
|
|
|
+ Self::GeneralTime(..) => Err(bterr!("unsupported Time variant encountered")),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ trait ExtensionsExt {
|
|
|
+ fn find_subject_alt_name(&self) -> Result<SubjectAltName>;
|
|
|
+ }
|
|
|
+
|
|
|
+ impl ExtensionsExt for Extensions {
|
|
|
+ fn find_subject_alt_name(&self) -> Result<SubjectAltName> {
|
|
|
+ let extensions: &[Extension] = self.deref();
|
|
|
+ for extension in extensions {
|
|
|
+ if extension.id.as_ref() == SubjectAltName::OID {
|
|
|
+ return SubjectAltName::decode_der(extension.value.to_bytes());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Err(bterr!("SubjectAltName not found"))
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ impl Writecap {
|
|
|
+ pub fn to_cert(&self, subject_key: &AsymKeyPub<Sign>) -> Result<Vec<u8>> {
|
|
|
let version = Some(Version::V3);
|
|
|
let serial_number = CertificateSerialNumber::from(1);
|
|
|
- let signature_algorithm = scheme_to_algo_id(&self.body.signing_key.scheme)?;
|
|
|
+ let signature_algorithm = self.body.signing_key.scheme.to_algo_id()?;
|
|
|
let mut issuer = Name::default();
|
|
|
issuer
|
|
|
.append_common_name_utf8_string(&self.body.signing_key.principal().to_string())
|
|
@@ -282,9 +410,9 @@ mod private {
|
|
|
.map_err(|_| bterr!("failed to create subject common name"))?;
|
|
|
let subject_public_key_info = subject_key.subject_public_key_info()?;
|
|
|
let mut extensions = Extensions::default();
|
|
|
- let san = SubjectAltName::new(issued_to)?;
|
|
|
+ let san = SubjectAltName::new(&self.body.path)?;
|
|
|
extensions.push(Extension {
|
|
|
- id: oid(SAN_OID),
|
|
|
+ id: oid(SubjectAltName::OID),
|
|
|
critical: Some(false),
|
|
|
value: OctetString::new(san.encode_der()?),
|
|
|
});
|
|
@@ -306,17 +434,119 @@ mod private {
|
|
|
signature_algorithm,
|
|
|
signature: bit_string!(self.signature.data.clone()),
|
|
|
};
|
|
|
- chain.push(cert.into());
|
|
|
+ let cert: X509Certificate = cert.into();
|
|
|
+ cert.encode_der().map_err(|err| err.into())
|
|
|
+ }
|
|
|
+
|
|
|
+ fn to_cert_chain_impl(&self, subject_key: &AsymKeyPub<Sign>) -> Result<Vec<Vec<u8>>> {
|
|
|
+ let mut chain = match self.next.as_ref() {
|
|
|
+ Some(next) => next.as_ref().to_cert_chain(&self.body.signing_key)?,
|
|
|
+ None => {
|
|
|
+ // An extra cert is added to the end of the chain to contain the root
|
|
|
+ // principal's signing key.
|
|
|
+ let mut vec = Vec::with_capacity(2);
|
|
|
+ let root_principal = self.body.signing_key.principal();
|
|
|
+ let path = BlockPath::new(root_principal.clone(), vec![]);
|
|
|
+ let writecap = Writecap {
|
|
|
+ body: WritecapBody {
|
|
|
+ issued_to: root_principal,
|
|
|
+ expires: Epoch::now(),
|
|
|
+ path,
|
|
|
+ signing_key: self.body.signing_key.clone(),
|
|
|
+ },
|
|
|
+ signature: self.signature.clone(),
|
|
|
+ next: None,
|
|
|
+ };
|
|
|
+ vec.push(writecap.to_cert(&self.body.signing_key)?);
|
|
|
+ vec
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ chain.push(self.to_cert(subject_key)?);
|
|
|
Ok(chain)
|
|
|
}
|
|
|
+
|
|
|
+ pub fn to_cert_chain(&self, subject_key: &AsymKeyPub<Sign>) -> Result<Vec<Vec<u8>>> {
|
|
|
+ let mut chain = self.to_cert_chain_impl(subject_key)?;
|
|
|
+ chain.reverse();
|
|
|
+ Ok(chain)
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn from_cert_chain<B: AsRef<[u8]>>(
|
|
|
+ chain: &[B],
|
|
|
+ ) -> Result<(Writecap, AsymKeyPub<Sign>)> {
|
|
|
+ let der = chain
|
|
|
+ .first()
|
|
|
+ .ok_or_else(|| bterr!("at least one certificate must be supplied"))?;
|
|
|
+ let (next, signing_key) = if chain.len() > 1 {
|
|
|
+ let (writecap, signing_key) = Self::from_cert_chain(&chain[1..])?;
|
|
|
+ // Remove the extra writecap at the end.
|
|
|
+ let writecap = if chain.len() == 2 {
|
|
|
+ None
|
|
|
+ } else {
|
|
|
+ Some(writecap)
|
|
|
+ };
|
|
|
+ (writecap, Some(signing_key))
|
|
|
+ } else {
|
|
|
+ (None, None)
|
|
|
+ };
|
|
|
+
|
|
|
+ let x509_cert = X509Certificate::from_der(der)?;
|
|
|
+ let cert: &Certificate = x509_cert.as_ref();
|
|
|
+ if cert.signature.unused() > 0 {
|
|
|
+ return Err(bterr!("signature length is not divisible by 8"));
|
|
|
+ }
|
|
|
+ let extensions = cert
|
|
|
+ .tbs_certificate
|
|
|
+ .extensions
|
|
|
+ .as_ref()
|
|
|
+ .ok_or_else(|| bterr!("no extensions present"))?;
|
|
|
+ let san = extensions.find_subject_alt_name()?;
|
|
|
+ let path = BlockPath::try_from(san.block_path.into_bytes().try_as_str()?)
|
|
|
+ .map_err(|err| bterr!(err))?;
|
|
|
+ let sig_octet_len: u32 = cert.signature.octet_len().try_into()?;
|
|
|
+ let scheme = Sign::from_algo_id(
|
|
|
+ sig_octet_len,
|
|
|
+ &cert.tbs_certificate.subject_public_key_info.algorithm,
|
|
|
+ )?;
|
|
|
+ let signature = Signature::new(scheme, cert.signature.octet_bytes().into());
|
|
|
+ let cert = &cert.tbs_certificate;
|
|
|
+ let subject_key = AsymKeyPub::from_subject_public_key_info(
|
|
|
+ sig_octet_len,
|
|
|
+ &cert.subject_public_key_info,
|
|
|
+ )?;
|
|
|
+ let issued_to: Principal = cert
|
|
|
+ .subject
|
|
|
+ .try_get_common_name()?
|
|
|
+ .to_string()?
|
|
|
+ .as_str()
|
|
|
+ .try_into()?;
|
|
|
+ let expires = cert.validity.not_after.try_to_epoch()?;
|
|
|
+ // If signing_key is None, then we're at the last certificate in the chain, which is
|
|
|
+ // self-signed. So the subject_key is the same as the issuer's signing key.
|
|
|
+ let signing_key = signing_key.unwrap_or_else(|| subject_key.clone());
|
|
|
+ let writecap = Writecap {
|
|
|
+ body: WritecapBody {
|
|
|
+ issued_to,
|
|
|
+ signing_key,
|
|
|
+ path,
|
|
|
+ expires,
|
|
|
+ },
|
|
|
+ signature,
|
|
|
+ next: next.map(Box::new),
|
|
|
+ };
|
|
|
+ Ok((writecap, subject_key))
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
+ use super::*;
|
|
|
+
|
|
|
use webpki::EndEntityCert;
|
|
|
|
|
|
- use crate::test_helpers::node_creds;
|
|
|
+ use crate::{crypto::CredsPub, test_helpers::node_creds};
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
fn save_first_writecap_der_to_file() {
|
|
@@ -328,13 +558,7 @@ mod tests {
|
|
|
.to_cert_chain(&node_creds.sign.public)
|
|
|
.unwrap();
|
|
|
let first = chain.first().unwrap();
|
|
|
- let mut buf = Vec::new();
|
|
|
-
|
|
|
- first.encode_der_to(&mut buf).unwrap();
|
|
|
- std::fs::write("/tmp/cert.der", buf).unwrap();
|
|
|
-
|
|
|
- let pem = first.encode_pem().unwrap();
|
|
|
- std::fs::write("/tmp/cert.pem", &pem).unwrap();
|
|
|
+ std::fs::write("/tmp/cert.der", first).unwrap();
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
@@ -359,10 +583,24 @@ mod tests {
|
|
|
.unwrap()
|
|
|
.to_cert_chain(&node_creds.sign.public)
|
|
|
.unwrap();
|
|
|
- let der = chain.first().unwrap().encode_der().unwrap();
|
|
|
+ let der = chain.first().unwrap();
|
|
|
|
|
|
let result = EndEntityCert::try_from(der.as_slice());
|
|
|
|
|
|
result.unwrap();
|
|
|
}
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn round_trip_writecap() {
|
|
|
+ let node_creds = node_creds();
|
|
|
+ let expected_key = node_creds.public_sign();
|
|
|
+ let expected_wc = node_creds.writecap.as_ref().unwrap();
|
|
|
+
|
|
|
+ let certs = expected_wc.to_cert_chain(expected_key).unwrap();
|
|
|
+ let (actual_wc, actual_key) = Writecap::from_cert_chain(certs.as_slice()).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(expected_key, &actual_key);
|
|
|
+ assert_eq!(expected_wc, &actual_wc);
|
|
|
+ actual_wc.assert_valid_for(&expected_wc.body.path).unwrap();
|
|
|
+ }
|
|
|
}
|