1use baseid_core::error::{CryptoError, SerializationError};
8use baseid_core::types::SignatureAlgorithm;
9use baseid_crypto::signer::{Signer, Verifier};
10use coset::iana::Algorithm;
11use coset::{CborSerializable, CoseSign1, CoseSign1Builder, HeaderBuilder};
12
13use crate::mso::MobileSecurityObject;
14
15fn to_cose_algorithm(alg: SignatureAlgorithm) -> Algorithm {
17 match alg {
18 SignatureAlgorithm::EdDsa => Algorithm::EdDSA,
19 SignatureAlgorithm::Es256 => Algorithm::ES256,
20 SignatureAlgorithm::Es384 => Algorithm::ES384,
21 SignatureAlgorithm::Es256k => Algorithm::ES256K,
22 SignatureAlgorithm::BbsPlus => Algorithm::ES256, }
24}
25
26pub fn sign_mso(mso: &MobileSecurityObject, signer: &dyn Signer) -> baseid_core::Result<Vec<u8>> {
31 let mut mso_cbor = Vec::new();
33 ciborium::into_writer(mso, &mut mso_cbor).map_err(|_| SerializationError::Cbor)?;
34
35 let alg = to_cose_algorithm(signer.algorithm());
37 let protected = HeaderBuilder::new().algorithm(alg).build();
38
39 let unsigned = CoseSign1Builder::new()
41 .protected(protected)
42 .payload(mso_cbor)
43 .build();
44
45 let tbs = unsigned.tbs_data(b"");
47 let signature = signer.sign(&tbs)?;
48
49 let signed = CoseSign1 {
51 signature,
52 ..unsigned
53 };
54
55 signed.to_vec().map_err(|_| SerializationError::Cbor.into())
57}
58
59pub fn verify_signed_mso(
64 cose_bytes: &[u8],
65 verifier: &dyn Verifier,
66) -> baseid_core::Result<MobileSecurityObject> {
67 let sign1 = CoseSign1::from_slice(cose_bytes).map_err(|_| CryptoError::VerificationFailed)?;
69
70 let tbs = sign1.tbs_data(b"");
72 let valid = verifier.verify(&tbs, &sign1.signature)?;
73 if !valid {
74 return Err(CryptoError::VerificationFailed.into());
75 }
76
77 let payload = sign1
79 .payload
80 .as_ref()
81 .ok_or(CryptoError::VerificationFailed)?;
82
83 let mso: MobileSecurityObject =
84 ciborium::from_reader(payload.as_slice()).map_err(|_| SerializationError::Cbor)?;
85
86 Ok(mso)
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use crate::mdoc::{DataElement, MobileDocument};
93 use crate::mso::ValidityInfo;
94 use baseid_core::types::KeyType;
95 use baseid_crypto::KeyPair;
96 use std::collections::BTreeMap;
97
98 fn sample_mso() -> MobileSecurityObject {
99 let mut namespaces = BTreeMap::new();
100 namespaces.insert(
101 "org.iso.18013.5.1".to_string(),
102 vec![
103 DataElement {
104 identifier: "family_name".to_string(),
105 value: serde_json::json!("Doe"),
106 },
107 DataElement {
108 identifier: "given_name".to_string(),
109 value: serde_json::json!("John"),
110 },
111 ],
112 );
113 let doc = MobileDocument {
114 doc_type: "org.iso.18013.5.1.mDL".to_string(),
115 namespaces,
116 };
117 let validity = ValidityInfo {
118 signed: "2024-01-01T00:00:00Z".to_string(),
119 valid_from: "2024-01-01T00:00:00Z".to_string(),
120 valid_until: "2025-01-01T00:00:00Z".to_string(),
121 };
122 MobileSecurityObject::create(&doc, validity).unwrap()
123 }
124
125 #[test]
126 fn sign_and_verify_roundtrip() {
127 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
128 let mso = sample_mso();
129
130 let cose_bytes = sign_mso(&mso, &kp).unwrap();
131 let recovered = verify_signed_mso(&cose_bytes, &kp.public).unwrap();
132
133 assert_eq!(recovered.doc_type, mso.doc_type);
134 assert_eq!(recovered.digest_algorithm, mso.digest_algorithm);
135 assert_eq!(recovered.value_digests.len(), mso.value_digests.len());
136 assert_eq!(
137 recovered.validity_info.valid_from,
138 mso.validity_info.valid_from
139 );
140 }
141
142 #[test]
143 fn tampered_signature_rejected() {
144 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
145 let mso = sample_mso();
146
147 let mut cose_bytes = sign_mso(&mso, &kp).unwrap();
148
149 let len = cose_bytes.len();
151 cose_bytes[len - 1] ^= 0xFF;
152
153 let result = verify_signed_mso(&cose_bytes, &kp.public);
154 assert!(result.is_err());
155 }
156
157 fn sign_verify_with_key_type(key_type: KeyType) {
158 let kp = KeyPair::generate(key_type).unwrap();
159 let mso = sample_mso();
160
161 let cose_bytes = sign_mso(&mso, &kp).unwrap();
162 let recovered = verify_signed_mso(&cose_bytes, &kp.public).unwrap();
163
164 assert_eq!(recovered.doc_type, mso.doc_type);
165 }
166
167 #[test]
168 fn each_key_type_ed25519() {
169 sign_verify_with_key_type(KeyType::Ed25519);
170 }
171
172 #[test]
173 fn each_key_type_p256() {
174 sign_verify_with_key_type(KeyType::P256);
175 }
176
177 #[test]
178 fn each_key_type_p384() {
179 sign_verify_with_key_type(KeyType::P384);
180 }
181
182 #[test]
183 fn each_key_type_secp256k1() {
184 sign_verify_with_key_type(KeyType::Secp256k1);
185 }
186
187 #[test]
188 fn wrong_key_rejected() {
189 let kp1 = KeyPair::generate(KeyType::Ed25519).unwrap();
190 let kp2 = KeyPair::generate(KeyType::Ed25519).unwrap();
191 let mso = sample_mso();
192
193 let cose_bytes = sign_mso(&mso, &kp1).unwrap();
194 let result = verify_signed_mso(&cose_bytes, &kp2.public);
195 assert!(result.is_err());
196 }
197
198 #[test]
199 fn invalid_cbor_rejected() {
200 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
201 assert!(verify_signed_mso(&[0xFF, 0xFF], &kp.public).is_err());
202 }
203}