use crate::cms::content_info::CmsVersion;
use crate::cms::signed_data::{EncapsulatedContentInfo, SignedData, SignerIdentifier, SignerInfo};
use crate::crypto::hash::Digest;
use crate::crypto::profiles::CryptoProvider;
use crate::crypto::sign::Signer;
use crate::crypto::sign::{Keypair, SignatureEncoding};
use crate::crypto::x509::utils::compute_signer_identifier;
use crate::der::asn1::{ObjectIdentifier, OctetString};
use crate::der::oid::AssociatedOid;
use crate::der::{Decode, Encode};
use crate::spki::{AlgorithmIdentifierOwned, EncodePublicKey};
use crate::transport::handshake::error::HandshakeError;
pub struct TightBeamSignedDataBuilder<'a, P, K>
where
P: CryptoProvider,
{
signer: &'a K,
digest_alg: AlgorithmIdentifierOwned,
signature_alg: AlgorithmIdentifierOwned,
signer_id: SignerIdentifier,
content_type: ObjectIdentifier,
_phantom: core::marker::PhantomData<P>,
}
impl<'a, P, K> TightBeamSignedDataBuilder<'a, P, K>
where
P: CryptoProvider,
P::Signature: SignatureEncoding,
P::Digest: Digest + AssociatedOid,
K: Signer<P::Signature> + Keypair,
K::VerifyingKey: EncodePublicKey,
{
pub fn new(
signer: &'a K,
digest_alg: AlgorithmIdentifierOwned,
signature_alg: AlgorithmIdentifierOwned,
) -> Result<Self, HandshakeError> {
let verifying_key = signer.verifying_key();
let signer_id = compute_signer_identifier::<P::Digest, _>(&verifying_key)?;
Ok(Self {
signer,
digest_alg,
signature_alg,
signer_id,
content_type: crate::oids::DATA,
_phantom: core::marker::PhantomData,
})
}
pub fn with_content_type(mut self, content_type: ObjectIdentifier) -> Self {
self.content_type = content_type;
self
}
pub fn build(self, content: &[u8]) -> Result<SignedData, HandshakeError> {
let mut hasher = P::Digest::new();
hasher.update(content);
let digest = hasher.finalize();
let digest_bytes = digest.as_slice();
let signature = self.signer.try_sign(digest_bytes)?;
let signature_bytes = signature.to_bytes();
let signer_info = SignerInfo {
version: CmsVersion::V1,
sid: self.signer_id,
digest_alg: self.digest_alg.clone(),
signed_attrs: None,
signature_algorithm: self.signature_alg,
signature: OctetString::new(signature_bytes.as_ref())?,
unsigned_attrs: None,
};
let octet_string = OctetString::new(content)?;
let econtent_der = octet_string.to_der()?;
let econtent_any = der::Any::from_der(&econtent_der)?;
let econtent = Some(econtent_any);
let encap_content = EncapsulatedContentInfo { econtent_type: self.content_type, econtent };
Ok(SignedData {
version: CmsVersion::V1,
digest_algorithms: vec![self.digest_alg].try_into()?,
encap_content_info: encap_content,
certificates: None,
crls: None,
signer_infos: vec![signer_info].try_into()?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::profiles::DefaultCryptoProvider;
use crate::crypto::sign::ecdsa::Secp256k1SigningKey;
use crate::der::Decode;
use crate::oids::{DATA, HASH_SHA3_256, SIGNER_ECDSA_WITH_SHA3_256};
use crate::random::OsRng;
fn create_test_signing_key() -> Secp256k1SigningKey {
Secp256k1SigningKey::random(&mut OsRng)
}
fn create_sha3_256_digest_alg() -> AlgorithmIdentifierOwned {
AlgorithmIdentifierOwned { oid: HASH_SHA3_256, parameters: None }
}
fn create_ecdsa_sha256_signature_alg() -> AlgorithmIdentifierOwned {
AlgorithmIdentifierOwned { oid: SIGNER_ECDSA_WITH_SHA3_256, parameters: None }
}
fn create_test_signed_data_builder<'a>(
signing_key: &'a Secp256k1SigningKey,
) -> Result<TightBeamSignedDataBuilder<'a, DefaultCryptoProvider, Secp256k1SigningKey>, HandshakeError> {
let digest_alg = create_sha3_256_digest_alg();
let signature_alg = create_ecdsa_sha256_signature_alg();
TightBeamSignedDataBuilder::<DefaultCryptoProvider, _>::new(signing_key, digest_alg, signature_alg)
}
#[test]
fn test_build_signed_data() -> Result<(), Box<dyn std::error::Error>> {
let signing_key = create_test_signing_key();
let builder = create_test_signed_data_builder(&signing_key)?;
let transcript_hash = b"handshake_transcript_hash_placeholder_32bytes";
let signed_data = builder.build(transcript_hash)?;
assert_eq!(signed_data.version, CmsVersion::V1);
assert_eq!(signed_data.digest_algorithms.len(), 1);
assert_eq!(signed_data.signer_infos.0.len(), 1);
assert_eq!(signed_data.encap_content_info.econtent_type, DATA);
assert!(signed_data.encap_content_info.econtent.is_some());
let signer_info = &signed_data.signer_infos.0.as_ref()[0];
assert_eq!(signer_info.version, CmsVersion::V1);
match signer_info.sid {
SignerIdentifier::SubjectKeyIdentifier(_) => {}
_ => unreachable!("SignedData builder should always create SubjectKeyIdentifier"),
}
assert!(!signer_info.signature.as_bytes().is_empty());
Ok(())
}
#[test]
fn test_der_encoding() -> Result<(), Box<dyn std::error::Error>> {
let signing_key = create_test_signing_key();
let builder = create_test_signed_data_builder(&signing_key)?;
let content = b"test_content";
let built = builder.build(content)?;
let der_bytes = built.to_der()?;
assert!(!der_bytes.is_empty());
let decoded = SignedData::from_der(&der_bytes)?;
assert_eq!(decoded.version, CmsVersion::V1);
assert_eq!(decoded.signer_infos.0.len(), 1);
Ok(())
}
#[test]
fn test_custom_content_type() -> Result<(), Box<dyn std::error::Error>> {
let signing_key = create_test_signing_key();
let builder = create_test_signed_data_builder(&signing_key)?;
let content = b"custom_content";
let custom_oid = ObjectIdentifier::new_unwrap("1.2.3.4.5.6");
let builder = builder.with_content_type(custom_oid);
let signed_data = builder.build(content)?;
assert_eq!(signed_data.encap_content_info.econtent_type, custom_oid);
Ok(())
}
}