use synta::traits::Encode;
use crate::ext_builder::encode_sequence;
use crate::logotype_cert_extn_types::{
HashAlgAndValue, LogotypeData, LogotypeDetails, LogotypeExtn, LogotypeImage, LogotypeInfo,
OtherLogotypeInfo,
};
#[derive(Debug, Clone, Copy)]
pub struct LogotypeDetailsSpec<'a> {
pub media_type: &'a str,
pub hash_alg_oid: &'a [u32],
pub hash_value: &'a [u8],
pub uri: &'a str,
}
#[derive(Debug, Default)]
pub struct LogotypeExtnBuilder {
community_logos_encoded: Vec<u8>,
issuer_logo: Option<Vec<u8>>,
subject_logo: Option<Vec<u8>>,
other_logos_encoded: Vec<u8>,
error: Option<String>,
}
impl LogotypeExtnBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn issuer_logo_direct(mut self, details: LogotypeDetailsSpec<'_>) -> Self {
if self.error.is_some() {
return self;
}
match encode_logotype_info_direct_image(&details) {
Ok(bytes) => self.issuer_logo = Some(bytes),
Err(e) => self.error = Some(e),
}
self
}
pub fn subject_logo_direct(mut self, details: LogotypeDetailsSpec<'_>) -> Self {
if self.error.is_some() {
return self;
}
match encode_logotype_info_direct_image(&details) {
Ok(bytes) => self.subject_logo = Some(bytes),
Err(e) => self.error = Some(e),
}
self
}
pub fn add_community_logo_direct(mut self, details: LogotypeDetailsSpec<'_>) -> Self {
if self.error.is_some() {
return self;
}
match encode_logotype_info_direct_image(&details) {
Ok(bytes) => self.community_logos_encoded.extend_from_slice(&bytes),
Err(e) => self.error = Some(e),
}
self
}
pub fn add_other_logo_direct(
mut self,
logotype_type_oid: &[u32],
details: LogotypeDetailsSpec<'_>,
) -> Self {
if self.error.is_some() {
return self;
}
let info_bytes = match encode_logotype_info_direct_image(&details) {
Ok(b) => b,
Err(e) => {
self.error = Some(e);
return self;
}
};
let info: LogotypeInfo<'_> = match synta::Decoder::new(&info_bytes, synta::Encoding::Der)
.decode::<LogotypeInfo<'_>>()
{
Ok(li) => li,
Err(e) => {
self.error = Some(format!("LogotypeInfo re-decode failed: {e}"));
return self;
}
};
let logotype_type = match synta::ObjectIdentifier::new(logotype_type_oid) {
Ok(oid) => oid,
Err(e) => {
self.error = Some(format!("invalid logotype type OID: {e}"));
return self;
}
};
let other = OtherLogotypeInfo {
logotype_type,
info,
};
let mut enc = synta::Encoder::new(synta::Encoding::Der);
match other.encode(&mut enc).and_then(|()| enc.finish()) {
Ok(bytes) => self.other_logos_encoded.extend_from_slice(&bytes),
Err(e) => {
self.error = Some(format!("OtherLogotypeInfo DER encoding failed: {e}"));
}
}
self
}
pub fn build(self) -> Result<Vec<u8>, String> {
if let Some(e) = self.error {
return Err(e);
}
let has_community = !self.community_logos_encoded.is_empty();
let has_issuer = self.issuer_logo.is_some();
let has_subject = self.subject_logo.is_some();
let has_other = !self.other_logos_encoded.is_empty();
if !has_community && !has_issuer && !has_subject && !has_other {
return Err(
"at least one of communityLogos, issuerLogo, subjectLogo, or otherLogos must be set"
.to_string(),
);
}
let community_seq_storage: Vec<u8>;
let other_seq_storage: Vec<u8>;
let community_logos: Option<Vec<LogotypeInfo<'_>>>;
let issuer_logo_bytes = self.issuer_logo;
let issuer_logo: Option<LogotypeInfo<'_>>;
let subject_logo_bytes = self.subject_logo;
let subject_logo: Option<LogotypeInfo<'_>>;
let other_logos: Option<Vec<OtherLogotypeInfo<'_>>>;
if has_community {
community_seq_storage = encode_sequence(self.community_logos_encoded);
let logos: Vec<LogotypeInfo<'_>> =
synta::Decoder::new(&community_seq_storage, synta::Encoding::Der)
.decode::<Vec<LogotypeInfo<'_>>>()
.map_err(|e| format!("communityLogos Vec decode failed: {e}"))?;
community_logos = Some(logos);
} else {
community_logos = None;
}
if let Some(ref bytes) = issuer_logo_bytes {
issuer_logo = Some(
synta::Decoder::new(bytes, synta::Encoding::Der)
.decode::<LogotypeInfo<'_>>()
.map_err(|e| format!("issuerLogo re-decode failed: {e}"))?,
);
} else {
issuer_logo = None;
}
if let Some(ref bytes) = subject_logo_bytes {
subject_logo = Some(
synta::Decoder::new(bytes, synta::Encoding::Der)
.decode::<LogotypeInfo<'_>>()
.map_err(|e| format!("subjectLogo re-decode failed: {e}"))?,
);
} else {
subject_logo = None;
}
if has_other {
other_seq_storage = encode_sequence(self.other_logos_encoded);
let logos: Vec<OtherLogotypeInfo<'_>> =
synta::Decoder::new(&other_seq_storage, synta::Encoding::Der)
.decode::<Vec<OtherLogotypeInfo<'_>>>()
.map_err(|e| format!("otherLogos Vec decode failed: {e}"))?;
other_logos = Some(logos);
} else {
other_logos = None;
}
let extn = LogotypeExtn {
community_logos,
issuer_logo,
subject_logo,
other_logos,
};
let mut enc = synta::Encoder::new(synta::Encoding::Der);
extn.encode(&mut enc)
.map_err(|e| format!("LogotypeExtn DER encoding failed: {e}"))?;
enc.finish().map_err(|e| format!("DER finish failed: {e}"))
}
}
fn encode_logotype_info_direct_image(details: &LogotypeDetailsSpec<'_>) -> Result<Vec<u8>, String> {
let hash_oid = synta::ObjectIdentifier::new(details.hash_alg_oid)
.map_err(|e| format!("invalid hash algorithm OID: {e}"))?;
let hash_alg = crate::AlgorithmIdentifier {
algorithm: hash_oid,
parameters: Some(synta::Element::Null(synta::Null)),
};
let hash_value = synta::OctetStringRef::new(details.hash_value);
let hash_alg_and_value = HashAlgAndValue {
hash_alg,
hash_value,
};
let media_type = synta::IA5StringRef::new(details.media_type)
.map_err(|e| format!("media_type is not valid IA5String: {e}"))?;
let uri = synta::IA5StringRef::new(details.uri)
.map_err(|e| format!("URI is not valid IA5String: {e}"))?;
let logotype_details = LogotypeDetails {
media_type,
logotype_hash: vec![hash_alg_and_value],
logotype_uri: vec![uri],
};
let logotype_image = LogotypeImage {
image_details: logotype_details,
image_info: None,
};
let logotype_data = LogotypeData {
image: Some(vec![logotype_image]),
audio: None,
};
let info = LogotypeInfo::Direct(logotype_data);
let mut enc = synta::Encoder::new(synta::Encoding::Der);
info.encode(&mut enc)
.map_err(|e| format!("LogotypeInfo DER encoding failed: {e}"))?;
enc.finish().map_err(|e| format!("DER finish failed: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
fn test_spec() -> LogotypeDetailsSpec<'static> {
LogotypeDetailsSpec {
media_type: "image/png",
hash_alg_oid: crate::oids::ID_SHA256,
hash_value: &[0xabu8; 32],
uri: "https://example.com/logo.png",
}
}
#[test]
fn build_subject_logo() {
let der = LogotypeExtnBuilder::new()
.subject_logo_direct(test_spec())
.build()
.expect("build should succeed");
let decoded = crate::logotype_cert_extn_types::LogotypeExtn::from_der(&der)
.expect("round-trip decode failed");
assert!(decoded.subject_logo.is_some());
assert!(decoded.issuer_logo.is_none());
assert!(decoded.community_logos.is_none());
}
#[test]
fn build_issuer_logo() {
let der = LogotypeExtnBuilder::new()
.issuer_logo_direct(test_spec())
.build()
.expect("build should succeed");
let decoded = crate::logotype_cert_extn_types::LogotypeExtn::from_der(&der)
.expect("round-trip decode failed");
assert!(decoded.issuer_logo.is_some());
}
#[test]
fn build_community_logos() {
let der = LogotypeExtnBuilder::new()
.add_community_logo_direct(test_spec())
.add_community_logo_direct(test_spec())
.build()
.expect("build should succeed");
let decoded = crate::logotype_cert_extn_types::LogotypeExtn::from_der(&der)
.expect("round-trip decode failed");
assert_eq!(decoded.community_logos.as_ref().map(|v| v.len()), Some(2));
}
#[test]
fn build_other_logo() {
let der = LogotypeExtnBuilder::new()
.add_other_logo_direct(
crate::logotype_cert_extn_types::ID_LOGO_BACKGROUND,
test_spec(),
)
.build()
.expect("build should succeed");
let decoded = crate::logotype_cert_extn_types::LogotypeExtn::from_der(&der)
.expect("round-trip decode failed");
assert_eq!(decoded.other_logos.as_ref().map(|v| v.len()), Some(1));
}
#[test]
fn build_without_any_logo_returns_error() {
let err = LogotypeExtnBuilder::new().build();
assert!(err.is_err());
}
}