use bytes::Bytes;
use chrono::Utc;
use bcder::encode::{Constructed, PrimitiveContent, Values};
use bcder::string::OctetString;
use bcder::{decode, encode, BitString, Unsigned};
use bcder::{Mode, Oid, Tag};
use rpki::crypto::{DigestAlgorithm, KeyIdentifier, PublicKey, Signature, SignatureAlgorithm};
use rpki::oid;
use rpki::sigobj::{MessageDigest, SignedAttrs};
use rpki::x509::{update_once, Name, SignedData, Time, ValidationError};
use crate::commons::crypto::{CryptoResult, IdCert, IdCertBuilder, KrillSigner, SignedAttributes};
pub struct ProtocolCmsBuilder {
content: OctetString,
signer_info: ProtocolSignerInfo,
ee_cert: IdCert,
crl: ProtocolCrl,
}
impl ProtocolCmsBuilder {
pub fn create(
issuing_key: &KeyIdentifier,
signer: &KrillSigner,
message: Bytes,
) -> CryptoResult<ProtocolCmsBuilder> {
let content = OctetString::new(message);
let signer_info = ProtocolSignerInfoBuilder::create(signer, &content.to_bytes())?;
let ee_cert = IdCertBuilder::new_ee_cert(issuing_key, signer_info.one_off_key(), signer)?;
let crl = ProtocolCrlBuilder::create(issuing_key, signer)?;
Ok(ProtocolCmsBuilder {
content,
signer_info,
ee_cert,
crl,
})
}
pub fn as_bytes(&self) -> Bytes {
self.encode().to_captured(Mode::Der).into_bytes()
}
#[allow(clippy::needless_lifetimes)]
pub fn encode<'a>(&'a self) -> impl encode::Values + 'a {
let digest_algorithms = encode::set(encode::sequence(rpki::oid::SHA256.encode()));
let encap_content_info = encode::sequence((
oid::PROTOCOL_CONTENT_TYPE.encode(),
Constructed::new(Tag::CTX_0, self.content.clone().encode()),
));
let certificates = Constructed::new(Tag::CTX_0, self.ee_cert.encode());
let crls = Constructed::new(Tag::CTX_1, self.crl.encode_ref());
let signer_infos = encode::set(self.signer_info.encode());
encode::sequence((
oid::SIGNED_DATA.encode(),
Constructed::new(
Tag::CTX_0,
encode::sequence((
(3.encode(), digest_algorithms, encap_content_info),
(certificates, crls, signer_infos),
)),
),
))
}
}
#[derive(Clone, Debug)]
pub struct ProtocolCms {
digest_algorithm: DigestAlgorithm,
content_type: Oid<Bytes>,
content: OctetString,
id_cert: IdCert,
crl: ProtocolCrl,
sid: KeyIdentifier,
signed_attrs: SignedAttrs,
signature: Signature,
message_digest: MessageDigest,
}
impl ProtocolCms {
pub fn decode<S: decode::Source>(source: S, strict: bool) -> Result<Self, S::Err> {
if strict { Mode::Der } else { Mode::Ber }.decode(source, Self::take_from)
}
}
impl ProtocolCms {
pub fn content(&self) -> &OctetString {
&self.content
}
}
impl ProtocolCms {
fn take_signed_data<S: decode::Source>(cons: &mut decode::Constructed<S>) -> Result<Self, S::Err> {
cons.take_sequence(|cons| {
cons.skip_u8_if(3)?;
let digest_algorithm = DigestAlgorithm::take_set_from(cons)?;
let (content_type, content) = {
cons.take_sequence(|cons| {
Ok((
Oid::take_from(cons)?,
cons.take_constructed_if(Tag::CTX_0, OctetString::take_from)?,
))
})?
};
if content_type != oid::PROTOCOL_CONTENT_TYPE {
return xerr!(Err(decode::Malformed.into()));
}
let id_cert = Self::take_certificates(cons)?;
let crl = Self::take_crl(cons)?;
let (sid, attrs, signature) = {
cons.take_set(|cons| {
cons.take_sequence(|cons| {
cons.skip_u8_if(3)?;
let sid = cons.take_value_if(Tag::CTX_0, |content| KeyIdentifier::from_content(content))?;
let alg = DigestAlgorithm::take_from(cons)?;
if alg != digest_algorithm {
return Err(decode::Malformed.into());
}
let attrs = SignedAttrs::take_from_signed_message(cons)?;
if attrs.2 != content_type {
return Err(decode::Malformed.into());
}
let signature = Signature::new(
SignatureAlgorithm::cms_take_from(cons)?,
OctetString::take_from(cons)?.into_bytes(),
);
Ok((sid, attrs, signature))
})
})?
};
Ok(Self {
digest_algorithm,
content_type,
content,
id_cert,
crl,
sid,
signed_attrs: attrs.0,
signature,
message_digest: attrs.1,
})
})
}
pub fn take_from<S: decode::Source>(cons: &mut decode::Constructed<S>) -> Result<Self, S::Err> {
cons.take_sequence(|cons| {
oid::SIGNED_DATA.skip_if(cons)?; cons.take_constructed_if(Tag::CTX_0, Self::take_signed_data)
})
}
fn take_certificates<S: decode::Source>(cons: &mut decode::Constructed<S>) -> Result<IdCert, S::Err> {
cons.take_constructed_if(Tag::CTX_0, |cons| {
cons.take_constructed(|tag, cons| match tag {
Tag::SEQUENCE => IdCert::from_constructed(cons),
_ => xerr!(Err(decode::Unimplemented.into())),
})
})
}
fn take_crl<S: decode::Source>(cons: &mut decode::Constructed<S>) -> Result<ProtocolCrl, S::Err> {
cons.take_constructed_if(Tag::CTX_1, |cons| ProtocolCrl::take_from(cons))
}
}
impl ProtocolCms {
pub fn validate(&self, issuer: &IdCert) -> Result<(), ValidationError> {
self.validate_at(issuer, Time::now())
}
pub fn validate_at(&self, issuer: &IdCert, now: Time) -> Result<(), ValidationError> {
self.id_cert.validate_ee_at(issuer, now)?;
self.verify_signature()?;
Ok(())
}
fn verify_signature(&self) -> Result<(), ValidationError> {
let digest = {
let mut context = self.digest_algorithm.start();
self.content.iter().for_each(|x| context.update(x));
context.finish()
};
if digest.as_ref() != self.message_digest.as_ref() {
return Err(ValidationError);
}
let msg = self.signed_attrs.encode_verify();
self.id_cert
.subject_public_key_info()
.verify(&msg, &self.signature)
.map_err(Into::into)
}
}
struct ProtocolSignerInfo {
signed_attributes: SignedAttributes,
key: PublicKey,
key_id: KeyIdentifier,
signature: OctetString,
}
impl ProtocolSignerInfo {
#[allow(clippy::needless_lifetimes)]
pub fn encode<'a>(&'a self) -> impl encode::Values + 'a {
let version = 3.encode();
let sid = self.key_id.encode_as(Tag::CTX_0);
let digest_algo = encode::sequence(oid::SHA256.encode());
let signed_attrs = Constructed::new(Tag::CTX_0, self.signed_attributes.encode());
encode::sequence((
(version, sid, digest_algo, signed_attrs),
(
SignatureAlgorithm::default().cms_encode(),
self.signature.clone().encode(),
),
))
}
pub fn one_off_key(&self) -> &PublicKey {
&self.key
}
}
struct ProtocolSignerInfoBuilder;
impl ProtocolSignerInfoBuilder {
fn create(signer: &KrillSigner, message: &Bytes) -> CryptoResult<ProtocolSignerInfo> {
let signed_attributes = SignedAttributes::new(
&oid::PROTOCOL_CONTENT_TYPE, message,
);
let (signature, key) = signed_attributes.sign(signer)?;
let key_id = KeyIdentifier::from_public_key(&key);
let signature = OctetString::new(signature.value().clone());
Ok(ProtocolSignerInfo {
signed_attributes,
key,
key_id,
signature,
})
}
}
struct ProtocolCrlBuilder;
impl ProtocolCrlBuilder {
fn create(issuing_key: &KeyIdentifier, signer: &KrillSigner) -> CryptoResult<ProtocolCrl> {
let pub_key = signer.get_key_info(issuing_key)?;
let name = Name::from_pub_key(&pub_key);
let just_now = Time::new(Utc::now()) - ::chrono::Duration::minutes(5);
let in_a_bit = Time::new(Utc::now() + ::chrono::Duration::minutes(5));
let crl_number = CrlNumber::new(1);
let extensions = Constructed::new(Tag::CTX_0, encode::sequence(crl_number.encode()));
let crl_data = encode::sequence((
(
1.encode(),
SignatureAlgorithm::default().x509_encode(),
name.encode_ref(),
),
(
just_now.encode_varied(),
in_a_bit.encode_varied(),
extensions,
),
));
let signature = BitString::new(
0,
signer
.sign(issuing_key, crl_data.to_captured(Mode::Der).as_slice())?
.value()
.clone(),
);
let crl_obj = encode::sequence((
crl_data,
SignatureAlgorithm::default().x509_encode(),
signature.encode(),
));
let crl = ProtocolCrl::decode(crl_obj.to_captured(Mode::Der).as_ref())?;
Ok(crl)
}
}
#[derive(Clone, Debug)]
struct ProtocolCrl {
signed_data: SignedData,
}
impl ProtocolCrl {
pub fn decode<S: decode::Source>(source: S) -> Result<Self, S::Err> {
Mode::Der.decode(source, Self::take_from)
}
pub fn take_from<S: decode::Source>(cons: &mut decode::Constructed<S>) -> Result<Self, S::Err> {
cons.take_sequence(Self::from_constructed)
}
pub fn from_constructed<S: decode::Source>(cons: &mut decode::Constructed<S>) -> Result<Self, S::Err> {
let signed_data = SignedData::from_constructed(cons)?;
Ok(Self { signed_data })
}
#[cfg(test)]
pub fn validate(&self, public_key: &PublicKey) -> Result<(), ValidationError> {
self.signed_data.verify_signature(public_key)
}
#[allow(clippy::needless_lifetimes)]
pub fn encode_ref<'a>(&'a self) -> impl encode::Values + 'a {
self.signed_data.encode_ref()
}
}
#[derive(Clone, Debug)]
pub struct CrlNumber {
number: Unsigned,
}
impl CrlNumber {
pub fn new(number: u32) -> Self {
CrlNumber { number: number.into() }
}
}
impl CrlNumber {
pub fn take<S: decode::Source>(
cons: &mut decode::Constructed<S>,
_critical: bool,
crl_number: &mut Option<Self>,
) -> Result<(), S::Err> {
update_once(crl_number, || {
Ok(CrlNumber {
number: Unsigned::take_from(cons)?,
})
})
}
#[allow(clippy::needless_lifetimes)]
pub fn encode<'a>(&'a self) -> impl encode::Values + 'a {
encode::sequence((
oid::CE_CRL_NUMBER.encode(),
OctetString::encode_wrapped(Mode::Der, self.number.encode()),
))
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::commons::api::Handle;
use crate::commons::remote::rfc6492::Message;
use crate::test::test_under_tmp;
use super::*;
#[test]
fn should_parse_and_validate_signed_message() {
let der = include_bytes!("../../../test-resources/remote/pdu_200.der");
let msg = ProtocolCms::decode(Bytes::from_static(der), false).unwrap();
let b = include_bytes!("../../../test-resources/remote/cms_ta.cer");
let id_cert = IdCert::decode(Bytes::from_static(b)).unwrap();
msg.validate_at(&id_cert, Time::utc(2012, 1, 1, 0, 0, 0)).unwrap();
}
#[test]
fn should_reject_invalid_signed_message() {
let der = include_bytes!("../../../test-resources/remote/pdu_200.der");
let msg = ProtocolCms::decode(Bytes::from_static(der), false).unwrap();
let b = include_bytes!("../../../test-resources/oob/id_publisher_ta.cer");
let id_cert = IdCert::decode(Bytes::from_static(b)).unwrap();
assert_eq!(
msg.validate_at(&id_cert, Time::utc(2012, 1, 1, 0, 0, 0)).unwrap_err(),
ValidationError,
);
}
#[test]
fn should_create_crl_for_protocol() {
test_under_tmp(|d| {
let s = KrillSigner::build(&d).unwrap();
let key_id = s.create_key().unwrap();
let key_info = s.get_key_info(&key_id).unwrap();
let crl = ProtocolCrlBuilder::create(&key_id, &s).unwrap();
crl.validate(&key_info).unwrap();
})
}
#[test]
fn should_create_signed_publication_message() {
test_under_tmp(|d| {
let s = KrillSigner::build(&d).unwrap();
let key_id = s.create_key().unwrap();
let id_cert = IdCertBuilder::new_ta_id_cert(&key_id, &s).unwrap();
let sender = Handle::from_str("sender").unwrap();
let rcpt = Handle::from_str("rcpt").unwrap();
let message = Message::list(sender, rcpt);
let builder = ProtocolCmsBuilder::create(&key_id, &s, message.clone().into_bytes()).unwrap();
let encoded_cms = builder.encode().to_captured(Mode::Der);
let msg = ProtocolCms::decode(encoded_cms.as_ref(), true).unwrap();
msg.validate(&id_cert).unwrap();
let parsed_message = Message::from_signed_message(&msg).unwrap();
assert_eq!(message, parsed_message);
});
}
}