use std::{cmp, fmt, io};
use bcder::{decode, encode};
use bcder::{Captured, Mode, OctetString, Oid, Tag};
use bcder::decode::{ContentError, DecodeError, IntoSource, Source};
use bcder::encode::PrimitiveContent;
use bcder::string::OctetStringSource;
use bytes::Bytes;
use crate::{oid, uri};
use crate::crypto::{
Digest, DigestAlgorithm, KeyIdentifier, RpkiSignature,
RpkiSignatureAlgorithm, Signer, SigningError
};
use super::cert::{Cert, KeyUsage, Overclaim, ResourceCert, TbsCert};
use super::error::{
InspectionError, ValidationError, VerificationError
};
use super::resources::{
AsBlocksBuilder, AsResources, AsResourcesBuilder, IpBlocksBuilder,
IpResources, IpResourcesBuilder
};
use super::x509::{Name, Serial, Time, Validity};
#[derive(Clone, Debug)]
pub struct SignedObject {
digest_algorithm: DigestAlgorithm,
content_type: Oid<Bytes>,
content: OctetString,
cert: Cert,
sid: KeyIdentifier,
signed_attrs: SignedAttrs,
signature: RpkiSignature,
message_digest: MessageDigest,
signing_time: Option<Time>,
binary_signing_time: Option<u64>,
}
impl SignedObject {
pub fn content_type(&self) -> &Oid<Bytes> {
&self.content_type
}
pub fn content(&self) -> &OctetString {
&self.content
}
pub fn decode_content<F, T>(
&self, op: F
) -> Result<T, DecodeError<<OctetStringSource as decode::Source>::Error>>
where F: FnOnce(
&mut decode::Constructed<OctetStringSource>
) -> Result<T, DecodeError<<OctetStringSource as decode::Source>::Error>> {
Mode::Der.decode(self.content.clone(), op)
}
pub fn cert(&self) -> &Cert {
&self.cert
}
pub fn signing_time(&self) -> Option<Time> {
self.signing_time
}
pub fn binary_signing_time(&self) -> Option<u64> {
self.binary_signing_time
}
}
impl SignedObject {
pub fn decode<S: IntoSource>(
source: S,
strict: bool
) -> Result<Self, DecodeError<<S::Source as Source>::Error>> {
if strict {
Mode::Der
}
else {
Mode::Ber
}.decode(source.into_source(), Self::take_from)
}
pub fn decode_if_type<S: IntoSource>(
source: S,
content_type: &impl PartialEq<Oid>,
strict: bool,
) -> Result<Self, DecodeError<<S::Source as Source>::Error>> {
let res = Self::decode(source, strict)?;
if content_type.ne(res.content_type()) {
return Err(DecodeError::content(
"invalid content type", Default::default()
))
}
Ok(res)
}
pub fn take_from<S: decode::Source>(
cons: &mut decode::Constructed<S>
) -> Result<Self, DecodeError<S::Error>> {
cons.take_sequence(|cons| { oid::SIGNED_DATA.skip_if(cons)?; cons.take_constructed_if(Tag::CTX_0, |cons| { 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
)?
))
})?
};
let cert = cons.take_constructed_if( Tag::CTX_0,
Cert::take_from
)?;
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(cons.content_err(
"digest algorithm mismatch"
))
}
let attrs = SignedAttrs::take_from(cons)?;
if attrs.2 != content_type {
return Err(cons.content_err(
"content type in signed attributes \
differs"
))
}
let signature = RpkiSignature::new(
RpkiSignatureAlgorithm::cms_take_from(
cons
)?,
OctetString::take_from(cons)?.into_bytes()
);
Ok((sid, attrs, signature))
})
})?
};
Ok(Self {
digest_algorithm,
content_type,
content,
cert,
sid,
signed_attrs: attrs.0,
signature,
message_digest: attrs.1,
signing_time: attrs.3,
binary_signing_time: attrs.4
})
})
})
})
}
pub fn process<F>(
self,
issuer: &ResourceCert,
strict: bool,
check_crl: F
) -> Result<(ResourceCert, Bytes), ValidationError>
where F: FnOnce(&Cert) -> Result<(), ValidationError> {
let res = self.content.clone();
let cert = self.validate(issuer, strict)?;
check_crl(cert.as_ref())?;
Ok((cert, res.into_bytes()))
}
pub fn validate(
self,
issuer: &ResourceCert,
strict: bool,
) -> Result<ResourceCert, ValidationError> {
self.validate_at(issuer, strict, Time::now())
}
pub fn validate_at(
self,
issuer: &ResourceCert,
strict: bool,
now: Time,
) -> Result<ResourceCert, ValidationError> {
self.inspect(strict)?;
self.verify(strict)?;
self.cert.validate_ee_at(issuer, strict, now).map_err(Into::into)
}
fn inspect(
&self,
_strict: bool
) -> Result<(), InspectionError> {
if self.sid != self.cert.subject_key_identifier() {
return Err(InspectionError::new(
"Subject Key Identifier mismatch in signed object"
))
}
Ok(())
}
fn verify(
&self, _strict: bool
) -> Result<(), VerificationError> {
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(VerificationError::new(
"message digest mismatch in signed object"
))
}
let msg = self.signed_attrs.encode_verify();
self.cert.subject_public_key_info().verify(
&msg,
&self.signature
).map_err(Into::into)
}
pub fn encode_ref(&self) -> impl encode::Values + '_ {
encode::sequence((
oid::SIGNED_DATA.encode(), encode::sequence_as(Tag::CTX_0, encode::sequence((
3u8.encode(), self.digest_algorithm.encode_set(), encode::sequence(( self.content_type.encode_ref(),
encode::sequence_as(Tag::CTX_0,
self.content.encode_ref()
),
)),
encode::sequence_as(Tag::CTX_0, self.cert.encode_ref(),
),
encode::set( encode::sequence(( 3u8.encode(), self.sid.encode_ref_as(Tag::CTX_0),
self.digest_algorithm.encode(), self.signed_attrs.encode_ref(), self.signature.algorithm().cms_encode(),
OctetString::encode_slice( self.signature.value().as_ref()
),
))
)
))
)
))
}
}
#[derive(Clone, Debug)]
pub struct SignedAttrs(Captured);
impl SignedAttrs {
pub(crate) fn new(
content_type: &Oid<impl AsRef<[u8]>>,
digest: &MessageDigest,
signing_time: Option<Time>,
binary_signing_time: Option<u64>,
) -> Self {
let mut content_type = Some(encode::sequence((
oid::CONTENT_TYPE.encode(),
encode::set(
content_type.encode_ref(),
)
)));
let mut signing_time = signing_time.map(|time| {
encode::sequence((
oid::SIGNING_TIME.encode(),
encode::set(
time.encode_varied(),
)
))
});
let mut message_digest = Some(encode::sequence((
oid::MESSAGE_DIGEST.encode(),
encode::set(
digest.encode_ref(),
)
)));
let mut binary_signing_time = binary_signing_time.map(|time| {
encode::sequence((
oid::AA_BINARY_SIGNING_TIME.encode(),
encode::set(
time.encode()
)
))
});
let mut len = [
(0, StartOfValue::new(&content_type)),
(1, StartOfValue::new(&signing_time)),
(2, StartOfValue::new(&message_digest)),
(3, StartOfValue::new(&binary_signing_time)),
];
len.sort_by_key(|&(_, len)| len.unwrap());
let mut res = Captured::builder(Mode::Der);
for &(idx, _) in &len {
match idx {
0 => {
if let Some(val) = content_type.take() {
res.extend(val)
}
}
1 => {
if let Some(val) = signing_time.take() {
res.extend(val)
}
}
2 => {
if let Some(val) = message_digest.take() {
res.extend(val)
}
}
3 => {
if let Some(val) = binary_signing_time.take() {
res.extend(val)
}
}
_ => unreachable!()
}
}
SignedAttrs(res.freeze())
}
#[allow(clippy::type_complexity)]
fn take_from_with_mode<S: decode::Source>(
cons: &mut decode::Constructed<S>,
strict: bool
) -> Result<
(Self, MessageDigest, Oid<Bytes>, Option<Time>, Option<u64>),
DecodeError<S::Error>
> {
let mut message_digest = None;
let mut content_type = None;
let mut signing_time = None;
let mut binary_signing_time = None;
let raw = cons.take_constructed_if(Tag::CTX_0, |cons| {
cons.capture(|cons| {
while let Some(()) = cons.take_opt_sequence(|cons| {
let oid = Oid::take_from(cons)?;
if oid == oid::CONTENT_TYPE {
Self::take_content_type(cons, &mut content_type)
}
else if oid == oid::MESSAGE_DIGEST {
Self::take_message_digest(cons, &mut message_digest)
}
else if oid == oid::SIGNING_TIME {
Self::take_signing_time(cons, &mut signing_time)
}
else if oid == oid::AA_BINARY_SIGNING_TIME {
Self::take_bin_signing_time(
cons,
&mut binary_signing_time
)
}
else if !strict {
cons.skip_all()
} else {
Err(cons.content_err(InvalidSignedAttr::new(oid)))
}
})? { }
Ok(())
})
})?;
if raw.len() > 0xFFFF {
return Err(cons.content_err(
"signed attributes over 65535 bytes not supported"
))
}
let message_digest = match message_digest {
Some(some) => MessageDigest(some.into_bytes()),
None => {
return Err(cons.content_err(
"missing message digest in signed attributes"
))
}
};
let content_type = match content_type {
Some(some) => some,
None => {
return Err(cons.content_err(
"missing content type in signed attributes",
))
}
};
Ok((
Self(raw), message_digest, content_type, signing_time,
binary_signing_time
))
}
#[allow(clippy::type_complexity)]
pub fn take_from<S: decode::Source>(
cons: &mut decode::Constructed<S>
) -> Result<
(Self, MessageDigest, Oid<Bytes>, Option<Time>, Option<u64>),
DecodeError<S::Error>
> {
Self::take_from_with_mode(cons, true)
}
#[allow(clippy::type_complexity)]
pub fn take_from_signed_message<S: decode::Source>(
cons: &mut decode::Constructed<S>
) -> Result<
(Self, MessageDigest, Oid<Bytes>, Option<Time>, Option<u64>),
DecodeError<S::Error>
> {
Self::take_from_with_mode(cons, false)
}
fn take_content_type<S: decode::Source>(
cons: &mut decode::Constructed<S>,
content_type: &mut Option<Oid<Bytes>>
) -> Result<(), DecodeError<S::Error>> {
if content_type.is_some() {
Err(cons.content_err("duplicate Content Type attribute"))
}
else {
*content_type = Some(
cons.take_set(|cons| Oid::take_from(cons))?
);
Ok(())
}
}
fn take_message_digest<S: decode::Source>(
cons: &mut decode::Constructed<S>,
message_digest: &mut Option<OctetString>
) -> Result<(), DecodeError<S::Error>> {
if message_digest.is_some() {
Err(cons.content_err("duplicate Message Digest attribute"))
}
else {
*message_digest = Some(
cons.take_set(|cons| OctetString::take_from(cons))?
);
Ok(())
}
}
fn take_signing_time<S: decode::Source>(
cons: &mut decode::Constructed<S>,
signing_time: &mut Option<Time>
) -> Result<(), DecodeError<S::Error>> {
if signing_time.is_some() {
Err(cons.content_err("duplicate Signing Time attribute"))
}
else {
*signing_time = Some(
cons.take_set(Time::take_from)?
);
Ok(())
}
}
fn take_bin_signing_time<S: decode::Source>(
cons: &mut decode::Constructed<S>,
bin_signing_time: &mut Option<u64>
) -> Result<(), DecodeError<S::Error>> {
if bin_signing_time.is_some() {
Err(cons.content_err("duplicate Binary Signing Time attribute"))
}
else {
*bin_signing_time = Some(
cons.take_set(|cons| cons.take_u64())?
);
Ok(())
}
}
pub fn encode_ref(&self) -> impl encode::Values + '_ {
encode::sequence_as(Tag::CTX_0, &self.0)
}
pub fn encode_verify(&self) -> Vec<u8> {
let len = self.0.len();
let mut res = Vec::with_capacity(len + 4);
res.push(0x31); if len < 128 {
res.push(len as u8)
}
else if len < 0x10000 {
res.push(2);
res.push((len >> 8) as u8);
res.push(len as u8);
}
else {
panic!("overly long signed attrs");
}
res.extend_from_slice(self.0.as_ref());
res
}
}
impl AsRef<[u8]> for SignedAttrs {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
#[derive(Clone, Debug)]
pub struct MessageDigest(Bytes);
impl MessageDigest {
pub fn encode_ref(&self) -> impl encode::Values + '_ {
OctetString::encode_slice(self.0.as_ref())
}
}
impl From<OctetString> for MessageDigest {
fn from(src: OctetString) -> Self {
MessageDigest(src.into_bytes())
}
}
impl From<Digest> for MessageDigest {
fn from(digest: Digest) -> Self {
MessageDigest(Bytes::copy_from_slice(digest.as_ref()))
}
}
impl AsRef<[u8]> for MessageDigest {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
#[derive(Clone, Debug)]
pub struct SignedObjectBuilder {
digest_algorithm: DigestAlgorithm,
serial_number: Serial,
validity: Validity,
issuer: Option<Name>,
subject: Option<Name>,
crl_uri: uri::Rsync,
ca_issuer: uri::Rsync,
signed_object: uri::Rsync,
v4_resources: IpResources,
v6_resources: IpResources,
as_resources: AsResources,
signing_time: Option<Time>,
binary_signing_time: Option<u64>,
}
impl SignedObjectBuilder {
pub fn new(
serial_number: Serial,
validity: Validity,
crl_uri: uri::Rsync,
ca_issuer: uri::Rsync,
signed_object: uri::Rsync
) -> Self {
Self {
digest_algorithm: DigestAlgorithm::default(),
serial_number,
validity,
issuer: None,
subject: None,
crl_uri,
ca_issuer,
signed_object,
v4_resources: IpResources::missing(),
v6_resources: IpResources::missing(),
as_resources: AsResources::missing(),
signing_time: None,
binary_signing_time: None,
}
}
pub fn digest_algorithm(&self) -> DigestAlgorithm {
self.digest_algorithm
}
pub fn set_digest_algorithm(&mut self, algorithm: DigestAlgorithm) {
self.digest_algorithm = algorithm
}
pub fn serial_number(&self) -> Serial {
self.serial_number
}
pub fn set_serial_number(&mut self, serial: Serial) {
self.serial_number = serial
}
pub fn validity(&self) -> Validity {
self.validity
}
pub fn set_validity(&mut self, validity: Validity) {
self.validity = validity
}
pub fn issuer(&self) -> Option<&Name> {
self.issuer.as_ref()
}
pub fn set_issuer(&mut self, name: Option<Name>) {
self.issuer = name
}
pub fn subject(&self) -> Option<&Name> {
self.subject.as_ref()
}
pub fn set_subject(&mut self, name: Option<Name>) {
self.subject = name
}
pub fn crl_uri(&self) -> &uri::Rsync {
&self.crl_uri
}
pub fn set_crl_uri(&mut self, uri: uri::Rsync) {
self.crl_uri = uri
}
pub fn ca_issuer(&self) -> &uri::Rsync {
&self.ca_issuer
}
pub fn set_ca_issuer(&mut self, uri: uri::Rsync) {
self.ca_issuer = uri
}
pub fn signed_object(&self) -> &uri::Rsync {
&self.signed_object
}
pub fn set_signed_object(&mut self, uri: uri::Rsync) {
self.signed_object = uri
}
pub fn v4_resources(&self) -> &IpResources {
&self.v4_resources
}
pub fn set_v4_resources(&mut self, resources: IpResources) {
self.v4_resources = resources
}
pub fn set_v4_resources_inherit(&mut self) {
self.set_v4_resources(IpResources::inherit())
}
pub fn build_v4_resource_blocks<F>(&mut self, op: F)
where F: FnOnce(&mut IpBlocksBuilder) {
let mut builder = IpResourcesBuilder::new();
builder.blocks(op);
self.set_v4_resources(builder.finalize())
}
pub fn v6_resources(&self) -> &IpResources {
&self.v6_resources
}
pub fn set_v6_resources(&mut self, resources: IpResources) {
self.v6_resources = resources
}
pub fn set_v6_resources_inherit(&mut self) {
self.set_v6_resources(IpResources::inherit())
}
pub fn build_v6_resource_blocks<F>(&mut self, op: F)
where F: FnOnce(&mut IpBlocksBuilder) {
let mut builder = IpResourcesBuilder::new();
builder.blocks(op);
self.set_v6_resources(builder.finalize())
}
pub fn has_ip_resources(&self) -> bool {
self.v4_resources.is_present() || self.v6_resources().is_present()
}
pub fn as_resources(&self) -> &AsResources {
&self.as_resources
}
pub fn set_as_resources(&mut self, resources: AsResources) {
self.as_resources = resources
}
pub fn set_as_resources_inherit(&mut self) {
self.set_as_resources(AsResources::inherit())
}
pub fn build_as_resource_blocks<F>(&mut self, op: F)
where F: FnOnce(&mut AsBlocksBuilder) {
let mut builder = AsResourcesBuilder::new();
builder.blocks(op);
self.set_as_resources(builder.finalize())
}
pub fn signing_time(&self) -> Option<Time> {
self.signing_time
}
pub fn set_signing_time(&mut self, signing_time: Option<Time>) {
self.signing_time = signing_time
}
pub fn binary_signing_time(&self) -> Option<u64> {
self.binary_signing_time
}
pub fn set_binary_signing_time(&mut self, time: Option<u64>) {
self.binary_signing_time = time
}
pub fn finalize<S: Signer>(
self,
content_type: Oid<Bytes>,
content: Bytes,
signer: &S,
issuer_key: &S::KeyId,
) -> Result<SignedObject, SigningError<S::Error>> {
let issuer_pub = signer.get_key_info(issuer_key)?;
let message_digest = self.digest_algorithm.digest(&content).into();
let signed_attrs = SignedAttrs::new(
&content_type,
&message_digest,
self.signing_time,
self.binary_signing_time
);
let (signature, key_info) = signer.sign_one_off(
RpkiSignatureAlgorithm::default(), &signed_attrs.encode_verify()
)?;
let sid = key_info.key_identifier();
let mut cert = TbsCert::new(
self.serial_number,
self.issuer.unwrap_or_else(|| issuer_pub.to_subject_name()),
self.validity,
self.subject,
key_info,
KeyUsage::Ee,
Overclaim::Refuse,
);
cert.set_authority_key_identifier(Some(issuer_pub.key_identifier()));
cert.set_crl_uri(Some(self.crl_uri));
cert.set_ca_issuer(Some(self.ca_issuer));
cert.set_signed_object(Some(self.signed_object));
cert.set_v4_resources(self.v4_resources);
cert.set_v6_resources(self.v6_resources);
cert.set_as_resources(self.as_resources);
let cert = cert.into_cert(signer, issuer_key)?;
Ok(SignedObject {
digest_algorithm: self.digest_algorithm,
content_type,
content: OctetString::new(content),
cert,
sid,
signed_attrs,
signature,
message_digest,
signing_time: self.signing_time,
binary_signing_time: self.binary_signing_time,
})
}
}
#[derive(Clone, Copy, Debug)]
struct StartOfValue {
res: [u8; 8],
pos: usize,
}
impl StartOfValue {
fn new<V: encode::Values>(values: &V) -> Self {
let mut res = StartOfValue {
res: [0; 8],
pos: 0
};
values.write_encoded(Mode::Der, &mut res).unwrap();
res
}
fn unwrap(self) -> [u8; 8] {
self.res
}
}
impl io::Write for StartOfValue {
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
let slice = &mut self.res[self.pos..];
let len = cmp::min(slice.len(), buf.len());
slice[..len].copy_from_slice(&buf[..len]);
self.pos += len;
Ok(buf.len())
}
fn flush(&mut self) -> Result<(), io::Error> {
Ok(())
}
}
#[derive(Clone, Debug)]
pub(crate) struct InvalidSignedAttr {
oid: Oid<Bytes>,
}
impl InvalidSignedAttr {
fn new(oid: Oid<Bytes>) -> Self {
InvalidSignedAttr { oid }
}
}
impl From<InvalidSignedAttr> for ContentError {
fn from(err: InvalidSignedAttr) -> Self {
ContentError::from_boxed(Box::new(err))
}
}
impl fmt::Display for InvalidSignedAttr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid extension {}", self.oid)
}
}
#[cfg(test)]
mod test {
use crate::repository::tal::TalInfo;
use super::*;
#[test]
fn decode() {
let talinfo = TalInfo::from_name("foo".into()).into_arc();
let at = Time::utc(2019, 5, 1, 0, 0, 0);
let issuer = Cert::decode(
include_bytes!("../../test-data/ta.cer").as_ref()
).unwrap();
let issuer = issuer.validate_ta_at(talinfo, false, at).unwrap();
let obj = SignedObject::decode(
include_bytes!("../../test-data/ta.mft").as_ref(),
false
).unwrap();
obj.validate_at(&issuer, false, at).unwrap();
let obj = SignedObject::decode(
include_bytes!("../../test-data/ca1.mft").as_ref(),
false
).unwrap();
assert!(obj.validate_at(&issuer, false, at).is_err());
}
}
#[cfg(all(test, feature="softkeys"))]
mod signer_test {
use std::str::FromStr;
use bcder::Oid;
use bcder::encode::Values;
use crate::uri;
use crate::crypto::PublicKeyFormat;
use crate::crypto::softsigner::OpenSslSigner;
use crate::repository::resources::{Asn, Prefix};
use crate::repository::tal::TalInfo;
use super::*;
#[test]
fn encode_signed_object() {
let signer = OpenSslSigner::new();
let key = signer.create_key(PublicKeyFormat::Rsa).unwrap();
let pubkey = signer.get_key_info(&key).unwrap();
let uri = uri::Rsync::from_str("rsync://example.com/m/p").unwrap();
let mut cert = TbsCert::new(
12u64.into(), pubkey.to_subject_name(),
Validity::from_secs(86400), None, pubkey, KeyUsage::Ca,
Overclaim::Trim
);
cert.set_basic_ca(Some(true));
cert.set_ca_repository(Some(uri.clone()));
cert.set_rpki_manifest(Some(uri.clone()));
cert.build_v4_resource_blocks(|b| b.push(Prefix::new(0, 0)));
cert.build_v6_resource_blocks(|b| b.push(Prefix::new(0, 0)));
cert.build_as_resource_blocks(|b| b.push((Asn::MIN, Asn::MAX)));
let cert = cert.into_cert(&signer, &key).unwrap();
let mut sigobj = SignedObjectBuilder::new(
12u64.into(), Validity::from_secs(86400), uri.clone(),
uri.clone(), uri
);
sigobj.set_v4_resources_inherit();
let sigobj = sigobj.finalize(
Oid(oid::SIGNED_DATA.0.into()),
Bytes::from(b"1234".as_ref()),
&signer,
&key,
).unwrap();
let sigobj = sigobj.encode_ref().to_captured(Mode::Der);
let sigobj = SignedObject::decode(sigobj.as_slice(), true).unwrap();
let cert = cert.validate_ta(
TalInfo::from_name("foo".into()).into_arc(), true
).unwrap();
sigobj.validate(&cert, true).unwrap();
}
}
pub mod spec { }