diff --git a/src/repository/cert.rs b/src/repository/cert.rs index 50fc94d0..3a8a30d1 100644 --- a/src/repository/cert.rs +++ b/src/repository/cert.rs @@ -31,6 +31,8 @@ use crate::uri; use super::crypto::{ KeyIdentifier, PublicKey, SignatureAlgorithm, Signer, SigningError }; +use super::crypto::signer::{AlgorithmError, Sign, SignWithKey}; +use super::crypto::signature::Signature; use super::oid; use super::resources::{ AsBlock, AsBlocks, AsBlocksBuilder, AsResources, AsResourcesBuilder, @@ -996,6 +998,11 @@ impl TbsCert { tbs: self }) } + + pub fn sign(self) -> EncodedTbsCert { + let encoded = Captured::from_values(Mode::Der, self.encode_ref()); + EncodedTbsCert { tbs: self, encoded } + } } @@ -1990,6 +1997,38 @@ impl Sia { } +//------------ EncodedTbsCert ------------------------------------------------ + +#[derive(Clone, Debug)] +pub struct EncodedTbsCert { + tbs: TbsCert, + encoded: Captured, +} + +impl SignWithKey for EncodedTbsCert { + type Sign = Self; + + fn set_key(self, _key: &PublicKey) -> Result { + Ok(self) + } +} + +impl Sign for EncodedTbsCert { + type Final = Cert; + + fn signed_data(&self) -> &[u8] { + self.encoded.as_ref() + } + + fn sign(self, signature: Signature) -> Self::Final { + Cert { + signed_data: SignedData::new(self.encoded, signature), + tbs: self.tbs + } + } +} + + //------------ CertBuilder --------------------------------------------------- #[derive(Clone, Debug)] @@ -2430,6 +2469,42 @@ impl CertBuilder { } } +impl SignWithKey for CertBuilder { + type Sign = CertBuilderWithKey; + + fn set_key( + self, public_key: &PublicKey + ) -> Result { + let alg = match public_key.signature_algorithm() { + Some(alg) => alg, + None => return Err(AlgorithmError) + }; + Ok(CertBuilderWithKey { + tbs_cert: self.encode_tbs_cert(alg, public_key) + }) + } +} + + +//------------ CertBuilderWithKey -------------------------------------------- + +#[derive(Clone, Debug)] +pub struct CertBuilderWithKey { + tbs_cert: Captured, +} + +impl Sign for CertBuilderWithKey { + type Final = SignedData; + + fn signed_data(&self) -> &[u8] { + self.tbs_cert.as_ref() + } + + fn sign(self, signature: Signature) -> Self::Final { + SignedData::new(self.tbs_cert, signature) + } +} + //------------ ResourceCert -------------------------------------------------- @@ -2703,7 +2778,6 @@ mod signer_test { use crate::repository::tal::TalInfo; use super::*; - #[test] fn build_ta_cert() { let signer = OpenSslSigner::new(); @@ -2728,4 +2802,39 @@ mod signer_test { } } +#[cfg(all(test, feature="softkeys"))] +mod reverse_signer_test { + use std::str::FromStr; + use crate::repository::cert::Cert; + use crate::repository::crypto::PublicKeyFormat; + use crate::repository::crypto::softsigner::OpenSslSigner; + use crate::repository::resources::{AsId, Prefix}; + use crate::repository::tal::TalInfo; + use super::*; + + #[test] + fn build_ta_cert() { + 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((AsId::MIN, AsId::MAX))); + let cert = signer.sign_with_key( + key, cert.sign() + ).unwrap().to_captured(); + let cert = Cert::decode(cert.as_slice()).unwrap(); + let talinfo = TalInfo::from_name("foo".into()).into_arc(); + cert.validate_ta(talinfo, true).unwrap(); + } +} diff --git a/src/repository/crypto/keys.rs b/src/repository/crypto/keys.rs index 582c67b5..8b9c2eb9 100644 --- a/src/repository/crypto/keys.rs +++ b/src/repository/crypto/keys.rs @@ -14,7 +14,7 @@ use untrusted::Input; use super::super::oid; use super::super::util::hex; use super::super::x509::{Name, RepresentationError}; -use super::signature::Signature; +use super::signature::{Signature, SignatureAlgorithm}; //------------ PublicKeyFormat ----------------------------------------------- @@ -51,6 +51,17 @@ impl PublicKeyFormat { matches!(self, PublicKeyFormat::Rsa) } + /// Returns the RPKI signature algorithm for this key if available. + /// + /// Only keys for which [`allow_rpki_cert`][Self::allow_rpki_cert] returns + /// `true` will return a value. + pub fn signature_algorithm(self) -> Option { + match self { + PublicKeyFormat::Rsa => Some(SignatureAlgorithm::default()), + _ => None + } + } + /// Returns whether the format is acceptable for router certificates. pub fn allow_router_cert(self) -> bool { matches!(self, PublicKeyFormat::EcdsaP256) @@ -165,6 +176,14 @@ impl PublicKey { self.algorithm.allow_rpki_cert() } + /// Returns the RPKI signature algorithm for this key if available. + /// + /// Only keys for which [`allow_rpki_cert`][Self::allow_rpki_cert] returns + /// `true` will return a value. + pub fn signature_algorithm(&self) -> Option { + self.algorithm.signature_algorithm() + } + /// Returns whether the key is acceptable for BGPSec router certificates. pub fn allow_router_cert(&self) -> bool { self.algorithm.allow_router_cert() diff --git a/src/repository/crypto/signer.rs b/src/repository/crypto/signer.rs index 0208d89f..1ec83aa4 100644 --- a/src/repository/crypto/signer.rs +++ b/src/repository/crypto/signer.rs @@ -5,6 +5,58 @@ use super::keys::{PublicKey, PublicKeyFormat}; use super::signature::{Signature, SignatureAlgorithm}; +//------------ Experimental Reversed Signer Traits --------------------------- +// +// These will make it possible to have async signers. Instead of implementing +// a signer, we define the logic for signing something. Each signer can then +// implement a method to sign with a key or one off. + + +/// A type that can be signed but needs to know about the signing key. +/// +/// A type implementing this trait will have to be signed in two steps. First, +/// the key is presented to the object via [`SignWithKey::set_key`]. This +/// step returns a transitional value that knows about the signing key and +/// can now be signed via the [`Sign`] trait. +pub trait SignWithKey { + /// The transitional type for actual signing. + type Sign: Sign + Sized; + + fn set_key( + self, public_key: &PublicKey + ) -> Result; +} + +/// A type that can be signed. +/// +/// The type provides access to the data to be signed via +/// [`signed_data`][Self::signed_data]. Once the signer has finished its job, +/// it can provide the signature to the value through [`sign`][Self::sign], +/// which will apply the signature and return the final, signed value. +pub trait Sign { + /// The type of a final, signed value. + type Final: Sized; + + /// Returns a reference to the data to be signed. + fn signed_data(&self) -> &[u8]; + + /// Applies the signature and returns the final, signed value. + fn sign(self, signature: Signature) -> Self::Final; +} + +/// A key of the given algorithm cannot be used to sign this object. +#[derive(Clone, Copy, Debug)] +pub struct AlgorithmError; + +impl fmt::Display for AlgorithmError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("invalid algorithm") + } +} + +impl std::error::Error for AlgorithmError { } + + //------------ Signer -------------------------------------------------------- /// A type that allow creating signatures. diff --git a/src/repository/crypto/softsigner.rs b/src/repository/crypto/softsigner.rs index f7721deb..60e8589c 100644 --- a/src/repository/crypto/softsigner.rs +++ b/src/repository/crypto/softsigner.rs @@ -14,7 +14,9 @@ use ring::rand; use ring::rand::SecureRandom; use super::keys::{PublicKey, PublicKeyFormat}; use super::signature::{Signature, SignatureAlgorithm}; -use super::signer::{KeyError, Signer, SigningError}; +use super::signer::{ + AlgorithmError, KeyError, Sign, SignWithKey, Signer, SigningError +}; @@ -44,6 +46,26 @@ impl OpenSslSigner { Ok(self.insert_key(KeyPair::from_pem(pem)?)) } + pub fn sign_with_key( + &self, key: KeyId, what: What + ) -> Result<::Final, SignError> { + let key = self.get_key(key).map_err(|_| SignError::KeyNotFound)?; + let info = key.get_key_info()?; + let what = what.set_key(&info)?; + let signature = key.sign(what.signed_data())?; + Ok(what.sign(signature)) + } + + pub fn sign_with_one_off_key( + &self, algorithm: SignatureAlgorithm, what: What + ) -> Result<::Final, SignError> { + let key = KeyPair::new(algorithm.public_key_format())?; + let info = key.get_key_info()?; + let what = what.set_key(&info)?; + let signature = key.sign(what.signed_data())?; + Ok(what.sign(signature)) + } + fn insert_key(&self, key: KeyPair) -> KeyId { let mut keys = self.keys.write().unwrap(); let res = keys.len(); @@ -100,10 +122,10 @@ impl Signer for OpenSslSigner { fn sign + ?Sized>( &self, key: &Self::KeyId, - algorithm: SignatureAlgorithm, + _algorithm: SignatureAlgorithm, data: &D ) -> Result> { - self.get_key(*key)?.sign(algorithm, data.as_ref()).map_err(Into::into) + self.get_key(*key)?.sign(data.as_ref()).map_err(Into::into) } fn sign_one_off + ?Sized>( @@ -113,7 +135,7 @@ impl Signer for OpenSslSigner { ) -> Result<(Signature, PublicKey), Self::Error> { let key = KeyPair::new(algorithm.public_key_format())?; let info = key.get_key_info()?; - let sig = key.sign(algorithm, data.as_ref())?; + let sig = key.sign(data.as_ref())?; Ok((sig, info)) } @@ -193,7 +215,6 @@ impl KeyPair { fn sign( &self, - _algorithm: SignatureAlgorithm, data: &[u8] ) -> Result { let mut signer = ::openssl::sign::Signer::new( @@ -208,6 +229,28 @@ impl KeyPair { } +//------------ SignError ----------------------------------------------------- + +#[derive(Debug)] +pub enum SignError { + KeyNotFound, + AlgorithmError, + Io(io::Error) +} + +impl From for SignError { + fn from(_: AlgorithmError) -> Self { + SignError::AlgorithmError + } +} + +impl From for SignError { + fn from(err: io::Error) -> Self { + SignError::Io(err) + } +} + + //------------ Tests --------------------------------------------------------- #[cfg(test)] diff --git a/src/repository/roa.rs b/src/repository/roa.rs index fd92b866..1c4d6714 100644 --- a/src/repository/roa.rs +++ b/src/repository/roa.rs @@ -718,6 +718,61 @@ mod signer_test { } } +#[cfg(all(test, feature="softkeys"))] +mod reverse_signer_test { + use std::str::FromStr; + use bcder::encode::Values; + use crate::uri; + use crate::repository::cert::{KeyUsage, Overclaim, TbsCert}; + use crate::repository::crypto::{PublicKeyFormat, Signer}; + use crate::repository::crypto::softsigner::OpenSslSigner; + use crate::repository::resources::{AsId, Prefix}; + use crate::repository::tal::TalInfo; + use crate::repository::x509::Validity; + use super::*; + + #[test] + fn make_roa() { + 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((AsId::MIN, AsId::MAX))); + let cert = signer.sign_with_key( + key, cert.sign() + ).unwrap(); + + let mut roa = RoaBuilder::new(64496.into()); + roa.push_v4_addr(Ipv4Addr::new(192, 0, 2, 0), 24, None); + + let roa = roa.finalize( + SignedObjectBuilder::new( + 12u64.into(), Validity::from_secs(86400), uri.clone(), + uri.clone(), uri.clone() + ), + &signer, &key + ).unwrap(); + let roa = roa.encode_ref().to_captured(Mode::Der); + + let roa = Roa::decode(roa.as_slice(), true).unwrap(); + let cert = cert.validate_ta( + TalInfo::from_name("foo".into()).into_arc(), true + ).unwrap(); + roa.clone().process(&cert, true, |_| Ok(())).unwrap(); + } +} + //============ Specification Documentation =================================== diff --git a/src/repository/sigobj.rs b/src/repository/sigobj.rs index 7a3b1e95..67a78729 100644 --- a/src/repository/sigobj.rs +++ b/src/repository/sigobj.rs @@ -10,11 +10,14 @@ use bcder::string::OctetStringSource; use bytes::Bytes; use crate::uri; use super::oid; -use super::cert::{Cert, KeyUsage, Overclaim, ResourceCert, TbsCert}; +use super::cert::{ + Cert, EncodedTbsCert, KeyUsage, Overclaim, ResourceCert, TbsCert +}; use super::crypto::{ - Digest, DigestAlgorithm, KeyIdentifier, Signature, SignatureAlgorithm, - Signer, SigningError + Digest, DigestAlgorithm, KeyIdentifier, PublicKey, Signature, + SignatureAlgorithm, Signer, SigningError }; +use super::crypto::signer::{AlgorithmError, Sign, SignWithKey}; use super::resources::{ AsBlocksBuilder, AsResources, AsResourcesBuilder, IpBlocksBuilder, IpResources, IpResourcesBuilder @@ -888,6 +891,165 @@ impl SignedObjectBuilder { binary_signing_time: self.binary_signing_time, }) } + + pub fn sign( + self, + content_type: Oid, + content: Bytes, + ) -> CompleteSignedObject { + CompleteSignedObject { + builder: self, + content_type, + content + } + } +} + + +pub struct CompleteSignedObject { + builder: SignedObjectBuilder, + content_type: Oid, + content: Bytes, +} + +impl SignWithKey for CompleteSignedObject { + type Sign = SignedObjectWithKey; + + fn set_key(self, key: &PublicKey) -> Result { + if !key.allow_rpki_cert() { + return Err(AlgorithmError) + } + let message_digest = self.builder.digest_algorithm.digest( + &self.content + ).into(); + let signed_attrs = SignedAttrs::new( + &self.content_type, + &message_digest, + self.builder.signing_time, + self.builder.binary_signing_time + ); + Ok(SignedObjectWithKey { + signed_attrs_data: signed_attrs.encode_verify(), + signed_attrs, + message_digest, + object: self, + key: key.clone() + }) + } +} + + +pub struct SignedObjectWithKey { + signed_attrs_data: Vec, + signed_attrs: SignedAttrs, + message_digest: MessageDigest, + object: CompleteSignedObject, + key: PublicKey, +} + +impl Sign for SignedObjectWithKey { + type Final = SignedObjectWithSignature; + + fn signed_data(&self) -> &[u8] { + &self.signed_attrs_data + } + + fn sign(self, signature: Signature) -> Self::Final { + SignedObjectWithSignature { + signed_attrs: self.signed_attrs, + message_digest: self.message_digest, + signature, + object: self.object, + key: self.key, + } + } +} + + +pub struct SignedObjectWithSignature { + signed_attrs: SignedAttrs, + message_digest: MessageDigest, + signature: Signature, + object: CompleteSignedObject, + key: PublicKey, +} + +impl SignWithKey for SignedObjectWithSignature { + type Sign = SignedObjectWithIssuer; + + fn set_key(self, key: &PublicKey) -> Result { + if !key.allow_rpki_cert() { + return Err(AlgorithmError) + } + let sid = KeyIdentifier::from_public_key(&self.key); + let mut cert = TbsCert::new( + self.object.builder.serial_number, + self.object.builder.issuer.unwrap_or_else(|| key.to_subject_name()), + self.object.builder.validity, + self.object.builder.subject, + self.key, + KeyUsage::Ee, + Overclaim::Refuse, + ); + cert.set_authority_key_identifier(Some(key.key_identifier())); + cert.set_crl_uri(Some(self.object.builder.crl_uri)); + cert.set_ca_issuer(Some(self.object.builder.ca_issuer)); + cert.set_signed_object(Some(self.object.builder.signed_object)); + cert.set_v4_resources(self.object.builder.v4_resources); + cert.set_v6_resources(self.object.builder.v6_resources); + cert.set_as_resources(self.object.builder.as_resources); + + Ok(SignedObjectWithIssuer { + digest_algorithm: self.object.builder.digest_algorithm, + content_type: self.object.content_type, + content: OctetString::new(self.object.content), + sid, + signed_attrs: self.signed_attrs, + signature: self.signature, + message_digest: self.message_digest, + signing_time: self.object.builder.signing_time, + binary_signing_time: self.object.builder.binary_signing_time, + tbs_cert: cert.sign() + }) + } +} + + +pub struct SignedObjectWithIssuer { + digest_algorithm: DigestAlgorithm, + content_type: Oid, + content: OctetString, + sid: KeyIdentifier, + signed_attrs: SignedAttrs, + signature: Signature, + message_digest: MessageDigest, + signing_time: Option