How do electronic passports work? (Part 2)
In the second part of this post I will try to give code examples for some basic functionality in eMRTDs. I will add code snippets for each of the sections. The code is mostly a port of my other project eMRTD_face_access from Python to Rust (emrtd crate). It came to life because I was interested in a rewrite of the project in Golang to take it away from Python. During the initial research of PSCS libraries in Golang, I found that there wasn’t a cross platform PCSC library that supports Windows, Linux and macOS. Then taking advantage of this, I decided to learn Rust and rewrite the project in Rust instead.
I also would like to mention that in this blog post, I won’t be implementing all the features of the Python program, nor all the features supported by ICAO Doc 9303. If you want a feature complete implementation, please look into JMRTD or others.
First of all, if you don’t have Rust set up, set it up by following the steps here. Then clone the emrtd
repository and switch to the commit that is used in this blog post:
$ git clone https://github.com/Fethbita/emrtd.git
$ cd emrtd
$ git checkout d9e3064b11964f8ea7c755f52128f1b2a2eafbcd
Let’s take a look at cargo.toml
first:
cargo.toml
[package]
name = "emrtd"
version = "0.0.1"
authors = ["Burak Can Kus"]
edition = "2021"
rust-version = "1.66.1"
description = "A library that can read an eMRTD and do security checks."
readme = "README.md"
repository = "https://github.com/Fethbita/emrtd"
license = "MIT OR Apache-2.0"
keywords = ["emrtd", "epassport", "eid", "electronic_id", "smartcard"]
categories = ["cryptography", "authentication", "command-line-utilities"]
[dependencies]
pcsc = "2.8.2"
openssl = { version = "0.10.64", features = ["vendored"] }
cipher = { version = "0.4.4", features = ["block-padding", "alloc"] }
des = "0.8.1"
aes = "0.8.4"
ecb = "0.1.2"
cbc = "0.1.2"
rasn = "0.14.0"
rasn-cms = "0.14.0"
rasn-pkix = "0.14.0"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
[dev-dependencies]
hex-literal = "0.4.1"
Most of the things in this file are required or recommended to get published on crates.io. We try to use few non-Rust dependencies.
Connecting to a card
First thing we need to do is to connect a card to our card reader. For everything card related, I use the awesome pcsc crate. Let’s check the examples/read_emrtd.rs
file as an example:
examples/read_emrtd.rs
1use std::env;
2
3use emrtd::{
4 bytes2hex, get_jpeg_from_ef_dg2, other_mrz, parse_master_list, passive_authentication,
5 validate_dg, EmrtdComms, EmrtdError,
6};
7use tracing::{error, info};
8
9fn main() -> Result<(), EmrtdError> {
10 tracing_subscriber::fmt()
11 .with_max_level(tracing::Level::TRACE)
12 .init();
13
14 // Establish a PC/SC context.
15 let ctx = match pcsc::Context::establish(pcsc::Scope::User) {
16 Ok(ctx) => ctx,
17 Err(err) => {
18 error!("Failed to establish context: {err}");
19 std::process::exit(1);
20 }
21 };
22
23 // List available readers.
24 let mut readers_buf = [0; 2048];
25 let mut readers = match ctx.list_readers(&mut readers_buf) {
26 Ok(readers) => readers,
27 Err(err) => {
28 error!("Failed to list readers: {err}");
29 std::process::exit(1);
30 }
31 };
32
33 // Use the first reader.
34 let reader = match readers.next() {
35 Some(reader) => reader,
36 None => {
37 error!("No readers are connected.");
38 std::process::exit(1);
39 }
40 };
41 info!("Using reader: {reader:?}");
42
43 // Connect to the card.
44 let card = match ctx.connect(reader, pcsc::ShareMode::Shared, pcsc::Protocols::ANY) {
45 Ok(card) => card,
46 Err(pcsc::Error::NoSmartcard) => {
47 error!("A smartcard is not present in the reader.");
48 std::process::exit(1);
49 }
50 Err(err) => {
51 error!("Failed to connect to card: {err}");
52 std::process::exit(1);
53 }
54 };
55
56 let mut sm_object = EmrtdComms::new(card);
57
58 // Get the card's ATR.
59 info!("ATR from attribute: {}", bytes2hex(&sm_object.get_atr()?));
60
61 // Read EF.CardAccess
62 // sm_object.select_ef(b"\x01\x1C", "EF.CardAccess", false)?;
63 // let ef_cardacess = sm_object.read_data_from_ef(false)?;
64 // info!("Data from the EF.CardAccess: {}", bytes2hex(&ef_cardacess));
65
66 // Read EF.DIR
67 // sm_object.select_ef(b"\x2F\x00", "EF.DIR", false)?;
68 // let ef_dir = sm_object.read_data_from_ef(false)?;
69 // info!("Data from the EF.DIR: {}", bytes2hex(&ef_dir));
70
71 // Select eMRTD application
72 sm_object.select_emrtd_application()?;
73
74 let doc_no = env::var("DOCNO").expect("Please set DOCNO environment variable");
75 let birthdate = env::var("BIRTHDATE").expect("Please set BIRTHDATE environment variable");
76 let expirydate = env::var("EXPIRYDATE").expect("Please set EXPIRYDATE environment variable");
77
78 let secret = other_mrz(&doc_no, &birthdate, &expirydate)?;
79
80 sm_object.establish_bac_session_keys(secret.as_bytes())?;
81
82 // Read EF.COM
83 sm_object.select_ef(b"\x01\x1E", "EF.COM", true)?;
84 let ef_com = sm_object.read_data_from_ef(true)?;
85 info!("Data from the EF.COM: {}", bytes2hex(&ef_com));
86
87 // Read EF.SOD
88 sm_object.select_ef(b"\x01\x1D", "EF.SOD", true)?;
89 let ef_sod = sm_object.read_data_from_ef(true)?;
90 info!("Data from the EF.SOD: {}", bytes2hex(&ef_sod));
91
92 let master_list = include_bytes!("../data/DE_ML_2024-04-10-10-54-13.ml");
93
94 let csca_cert_store = parse_master_list(master_list)?;
95
96 info!("Number of certificates parse from the Master List in the store {}", csca_cert_store.all_certificates().len());
97
98 let result = passive_authentication(&ef_sod, &csca_cert_store).unwrap();
99 info!("{:?} {:?} {:?}", result.0.type_(), result.1, result.2);
100
101 // Read EF.DG1
102 sm_object.select_ef(b"\x01\x01", "EF.DG1", true)?;
103 let ef_dg1 = sm_object.read_data_from_ef(true)?;
104 info!("Data from the EF.DG1: {}", bytes2hex(&ef_dg1));
105 validate_dg(&ef_dg1, 1, result.0, &result.1)?;
106
107 // Read EF.DG2
108 sm_object.select_ef(b"\x01\x02", "EF.DG2", true)?;
109 let ef_dg2 = sm_object.read_data_from_ef(true)?;
110 info!("Data from the EF.DG2: {}", bytes2hex(&ef_dg2));
111 validate_dg(&ef_dg2, 2, result.0, &result.1)?;
112
113 let jpeg = get_jpeg_from_ef_dg2(&ef_dg2)?;
114 std::fs::write("face.jpg", jpeg).expect("Error writing file");
115
116 return Ok(());
117}
In the example we get a card
handle we can use to send APDU packages to[1] and read environment variables DOCNO
, BIRTHDATE
and EXPIRYDATE
to use during session key establishment. We establish the session keys using the establish_bac_session_keys
function and then parse the Master List using the parse_master_list
function. We then perform passive authentication using the passive_authentication
function using the EF.SOD
file.
Next, let’s look at the src/lib.rs
file and go into a bit more detail about the functions there. The file begins with the definition of EmrtdError
which is used for all errors that can occur in the library:
src/lib.rs#132
132#![forbid(unsafe_code)]
133
134extern crate alloc;
135use alloc::{borrow::ToOwned, collections::BTreeMap, format, string::String, vec, vec::Vec};
136use cipher::{BlockDecryptMut, BlockEncryptMut, KeyInit, KeyIvInit};
137use core::{
138 fmt::{self, Debug, Write},
139 iter, mem,
140};
141use openssl::{
142 hash::{hash, MessageDigest},
143 memcmp::eq,
144 rand::rand_bytes,
145 sign::Verifier,
146 stack::Stack,
147 x509::{
148 store::{X509Store, X509StoreBuilder},
149 X509StoreContext, X509,
150 },
151};
152use pcsc::{Attribute::AtrString, Card};
153use rasn::{der, types::Oid};
154use rasn_cms::{CertificateChoices, RevocationInfoChoice};
155use tracing::{error, info, trace, warn};
156
157#[derive(Debug)]
158#[non_exhaustive]
159pub enum EmrtdError {
160 RecvApduError(u8, u8),
161 ParseMrzCharError(char),
162 ParseMrzFieldError(&'static str, String),
163 ParseAsn1DataError(usize, usize),
164 InvalidMacKeyError(usize, usize),
165 ParseDataError(String),
166 InvalidArgument(&'static str),
167 VerifyMacError(),
168 InvalidResponseError(),
169 OverflowSscError(),
170 InvalidOidError(),
171 ParseAsn1TagError(String, String),
172 InvalidFileStructure(&'static str),
173 VerifySignatureError(&'static str),
174 VerifyHashError(String),
175 PcscError(pcsc::Error),
176 BoringErrorStack(openssl::error::ErrorStack),
177 RasnEncodeError(rasn::error::EncodeError),
178 RasnDecodeError(rasn::error::DecodeError),
179 PadError(cipher::inout::PadError),
180 UnpadError(cipher::block_padding::UnpadError),
181}
182impl fmt::Display for EmrtdError {
183 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184 match *self {
185 Self::RecvApduError(ref sw1, ref sw2) => write!(
186 f,
187 "APDU command failed with status code: {sw1:02X} {sw2:02X}"
188 ),
189 Self::ParseMrzCharError(ref c) => {
190 write!(f, "MRZ can not contain the character: {c}")
191 }
192 Self::ParseMrzFieldError(mrz_field, ref value) => {
193 write!(f, "MRZ field {mrz_field} is invalid: {value}")
194 }
195 Self::ParseAsn1DataError(ref e_len, ref f_len) => write!(
196 f,
197 "ASN.1 data is incomplete, expected len: {e_len}, found len: {f_len}"
198 ),
199 Self::InvalidMacKeyError(ref e_len, ref f_len) => write!(
200 f,
201 "Invalid MAC key, expected len: {e_len}, found len: {f_len}"
202 ),
203 Self::ParseDataError(ref error) => write!(f, "Invalid data length: {error}"),
204 Self::InvalidArgument(error_msg) => write!(f, "Invalid argument: {error_msg}"),
205 Self::VerifyMacError() => {
206 write!(f, "Encrypted message MAC is not correct")
207 }
208 Self::InvalidResponseError() => {
209 write!(f, "Card response is invalid")
210 }
211 Self::OverflowSscError() => write!(f, "SSC overflew error"),
212 Self::InvalidOidError() => write!(f, "Invalid OID given"),
213 Self::ParseAsn1TagError(ref expected, ref found) => {
214 write!(f, "Invalid ASN.1 tag, expected: {expected}, found: {found}")
215 }
216 Self::InvalidFileStructure(error_msg) => {
217 write!(f, "Invalid EF structure: {error_msg}")
218 }
219 Self::VerifySignatureError(error_msg) => {
220 write!(f, "Signature verification failure: {error_msg}")
221 }
222 Self::VerifyHashError(ref error_msg) => {
223 write!(f, "Failure during comparison of hashes: {error_msg}")
224 }
225 Self::PcscError(ref e) => fmt::Display::fmt(&e, f),
226 Self::BoringErrorStack(ref e) => fmt::Display::fmt(&e, f),
227 Self::RasnEncodeError(ref e) => fmt::Display::fmt(&e, f),
228 Self::RasnDecodeError(ref e) => fmt::Display::fmt(&e, f),
229 Self::PadError(ref e) => fmt::Display::fmt(&e, f),
230 Self::UnpadError(ref e) => fmt::Display::fmt(&e, f),
231 }
232 }
233}
234// TODO, change to core::error soon, hopefully?
235impl std::error::Error for EmrtdError {}
I believe these errors are pretty self explanatory. Next, let’s look at the APDU
struct:
src/lib.rs#APDU
2503/// An Application Protocol Data Unit (APDU) used in smart card communication.
2504#[derive(Debug, Clone)]
2505pub struct APDU {
2506 /// Class byte of the APDU
2507 cla: u8,
2508 /// Instruction byte of the APDU
2509 ins: u8,
2510 /// Parameter 1 byte of the APDU
2511 p1: u8,
2512 /// Parameter 2 byte of the APDU
2513 p2: u8,
2514 /// Length of the command data field (Lc) in the APDU
2515 lc: Option<Vec<u8>>,
2516 /// Command data field of the APDU
2517 cdata: Option<Vec<u8>>,
2518 /// Expected length of the response data field (Le) in the APDU
2519 le: Option<Vec<u8>>,
2520}
2521
2522impl APDU {
2523 /// Constructs a new APDU instance with the specified parameters.
2524 ///
2525 /// # Arguments
2526 ///
2527 /// * `cla` - The class byte of the APDU.
2528 /// * `ins` - The instruction byte of the APDU.
2529 /// * `p1` - The parameter 1 byte of the APDU.
2530 /// * `p2` - The parameter 2 byte of the APDU.
2531 /// * `lc` - Optional command data field length (Lc) of the APDU.
2532 /// * `cdata` - Optional command data field of the APDU.
2533 /// * `le` - Optional expected response data field length (Le) of the APDU.
2534 ///
2535 /// # Panics
2536 ///
2537 /// Panics if the lengths of `lc` and `le` violate ISO/IEC 7816-4 specifications.
2538 /// See the wiki article for more details:
2539 /// <https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit>
2540 ///
2541 /// # Example
2542 ///
2543 /// ```
2544 /// # use emrtd::EmrtdError;
2545 /// #
2546 /// # fn main() -> Result<(), EmrtdError> {
2547 /// use emrtd::APDU;
2548 /// let apdu = APDU::new(b'\x00', b'\x84', b'\x00', b'\x00', None, None, Some(vec![b'\x08']));
2549 /// #
2550 /// # Ok(())
2551 /// # }
2552 /// ```
2553 pub fn new(
2554 cla: u8,
2555 ins: u8,
2556 p1: u8,
2557 p2: u8,
2558 lc: Option<Vec<u8>>,
2559 cdata: Option<Vec<u8>>,
2560 le: Option<Vec<u8>>,
2561 ) -> Self {
2562 match (lc.as_ref().map(Vec::len), le.as_ref().map(Vec::len)) {
2563 (None | Some(1 | 3), None)
2564 | (None | Some(1), Some(1))
2565 | (Some(3), Some(2))
2566 | (None, Some(3)) => { /* Valid */ }
2567 (_, _) => {
2568 panic!("lc and le length error");
2569 }
2570 }
2571
2572 Self {
2573 cla,
2574 ins,
2575 p1,
2576 p2,
2577 lc,
2578 cdata,
2579 le,
2580 }
2581 }
2582
2583 /// Retrieves the command header of the APDU.
2584 ///
2585 /// The command header consists of the class byte, instruction byte,
2586 /// parameter 1 byte, and parameter 2 byte of the APDU.
2587 ///
2588 /// # Returns
2589 ///
2590 /// The command header.
2591 ///
2592 /// # Examples
2593 ///
2594 /// ```
2595 /// # use emrtd::EmrtdError;
2596 /// #
2597 /// # fn main() -> Result<(), EmrtdError> {
2598 /// use emrtd::APDU;
2599 /// use hex_literal::hex;
2600 ///
2601 /// let apdu = APDU::new(b'\x00', b'\x84', b'\x00', b'\x00', None, None, Some(vec![b'\x08']));
2602 /// assert_eq!(apdu.get_command_header(), hex!("00840000"));
2603 /// #
2604 /// # Ok(())
2605 /// # }
2606 /// ```
2607 #[must_use]
2608 pub fn get_command_header(&self) -> Vec<u8> {
2609 vec![self.cla, self.ins, self.p1, self.p2]
2610 }
2611}
We can use this struct to create new APDUs and internally use them to send commands to eMRTDs.
The usage of the struct is shown in the Doc comments of the implementation functions.
Before the next step, let’s look at a couple of utility functions and explain what they are:
pub fn bytes2hex(bytes: &[u8]) -> String;
: This function is used to generate a hex string from byte slices. It is generally used for logging purposes.fn len2int(data: &[u8], tag_len: usize) -> Result<(usize, usize), EmrtdError>;
: This function parses the ASN.1 length field and returns the header length and the length value.pub fn int2asn1len(length: usize) -> Vec<u8>;
: This function turns an integer length to ASN.1 length field (single byte or extended).fn generate_key_seed(secret: &[u8]) -> Result<Vec<u8>, EmrtdError>;
: This function calculates the SHA1 digest of the secret for usage in BAC.fn compute_key(key_seed: &[u8], key_type: &KeyType, alg: &EncryptionAlgorithm) -> Result<Vec<u8>, EmrtdError>;
: This function computes the encryption or MAC key given the key seed.fn compute_mac(key: &[u8], data: &[u8], alg: &MacAlgorithm) -> Result<Vec<u8>, EmrtdError>;
: This function computes the MAC of the data using the givenkey
andalgorigthm
.fn xor_slices(a: &[u8], b: &[u8]) -> Result<Vec<u8>, EmrtdError>;
: This functionxor
s two byte slices.fn padding_method_2(data: &[u8], pad_to: usize) -> Result<Vec<u8>, EmrtdError>
: This function pads the inputdata
topad_to
size. It uses the ISO/IEC 9797-1 padding method 2. It is necessary to use to pad the data that is encrpyted to be sent to the eMRTD.fn remove_padding(data: &[u8]) -> &[u8];
: This function removes the ISO/IEC 9797-1 padding method 2 padding that was applied withpadding_method_2
function.fn des3_adjust_parity_bits(mut key: Vec<u8>) -> Vec<u8>;
: This function adjusts the parity bits of 3DES keys. DES keys are 8 bytes, with a parity bit for each byte. So the key material out of 64 bits is only 56 bits, 8 bits being parity bits. Setting of these parity bits are optional. See Triple DES Wikipedia page for more details.fn calculate_check_digit(data: &str) -> Result<char, EmrtdError>;
: This function has the logic to calculate the check digits of the MRZ.pub fn other_mrz(doc_no: &str, birthdate: &str, expirydate: &str) -> Result<String, EmrtdError>;
: This function takes in the three required MRZ fields and calculates the check digits and creates a secret key to be used with BAC.fn oid2digestalg(oid: &rasn::types::ObjectIdentifier) -> Result<MessageDigest, EmrtdError>;
: This function maps an OID to a MessageDigest.fn validate_asn1_tag(data: &[u8], tag: &[u8]) -> Result<(), EmrtdError>;
: This function validates the ASN.1 tag.fn get_asn1_child(data: &[u8], tag_len: usize) -> Result<(&[u8], &[u8]), EmrtdError>;
: This function gets the ASN.1 child from the provided data.
src/lib.rs#bytes2hex
514/// Helper function that converts a byte slice into a hex string.
515///
516/// # Arguments
517///
518/// * `bytes` - Bytes to be converted to a hex string.
519///
520/// # Returns
521///
522/// A hex string representation of the input bytes.
523///
524/// # Example
525///
526/// ```
527/// # use emrtd::EmrtdError;
528/// #
529/// # fn main() -> Result<(), EmrtdError> {
530/// use emrtd::bytes2hex;
531/// let bytes = vec![0xDE, 0xAD, 0xBE, 0xEF];
532/// let hex_string = bytes2hex(&bytes);
533/// assert_eq!(hex_string, "DEADBEEF");
534/// #
535/// # Ok(())
536/// # }
537/// ```
538#[must_use]
539pub fn bytes2hex(bytes: &[u8]) -> String {
540 bytes.iter().fold(String::new(), |mut acc, &byte| {
541 write!(&mut acc, "{byte:02X}").expect("Failed to write to string");
542 acc
543 })
544}
src/lib.rs#len2int
546/// Parses the ASN.1 length field.
547///
548/// ASN.1 length encoding can use a single byte for short lengths (up to 127) or multiple bytes
549/// for longer lengths.
550///
551/// # Arguments
552///
553/// * `data` - The ASN.1 data.
554/// * `tag_len` - Length of ASN.1 tag (T of TLV).
555///
556/// # Returns
557///
558/// Result containing a tuple with the start index and length field value, or an `EmrtdError`.
559///
560/// For example, if `tag_len` is 3 and the length field is a single byte with value 42,
561/// the returned value will be (4, 42).
562///
563/// If `tag_len` is 3 and the length field is 3 bytes long with value 2024,
564/// the returned value will be (6, 2024).
565///
566/// # Errors
567///
568/// * `EmrtdError` if the input is incomplete, i.e. if the data is too short to read the length value.
569fn len2int(data: &[u8], tag_len: usize) -> Result<(usize, usize), EmrtdError> {
570 if data.len() < tag_len + 1 {
571 error!(
572 "Error during len2int, `data.len()`: `{}` is less than `tag_len`: `{}`",
573 data.len(),
574 tag_len
575 );
576 return Err(EmrtdError::ParseAsn1DataError(tag_len + 1, data.len()));
577 }
578
579 if data[tag_len] & 0x80 == 0 {
580 Ok((tag_len + 1, data[tag_len] as usize))
581 } else {
582 let length_of_length = ((1 << 7) ^ data[tag_len]) as usize;
583
584 if data.len() < tag_len + 1 + length_of_length {
585 error!("Error during len2int, `data.len()`: `{}` is less than `tag_len + 1 + length_of_length`: `{}`", data.len(), tag_len + 1 + length_of_length);
586 return Err(EmrtdError::ParseAsn1DataError(
587 tag_len + 1 + length_of_length,
588 data.len(),
589 ));
590 }
591
592 let mut buf = [0_u8; mem::size_of::<usize>()];
593 buf[mem::size_of::<usize>() - length_of_length..]
594 .copy_from_slice(&data[tag_len + 1..tag_len + 1 + length_of_length]);
595
596 Ok((tag_len + 1 + length_of_length, usize::from_be_bytes(buf)))
597 }
598}
src/lib.rs#int2asn1len
600/// Encodes the length field in ASN.1 format.
601///
602/// If the length is less than 128, a single octet is used to represent the length.
603/// Otherwise, the long form is used, where the first octet specifies the number of
604/// octets used for the length, followed by the length encoded in big-endian order.
605///
606/// # Arguments
607///
608/// * `length` - The length to be encoded.
609///
610/// # Returns
611///
612/// The ASN.1 encoded length.
613///
614/// # Panics
615/// Should not panic.
616///
617/// # Examples
618///
619/// ```
620/// # use emrtd::EmrtdError;
621/// #
622/// # fn main() -> Result<(), EmrtdError> {
623/// use emrtd::int2asn1len;
624/// use hex_literal::hex;
625///
626/// let result = int2asn1len(0);
627/// assert_eq!(result, hex!("00").to_vec());
628///
629/// let result = int2asn1len(42);
630/// assert_eq!(result, hex!("2A").to_vec());
631///
632/// let result = int2asn1len(127);
633/// assert_eq!(result, hex!("7F").to_vec());
634///
635/// let result = int2asn1len(2024);
636/// assert_eq!(result, hex!("8207E8").to_vec());
637///
638/// let result = int2asn1len(65536);
639/// assert_eq!(result, hex!("83010000").to_vec());
640///
641/// let result = int2asn1len(usize::MAX);
642/// assert_eq!(result, hex!("88FFFFFFFFFFFFFFFF").to_vec());
643/// #
644/// # Ok(())
645/// # }
646/// ```
647#[must_use]
648pub fn int2asn1len(length: usize) -> Vec<u8> {
649 if length < 128 {
650 vec![u8::try_from(length).expect("`length` is less than 128")]
651 } else {
652 let mut length_bytes: Vec<u8> = Vec::new();
653 let mut len = length;
654
655 let mut octet_count: u8 = 0;
656 while len > 0 {
657 octet_count += 1;
658 len >>= 8;
659 }
660 length_bytes.push(0x80 | octet_count);
661 for i in (0..octet_count).rev() {
662 let masked_bits = (length >> (8 * i)) & 0xFF;
663 length_bytes
664 .push(u8::try_from(masked_bits).expect("Bits are masked, must fit in a u8"));
665 }
666 length_bytes
667 }
668}
src/lib.rs#generate_key_seed
670/// Generates a key seed from the given secret.
671///
672/// Calculates the SHA-1 of `secret` and returns the result.
673///
674/// Calculation is explained at ICAO Doc 9303-11 Section 4.3.2:
675/// <https://www.icao.int/publications/Documents/9303_p11_cons_en.pdf>
676///
677/// # Arguments
678///
679/// * `secret` - The secret from which to generate the key seed.
680///
681/// # Returns
682///
683/// The generated key seed if successful.
684///
685/// # Errors
686///
687/// `EmrtdError` if 'SHA1' fails.
688fn generate_key_seed(secret: &[u8]) -> Result<Vec<u8>, EmrtdError> {
689 let hash_bytes = hash(MessageDigest::sha1(), secret).map_err(EmrtdError::BoringErrorStack)?;
690 Ok(hash_bytes.to_vec())
691}
src/lib.rs#compute_key
892/// Computes a key based on the given key seed, key type, and encryption algorithm.
893///
894/// For calculation examples see ICAO Doc 9303-11 Appendix D.1:
895/// <https://www.icao.int/publications/Documents/9303_p11_cons_en.pdf>
896///
897/// # Arguments
898///
899/// * `key_seed` - The key seed.
900/// * `key_type` - The type of the key (Encryption or Mac) to be created.
901/// * `alg` - The encryption algorithm to be used (DES3, AES128, AES192, or AES256).
902///
903/// # Returns
904///
905/// Result containing the computed 3DES or AES key if successful.
906///
907/// # Errors
908///
909/// `EmrtdError` key computation fails.
910fn compute_key(
911 key_seed: &[u8],
912 key_type: &KeyType,
913 alg: &EncryptionAlgorithm,
914) -> Result<Vec<u8>, EmrtdError> {
915 let c: &[u8] = match *key_type {
916 KeyType::Encryption => b"\x00\x00\x00\x01",
917 KeyType::Mac => b"\x00\x00\x00\x02",
918 };
919
920 let mut d = key_seed.to_vec();
921 d.extend_from_slice(c);
922
923 match alg {
924 EncryptionAlgorithm::DES3 => {
925 let hash_bytes =
926 hash(MessageDigest::sha1(), &d).map_err(EmrtdError::BoringErrorStack)?;
927 let key_1_2 = des3_adjust_parity_bits(hash_bytes.iter().copied().take(16).collect());
928 match *key_type {
929 KeyType::Encryption => Ok([&key_1_2[..], &key_1_2[..8]].concat()),
930 KeyType::Mac => Ok(key_1_2),
931 }
932 }
933 EncryptionAlgorithm::AES128 => {
934 let hash_bytes =
935 hash(MessageDigest::sha1(), &d).map_err(EmrtdError::BoringErrorStack)?;
936 Ok(hash_bytes.iter().copied().take(16).collect())
937 }
938 EncryptionAlgorithm::AES192 => {
939 let hash_bytes =
940 hash(MessageDigest::sha256(), &d).map_err(EmrtdError::BoringErrorStack)?;
941 Ok(hash_bytes.iter().copied().take(24).collect())
942 }
943 EncryptionAlgorithm::AES256 => {
944 let hash_bytes =
945 hash(MessageDigest::sha256(), &d).map_err(EmrtdError::BoringErrorStack)?;
946 Ok(hash_bytes.to_vec())
947 }
948 }
949}
src/lib.rs#compute_mac
951/// Computes a MAC of data using the given key and MAC algorithm.
952///
953/// # Arguments
954///
955/// * `key` - The MAC key.
956/// * `data` - The data to calculate the MAC of.
957/// * `alg` - The MAC algorithm to be used (DES or AES-CMAC).
958///
959/// # Returns
960///
961/// Result containing the computed MAC or an `EmrtdError`.
962///
963/// # Errors
964///
965/// * `EmrtdError` if `key` or `data` length is wrong or cipher operation fails.
966fn compute_mac(key: &[u8], data: &[u8], alg: &MacAlgorithm) -> Result<Vec<u8>, EmrtdError> {
967 match *alg {
968 MacAlgorithm::DES => {
969 if key.len() != 16 {
970 error!("Can not compute MAC, MAC key is invalid.");
971 return Err(EmrtdError::InvalidMacKeyError(16, key.len()));
972 }
973
974 if data.len() % 8 != 0 {
975 error!("Can not compute MAC, data length is invalid.");
976 return Err(EmrtdError::ParseDataError(format!(
977 "MAC calculation should be a multiple of 8, but found {}",
978 key.len()
979 )));
980 }
981
982 let key1 = &key[..8];
983 let key2 = &key[8..];
984
985 let mut h = encrypt_ecb::<ecb::Encryptor<des::Des>>(key1, &data[..8])?;
986
987 for i in 1..(data.len() / 8) {
988 h = encrypt_ecb::<ecb::Encryptor<des::Des>>(
989 key1,
990 &xor_slices(&h, &data[8 * i..8 * (i + 1)])?,
991 )?;
992 }
993
994 let mac_x = encrypt_ecb::<ecb::Encryptor<des::Des>>(
995 key1,
996 &decrypt_ecb::<ecb::Decryptor<des::Des>>(key2, &h)?,
997 )?;
998
999 Ok(mac_x)
1000 }
1001 MacAlgorithm::AESCMAC => {
1002 unimplemented!("AES-CMAC MAC calculation is not yet implemented");
1003 }
1004 }
1005}
src/lib.rs#xor_slices
1007/// XORs two byte slices and returns the result.
1008///
1009/// # Arguments
1010///
1011/// * `a` - First input.
1012/// * `b` - Second input.
1013///
1014/// # Returns
1015///
1016/// Result containing the `XORed` result or an `EmrtdError`.
1017///
1018/// # Errors
1019///
1020/// * `EmrtdError` if input `a` and `b` have different lengths.
1021fn xor_slices(a: &[u8], b: &[u8]) -> Result<Vec<u8>, EmrtdError> {
1022 if a.len() == b.len() {
1023 let result: Vec<u8> = a.iter().zip(b.iter()).map(|(&x, &y)| x ^ y).collect();
1024 return Ok(result);
1025 }
1026 error!(
1027 "XORed slices must have the same length, found {}, {}",
1028 a.len(),
1029 b.len()
1030 );
1031 Err(EmrtdError::ParseDataError(format!(
1032 "XORed slices must have the same length, found {}, {}",
1033 a.len(),
1034 b.len()
1035 )))
1036}
src/lib.rs#padding_method_2
1038/// Pads the input data using padding method 2.
1039///
1040/// <https://en.wikipedia.org/wiki/ISO/IEC_9797-1#Padding_method_2>
1041///
1042/// # Arguments
1043///
1044/// * `data` - Data to be padded.
1045/// * `pad_to` - Length to pad to.
1046///
1047/// # Returns
1048///
1049/// Padded data or an `EmrtdError` if `pad_to` is 0.
1050///
1051/// # Errors
1052///
1053/// * `EmrtdError` if `pad_to` is 0.
1054fn padding_method_2(data: &[u8], pad_to: usize) -> Result<Vec<u8>, EmrtdError> {
1055 if pad_to == 0 {
1056 error!("pad_to must be greater than 0, found {}", pad_to);
1057 return Err(EmrtdError::InvalidArgument("pad_to must be greater than 0"));
1058 }
1059
1060 let mut data = data.to_vec();
1061 data.push(0x80);
1062 if data.len() % pad_to != 0 {
1063 let padding_len = pad_to - (data.len() % pad_to);
1064 data.extend(iter::repeat(0).take(padding_len));
1065 }
1066 Ok(data)
1067}
src/lib.rs#remove_padding
1069/// Removes the padding added by padding method 2 from the input data.
1070///
1071/// <https://en.wikipedia.org/wiki/ISO/IEC_9797-1#Padding_method_2>
1072///
1073/// # Arguments
1074///
1075/// * `data` - The padded data.
1076///
1077/// # Returns
1078///
1079/// If the padding exists, data with the padding removed, otherwise original data.
1080fn remove_padding(data: &[u8]) -> &[u8] {
1081 for (i, &b) in data.iter().rev().enumerate() {
1082 if b == 0x80 {
1083 return &data[..data.len() - 1 - i];
1084 }
1085 }
1086 data
1087}
src/lib.rs#des3_adjust_parity_bits
1089/// Adjusts the parity bits of a 3DES key.
1090///
1091/// # Arguments
1092///
1093/// * `key` - The 3DES key.
1094///
1095/// # Returns
1096///
1097/// 3DES key with adjusted parity bits.
1098fn des3_adjust_parity_bits(mut key: Vec<u8>) -> Vec<u8> {
1099 for byte in &mut key {
1100 let mut bitmask = 1;
1101 let mut b = *byte;
1102 for _ in 0..8 {
1103 bitmask ^= b & 0x1;
1104 b >>= 1;
1105 }
1106 *byte ^= bitmask;
1107 }
1108 key
1109}
src/lib.rs#calculate_check_digit
386/// Calculates the check digit for the given data using a specific algorithm.
387/// Calculation is explained at ICAO Doc 9303-3 Section 4.9:
388/// <https://www.icao.int/publications/Documents/9303_p3_cons_en.pdf>
389///
390/// # Arguments
391///
392/// * `data` - Data for which the check digit needs to be calculated.
393///
394/// # Returns
395///
396/// Result containing the calculated check digit or an `EmrtdError`.
397///
398/// # Errors
399///
400/// * `EmrtdError` if an invalid character is given.
401fn calculate_check_digit(data: &str) -> Result<char, EmrtdError> {
402 #[rustfmt::skip]
403 let values: BTreeMap<char, u32> = [
404 ('0', 0), ('1', 1), ('2', 2), ('3', 3), ('4', 4), ('5', 5), ('6', 6), ('7', 7),
405 ('8', 8), ('9', 9), ('<', 0), ('A', 10), ('B', 11), ('C', 12), ('D', 13), ('E', 14),
406 ('F', 15), ('G', 16), ('H', 17), ('I', 18), ('J', 19), ('K', 20), ('L', 21), ('M', 22),
407 ('N', 23), ('O', 24), ('P', 25), ('Q', 26), ('R', 27), ('S', 28), ('T', 29), ('U', 30),
408 ('V', 31), ('W', 32), ('X', 33), ('Y', 34), ('Z', 35),
409 ]
410 .iter()
411 .copied()
412 .collect();
413
414 let weights = [7, 3, 1];
415 let mut total = 0;
416
417 for (counter, value) in data.chars().enumerate() {
418 if let Some(weighted_value) = values.get(&value).copied() {
419 total += weights[counter % 3] * weighted_value;
420 } else {
421 error!("Can not calculate check digit for invalid character: `{value}`");
422 return Err(EmrtdError::ParseMrzCharError(value));
423 }
424 }
425
426 let check_digit =
427 char::from_digit(total % 10, 10).expect("usize % 10 can not be greater than 10");
428 Ok(check_digit)
429}
src/lib.rs#other_mrz
431/// Manually calculates the MRZ (Machine Readable Zone) string for BAC (Basic Access Control).
432///
433/// This function takes document number, birthdate, and expiry date as input, and calculates
434/// the MRZ string by appending check digits to each corresponding input.
435///
436/// # Arguments
437///
438/// * `doc_no` - Document number for MRZ calculation.
439/// * `birthdate` - Birthdate for MRZ calculation.
440/// * `expirydate` - Expiry date for MRZ calculation.
441///
442/// # Returns
443///
444/// Result containing a formatted MRZ string suitable for use in BAC (Basic Access Control) or an `EmrtdError`.
445///
446/// # Errors
447///
448/// * `EmrtdError` if MRZ field length is invalid or contains invalid characters
449///
450/// # Example
451///
452/// ```
453/// # use emrtd::EmrtdError;
454/// #
455/// # fn main() -> Result<(), EmrtdError> {
456/// use emrtd::other_mrz;
457/// let result = other_mrz("L898902C3", "740812", "120415")?;
458/// assert_eq!(result, String::from("L898902C3674081221204159"));
459/// #
460/// # Ok(())
461/// # }
462/// ```
463pub fn other_mrz(doc_no: &str, birthdate: &str, expirydate: &str) -> Result<String, EmrtdError> {
464 // Document number can be up to 9 characters on TD3 sized eMRTDs (https://www.icao.int/publications/Documents/9303_p4_cons_en.pdf Appendix B)
465 // Document number can be up to 22 characters on TD1 sized eMRTDs (https://www.icao.int/publications/Documents/9303_p5_cons_en.pdf 4.2.2)
466 // Document number can be up to 14 characters on TD2 sized eMRTDs (https://www.icao.int/publications/Documents/9303_p6_cons_en.pdf 4.2.2.2)
467 if doc_no.len() > 22
468 || doc_no
469 .chars()
470 .any(|c| !"0123456789<ABCDEFGHIJKLMNOPQRSTUVWXYZ".contains(c))
471 {
472 error!("Error during other_mrz, document number length must be less than 23 and should not contain illegal characters, received {doc_no}");
473 return Err(EmrtdError::ParseMrzFieldError(
474 "Document number",
475 doc_no.to_owned(),
476 ));
477 }
478 if birthdate.len() != 6
479 || birthdate
480 .chars()
481 .any(|c| !"0123456789<ABCDEFGHIJKLMNOPQRSTUVWXYZ".contains(c))
482 {
483 error!("Error during other_mrz, birth date length must be 6 and should not contain illegal characters, received {birthdate}");
484 return Err(EmrtdError::ParseMrzFieldError(
485 "Birth date",
486 birthdate.to_owned(),
487 ));
488 }
489 if expirydate.len() != 6
490 || expirydate
491 .chars()
492 .any(|c| !"0123456789<ABCDEFGHIJKLMNOPQRSTUVWXYZ".contains(c))
493 {
494 error!("Error during other_mrz, expiry date length must be 6 and should not contain illegal characters, received {expirydate}");
495 return Err(EmrtdError::ParseMrzFieldError(
496 "Expiry date",
497 expirydate.to_owned(),
498 ));
499 }
500
501 let formatted_mrz = format!(
502 "{:<9}{}{}{}{}{}",
503 doc_no,
504 calculate_check_digit(doc_no)?,
505 birthdate,
506 calculate_check_digit(birthdate)?,
507 expirydate,
508 calculate_check_digit(expirydate)?
509 );
510
511 Ok(formatted_mrz)
512}
src/lib.rs#oid2digestalg
1111/// Convert an OID (Object Identifier) byte array to its corresponding digest algorithm name.
1112///
1113/// # Arguments
1114///
1115/// * `oid` - OID.
1116///
1117/// # Returns
1118///
1119/// The name of the digest algorithm if the OID is recognized, else an `EmrtdError`.
1120///
1121/// # Errors
1122///
1123/// * `EmrtdError` if an unsupported OID is given.
1124fn oid2digestalg(oid: &rasn::types::ObjectIdentifier) -> Result<MessageDigest, EmrtdError> {
1125 let digest_alg_oid_dict: [(&Oid, MessageDigest); 6] = [
1126 (
1127 Oid::const_new(&[2, 16, 840, 1, 101, 3, 4, 2, 4]),
1128 MessageDigest::sha224(),
1129 ),
1130 (
1131 Oid::const_new(&[2, 16, 840, 1, 101, 3, 4, 2, 3]),
1132 MessageDigest::sha512(),
1133 ),
1134 (
1135 Oid::const_new(&[2, 16, 840, 1, 101, 3, 4, 2, 2]),
1136 MessageDigest::sha384(),
1137 ),
1138 (
1139 Oid::const_new(&[2, 16, 840, 1, 101, 3, 4, 2, 1]),
1140 MessageDigest::sha256(),
1141 ),
1142 (Oid::const_new(&[1, 3, 14, 3, 2, 26]), MessageDigest::sha1()),
1143 (
1144 Oid::const_new(&[1, 2, 840, 113549, 2, 5]),
1145 MessageDigest::md5(),
1146 ),
1147 ];
1148 for (digest_oid, digest) in digest_alg_oid_dict {
1149 if oid.eq(digest_oid) {
1150 return Ok(digest);
1151 }
1152 }
1153 error!("Invalid OID while finding a digest algorithm");
1154 Err(EmrtdError::InvalidOidError())
1155}
src/lib.rs#validate_asn1_tag
1157/// Validate the ASN.1 tag of the provided data. Multi-byte tags are supported.
1158///
1159/// # Arguments
1160///
1161/// * `data` - The data to validate.
1162/// * `tag` - The expected ASN.1 tag.
1163///
1164/// # Returns
1165///
1166/// Nothing if the tag is valid, or an `EmrtdError` if validation fails.
1167///
1168/// # Errors
1169///
1170/// * `EmrtdError` if the data is incomplete or the tags don't match.
1171fn validate_asn1_tag(data: &[u8], tag: &[u8]) -> Result<(), EmrtdError> {
1172 match data.get(..tag.len()) {
1173 Some(d) => {
1174 if !d.starts_with(tag) {
1175 error!(
1176 "Error while validating ASN1 tag, expected: {}, found {}",
1177 bytes2hex(tag),
1178 bytes2hex(d)
1179 );
1180 Err(EmrtdError::ParseAsn1TagError(bytes2hex(tag), bytes2hex(d)))
1181 } else {
1182 Ok(())
1183 }
1184 }
1185 None => {
1186 error!("Error while validating ASN1 tag, `data.len()`: `{}` is less than `tag.len()`: `{}`", data.len(), tag.len());
1187 Err(EmrtdError::ParseAsn1DataError(tag.len(), data.len()))
1188 }
1189 }
1190}
src/lib.rs#get_asn1_child
1192/// Retrieve the ASN.1 child from the provided data.
1193///
1194/// # Arguments
1195///
1196/// * `data` - The data containing the ASN.1 structure.
1197/// * `tag_len` - The length of the tag.
1198///
1199/// # Returns
1200///
1201/// A `Result` containing the child element and the remaining data,
1202/// or an `EmrtdError` if extraction fails.
1203///
1204/// # Errors
1205///
1206/// * `EmrtdError` if the data is incomplete.
1207fn get_asn1_child(data: &[u8], tag_len: usize) -> Result<(&[u8], &[u8]), EmrtdError> {
1208 if data.len() < tag_len {
1209 error!(
1210 "Error during get_asn1_child, `data.len()`: `{}` is less than `tag_len`: `{}`",
1211 data.len(),
1212 tag_len
1213 );
1214 return Err(EmrtdError::ParseAsn1DataError(tag_len, data.len()));
1215 }
1216
1217 let (tl, v) = len2int(data, tag_len)?;
1218 if data.len() < tl + v {
1219 error!(
1220 "Error during get_asn1_child, `data.len()`: `{}` is less than `tl + v`: `{}`",
1221 data.len(),
1222 tl + v
1223 );
1224 return Err(EmrtdError::ParseAsn1DataError(tl + v, data.len()));
1225 }
1226 Ok((&data[tl..tl + v], &data[tl + v..]))
1227}
Next we have 4 utility functions that are necessary to use in BAC. They are basic DES and 3DES encryption/decryption functions, wrapped for easier use. The reason we use this instead of openssl
is because openssl
does not support DES. Instead, we use the native Rust implementation of block ciphers.
src/lib.rs#encrypt(_ecb),decrypt(_ecb)
693/// Encrypts data using the specified block cipher and mode.
694///
695/// # Arguments
696///
697/// * `key` - The encryption key.
698/// * `iv` - An optional initialization vector.
699/// * `data` - The data to be encrypted.
700///
701/// # Returns
702///
703/// Encrypted data if successful.
704///
705/// # Errors
706///
707/// `EmrtdError` if encryption fails.
708fn encrypt<CM>(key: &[u8], iv: Option<&[u8]>, data: &[u8]) -> Result<Vec<u8>, EmrtdError>
709where
710 CM: BlockEncryptMut + KeyIvInit,
711{
712 if key.len() != CM::key_size() {
713 error!(
714 "Wrong key size for cipher encryption, expected {}, found {}",
715 CM::key_size(),
716 key.len()
717 );
718 return Err(EmrtdError::InvalidArgument(
719 "Wrong key size for cipher encryption",
720 ));
721 }
722 if let Some(iv) = iv {
723 if iv.len() != CM::iv_size() {
724 error!(
725 "Wrong IV size for cipher encryption, expected {}, found {}",
726 CM::iv_size(),
727 iv.len()
728 );
729 return Err(EmrtdError::InvalidArgument(
730 "Wrong IV size for cipher encryption",
731 ));
732 }
733 }
734 if data.len() % CM::block_size() != 0 {
735 error!(
736 "Wrong data size for cipher encryption, expected {}, found {}",
737 CM::block_size(),
738 data.len()
739 );
740 return Err(EmrtdError::InvalidArgument(
741 "Wrong data size for cipher encryption",
742 ));
743 }
744
745 Ok(CM::new(key.into(), iv.unwrap_or_default().into())
746 .encrypt_padded_vec_mut::<cipher::block_padding::NoPadding>(data))
747}
748
749/// Encrypts data using the specified block cipher in Electronic Codebook (ECB) mode.
750///
751/// # Arguments
752///
753/// * `key` - The encryption key.
754/// * `data` - The data to be encrypted.
755///
756/// # Returns
757///
758/// Encrypted data if successful.
759///
760/// # Errors
761///
762/// `EmrtdError` if encryption fails.
763fn encrypt_ecb<CM>(key: &[u8], data: &[u8]) -> Result<Vec<u8>, EmrtdError>
764where
765 CM: BlockEncryptMut + KeyInit,
766{
767 if key.len() != CM::key_size() {
768 error!(
769 "Wrong key size for cipher encryption, expected {}, found {}",
770 CM::key_size(),
771 key.len()
772 );
773 return Err(EmrtdError::InvalidArgument(
774 "Wrong key size for cipher encryption",
775 ));
776 }
777 if data.len() % CM::block_size() != 0 {
778 error!(
779 "Wrong data size for cipher encryption, expected {}, found {}",
780 CM::block_size(),
781 data.len()
782 );
783 return Err(EmrtdError::InvalidArgument(
784 "Wrong data size for cipher encryption",
785 ));
786 }
787
788 Ok(CM::new(key.into()).encrypt_padded_vec_mut::<cipher::block_padding::NoPadding>(data))
789}
790
791/// Decrypts data using the specified block cipher and mode.
792///
793/// # Arguments
794///
795/// * `key` - The decryption key.
796/// * `iv` - An optional initialization vector.
797/// * `data` - The data to be decrypted.
798///
799/// # Returns
800///
801/// Decrypted data if successful.
802///
803/// # Errors
804///
805/// `EmrtdError` if decryption fails.
806fn decrypt<CM>(key: &[u8], iv: Option<&[u8]>, data: &[u8]) -> Result<Vec<u8>, EmrtdError>
807where
808 CM: BlockDecryptMut + KeyIvInit,
809{
810 if key.len() != CM::key_size() {
811 error!(
812 "Wrong key size for cipher decryption, expected {}, found {}",
813 CM::key_size(),
814 key.len()
815 );
816 return Err(EmrtdError::InvalidArgument(
817 "Wrong key size for cipher decryption",
818 ));
819 }
820 if let Some(iv) = iv {
821 if iv.len() != CM::iv_size() {
822 error!(
823 "Wrong IV size for cipher decryption, expected {}, found {}",
824 CM::iv_size(),
825 iv.len()
826 );
827 return Err(EmrtdError::InvalidArgument(
828 "Wrong IV size for cipher decryption",
829 ));
830 }
831 }
832 if data.len() % CM::block_size() != 0 {
833 error!(
834 "Wrong data size for cipher decryption, expected {}, found {}",
835 CM::block_size(),
836 data.len()
837 );
838 return Err(EmrtdError::InvalidArgument(
839 "Wrong data size for cipher decryption",
840 ));
841 }
842
843 CM::new(key.into(), iv.unwrap_or_default().into())
844 .decrypt_padded_vec_mut::<cipher::block_padding::NoPadding>(data)
845 .map_err(EmrtdError::UnpadError)
846}
847
848/// Decrypts data using the specified block cipher in Electronic Codebook (ECB) mode.
849///
850/// # Arguments
851///
852/// * `key` - The decryption key.
853/// * `data` - The data to be decrypted.
854///
855/// # Returns
856///
857/// Decrypted data if successful.
858///
859/// # Errors
860///
861/// `EmrtdError` if decryption fails.
862fn decrypt_ecb<CM>(key: &[u8], data: &[u8]) -> Result<Vec<u8>, EmrtdError>
863where
864 CM: BlockDecryptMut + KeyInit,
865{
866 if key.len() != CM::key_size() {
867 error!(
868 "Wrong key size for cipher decryption, expected {}, found {}",
869 CM::key_size(),
870 key.len()
871 );
872 return Err(EmrtdError::InvalidArgument(
873 "Wrong key size for cipher decryption",
874 ));
875 }
876 if data.len() % CM::block_size() != 0 {
877 error!(
878 "Wrong data size for cipher decryption, expected {}, found {}",
879 CM::block_size(),
880 data.len()
881 );
882 return Err(EmrtdError::InvalidArgument(
883 "Wrong data size for cipher decryption, expected {}, found {}",
884 ));
885 }
886
887 CM::new(key.into())
888 .decrypt_padded_vec_mut::<cipher::block_padding::NoPadding>(data)
889 .map_err(EmrtdError::UnpadError)
890}
Now that we have the necessary utility functions, let’s look at another struct to let us communicate with the eMRTD and do the low level operations that are necessary.
src/lib.rs#EmrtdComms
2613pub struct EmrtdComms {
2614 /// The card interface used for communication with the eMRTD.
2615 card: Card,
2616 /// The encryption algorithm used for securing communication with the eMRTD.
2617 enc_alg: Option<EncryptionAlgorithm>,
2618 /// The MAC (Message Authentication Code) algorithm used for data integrity verification.
2619 mac_alg: Option<MacAlgorithm>,
2620 /// The padding length used for encryption, data will be padded to multiple of `pad_len`.
2621 pad_len: usize,
2622 /// The session key used for encryption.
2623 ks_enc: Option<Vec<u8>>,
2624 /// The session key used for MAC generation.
2625 ks_mac: Option<Vec<u8>>,
2626 /// The Secure Session Counter (SSC).
2627 ssc: Option<Vec<u8>>,
2628}
The implementation of the EmrtdComms
is the main code that is used in our project. I will quickly mention each of the functions and what they are used for and I hope that the implementation is clear enough to understand the details. I also provided links in the Doc comments where possible, if you would like to go and see it in ICAO Doc 9303 or relevant RFCs.
pub fn get_atr(&mut self) -> Result<Vec<u8>, EmrtdError>;
: This function uses thepcsc
library to get the card ATR[2].pub fn send(&mut self, apdu: &APDU, secure: bool) -> Result<(Vec<u8>, [u8; 2]), EmrtdError>;
: This function sends an APDU to the card. If a secure channel is established (such as using BAC or PACE), then a secure flag can be set to send a secure APDU.fn process_secure_rapdu(&mut self, rapdu: &[u8]) -> Result<Vec<u8>, EmrtdError>;
: A non-public function thesend
function uses to process the secured reply APDU received from the card.pub fn select_emrtd_application(&mut self) -> Result<(), EmrtdError>;
: This function selects the eMRTD application using the AIDA0000002471001
.pub fn select_ef(&mut self, fid: &[u8; 2], fname: &str, secure: bool) -> Result<(), EmrtdError>;
: This function selects an elementary file specified byfid
.pub fn read_data_from_ef(&mut self, secure: bool) -> Result<Vec<u8>, EmrtdError>;
: This function reads the whole file that was selected using theselect_ef
function.pub fn establish_bac_session_keys(&mut self, secret: &[u8]) -> Result<(), EmrtdError>;
: This function establishes BAC session keys, given the MRZ secret.fn increment_ssc(&mut self) -> Result<(), EmrtdError>;
: This non-public function increments the Secure Session Counter (SSC).
src/lib.rs#EmrtdComms
2630impl EmrtdComms {
2631 /// Constructs a new `EmrtdComms` instance with the smart card interface.
2632 ///
2633 /// # Arguments
2634 ///
2635 /// * `card` - The PC/SC smart card interface.
2636 ///
2637 /// # Returns
2638 ///
2639 /// A new `EmrtdComms` instance.
2640 #[must_use]
2641 pub fn new(card: Card) -> Self {
2642 Self {
2643 card,
2644 enc_alg: None,
2645 mac_alg: None,
2646 pad_len: 0,
2647 ks_enc: None,
2648 ks_mac: None,
2649 ssc: None,
2650 }
2651 }
2652
2653 // Other functions here ...
src/lib.rs#get_atr
2653 /// Retrieves the Answer to Reset (ATR) from the smart card.
2654 ///
2655 /// # Returns
2656 ///
2657 /// ATR or an `EmrtdError`.
2658 ///
2659 /// # Errors
2660 ///
2661 /// * `EmrtdError` wrapping `PscsError` in case of failure.
2662 pub fn get_atr(&mut self) -> Result<Vec<u8>, EmrtdError> {
2663 match self.card.get_attribute_owned(AtrString) {
2664 Ok(atr) => Ok(atr),
2665 Err(err) => Err(EmrtdError::PcscError(err)),
2666 }
2667 }
src/lib.rs#send
2669 /// Sends an APDU (Application Protocol Data Unit) to the smart card and receives the response.
2670 /// If `secure` is `false`, the APDU is sent in plaintext.
2671 /// If `secure` is `true`, the function checks that the secure channel is established previously,
2672 /// such as using `establish_bac_session_keys` function.
2673 /// For more details and examples, see ICAO Doc 9303-11 Section 9.8 and Appendix D.4
2674 /// <https://www.icao.int/publications/Documents/9303_p11_cons_en.pdf>
2675 ///
2676 /// # Arguments
2677 ///
2678 /// * `apdu` - The APDU to be sent.
2679 /// * `secure` - A flag indicating whether to send the APDU securely.
2680 ///
2681 /// # Returns
2682 ///
2683 /// The response data and status bytes if the operation succeeds else an `EmrtdError`.
2684 ///
2685 /// # Errors
2686 ///
2687 /// * `EmrtdError` in case of failure during sending or receiving an APDU.
2688 pub fn send(&mut self, apdu: &APDU, secure: bool) -> Result<(Vec<u8>, [u8; 2]), EmrtdError> {
2689 // Sending APDU in plaintext
2690 if !secure {
2691 let mut apdu_bytes = vec![];
2692 apdu_bytes.extend(&apdu.get_command_header());
2693 apdu_bytes.extend(&apdu.lc.clone().unwrap_or_default());
2694 apdu_bytes.extend(&apdu.cdata.clone().unwrap_or_default());
2695 apdu_bytes.extend(&apdu.le.clone().unwrap_or_default());
2696
2697 trace!("Sending APDU: {}", bytes2hex(&apdu_bytes));
2698 let mut response_buffer = [0; pcsc::MAX_BUFFER_SIZE];
2699
2700 return match self.card.transmit(&apdu_bytes, &mut response_buffer) {
2701 Ok(response) => {
2702 if response.len() < 2 {
2703 error!(
2704 "Card response length should be greater than or equal to 2, found {}",
2705 response.len()
2706 );
2707 return Err(EmrtdError::InvalidResponseError());
2708 }
2709
2710 let status_bytes: [u8; 2] =
2711 [response[response.len() - 2], response[response.len() - 1]];
2712
2713 let data = response[..response.len() - 2].to_vec();
2714
2715 trace!(
2716 "APDU response ({:02X}{:02X}): {}",
2717 status_bytes[0],
2718 status_bytes[1],
2719 bytes2hex(&data)
2720 );
2721
2722 Ok((data, status_bytes))
2723 }
2724 Err(err) => Err(EmrtdError::PcscError(err)),
2725 };
2726 }
2727
2728 self.increment_ssc()?;
2729
2730 let Some(ref ssc) = self.ssc else {
2731 error!("SSC is not set but trying to send securely");
2732 return Err(EmrtdError::InvalidArgument(
2733 "SSC is not set but trying to send securely",
2734 ));
2735 };
2736 let Some(ref enc_alg) = self.enc_alg else {
2737 error!("Enc algorithm is not set but trying to send securely");
2738 return Err(EmrtdError::InvalidArgument(
2739 "Enc algorithm is not set but trying to send securely",
2740 ));
2741 };
2742 let Some(ref mac_alg) = self.mac_alg else {
2743 error!("MAC algorithm is not set but trying to send securely");
2744 return Err(EmrtdError::InvalidArgument(
2745 "MAC algorithm is not set but trying to send securely",
2746 ));
2747 };
2748 let Some(ref ks_enc) = self.ks_enc else {
2749 error!("Session key ks_enc is not set but trying to send securely");
2750 return Err(EmrtdError::InvalidArgument(
2751 "Session key ks_enc is not set but trying to send securely",
2752 ));
2753 };
2754 let Some(ref ks_mac) = self.ks_mac else {
2755 error!("Session key ks_mac is not set but trying to send securely");
2756 return Err(EmrtdError::InvalidArgument(
2757 "Session key ks_mac is not set but trying to send securely",
2758 ));
2759 };
2760 if self.pad_len == 0 {
2761 error!("Padding length is 0 but trying to send securely");
2762 return Err(EmrtdError::InvalidArgument(
2763 "Padding length is 0 but trying to send securely",
2764 ));
2765 }
2766 let pad_len = self.pad_len;
2767 let mut apdu = apdu.clone();
2768
2769 apdu.cla |= 0x0C;
2770
2771 let mut payload = Vec::new();
2772 if let Some(cdata) = &apdu.cdata {
2773 let data = &padding_method_2(cdata, pad_len)?;
2774 let encrypted_data = match enc_alg {
2775 EncryptionAlgorithm::DES3 => {
2776 encrypt::<cbc::Encryptor<des::TdesEde3>>(ks_enc, Some(&[0; 8]), data)?
2777 }
2778 EncryptionAlgorithm::AES128 => {
2779 let ssc_enc = encrypt_ecb::<ecb::Encryptor<aes::Aes128>>(ks_enc, ssc)?;
2780 encrypt::<cbc::Encryptor<aes::Aes128>>(ks_enc, Some(&ssc_enc), data)?
2781 }
2782 EncryptionAlgorithm::AES192 => {
2783 let ssc_enc = encrypt_ecb::<ecb::Encryptor<aes::Aes192>>(ks_enc, ssc)?;
2784 encrypt::<cbc::Encryptor<aes::Aes192>>(ks_enc, Some(&ssc_enc), data)?
2785 }
2786 EncryptionAlgorithm::AES256 => {
2787 let ssc_enc = encrypt_ecb::<ecb::Encryptor<aes::Aes256>>(ks_enc, ssc)?;
2788 encrypt::<cbc::Encryptor<aes::Aes256>>(ks_enc, Some(&ssc_enc), data)?
2789 }
2790 };
2791
2792 if apdu.ins % 2 == 0 {
2793 // For a command with even INS, any command data is encrypted
2794 // and encapsulated in a Tag 87 with padding indicator (01).
2795 let do87 = [
2796 b"\x87",
2797 (&*int2asn1len([&b"\x01"[..], &encrypted_data].concat().len())),
2798 &[&b"\x01"[..], &encrypted_data].concat(),
2799 ]
2800 .concat();
2801 payload.extend_from_slice(&do87);
2802 } else {
2803 // For a command with odd INS, any command data is encrypted
2804 // and encapsulated in a Tag 85 without padding indicator.
2805 let do85 = [
2806 b"\x85",
2807 (&*int2asn1len(encrypted_data.len())),
2808 &encrypted_data,
2809 ]
2810 .concat();
2811 payload.extend_from_slice(&do85);
2812 }
2813 }
2814
2815 if let Some(le) = &apdu.le {
2816 // Commands with response (Le field not empty)
2817 // have a protected Le-field (Tag 97) in the command data.
2818 let do97 = [b"\x97", (&*int2asn1len(le.len())), le].concat();
2819 payload.extend_from_slice(&do97);
2820 }
2821
2822 let padded_header = padding_method_2(&apdu.get_command_header(), pad_len)?;
2823 let n = padding_method_2(&[&ssc, (&*padded_header), &payload].concat(), pad_len)?;
2824 let cc = compute_mac(ks_mac, &n, mac_alg)?;
2825
2826 let do8e = [b"\x8E", (&*int2asn1len(cc.len())), &cc].concat();
2827 payload.extend_from_slice(&do8e);
2828
2829 let protected_apdu = [
2830 apdu.get_command_header(),
2831 [payload.len() as u8].to_vec(),
2832 payload,
2833 b"\x00".to_vec(),
2834 ]
2835 .concat();
2836
2837 trace!("Sending Protected APDU: {}", bytes2hex(&protected_apdu));
2838 let mut response_buffer = [0; pcsc::MAX_BUFFER_SIZE];
2839
2840 match self.card.transmit(&protected_apdu, &mut response_buffer) {
2841 Ok(response) => {
2842 if response.len() < 2 {
2843 error!(
2844 "Card response length should be greater than or equal to 2, found {}",
2845 response.len()
2846 );
2847 return Err(EmrtdError::InvalidResponseError());
2848 }
2849
2850 let status_bytes: [u8; 2] =
2851 [response[response.len() - 2], response[response.len() - 1]];
2852
2853 let data = self.process_secure_rapdu(&response[..response.len() - 2])?;
2854
2855 trace!(
2856 "APDU response ({:02X}{:02X}): {}",
2857 status_bytes[0],
2858 status_bytes[1],
2859 bytes2hex(&data)
2860 );
2861
2862 Ok((data, status_bytes))
2863 }
2864 Err(err) => Err(EmrtdError::PcscError(err)),
2865 }
2866 }
src/lib.rs#process_secure_rapdu
2868 /// Processes a secured Application Protocol Data Unit response received from the smart card.
2869 ///
2870 /// # Arguments
2871 ///
2872 /// * `rapdu` - A slice containing the Secure `R_APDU` to be processed.
2873 ///
2874 /// # Returns
2875 ///
2876 /// Decrypted data or an `EmrtdError`.
2877 ///
2878 /// # Errors
2879 ///
2880 /// * `EmrtdError` in case of failure during processing of received APDU.
2881 fn process_secure_rapdu(&mut self, rapdu: &[u8]) -> Result<Vec<u8>, EmrtdError> {
2882 self.increment_ssc()?;
2883
2884 let Some(ref ssc) = self.ssc else {
2885 error!("SSC is not set but trying to process R_APDU");
2886 return Err(EmrtdError::InvalidArgument(
2887 "SSC is not set but trying to process R_APDU",
2888 ));
2889 };
2890 let Some(ref enc_alg) = self.enc_alg else {
2891 error!("Enc algorithm is not set but trying to process R_APDU");
2892 return Err(EmrtdError::InvalidArgument(
2893 "Enc algorithm is not set but trying to process R_APDU",
2894 ));
2895 };
2896 let Some(ref mac_alg) = self.mac_alg else {
2897 error!("MAC algorithm is not set but trying to process R_APDU");
2898 return Err(EmrtdError::InvalidArgument(
2899 "MAC algorithm is not set but trying to process R_APDU",
2900 ));
2901 };
2902 let Some(ref ks_enc) = self.ks_enc else {
2903 error!("Session key ks_enc is not set but trying to process R_APDU");
2904 return Err(EmrtdError::InvalidArgument(
2905 "Session key ks_enc is not set but trying to process R_APDU",
2906 ));
2907 };
2908 let Some(ref ks_mac) = self.ks_mac else {
2909 error!("Session key ks_mac is not set but trying to process R_APDU");
2910 return Err(EmrtdError::InvalidArgument(
2911 "Session key ks_mac is not set but trying to process R_APDU",
2912 ));
2913 };
2914 if self.pad_len == 0 {
2915 error!("Padding length is 0 but trying to process R_APDU");
2916 return Err(EmrtdError::InvalidArgument(
2917 "Padding length is 0 but trying to process R_APDU",
2918 ));
2919 }
2920 let pad_len = self.pad_len;
2921
2922 let mut encrypted_data = Vec::new();
2923 let mut decrypted_data = Vec::new();
2924 let mut do85: Option<&[u8]> = None;
2925 let mut do87: Option<&[u8]> = None;
2926 let mut do99: Option<&[u8]> = None;
2927 let mut do8e: Option<&[u8]> = None;
2928
2929 trace!("R_APDU: {}", bytes2hex(rapdu));
2930
2931 let mut rapdu = rapdu;
2932 loop {
2933 let (tl_len, value_len) = len2int(rapdu, 1)?;
2934 match rapdu[0] {
2935 b'\x85' => {
2936 encrypted_data = rapdu[tl_len..tl_len + value_len].to_vec();
2937 do85 = Some(&rapdu[..tl_len + value_len]);
2938 }
2939 b'\x87' => {
2940 encrypted_data = rapdu[tl_len..tl_len + value_len].to_vec();
2941 do87 = Some(&rapdu[..tl_len + value_len]);
2942 }
2943 b'\x99' => do99 = Some(&rapdu[..tl_len + value_len]),
2944 b'\x8e' => {
2945 do8e = Some(&rapdu[tl_len..tl_len + value_len]);
2946 }
2947 _ => {
2948 error!("Tag not supported in encrypted R_APDU");
2949 return Err(EmrtdError::ParseDataError(format!(
2950 "Tag {:02X} not supported in encrypted R_APDU",
2951 rapdu[0]
2952 )));
2953 }
2954 }
2955 rapdu = &rapdu[tl_len + value_len..];
2956 if rapdu.is_empty() {
2957 break;
2958 }
2959 }
2960
2961 let k = padding_method_2(
2962 &[
2963 &ssc,
2964 do85.unwrap_or_default(),
2965 do87.unwrap_or_default(),
2966 do99.unwrap_or_default(),
2967 ]
2968 .concat(),
2969 pad_len,
2970 )?;
2971 let cc = compute_mac(ks_mac, &k, mac_alg)?;
2972 if !eq(&cc, do8e.unwrap_or_default()) {
2973 error!("MAC verification failed");
2974 return Err(EmrtdError::VerifyMacError());
2975 }
2976
2977 if !encrypted_data.is_empty() {
2978 // If INS is even, remove the padding indicator (01)
2979 if do87.is_some() {
2980 encrypted_data = encrypted_data[1..].to_vec();
2981 }
2982 // Decrypt
2983 let decrypted_padded_data = match enc_alg {
2984 EncryptionAlgorithm::DES3 => decrypt::<cbc::Decryptor<des::TdesEde3>>(
2985 ks_enc,
2986 Some(&[0; 8]),
2987 &encrypted_data,
2988 )?,
2989 EncryptionAlgorithm::AES128 => {
2990 let ssc_enc = encrypt_ecb::<ecb::Encryptor<aes::Aes128>>(ks_enc, ssc)?;
2991 decrypt::<cbc::Decryptor<aes::Aes128>>(ks_enc, Some(&ssc_enc), &encrypted_data)?
2992 }
2993 EncryptionAlgorithm::AES192 => {
2994 let ssc_enc = encrypt_ecb::<ecb::Encryptor<aes::Aes192>>(ks_enc, ssc)?;
2995 decrypt::<cbc::Decryptor<aes::Aes192>>(ks_enc, Some(&ssc_enc), &encrypted_data)?
2996 }
2997 EncryptionAlgorithm::AES256 => {
2998 let ssc_enc = encrypt_ecb::<ecb::Encryptor<aes::Aes256>>(ks_enc, ssc)?;
2999 decrypt::<cbc::Decryptor<aes::Aes256>>(ks_enc, Some(&ssc_enc), &encrypted_data)?
3000 }
3001 };
3002 // Remove padding
3003 decrypted_data = remove_padding(&decrypted_padded_data).to_vec();
3004 }
3005 Ok(decrypted_data)
3006 }
src/lib.rs#select_emrtd_application
3008 /// Selects the eMRTD application on the card.
3009 ///
3010 /// This function sends a command to select the eMRTD application using AID `A0000002471001`.
3011 ///
3012 /// # Returns
3013 ///
3014 /// Nothing if the selection is successful.
3015 ///
3016 /// # Errors
3017 ///
3018 /// `EmrtdError` in case of failure during sending the APDU.
3019 pub fn select_emrtd_application(&mut self) -> Result<(), EmrtdError> {
3020 // Select eMRTD application
3021 let aid = b"\xA0\x00\x00\x02\x47\x10\x01";
3022 info!(
3023 "Selecting eMRTD Application `International AID`: {}...",
3024 bytes2hex(aid)
3025 );
3026 let apdu = APDU::new(
3027 b'\x00',
3028 b'\xA4',
3029 b'\x04',
3030 b'\x0C',
3031 Some(int2asn1len(aid.len())),
3032 Some(aid.to_vec()),
3033 None,
3034 );
3035 match self.send(&apdu, false) {
3036 Ok((_, status)) => match status {
3037 [0x90, 0x00] => Ok(()),
3038 [sw1, sw2] => {
3039 error!("Received invalid SW during Select eMRTD Application command: {sw1:02X} {sw2:02X}");
3040 Err(EmrtdError::RecvApduError(sw1, sw2))
3041 }
3042 },
3043 Err(err) => {
3044 error!("Error while selecting eMRTD Application.");
3045 Err(err)
3046 }
3047 }
3048 }
src/lib.rs#select_ef
3050 /// Selects a specific Elementary File (EF) on the smart card by sending a "Select File" APDU.
3051 ///
3052 /// # Arguments
3053 ///
3054 /// * `fid` - Array representing the File Identifier (FID) of the EF to select.
3055 /// * `fname` - The name of the file being selected (used for logging purposes).
3056 ///
3057 /// # Returns
3058 ///
3059 /// Nothing if successful, else an `EmrtdError`.
3060 ///
3061 /// # Errors
3062 ///
3063 /// * `EmrtdError` in case of failure during sending the APDU.
3064 pub fn select_ef(
3065 &mut self,
3066 fid: &[u8; 2],
3067 fname: &str,
3068 secure: bool,
3069 ) -> Result<(), EmrtdError> {
3070 // Send "Select File" APDU
3071 trace!("Selecting File {fname}: {}...", bytes2hex(fid));
3072 let apdu = APDU::new(
3073 b'\x00',
3074 b'\xA4',
3075 b'\x02',
3076 b'\x0C',
3077 Some(int2asn1len(fid.len())),
3078 Some(fid.to_vec()),
3079 None,
3080 );
3081 match self.send(&apdu, secure) {
3082 Ok((_, status)) => match status {
3083 [0x90, 0x00] => Ok(()),
3084 [sw1, sw2] => {
3085 error!("Received invalid SW during Select EF command: {sw1:02X} {sw2:02X}");
3086 Err(EmrtdError::RecvApduError(sw1, sw2))
3087 }
3088 },
3089 Err(err) => {
3090 error!("Error while selecting an EF.");
3091 Err(err)
3092 }
3093 }
3094 }
src/lib.rs#read_data_from_ef
3096 /// Reads data from an EF (Elementary File) in an eMRTD (electronic Machine Readable Travel Document).
3097 ///
3098 /// This function sends APDU (Application Protocol Data Unit) commands to read the data from the EF.
3099 /// It starts by reading the first four bytes of the file, then determines the total length of the file.
3100 /// Afterward, it reads the rest of the bytes in chunks until it reaches the end of the file.
3101 /// `select_ef` function must be called before calling this function.
3102 ///
3103 /// # Returns
3104 ///
3105 /// The data read from the EF if successful, else an `EmrtdError`.
3106 ///
3107 /// # Errors
3108 ///
3109 /// * `EmrtdError` in case of failure.
3110 pub fn read_data_from_ef(&mut self, secure: bool) -> Result<Vec<u8>, EmrtdError> {
3111 // Read Binary of first four bytes
3112 let apdu = APDU::new(
3113 b'\x00',
3114 b'\xB0',
3115 b'\x00',
3116 b'\x00',
3117 None,
3118 None,
3119 Some(vec![b'\x04']),
3120 );
3121 // Send "Read Binary" APDU for the first 4 bytes
3122 trace!("Reading first 4 bytes from EF...");
3123 let mut data = match self.send(&apdu, secure) {
3124 Ok((data, status)) => match status {
3125 [0x90, 0x00] => data,
3126 [sw1, sw2] => {
3127 error!("Received invalid SW during reading first 4 bytes of EF: {sw1:02X} {sw2:02X}");
3128 return Err(EmrtdError::RecvApduError(sw1, sw2));
3129 }
3130 },
3131 Err(err) => {
3132 error!("Error while reading 4 bytes from EF.");
3133 return Err(err);
3134 }
3135 };
3136
3137 if data.len() != 4 {
3138 error!(
3139 "Card response length should be equal to the requested amount 4, found {}",
3140 data.len()
3141 );
3142 return Err(EmrtdError::InvalidResponseError());
3143 }
3144
3145 let data_len;
3146 {
3147 let (tl, v) = len2int(&data, 1)?;
3148 data_len = tl + v;
3149 };
3150
3151 let mut offset = 4;
3152
3153 // Read the rest of the bytes
3154 trace!("Reading {data_len} bytes from EF...");
3155 while offset < data_len {
3156 let le = if data_len - offset < 0xFA {
3157 [((data_len - offset) & 0xFF) as u8]
3158 } else {
3159 [0x00]
3160 };
3161
3162 // Send "Read Binary" APDU for the next chunk
3163 let offset_bytes = [(offset >> 8) as u8, (offset & 0xFF) as u8];
3164 let read_apdu = APDU::new(
3165 b'\x00',
3166 b'\xB0',
3167 offset_bytes[0],
3168 offset_bytes[1],
3169 None,
3170 None,
3171 Some(vec![le[0]]),
3172 );
3173 trace!("Reading next {} bytes from EF...", data_len - offset);
3174 let data_read = match self.send(&read_apdu, secure) {
3175 Ok((data, status)) => match status {
3176 [0x90, 0x00] => data,
3177 [sw1, sw2] => {
3178 error!("Received invalid SW during reading bytes {} of EF: {sw1:02X} {sw2:02X}", data_len - offset);
3179 return Err(EmrtdError::RecvApduError(sw1, sw2));
3180 }
3181 },
3182 Err(err) => {
3183 error!("Error while reading bytes from EF.");
3184 return Err(err);
3185 }
3186 };
3187
3188 if data_read.is_empty() {
3189 error!("Requested bytes while reading EF but received 0 bytes.");
3190 return Err(EmrtdError::InvalidResponseError());
3191 }
3192
3193 // Append the new data to the result
3194 data.extend_from_slice(&data_read);
3195 offset += data_read.len();
3196 }
3197
3198 if offset != data_len {
3199 error!(
3200 "Error while parsing EF data from the card, expected {offset}, found {data_len}."
3201 );
3202 return Err(EmrtdError::InvalidResponseError());
3203 }
3204
3205 Ok(data)
3206 }
src/lib.rs#establish_bac_session_keys
3208 /// Establishes session keys for Basic Access Control (BAC) protocol.
3209 ///
3210 /// For more details and examples, see ICAO Doc 9303-11 Section 4.3 and Appendix D.3
3211 /// <https://www.icao.int/publications/Documents/9303_p11_cons_en.pdf>
3212 ///
3213 /// # Arguments
3214 ///
3215 /// * `secret` - The secret key used for BAC (from MRZ, generate it using `other_mrz` function.
3216 ///
3217 /// # Returns
3218 ///
3219 /// * Nothing if successful, else an `EmrtdError`.
3220 ///
3221 /// # Errors
3222 ///
3223 /// * `EmrtdError` in case of failure during BAC session key establishment.
3224 pub fn establish_bac_session_keys(&mut self, secret: &[u8]) -> Result<(), EmrtdError> {
3225 let ba_key_seed = &generate_key_seed(secret)?[..16];
3226
3227 // Calculate the basic access keys (ba_key_enc and ba_key_mac)
3228 trace!("Computing basic access keys...");
3229 let ba_key_enc = &compute_key(
3230 ba_key_seed,
3231 &KeyType::Encryption,
3232 &EncryptionAlgorithm::DES3,
3233 )?;
3234 let ba_key_mac = &compute_key(ba_key_seed, &KeyType::Mac, &EncryptionAlgorithm::DES3)?;
3235
3236 // AUTHENTICATION AND ESTABLISHMENT OF SESSION KEYS
3237 trace!("Establishing session keys...");
3238 let apdu = APDU::new(
3239 b'\x00',
3240 b'\x84',
3241 b'\x00',
3242 b'\x00',
3243 None,
3244 None,
3245 Some(vec![b'\x08']),
3246 );
3247 let rnd_ic = match self.send(&apdu, false) {
3248 Ok((rnd_ic, status)) => match status {
3249 [0x90, 0x00] => rnd_ic,
3250 [sw1, sw2] => {
3251 error!("Received invalid SW during establishing BAC session keys: {sw1:02X} {sw2:02X}");
3252 return Err(EmrtdError::RecvApduError(sw1, sw2));
3253 }
3254 },
3255 Err(err) => {
3256 error!("Error while establishing BAC session keys.");
3257 return Err(err);
3258 }
3259 };
3260
3261 let mut rnd_ifd: [u8; 8] = [0; 8];
3262 rand_bytes(&mut rnd_ifd).map_err(EmrtdError::BoringErrorStack)?;
3263 let mut k_ifd: [u8; 16] = [0; 16];
3264 rand_bytes(&mut k_ifd).map_err(EmrtdError::BoringErrorStack)?;
3265
3266 let e_ifd = encrypt::<cbc::Encryptor<des::TdesEde3>>(
3267 ba_key_enc,
3268 Some(&[0; 8]),
3269 &[&rnd_ifd[..], (&*rnd_ic), &k_ifd[..]].concat(),
3270 )?;
3271
3272 let m_ifd = compute_mac(
3273 &ba_key_mac.clone(),
3274 &padding_method_2(&e_ifd, 8)?,
3275 &MacAlgorithm::DES,
3276 )?;
3277 let cmd_data = [&e_ifd, (&*m_ifd)].concat();
3278
3279 let apdu = APDU::new(
3280 b'\x00',
3281 b'\x82',
3282 b'\x00',
3283 b'\x00',
3284 Some(int2asn1len(cmd_data.len())),
3285 Some(cmd_data),
3286 Some(vec![b'\x28']),
3287 );
3288 let resp_data_enc = match self.send(&apdu, false) {
3289 Ok((resp_data_enc, status)) => match status {
3290 [0x90, 0x00] => resp_data_enc,
3291 [sw1, sw2] => {
3292 error!("Received invalid SW during establishing BAC session keys: {sw1:02X} {sw2:02X}");
3293 return Err(EmrtdError::RecvApduError(sw1, sw2));
3294 }
3295 },
3296 Err(err) => {
3297 error!("Error while establishing BAC session keys.");
3298 return Err(err);
3299 }
3300 };
3301
3302 let m_ic = compute_mac(
3303 &ba_key_mac.clone(),
3304 &padding_method_2(&resp_data_enc[..resp_data_enc.len() - 8], 8)?,
3305 &MacAlgorithm::DES,
3306 )?;
3307 if !eq(&m_ic, &resp_data_enc[resp_data_enc.len() - 8..]) {
3308 error!("MAC verification failed");
3309 return Err(EmrtdError::VerifyMacError());
3310 }
3311
3312 let resp_data = decrypt::<cbc::Decryptor<des::TdesEde3>>(
3313 ba_key_enc,
3314 Some(&[0; 8]),
3315 &resp_data_enc[..resp_data_enc.len() - 8],
3316 )?;
3317
3318 if !eq(&resp_data[..8], &rnd_ic[..]) {
3319 error!("Error while establishing BAC session keys.");
3320 return Err(EmrtdError::InvalidResponseError());
3321 }
3322
3323 if !eq(&resp_data[8..16], &rnd_ifd[..]) {
3324 error!("Error while establishing BAC session keys.");
3325 return Err(EmrtdError::InvalidResponseError());
3326 }
3327
3328 let k_ic: &[u8] = &resp_data[16..32];
3329
3330 let ses_key_seed = xor_slices(&k_ifd, k_ic)?;
3331
3332 let ks_enc = compute_key(
3333 &ses_key_seed,
3334 &KeyType::Encryption,
3335 &EncryptionAlgorithm::DES3,
3336 )?;
3337 let ks_mac = compute_key(&ses_key_seed, &KeyType::Mac, &EncryptionAlgorithm::DES3)?;
3338
3339 let ssc = [&rnd_ic[4..], &rnd_ifd[4..]].concat();
3340
3341 self.enc_alg = Some(EncryptionAlgorithm::DES3);
3342 self.mac_alg = Some(MacAlgorithm::DES);
3343 self.pad_len = 8;
3344 self.ks_enc = Some(ks_enc);
3345 self.ks_mac = Some(ks_mac);
3346 self.ssc = Some(ssc);
3347
3348 Ok(())
3349 }
src/lib.rs#increment_ssc
3351 /// Increment the Secure Session Counter (SSC).
3352 ///
3353 /// # Returns
3354 ///
3355 /// Nothing if successful, else an `EmrtdError` if the SSC is not set or overflows.
3356 ///
3357 /// # Errors
3358 ///
3359 /// * `EmrtdError` if SSC is invalid or overflows.
3360 fn increment_ssc(&mut self) -> Result<(), EmrtdError> {
3361 if let Some(ref mut ssc) = self.ssc {
3362 if ssc.len() == 8 {
3363 let int_val = u64::from_be_bytes(ssc.as_slice().try_into().unwrap());
3364 let incremented_val = int_val
3365 .checked_add(1)
3366 .ok_or(EmrtdError::OverflowSscError())?;
3367 *ssc = incremented_val.to_be_bytes().to_vec();
3368 Ok(())
3369 } else if ssc.len() == 16 {
3370 let int_val = u128::from_be_bytes(ssc.as_slice().try_into().unwrap());
3371 let incremented_val = int_val
3372 .checked_add(1)
3373 .ok_or(EmrtdError::OverflowSscError())?;
3374 *ssc = incremented_val.to_be_bytes().to_vec();
3375 Ok(())
3376 } else {
3377 unimplemented!("Only 8 and 16 byte SSC is supported");
3378 }
3379 } else {
3380 error!("SSC is not set but trying to increment");
3381 Err(EmrtdError::InvalidArgument(
3382 "SSC is not set but trying to increment",
3383 ))
3384 }
3385 }
All this above lets us read files from eMRTDs. Next, we need functions that will help us decode the information in these files and do higher level validations, such as Passive, Active, or Chip Authentication.
pub fn parse_master_list(master_list: &[u8]) -> Result<X509Store, EmrtdError>;
: This function parses a Master List. The signature checks are done via OpenSSL and the MasterList structure is created viarasn_compiler
. The first version of this function manually parsed the ASN.1 structure by finding the tags and assertions. The current version is created using therasn
crate. The result is anopenssl::x509::X509Store
which is a collection of CSCA and CSCA link certificates. Currently all of the CSCA certificates are included in the store without an option to filter them.pub fn passive_authentication(ef_sod: &[u8], cert_store: &X509Store) -> Result<(MessageDigest, Vec<lds_security_object::DataGroupHash>, X509), EmrtdError>;
: This function verifies the eMRTD signature. The signature is verified using OpenSSL. The function then returns the digest function that was used to generate data group digests, the verified Data Group Hashes and the Document Signer Certificate (CDS). The LDSSecurityObject structure is created viarasn_compiler
. The first version of this function manually parsed the ASN.1 structure by finding the tags and assertions. The current version is created using therasn
crate.pub fn get_jpeg_from_ef_dg2(ef_dg2: &[u8]) -> Result<&[u8], EmrtdError>;
: This function gets the JPEG image from the EF DG2. We skip many of the information encoded in the beginning of theEF.DG2
file. In some cases, a JPEG2000 image is stored in theEF.DG2
file and it is not possible to directly view the output. In those cases you can useimagemagick
to convert the image to a PNG.pub fn validate_dg(dg: &[u8], dg_number: i32, message_digest: MessageDigest, verified_hashes: &[lds_security_object::DataGroupHash]) -> Result<(), EmrtdError>;
: This function validates theEF.DG
hash to see if it matches the verified hashes.
src/lib.rs#parse_master_list
1229/// Parses the master list data and constructs an `X509Store`.
1230///
1231/// # Arguments
1232///
1233/// * `master_list` - The master list data to be parsed as a byte slice.
1234///
1235/// # Returns
1236///
1237/// * `X509Store` containing the parsed CSCA certificates if successful.
1238///
1239/// # Errors
1240///
1241/// `EmrtdError` if parsing fails.
1242///
1243/// # Panics
1244///
1245/// Might panic under different circumstances.
1246///
1247/// # Examples
1248///
1249/// ```
1250/// # use emrtd::EmrtdError;
1251/// #
1252/// # fn main() -> Result<(), EmrtdError> {
1253/// use emrtd::parse_master_list;
1254/// use tracing::{info, error};
1255///
1256/// let master_list_bytes = &[/* EF.SOD Data */];
1257///
1258/// match parse_master_list(master_list_bytes) {
1259/// Ok(cert_store) => {
1260/// info!("Master list successfully parsed, number of certificates in the store {}", cert_store.all_certificates().len());
1261/// },
1262/// Err(err) => error!("Master list parsing failed: {:?}", err),
1263/// }
1264/// #
1265/// # Ok(())
1266/// # }
1267/// ```
1268pub fn parse_master_list(master_list: &[u8]) -> Result<X509Store, EmrtdError> {
1269 // We expect a Master List as specified in
1270 // ICAO Doc 9303-12 Section 9
1271 // <https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf>
1272
1273 // RFC 5652 Section 12.1, Cryptographic Message Syntax
1274 // <https://datatracker.ietf.org/doc/html/rfc5652#section-12.1>
1275 //
1276 // ContentInfo ::= SEQUENCE {
1277 // contentType ContentType,
1278 // content [0] EXPLICIT ANY DEFINED BY contentType }
1279 //
1280 // ContentType ::= OBJECT IDENTIFIER
1281 let content_info =
1282 der::decode::<rasn_cms::ContentInfo>(master_list).map_err(EmrtdError::RasnDecodeError)?;
1283
1284 // RFC 5652 Section 5.1, SignedData Type
1285 // <https://datatracker.ietf.org/doc/html/rfc5652#section-5.1>
1286 // Verify the id-signedData OID
1287 if content_info
1288 .content_type
1289 .ne(Oid::const_new(&[1, 2, 840, 113549, 1, 7, 2]))
1290 {
1291 error!("Master List ContentInfo contentType OID must be id-signedData");
1292 return Err(EmrtdError::InvalidFileStructure(
1293 "Master List ContentInfo contentType OID must be id-signedData",
1294 ));
1295 }
1296
1297 // RFC 5652 Section 12.1, Cryptographic Message Syntax
1298 // <https://datatracker.ietf.org/doc/html/rfc5652#section-12.1>
1299 //
1300 // SignedData ::= SEQUENCE {
1301 // version CMSVersion,
1302 // digestAlgorithms DigestAlgorithmIdentifiers,
1303 // encapContentInfo EncapsulatedContentInfo,
1304 // certificates [0] IMPLICIT CertificateSet OPTIONAL,
1305 // crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
1306 // signerInfos SignerInfos }
1307 let signed_data = der::decode::<rasn_cms::SignedData>(content_info.content.as_bytes())
1308 .map_err(EmrtdError::RasnDecodeError)?;
1309
1310 // RFC 5652 Sections 12.1 and 10.2.5
1311 // <https://datatracker.ietf.org/doc/html/rfc5652>
1312 // First item in the SignedData Sequence is version
1313 // ICAO Doc 9303-12 Section 9
1314 // <https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf>
1315 // For Master Lists it is always set to Value 3
1316 if signed_data.version.ne(&rasn::types::Integer::from(3)) {
1317 error!("Master List SignedData version must be V3");
1318 return Err(EmrtdError::InvalidFileStructure(
1319 "Master List SignedData version must be V3",
1320 ));
1321 }
1322
1323 // RFC 5652 Section 12.1, Cryptographic Message Syntax
1324 // <https://datatracker.ietf.org/doc/html/rfc5652#section-12.1>
1325 // Second item in the SignedData Sequence is digestAlgorithms
1326 //
1327 // It is mandatory by ICAO Doc 9303-12 Section 9
1328 // <https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf>
1329 if signed_data.digest_algorithms.is_empty() {
1330 error!("Master List SignedData digestAlgorithms can not be empty");
1331 return Err(EmrtdError::InvalidFileStructure(
1332 "Master List SignedData digestAlgorithms can not be empty",
1333 ));
1334 }
1335
1336 // RFC 5652 Section 12.1, Cryptographic Message Syntax
1337 // <https://datatracker.ietf.org/doc/html/rfc5652#section-12.1>
1338 // Third item in the SignedData Sequence is encapContentInfo
1339 // It contains CscaMasterList
1340 //
1341 // ICAO Doc 9303-12 Section 9
1342 // <https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf>
1343 // CscaMasterList {joint-iso-itu-t(2) international-organization(23) icao(136) mrtd(1) security(1) masterlist(2)}
1344 if signed_data
1345 .encap_content_info
1346 .content_type
1347 .ne(Oid::const_new(&[2, 23, 136, 1, 1, 2]))
1348 {
1349 error!("Master List SignedData encapContentInfo OID must be id-icao-cscaMasterList");
1350 return Err(EmrtdError::InvalidFileStructure(
1351 "Master List SignedData encapContentInfo OID must be id-icao-cscaMasterList",
1352 ));
1353 }
1354
1355 // It is mandatory by ICAO Doc 9303-12 Section 9
1356 // <https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf>
1357 let Some(csca_master_list_bytes) = signed_data.encap_content_info.content else {
1358 error!("Master List SignedData must contain eContent CscaMasterList");
1359 return Err(EmrtdError::InvalidFileStructure(
1360 "Master List SignedData must contain eContent CscaMasterList",
1361 ));
1362 };
1363
1364 // RFC 5652 Section 12.1, Cryptographic Message Syntax
1365 // <https://datatracker.ietf.org/doc/html/rfc5652#section-12.1>
1366 // Fourth item in the SignedData Sequence is certificates
1367 //
1368 // It is mandatory by ICAO Doc 9303-12 Section 9
1369 // <https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf>
1370 //
1371 // > The Master List Signer certificate MUST be included and the
1372 // > CSCA certificate, which can be used to verify the signature in the
1373 // > signerInfos field SHOULD be included.
1374 let master_list_signer = {
1375 let mut possible_master_list_signer = None;
1376 let mut possible_csca_cert = None;
1377 for cert in signed_data.certificates.iter().flatten() {
1378 if let CertificateChoices::Certificate(c) = cert {
1379 match &c.tbs_certificate.extensions {
1380 Some(exts) => {
1381 if exts.is_empty() {
1382 error!("Certificate Extensions must exist certificates in Master List");
1383 return Err(EmrtdError::InvalidFileStructure(
1384 "Certificate Extensions must exist certificates in Master List",
1385 ));
1386 }
1387 if possible_master_list_signer.is_none() {
1388 for ext in exts.iter() {
1389 // It is mandatory by ICAO Doc 9303-12 Section 7.1.1.3
1390 // <https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf>
1391 //
1392 // > The Object Identifier (OID) that must be included in the extendedKeyUsage
1393 // extension for Master List Signer certificates is 2.23.136.1.1.3.
1394 if ext.extn_id.eq(Oid::const_new(&[2, 5, 29, 37]))
1395 && ext.extn_value.len() == 10
1396 && eq(
1397 &ext.extn_value,
1398 b"\x30\x08\x06\x06\x67\x81\x08\x01\x01\x03",
1399 )
1400 {
1401 let master_list_signer_bytes =
1402 der::encode(&c).map_err(EmrtdError::RasnEncodeError)?;
1403 let master_list_signer =
1404 X509::from_der(&master_list_signer_bytes)
1405 .map_err(EmrtdError::BoringErrorStack)?;
1406 possible_master_list_signer = Some(master_list_signer);
1407 break;
1408 // It is mandatory by ICAO Doc 9303-12 Table 6
1409 // <https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf>
1410 //
1411 // > Basic constraints cA is mandatory for CSCA certificates
1412 // > PathLenConstraint must always be '0'
1413 } else if ext.extn_id.eq(Oid::const_new(&[2, 5, 29, 19]))
1414 && ext.extn_value.len() == 8
1415 && eq(&ext.extn_value, b"\x30\x06\x01\x01\xFF\x02\x01\x00")
1416 {
1417 let csca_cert_bytes =
1418 der::encode(&c).map_err(EmrtdError::RasnEncodeError)?;
1419 let csca_cert = X509::from_der(&csca_cert_bytes)
1420 .map_err(EmrtdError::BoringErrorStack)?;
1421 possible_csca_cert = Some(csca_cert);
1422 break;
1423 }
1424 }
1425 }
1426 }
1427 None => {
1428 error!("Certificate Extensions must exist certificates in Master List");
1429 return Err(EmrtdError::InvalidFileStructure(
1430 "Certificate Extensions must exist certificates in Master List",
1431 ));
1432 }
1433 }
1434 }
1435 }
1436 // Make sure we got a possible certificate
1437 match possible_master_list_signer {
1438 Some(c) => {
1439 // And verify that the Master List Signer was issued by CSCA certificate if it exists
1440 match possible_csca_cert {
1441 Some(csca_cert) => {
1442 let chain = Stack::new().map_err(EmrtdError::BoringErrorStack)?;
1443 let mut store_bldr =
1444 X509StoreBuilder::new().map_err(EmrtdError::BoringErrorStack)?;
1445 store_bldr
1446 .add_cert(csca_cert)
1447 .map_err(EmrtdError::BoringErrorStack)?;
1448 let store = store_bldr.build();
1449
1450 let mut context =
1451 X509StoreContext::new().map_err(EmrtdError::BoringErrorStack)?;
1452 let master_list_verification = context
1453 .init(&store, &c, &chain, |c| {
1454 let verification = c.verify_cert()?;
1455 if verification {
1456 Ok((verification, ""))
1457 } else {
1458 Ok((verification, c.error().error_string()))
1459 }
1460 })
1461 .map_err(EmrtdError::BoringErrorStack)?;
1462 if !master_list_verification.0 {
1463 warn!("Error while verifying Master List Signer Certificate signature: {}", master_list_verification.1);
1464 }
1465 info!(
1466 "Master List Signer Certificate signature verification result: {}",
1467 master_list_verification.0
1468 );
1469 }
1470 None => {
1471 warn!("Master List Signer Certificate signature is not verified, no CSCA certificate found in signed_data.certificates");
1472 }
1473 }
1474 c
1475 }
1476 None => unimplemented!("Master List must include a Master List Signer"),
1477 }
1478 };
1479
1480 // RFC 3369 Section 12.1, Cryptographic Message Syntax
1481 // <https://datatracker.ietf.org/doc/html/rfc3369#section-12.1>
1482 // Fifth item in the SignedData Sequence is crls
1483 //
1484 // It should **not** be present ICAO Doc 9303-10 Section 4.6.2.2
1485 // <https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf>
1486 if signed_data.crls.is_some() {
1487 error!("Master List must not contain a CRL");
1488 return Err(EmrtdError::InvalidFileStructure(
1489 "Master List must not contain a CRL",
1490 ));
1491 }
1492
1493 // RFC 5652 Section 12.1, Cryptographic Message Syntax
1494 // <https://datatracker.ietf.org/doc/html/rfc5652#section-12.1>
1495 // Last item in the SignedData Sequence is signerInfos
1496 //
1497 // It is recommended to provide only 1 SignerInfo by
1498 // ICAO Doc 9303-12 Section 9
1499 // <https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf>
1500 if signed_data.signer_infos.is_empty() {
1501 error!("Master List must include at least one SignerInfo");
1502 return Err(EmrtdError::InvalidFileStructure(
1503 "Master List must include at least one SignerInfo",
1504 ));
1505 }
1506 // Only one signer_info is supported
1507 if signed_data.signer_infos.len() > 1 {
1508 unimplemented!("Master Lists that include more than one SignerInfo are not supported")
1509 }
1510
1511 let signer_info = signed_data
1512 .signer_infos
1513 .first()
1514 .expect("len of SignerInfos is 1");
1515
1516 // RFC 5652 Section 5.3
1517 // <https://datatracker.ietf.org/doc/html/rfc5652#section-5.3>
1518 // > version is the syntax version number. If the SignerIdentifier is
1519 // > the CHOICE issuerAndSerialNumber, then the version MUST be 1. If
1520 // > the SignerIdentifier is subjectKeyIdentifier, then the version
1521 // > MUST be 3.
1522 //
1523 // It is recommended to provide subjectKeyIdentifier (v3) instead of issuerandSerialNumber (v1)
1524 // by ICAO Doc 9303-12 Section 9
1525 // <https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf>
1526 match signer_info.sid {
1527 rasn_cms::SignerIdentifier::IssuerAndSerialNumber(_) => {
1528 // Recommended
1529 if signer_info.version.ne(&rasn::types::Integer::from(1)) {
1530 error!("Master List SignedData signerInfo IssuerAndSerialNumber is provided but version is not 1");
1531 return Err(EmrtdError::InvalidFileStructure("Master List SignedData signerInfo IssuerAndSerialNumber is provided but version is not 1"));
1532 }
1533 }
1534 rasn_cms::SignerIdentifier::SubjectKeyIdentifier(_) => {
1535 if signer_info.version.ne(&rasn::types::Integer::from(3)) {
1536 error!("Master List SignedData signerInfo SubjectKeyIdentifier is provided but version is not 3");
1537 return Err(EmrtdError::InvalidFileStructure("Master List SignedData signerInfo SubjectKeyIdentifier is provided but version is not 3"));
1538 }
1539 }
1540 };
1541
1542 // RFC 5652 Section 5.3
1543 // <https://datatracker.ietf.org/doc/html/rfc5652#section-5.3>
1544 // > The message digest algorithm SHOULD be among those
1545 // > listed in the digestAlgorithms field of the associated SignerData.
1546 // > Implementations MAY fail to validate signatures that use a digest
1547 // > algorithm that is not included in the SignedData digestAlgorithms
1548 // > set.
1549 if !signed_data
1550 .digest_algorithms
1551 .contains(&signer_info.digest_algorithm)
1552 {
1553 error!("Master List SignedData signerInfo DigestAlgorithm must be included in SignedData digestAlgorithms set");
1554 return Err(EmrtdError::InvalidFileStructure(
1555 "Master List SignedData signerInfo DigestAlgorithm must be included in SignedData digestAlgorithms set",
1556 ));
1557 }
1558 // Ignore digest_algorithm parameters
1559 let digest_algorithm = oid2digestalg(&signer_info.digest_algorithm.algorithm)?;
1560
1561 // RFC 5652 Section 5.3
1562 // <https://datatracker.ietf.org/doc/html/rfc5652#section-5.3>
1563 //
1564 // > signedAttrs is a collection of attributes that are signed. The
1565 // > field is optional, but it MUST be present if the content type of
1566 // > the EncapsulatedContentInfo value being signed is not id-data.
1567 //
1568 // EncapsulatedContentInfo
1569 // by ICAO Doc 9303-12 Section 9
1570 // <https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf>
1571 // is `(OID joint-iso-itu-t(2) international-organization(23) icao(136) mrtd(1) security(1) masterlist(2))`
1572 // So this field is mandatory
1573 let signed_attrs = match &signer_info.signed_attrs {
1574 None => {
1575 error!("Master List SignedData signerInfo signed_attrs can't be empty");
1576 return Err(EmrtdError::InvalidFileStructure(
1577 "Master List SignedData signerInfo signed_attrs can't be empty",
1578 ));
1579 }
1580 Some(signed_attrs) => signed_attrs,
1581 };
1582
1583 // RFC 5652 Section 5.3
1584 // <https://datatracker.ietf.org/doc/html/rfc5652#section-5.3>
1585 //
1586 // > If the field is present, it MUST
1587 // > contain, at a minimum, the following two attributes:
1588 // >
1589 // > * A content-type attribute [...]
1590 // > * A message-digest attribute [...]
1591 //
1592 // RFC 5652 Section 11.1
1593 // <https://datatracker.ietf.org/doc/html/rfc5652#section-11.1>
1594 //
1595 // > The following object identifier identifies the content-type
1596 // > attribute:
1597 // >
1598 // > id-contentType OBJECT IDENTIFIER ::= { iso(1) member-body(2)
1599 // > us(840) rsadsi(113549) pkcs(1) pkcs9(9) 3 }
1600 // >
1601 // > Content-type attribute values have ASN.1 type ContentType:
1602 // >
1603 // > ContentType ::= OBJECT IDENTIFIER
1604 //
1605 // RFC 5652 Section 11.2
1606 // <https://datatracker.ietf.org/doc/html/rfc5652#section-11.2>
1607 //
1608 // > The following object identifier identifies the message-digest
1609 // > attribute:
1610 // >
1611 // > id-messageDigest OBJECT IDENTIFIER ::= { iso(1) member-body(2)
1612 // > us(840) rsadsi(113549) pkcs(1) pkcs9(9) 4 }
1613 // >
1614 // > Message-digest attribute values have ASN.1 type MessageDigest:
1615 // >
1616 // > MessageDigest ::= OCTET STRING
1617 //
1618 // ICAO Doc 9303-12 Section 9
1619 // <https://www.icao.int/publications/Documents/9303_p12_cons_en.pdf>
1620 // also makes signing time mandatory
1621 // > signedAttrs MUST include signing time (see [PKCS #9]).
1622 // But we skip verifying that.
1623 let mut content_type = None;
1624 let mut message_digest = None;
1625
1626 for signed_attr in signed_attrs {
1627 if signed_attr
1628 .r#type
1629 .eq(Oid::const_new(&[1, 2, 840, 113549, 1, 9, 3]))
1630 {
1631 // ContentType
1632 if signed_attr.values.len() != 1 {
1633 error!("Master List SignedData signerInfo signed_attrs contentType attribute values must have a single item");
1634 return Err(EmrtdError::InvalidFileStructure("Master List SignedData signerInfo signed_attrs contentType attribute values must have a single item"));
1635 }
1636 let temp = signed_attr
1637 .values
1638 .first()
1639 .expect("There is only one item")
1640 .as_bytes();
1641 content_type = Some(
1642 der::decode::<rasn::types::ObjectIdentifier>(temp)
1643 .map_err(EmrtdError::RasnDecodeError)?,
1644 );
1645 } else if signed_attr
1646 .r#type
1647 .eq(Oid::const_new(&[1, 2, 840, 113549, 1, 9, 4]))
1648 {
1649 // MessageDigest
1650 if signed_attr.values.len() != 1 {
1651 error!("Master List SignedData signerInfo signed_attrs messageDigest attribute values must have a single item");
1652 return Err(EmrtdError::InvalidFileStructure("Master List SignedData signerInfo signed_attrs messageDigest attribute values must have a single item"));
1653 }
1654 let temp = signed_attr
1655 .values
1656 .first()
1657 .expect("There is only one item")
1658 .as_bytes();
1659 message_digest = Some(
1660 der::decode::<rasn::types::OctetString>(temp)
1661 .map_err(EmrtdError::RasnDecodeError)?,
1662 );
1663 }
1664 }
1665
1666 let (Some(content_type), Some(message_digest)) = (content_type, message_digest) else {
1667 error!("Master List SignedData signerInfo signed_attrs contentType or messageDigest values do not exist");
1668 return Err(EmrtdError::InvalidFileStructure("Master List SignedData signerInfo signed_attrs contentType or messageDigest values do not exist"));
1669 };
1670
1671 // RFC 5652 Section 5.3
1672 // <https://datatracker.ietf.org/doc/html/rfc5652#section-5.3>
1673 //
1674 // > A content-type attribute having as its value the content type
1675 // > of the EncapsulatedContentInfo value being signed.
1676 //
1677 // contentType inside signedAttrs must be id-icao-cscaMasterList
1678 if content_type.ne(Oid::const_new(&[2, 23, 136, 1, 1, 2])) {
1679 error!("Master List SignedData signerInfo signed_attrs contentType must be id-icao-cscaMasterList");
1680 return Err(EmrtdError::InvalidFileStructure("Master List SignedData signerInfo signed_attrs contentType must be id-icao-cscaMasterList",));
1681 }
1682
1683 // RFC 5652 Section 5.4
1684 // <https://datatracker.ietf.org/doc/html/rfc5652#section-5.4>
1685 //
1686 // Message Digest Calculation Process as specified in RFC 5652
1687 let csca_master_list_hash =
1688 hash(digest_algorithm, &csca_master_list_bytes).map_err(EmrtdError::BoringErrorStack)?;
1689
1690 if csca_master_list_hash.ne(&message_digest) {
1691 error!("Digest of cscaMasterList does not match with the digest in SignedAttributes");
1692 return Err(EmrtdError::InvalidFileStructure(
1693 "Digest of cscaMasterList does not match with the digest in SignedAttributes",
1694 ));
1695 }
1696 info!("Digest of cscaMasterList matches with the digest in SignedAttributes");
1697
1698 // Ignore unsignedAttrs
1699 _ = signer_info.unsigned_attrs;
1700
1701 // RFC 5652 Section 5.4
1702 // <https://datatracker.ietf.org/doc/html/rfc5652#section-5.4>
1703 //
1704 // > [...] A separate encoding of the signedAttrs field is performed for message digest calculation.
1705 // > The IMPLICIT [0] tag in the signedAttrs is not used for the DER
1706 // > encoding, rather an EXPLICIT SET OF tag is used. That is, the DER
1707 // > encoding of the EXPLICIT SET OF tag, rather than of the IMPLICIT [0]
1708 // > tag, MUST be included in the message digest calculation along with
1709 // > the length and content octets of the SignedAttributes value.
1710 let mut signed_attrs_bytes = der::encode(&signed_attrs).map_err(EmrtdError::RasnEncodeError)?;
1711 signed_attrs_bytes[0] = b'\x31';
1712
1713 // Signature Verification
1714 // Follows RFC 5652 Section 5.6 Signature Verification Process
1715 // <https://datatracker.ietf.org/doc/html/rfc5652#section-5.6>
1716 let _signature_algorithm = &signer_info.signature_algorithm;
1717 let signature = &signer_info.signature;
1718 info!("{:?}", master_list_signer);
1719 let pub_key = master_list_signer
1720 .public_key()
1721 .map_err(EmrtdError::BoringErrorStack)?;
1722 let mut verifier =
1723 Verifier::new(digest_algorithm, &pub_key).map_err(EmrtdError::BoringErrorStack)?;
1724 verifier
1725 .update(&signed_attrs_bytes)
1726 .map_err(EmrtdError::BoringErrorStack)?;
1727 let sig_verified = verifier
1728 .verify(signature)
1729 .map_err(EmrtdError::BoringErrorStack)?;
1730 info!("Signature verification: {sig_verified}");
1731
1732 if !sig_verified {
1733 error!("Signature verification failure during Master List parsing");
1734 return Err(EmrtdError::VerifySignatureError(
1735 "Signature verification failure during Master List parsing",
1736 ));
1737 }
1738
1739 // Parse the eContent
1740 let csca_master_list = der::decode::<csca_master_list::CscaMasterList>(&csca_master_list_bytes)
1741 .map_err(EmrtdError::RasnDecodeError)?;
1742
1743 if csca_master_list.version.ne(&rasn::types::Integer::from(0)) {
1744 error!("MasterList CscaMasterListVersion must be V0");
1745 return Err(EmrtdError::InvalidFileStructure(
1746 "MasterList CscaMasterListVersion must be V0",
1747 ));
1748 }
1749
1750 // Create a store that can be used to verify DSC certificate during passive authentication
1751 let mut store_bldr = X509StoreBuilder::new().map_err(EmrtdError::BoringErrorStack)?;
1752
1753 for csca_cert in csca_master_list.cert_list {
1754 match der::encode(&csca_cert).map_err(EmrtdError::RasnEncodeError) {
1755 Ok(c) => {
1756 let x509cert = X509::from_der(&c).map_err(EmrtdError::BoringErrorStack)?;
1757 store_bldr
1758 .add_cert(x509cert)
1759 .map_err(EmrtdError::BoringErrorStack)?;
1760 }
1761 Err(e) => return Err(e),
1762 }
1763 }
1764
1765 let store = store_bldr.build();
1766
1767 Ok(store)
1768}
src/lib.rs#passive_authentication
1770/// Perform passive authentication on the EF.SOD (Security Object Data) of an eMRTD (electronic Machine Readable Travel Document).
1771///
1772/// This function follows the specifications outlined in ICAO Doc 9303-10 Section 4.6.2 and RFC 3369.
1773/// It verifies the integrity and authenticity of the EF.SOD by validating its structure, signature, and other relevant attributes.
1774///
1775/// # Arguments
1776///
1777/// * `ef_sod` - The EF.SOD (Security Object Data) read from an eMRTD.
1778///
1779/// # Returns
1780///
1781/// A `Result` containing a tuple with the following elements if the passive authentication was successful:
1782/// * `MessageDigest` - The message digest algorithm used for hashing the data groups (EF.DG1..16).
1783/// * `Vec<DataGroupHash>` - A vector containing hashes of the data groups as specified in the `LDSSecurityObject`.
1784/// * `X509` - Document Signer Certificate (DSC) used for signing the EF.SOD.
1785///
1786/// If passive authentication fails due to any reason such as invalid structure, mismatched hashes, or signature verification failure,
1787/// an `EmrtdError` is returned indicating the specific failure reason.
1788///
1789/// # Errors
1790///
1791/// Returns an `EmrtdError` if passive authentication fails.
1792///
1793/// # Panics
1794///
1795/// Might panic under different circumstances.
1796///
1797/// # Examples
1798///
1799/// ```
1800/// # use emrtd::EmrtdError;
1801/// #
1802/// # fn main() -> Result<(), EmrtdError> {
1803/// use emrtd::passive_authentication;
1804/// use openssl::x509::store::X509StoreBuilder;
1805/// use tracing::{info, error};
1806///
1807/// let store = X509StoreBuilder::new().map_err(EmrtdError::BoringErrorStack)?.build();
1808///
1809/// let ef_sod_data = &[/* EF.SOD Data */];
1810/// match passive_authentication(ef_sod_data, &store) {
1811/// Ok((digest_algorithm, dg_hashes, dsc)) => {
1812/// info!("Passive authentication successful!");
1813/// info!("Message Digest Algorithm (openssl NID): {:?}", digest_algorithm.type_());
1814/// info!("Data Group Hashes: {:?}", dg_hashes);
1815/// info!("Document Signer Certificate: {:?}", dsc);
1816/// }
1817/// Err(err) => error!("Passive authentication failed: {:?}", err),
1818/// }
1819/// #
1820/// # Ok(())
1821/// # }
1822/// ```
1823///
1824/// # TODOs
1825///
1826/// * Instead of returning error immediately after an error, collect all errors and return at the end
1827/// of the function, i.e. in case of signature verification failure, maybe the user can still get the DG hashes
1828pub fn passive_authentication(
1829 ef_sod: &[u8],
1830 cert_store: &X509Store,
1831) -> Result<(MessageDigest, Vec<lds_security_object::DataGroupHash>, X509), EmrtdError> {
1832 // ICAO Doc 9303-10 Section 4.6.2
1833 // <https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf>
1834 // Strip Document Security Object Tag 0x77
1835 validate_asn1_tag(ef_sod, b"\x77")?;
1836 let (ef_sod_rem, empty) = get_asn1_child(ef_sod, 1)?;
1837 if !empty.is_empty() {
1838 error!("EF.SOD file tag is wrong, must be '0x77'");
1839 return Err(EmrtdError::InvalidFileStructure(
1840 "EF.SOD file tag is wrong, must be '0x77'",
1841 ));
1842 }
1843
1844 // RFC 3369 Section 12.1, Cryptographic Message Syntax
1845 // <https://datatracker.ietf.org/doc/html/rfc3369#section-12.1>
1846 //
1847 // ContentInfo ::= SEQUENCE {
1848 // contentType ContentType,
1849 // content [0] EXPLICIT ANY DEFINED BY contentType }
1850 //
1851 // ContentType ::= OBJECT IDENTIFIER
1852 let content_info =
1853 der::decode::<rasn_cms::ContentInfo>(ef_sod_rem).map_err(EmrtdError::RasnDecodeError)?;
1854
1855 // RFC 3369 Section 5.1, SignedData Type
1856 // <https://datatracker.ietf.org/doc/html/rfc3369#section-5.1>
1857 // Verify the id-signedData OID
1858 if content_info
1859 .content_type
1860 .ne(Oid::const_new(&[1, 2, 840, 113549, 1, 7, 2]))
1861 {
1862 error!("EF.SOD ContentInfo contentType OID must be id-signedData");
1863 return Err(EmrtdError::InvalidFileStructure(
1864 "EF.SOD ContentInfo contentType OID must be id-signedData",
1865 ));
1866 }
1867
1868 // RFC 3369 Section 12.1, Cryptographic Message Syntax
1869 // <https://datatracker.ietf.org/doc/html/rfc3369#section-12.1>
1870 //
1871 // SignedData ::= SEQUENCE {
1872 // version CMSVersion,
1873 // digestAlgorithms DigestAlgorithmIdentifiers,
1874 // encapContentInfo EncapsulatedContentInfo,
1875 // certificates [0] IMPLICIT CertificateSet OPTIONAL,
1876 // crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
1877 // signerInfos SignerInfos }
1878 let signed_data = der::decode::<rasn_cms::SignedData>(content_info.content.as_bytes())
1879 .map_err(EmrtdError::RasnDecodeError)?;
1880
1881 // RFC 3369 Sections 12.1 and 10.2.5
1882 // <https://datatracker.ietf.org/doc/html/rfc3369>
1883 // First item in the SignedData Sequence is version
1884 // ICAO Doc 9303-10 Section 4.6.2.2
1885 // <https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf>
1886 // For eMRTDs it is always set to Value 3
1887 if signed_data.version.ne(&rasn::types::Integer::from(3)) {
1888 error!("EF.SOD SignedData version must be V3");
1889 return Err(EmrtdError::InvalidFileStructure(
1890 "EF.SOD SignedData version must be V3",
1891 ));
1892 }
1893
1894 // RFC 3369 Section 12.1, Cryptographic Message Syntax
1895 // <https://datatracker.ietf.org/doc/html/rfc3369#section-12.1>
1896 // Second item in the SignedData Sequence is digestAlgorithms
1897 //
1898 // It is mandatory by ICAO Doc 9303-10 Section 4.6.2.2
1899 // <https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf>
1900 if signed_data.digest_algorithms.is_empty() {
1901 error!("EF.SOD SignedData digestAlgorithms can not be empty");
1902 return Err(EmrtdError::InvalidFileStructure(
1903 "EF.SOD SignedData digestAlgorithms can not be empty",
1904 ));
1905 }
1906
1907 // RFC 3369 Section 12.1, Cryptographic Message Syntax
1908 // <https://datatracker.ietf.org/doc/html/rfc3369#section-12.1>
1909 // Third item in the SignedData Sequence is encapContentInfo
1910 // It contains LDSSecurityObject
1911 //
1912 // ICAO Doc 9303-10 Section 4.6.2.3 and Appendix D.2
1913 // <https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf>
1914 // LDSSecurityObjectV0 {joint-iso-itu-t (2) international(23) icao(136) mrtd(1) security(1) ldsSecurityObject(1)}
1915 // LDSSecurityObjectV1 {joint-iso-itu-t (2) international(23) icao(136) mrtd(1) security(1) ldsSecurityObject(1)}
1916 if signed_data
1917 .encap_content_info
1918 .content_type
1919 .ne(Oid::const_new(&[2, 23, 136, 1, 1, 1]))
1920 {
1921 error!("EF.SOD SignedData encapContentInfo OID must be id-icao-mrtd-security-ldsSecurityObject");
1922 return Err(EmrtdError::InvalidFileStructure("EF.SOD SignedData encapContentInfo OID must be id-icao-mrtd-security-ldsSecurityObject"));
1923 }
1924
1925 // It is mandatory by ICAO Doc 9303-10 Section 4.6.2.2
1926 // <https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf>
1927 let Some(lds_security_object_bytes) = signed_data.encap_content_info.content else {
1928 error!("EF.SOD SignedData must contain eContent LDSSecurityObject");
1929 return Err(EmrtdError::InvalidFileStructure(
1930 "EF.SOD SignedData must contain eContent LDSSecurityObject",
1931 ));
1932 };
1933
1934 // RFC 3369 Section 12.1, Cryptographic Message Syntax
1935 // <https://datatracker.ietf.org/doc/html/rfc3369#section-12.1>
1936 // Fourth item in the SignedData Sequence is certificates
1937 //
1938 // It is mandatory by ICAO Doc 9303-10 Section 4.6.2.2 for LDS v1
1939 // <https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf>
1940 //
1941 // > States are REQUIRED to include the Document Signer Certificate (CDS) which can
1942 // > be used to verify the signature in the signerInfos field.
1943 //
1944 // But it is optional for LDS v0 ICAO Doc 9303-10 Appendix D.1
1945 let dsc = {
1946 let mut possible_dsc = None;
1947 for cert in signed_data.certificates.iter().flatten() {
1948 if let CertificateChoices::Certificate(c) = cert {
1949 let dsc_bytes = der::encode(&c).map_err(EmrtdError::RasnEncodeError)?;
1950 let dsc = X509::from_der(&dsc_bytes).map_err(EmrtdError::BoringErrorStack)?;
1951 possible_dsc = Some(dsc);
1952 break;
1953 }
1954 }
1955 // Make sure we got a possible certificate
1956 match possible_dsc {
1957 Some(c) => {
1958 let chain = Stack::new().map_err(EmrtdError::BoringErrorStack)?;
1959 let mut context = X509StoreContext::new().map_err(EmrtdError::BoringErrorStack)?;
1960 let dsc_verification = context.init(cert_store, &c, &chain, |c| {
1961 let verification = c.verify_cert()?;
1962 if verification {
1963 Ok((verification, ""))
1964 } else {
1965 Ok((verification, c.error().error_string()))
1966 }
1967 }).map_err(EmrtdError::BoringErrorStack)?;
1968 if !dsc_verification.0 {
1969 error!("Error while verifying Document Signer Certificate signature: {}", dsc_verification.1);
1970 return Err(EmrtdError::InvalidFileStructure("DSC certificate verification using CSCA store failed"));
1971 }
1972 info!("Document Signer Certificate signature verification result: {}", dsc_verification.0);
1973 c
1974 },
1975 None => unimplemented!("Documents that do not include a Document Signer Certificate are not yet supported, or the included certificate is not supported")
1976 }
1977 };
1978
1979 // RFC 3369 Section 12.1, Cryptographic Message Syntax
1980 // <https://datatracker.ietf.org/doc/html/rfc3369#section-12.1>
1981 // Fifth item in the SignedData Sequence is crls
1982 //
1983 // It is recommended **not** to use by ICAO Doc 9303-10 Section 4.6.2.2
1984 // <https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf>
1985 let crl_bytes = {
1986 let mut possible_crl = None;
1987 for crl in signed_data.crls.iter().flatten() {
1988 if let RevocationInfoChoice::Crl(c) = crl {
1989 possible_crl = Some(c);
1990 break;
1991 }
1992 }
1993 match possible_crl {
1994 Some(c) => {
1995 // Try to encode it
1996 match der::encode(&c).map_err(EmrtdError::RasnEncodeError) {
1997 Ok(c) => Some(c),
1998 Err(e) => return Err(e),
1999 }
2000 }
2001 None => None,
2002 }
2003 };
2004
2005 if let Some(_crl) = crl_bytes {
2006 // We just ignore it
2007 }
2008
2009 // RFC 3369 Section 12.1, Cryptographic Message Syntax
2010 // <https://datatracker.ietf.org/doc/html/rfc3369#section-12.1>
2011 // Last item in the SignedData Sequence is signerInfos
2012 //
2013 // It is recommended to provide only 1 SignerInfo by
2014 // ICAO Doc 9303-10 Section 4.6.2.2
2015 // <https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf>
2016 if signed_data.signer_infos.is_empty() {
2017 error!("EF.SOD SignedData signerInfos can't be empty");
2018 return Err(EmrtdError::InvalidFileStructure(
2019 "EF.SOD SignedData signerInfos can't be empty",
2020 ));
2021 }
2022
2023 // Only one signer_info is supported
2024 if signed_data.signer_infos.len() > 1 {
2025 unimplemented!("EF.SOD that include more than one SignerInfo are not supported")
2026 }
2027
2028 let signer_info = signed_data
2029 .signer_infos
2030 .first()
2031 .expect("len of SignerInfos is 1");
2032
2033 // RFC 3369 Section 5.3
2034 // <https://datatracker.ietf.org/doc/html/rfc3369#section-5.3>
2035 // > version is the syntax version number. If the SignerIdentifier is
2036 // > the CHOICE issuerAndSerialNumber, then the version MUST be 1. If
2037 // > the SignerIdentifier is subjectKeyIdentifier, then the version
2038 // > MUST be 3.
2039 //
2040 // It is recommended to provide issuerandSerialNumber (v1) instead of subjectKeyIdentifier (v3)
2041 // by ICAO Doc 9303-10 Section 4.6.2.2
2042 // <https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf>
2043 match signer_info.sid {
2044 rasn_cms::SignerIdentifier::IssuerAndSerialNumber(_) => {
2045 if signer_info.version.ne(&rasn::types::Integer::from(1)) {
2046 error!("EF.SOD SignedData signerInfo IssuerAndSerialNumber is provided but version is not 1");
2047 return Err(EmrtdError::InvalidFileStructure("EF.SOD SignedData signerInfo IssuerAndSerialNumber is provided but version is not 1"));
2048 }
2049 }
2050 rasn_cms::SignerIdentifier::SubjectKeyIdentifier(_) => {
2051 if signer_info.version.ne(&rasn::types::Integer::from(3)) {
2052 error!("EF.SOD SignedData signerInfo SubjectKeyIdentifier is provided but version is not 3");
2053 return Err(EmrtdError::InvalidFileStructure("EF.SOD SignedData signerInfo SubjectKeyIdentifier is provided but version is not 3"));
2054 }
2055 }
2056 };
2057
2058 // RFC 3369 Section 5.3
2059 // <https://datatracker.ietf.org/doc/html/rfc3369#section-5.3>
2060 // > The message digest algorithm SHOULD be among those
2061 // > listed in the digestAlgorithms field of the associated SignerData.
2062 // > Implementations MAY fail to validate signatures that use a digest
2063 // > algorithm that is not included in the SignedData digestAlgorithms
2064 // > set.
2065 if !signed_data
2066 .digest_algorithms
2067 .contains(&signer_info.digest_algorithm)
2068 {
2069 error!("EF.SOD SignedData signerInfo DigestAlgorithm must be included in SignedData digestAlgorithms set");
2070 return Err(EmrtdError::InvalidFileStructure(
2071 "EF.SOD SignedData signerInfo DigestAlgorithm must be included in SignedData digestAlgorithms set",
2072 ));
2073 }
2074 // Ignore digest_algorithm parameters
2075 let digest_algorithm = oid2digestalg(&signer_info.digest_algorithm.algorithm)?;
2076
2077 // RFC 3369 Section 5.3
2078 // <https://datatracker.ietf.org/doc/html/rfc3369#section-5.3>
2079 //
2080 // > signedAttrs is a collection of attributes that are signed. The
2081 // > field is optional, but it MUST be present if the content type of
2082 // > the EncapsulatedContentInfo value being signed is not id-data.
2083 //
2084 // EncapsulatedContentInfo
2085 // by ICAO Doc 9303-10 Section 4.6.2.2
2086 // <https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf>
2087 // is `(OID joint-iso-itu-t (2) international(23) icao(136) mrtd(1) security(1) ldsSecurityObject(1))`
2088 // So this field is mandatory
2089 let signed_attrs = match &signer_info.signed_attrs {
2090 None => {
2091 error!("EF.SOD SignedData signerInfo signed_attrs can't be empty");
2092 return Err(EmrtdError::InvalidFileStructure(
2093 "EF.SOD SignedData signerInfo signed_attrs can't be empty",
2094 ));
2095 }
2096 Some(signed_attrs) => signed_attrs,
2097 };
2098
2099 // RFC 3369 Section 5.3
2100 // <https://datatracker.ietf.org/doc/html/rfc3369#section-5.3>
2101 //
2102 // > If the field is present, it MUST
2103 // > contain, at a minimum, the following two attributes:
2104 // >
2105 // > * A content-type attribute [...]
2106 // > * A message-digest attribute [...]
2107 //
2108 // RFC 3369 Section 11.1
2109 // <https://datatracker.ietf.org/doc/html/rfc3369#section-11.1>
2110 //
2111 // > The following object identifier identifies the content-type
2112 // > attribute:
2113 // >
2114 // > id-contentType OBJECT IDENTIFIER ::= { iso(1) member-body(2)
2115 // > us(840) rsadsi(113549) pkcs(1) pkcs9(9) 3 }
2116 // >
2117 // > Content-type attribute values have ASN.1 type ContentType:
2118 // >
2119 // > ContentType ::= OBJECT IDENTIFIER
2120 //
2121 // RFC 3369 Section 11.2
2122 // <https://datatracker.ietf.org/doc/html/rfc3369#section-11.2>
2123 //
2124 // > The following object identifier identifies the message-digest
2125 // > attribute:
2126 // >
2127 // > id-messageDigest OBJECT IDENTIFIER ::= { iso(1) member-body(2)
2128 // > us(840) rsadsi(113549) pkcs(1) pkcs9(9) 4 }
2129 // >
2130 // > Message-digest attribute values have ASN.1 type MessageDigest:
2131 // >
2132 // > MessageDigest ::= OCTET STRING
2133 let mut content_type = None;
2134 let mut message_digest = None;
2135
2136 for signed_attr in signed_attrs {
2137 if signed_attr
2138 .r#type
2139 .eq(Oid::const_new(&[1, 2, 840, 113549, 1, 9, 3]))
2140 {
2141 // ContentType
2142 if signed_attr.values.len() != 1 {
2143 error!("EF.SOD SignedData signerInfo signed_attrs contentType attribute values must have a single item");
2144 return Err(EmrtdError::InvalidFileStructure("EF.SOD SignedData signerInfo signed_attrs contentType attribute values must have a single item"));
2145 }
2146 let temp = signed_attr
2147 .values
2148 .first()
2149 .expect("There is only one item")
2150 .as_bytes();
2151 content_type = Some(
2152 der::decode::<rasn::types::ObjectIdentifier>(temp)
2153 .map_err(EmrtdError::RasnDecodeError)?,
2154 );
2155 } else if signed_attr
2156 .r#type
2157 .eq(Oid::const_new(&[1, 2, 840, 113549, 1, 9, 4]))
2158 {
2159 // MessageDigest
2160 if signed_attr.values.len() != 1 {
2161 error!("EF.SOD SignedData signerInfo signed_attrs messageDigest attribute values must have a single item");
2162 return Err(EmrtdError::InvalidFileStructure("EF.SOD SignedData signerInfo signed_attrs messageDigest attribute values must have a single item"));
2163 }
2164 let temp = signed_attr
2165 .values
2166 .first()
2167 .expect("There is only one item")
2168 .as_bytes();
2169 message_digest = Some(
2170 der::decode::<rasn::types::OctetString>(temp)
2171 .map_err(EmrtdError::RasnDecodeError)?,
2172 );
2173 }
2174 }
2175
2176 let (Some(content_type), Some(message_digest)) = (content_type, message_digest) else {
2177 error!("EF.SOD SignedData signerInfo signed_attrs contentType or messageDigest values do not exist");
2178 return Err(EmrtdError::InvalidFileStructure("EF.SOD SignedData signerInfo signed_attrs contentType or messageDigest values do not exist"));
2179 };
2180
2181 // RFC 3369 Section 5.3
2182 // <https://datatracker.ietf.org/doc/html/rfc3369#section-5.3>
2183 //
2184 // > A content-type attribute having as its value the content type
2185 // > of the EncapsulatedContentInfo value being signed.
2186 //
2187 // contentType inside signedAttrs must be id-icao-mrtd-security-ldsSecurityObject
2188 if content_type.ne(Oid::const_new(&[2, 23, 136, 1, 1, 1])) {
2189 error!("EF.SOD SignedData signerInfo signed_attrs contentType must be id-icao-mrtd-security-ldsSecurityObject");
2190 return Err(EmrtdError::InvalidFileStructure("EF.SOD SignedData signerInfo signed_attrs contentType must be id-icao-mrtd-security-ldsSecurityObject",));
2191 }
2192
2193 // RFC 3369 Section 5.4
2194 // <https://datatracker.ietf.org/doc/html/rfc3369#section-5.4>
2195 //
2196 // Message Digest Calculation Process as specified in RFC 3369
2197 let lds_security_object_hash =
2198 hash(digest_algorithm, &lds_security_object_bytes).map_err(EmrtdError::BoringErrorStack)?;
2199
2200 if lds_security_object_hash.ne(&message_digest) {
2201 error!("Digest of LDSSecurityObject does not match with the digest in SignedAttributes");
2202 return Err(EmrtdError::InvalidFileStructure(
2203 "Digest of LDSSecurityObject does not match with the digest in SignedAttributes",
2204 ));
2205 }
2206 info!("Digest of LDSSecurityObject matches with the digest in SignedAttributes");
2207
2208 // Ignore unsignedAttrs
2209 _ = signer_info.unsigned_attrs;
2210
2211 // RFC 3369 Section 5.4
2212 // <https://datatracker.ietf.org/doc/html/rfc3369#section-5.4>
2213 //
2214 // > [...] A separate encoding of the signedAttrs field is performed for message digest calculation.
2215 // > The IMPLICIT [0] tag in the signedAttrs is not used for the DER
2216 // > encoding, rather an EXPLICIT SET OF tag is used. That is, the DER
2217 // > encoding of the EXPLICIT SET OF tag, rather than of the IMPLICIT [0]
2218 // > tag, MUST be included in the message digest calculation along with
2219 // > the length and content octets of the SignedAttributes value.
2220 let mut signed_attrs_bytes = der::encode(&signed_attrs).map_err(EmrtdError::RasnEncodeError)?;
2221 signed_attrs_bytes[0] = b'\x31';
2222
2223 // Signature Verification
2224 // Follows RFC 3369 Section 5.6 Signature Verification Process
2225 // <https://datatracker.ietf.org/doc/html/rfc3369#section-5.6>
2226 let _signature_algorithm = &signer_info.signature_algorithm;
2227 let signature = &signer_info.signature;
2228 let pub_key = dsc.public_key().map_err(EmrtdError::BoringErrorStack)?;
2229 let mut verifier =
2230 Verifier::new(digest_algorithm, &pub_key).map_err(EmrtdError::BoringErrorStack)?;
2231 verifier
2232 .update(&signed_attrs_bytes)
2233 .map_err(EmrtdError::BoringErrorStack)?;
2234 let sig_verified = verifier
2235 .verify(signature)
2236 .map_err(EmrtdError::BoringErrorStack)?;
2237 info!("Signature verification: {sig_verified}");
2238
2239 if !sig_verified {
2240 error!("Signature verification failure during EF.SOD parsing");
2241 return Err(EmrtdError::VerifySignatureError(
2242 "Signature verification failure during EF.SOD parsing",
2243 ));
2244 }
2245
2246 // Parse the eContent
2247 let lds_security_object =
2248 der::decode::<lds_security_object::LDSSecurityObject>(&lds_security_object_bytes)
2249 .map_err(EmrtdError::RasnDecodeError)?;
2250 // LDSSecurityObject has two versions, it is defined by ICAO Doc 9303-10
2251 if lds_security_object
2252 .version
2253 .eq(&rasn::types::Integer::from(0))
2254 {
2255 if lds_security_object.lds_version_info.is_some() {
2256 error!("EF.SOD LDSSecurityObjectVersion is V0, but ldsVersionInfo is present");
2257 return Err(EmrtdError::InvalidFileStructure(
2258 "EF.SOD LDSSecurityObjectVersion is V0, but ldsVersionInfo is present",
2259 ));
2260 }
2261 info!("LDSSecurityObjectVersion is V0");
2262 } else if lds_security_object
2263 .version
2264 .eq(&rasn::types::Integer::from(1))
2265 {
2266 if lds_security_object.lds_version_info.is_none() {
2267 error!("EF.SOD LDSSecurityObjectVersion is V1, but ldsVersionInfo is not present");
2268 return Err(EmrtdError::InvalidFileStructure(
2269 "EF.SOD LDSSecurityObjectVersion is V1, but ldsVersionInfo is not present",
2270 ));
2271 }
2272 info!("LDSSecurityObjectVersion is V1");
2273 }
2274 // Skip Algorithm parameters
2275 let file_digest_algorithm = oid2digestalg(&lds_security_object.hash_algorithm.algorithm)?;
2276 if lds_security_object.data_group_hash_values.len() < 2
2277 || lds_security_object.data_group_hash_values.len() > 16
2278 {
2279 error!("EF.SOD LDSSecurityObject DataGroupHash values are invalid");
2280 return Err(EmrtdError::InvalidFileStructure(
2281 "EF.SOD LDSSecurityObject DataGroupHash values are invalid",
2282 ));
2283 }
2284 for data_group_hash in &lds_security_object.data_group_hash_values {
2285 if data_group_hash
2286 .data_group_number
2287 .gt(&rasn::types::Integer::from(16))
2288 {
2289 error!("EF.SOD LDSSecurityObject invalid DataGroupHash number");
2290 return Err(EmrtdError::InvalidFileStructure(
2291 "EF.SOD LDSSecurityObject invalid DataGroupHash number",
2292 ));
2293 }
2294 }
2295 let dg_hashes = lds_security_object.data_group_hash_values;
2296
2297 Ok((file_digest_algorithm, dg_hashes, dsc))
2298}
src/lib.rs#get_jpeg_from_ef_dg2
2300/// Extracts the JPEG image from the EF.DG2.
2301///
2302/// This function follows the specifications outlined in ICAO Doc 9303-10 Section 4.7.2 for the structure of EF.DG2 files.
2303/// For details, refer to: [ICAO Doc 9303-10 Section 4.7.2](https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf)
2304///
2305/// # Arguments
2306///
2307/// * `ef_dg2` - EF.DG2 contents.
2308///
2309/// # Returns
2310///
2311/// Extracted JPEG image data if successful, else `EmrtdError`.
2312///
2313/// # Errors
2314///
2315/// `EmrtdError` if the EF.DG2 file structure is invalid or if errors occur during parsing.
2316///
2317/// # Examples
2318///
2319/// ```
2320/// # use emrtd::EmrtdError;
2321/// #
2322/// # fn main() -> Result<(), EmrtdError> {
2323/// use emrtd::get_jpeg_from_ef_dg2;
2324/// use tracing::{info, error};
2325///
2326/// let ef_dg2 = &[/* EF.DG2 Data */];
2327///
2328/// let jpeg = match get_jpeg_from_ef_dg2(ef_dg2) {
2329/// Ok(jpeg) => {
2330/// info!("JPEG successfully extracted.")
2331/// },
2332/// Err(e) => error!("Error during JPEG extraction from EF.DG2: {}", e)
2333/// };
2334/// #
2335/// # Ok(())
2336/// # }
2337/// ```
2338pub fn get_jpeg_from_ef_dg2(ef_dg2: &[u8]) -> Result<&[u8], EmrtdError> {
2339 // ICAO Doc 9303-10 Section 4.7.2
2340 // <https://www.icao.int/publications/Documents/9303_p10_cons_en.pdf>
2341 // Strip EF.DG2 Tag 0x75
2342 validate_asn1_tag(ef_dg2, b"\x75")?;
2343 let (ef_dg2_rem, empty) = get_asn1_child(ef_dg2, 1)?;
2344 if !empty.is_empty() {
2345 error!("EF.DG2 file tag is wrong, must be '0x75'");
2346 return Err(EmrtdError::InvalidFileStructure(
2347 "EF.DG2 file tag is wrong, must be '0x75'",
2348 ));
2349 }
2350 // Strip Biometric Information Template Group Template Tag 0x7F61
2351 validate_asn1_tag(ef_dg2_rem, b"\x7F\x61")?;
2352 let (ef_dg2_rem, empty) = get_asn1_child(ef_dg2_rem, 2)?;
2353 if !empty.is_empty() {
2354 error!("EF.DG2 Biometric Information Template Group Template Tag must be '0x7F61'");
2355 return Err(EmrtdError::InvalidFileStructure(
2356 "EF.DG2 Biometric Information Template Group Template Tag must be '0x7F61'",
2357 ));
2358 }
2359 // Find number of biometric templates
2360 validate_asn1_tag(ef_dg2_rem, b"\x02")?;
2361 let (number_of_face_images, ef_dg2_rem) = get_asn1_child(ef_dg2_rem, 1)?;
2362 if number_of_face_images.len() != 1 || number_of_face_images[0] < 1 {
2363 error!("EF.DG2 file invalid structure, must contain at least one face image");
2364 return Err(EmrtdError::InvalidFileStructure(
2365 "EF.DG2 file invalid structure, must contain at least one face image",
2366 ));
2367 }
2368 // We only get the first face image
2369 validate_asn1_tag(ef_dg2_rem, b"\x7F\x60")?;
2370 let (first_instance, _other_instances) = get_asn1_child(ef_dg2_rem, 2)?;
2371 // Skip the Biometric Header Template (BHT) parsing
2372 validate_asn1_tag(first_instance, b"\xA1")?;
2373 let (_bht, biometric_data) = get_asn1_child(first_instance, 1)?;
2374 if biometric_data.is_empty() {
2375 error!("EF.DG2 first biometric image must not be empty");
2376 return Err(EmrtdError::InvalidFileStructure(
2377 "EF.DG2 first biometric image must not be empty",
2378 ));
2379 }
2380 // Strip the image tag '5F2E' or '7F2E'
2381 match validate_asn1_tag(biometric_data, b"\x5F\x2E") {
2382 Ok(()) => {}
2383 Err(EmrtdError::ParseAsn1TagError(_, _)) => validate_asn1_tag(biometric_data, b"\x7F\x2E")?,
2384 Err(e) => return Err(e),
2385 }
2386 let (biometric_data, _) = get_asn1_child(biometric_data, 2)?;
2387 if biometric_data.len() < 46 {
2388 error!("EF.DG2 invalid biometric image structure");
2389 return Err(EmrtdError::InvalidFileStructure(
2390 "EF.DG2 invalid biometric image structure",
2391 ));
2392 }
2393 // Face Image Data Standard for e-Governance Applications in India
2394 // https://egovstandards.gov.in/sites/default/files/Face_Image_Data_Standard_Ver1.0.pdf
2395 // Section 6.3
2396 // Ideally we would get these values looking at ISO 19794-5:2005
2397 //
2398 // Strip the Facial Record Header (14 bytes)
2399 validate_asn1_tag(biometric_data, b"\x46\x41\x43\x00\x30\x31\x30\x00")?;
2400 let biometric_data = &biometric_data[14..];
2401 // Strip Facial Information (20 bytes)
2402 let biometric_data = &biometric_data[20..];
2403 // Strip Image Information (12 bytes)
2404 let biometric_data = &biometric_data[12..];
2405
2406 Ok(biometric_data)
2407}
src/lib.rs#validate_dg
2409/// Validates the integrity of a Data Group (DG) in an eMRTD.
2410///
2411/// # Arguments
2412///
2413/// * `dg` - Data Group contents.
2414/// * `dg_number` - The number of the Data Group to validate, ranging from 1 to 16.
2415/// * `message_digest` - The cryptographic hash function used to compute the hash of the Data Group in EF.SOD.
2416/// * `verified_hashes` - A slice of `DataGroupHash` objects representing the pre-verified hashes of Data Groups.
2417///
2418/// # Returns
2419///
2420/// * `Ok(())` if the validation succeeds, indicating that the Data Group is valid.
2421/// * `Err(EmrtdError)` if the validation fails, containing details about the failure.
2422///
2423/// # Errors
2424///
2425/// `EmrtdError` in case of failure during verification.
2426///
2427/// # Panics
2428///
2429/// Might panic under different circumstances.
2430///
2431/// # Examples
2432///
2433/// ```
2434/// # use emrtd::EmrtdError;
2435/// #
2436/// # fn main() -> Result<(), EmrtdError> {
2437/// use emrtd::{passive_authentication, validate_dg};
2438/// use openssl::x509::store::X509StoreBuilder;
2439/// use tracing::{info, error};
2440///
2441/// let store = X509StoreBuilder::new().map_err(EmrtdError::BoringErrorStack)?.build();
2442///
2443/// let ef_sod_data = &[/* EF.SOD Data */];
2444/// let ef_dg1 = &[/* EF.DG1 Data */];
2445/// match passive_authentication(ef_sod_data, &store) {
2446/// Ok((digest_algorithm, verified_dg_hashes, dsc)) => {
2447/// match validate_dg(ef_dg1, 1, digest_algorithm, &verified_dg_hashes) {
2448/// Ok(()) => {
2449/// info!("EF.DG1 successfully verified")
2450/// }
2451/// Err(err) => error!("Passive authentication failed: {:?}", err),
2452/// }
2453/// }
2454/// Err(err) => {
2455/// error!("Passive authentication failed: {:?}", err)
2456/// }
2457/// }
2458/// #
2459/// # Ok(())
2460/// # }
2461/// ```
2462pub fn validate_dg(
2463 dg: &[u8],
2464 dg_number: i32,
2465 message_digest: MessageDigest,
2466 verified_hashes: &[lds_security_object::DataGroupHash],
2467) -> Result<(), EmrtdError> {
2468 if !(1..=16).contains(&dg_number) {
2469 error!("Invalid Data Group number: {}", dg_number);
2470 return Err(EmrtdError::InvalidArgument("Invalid Data Group number"));
2471 }
2472
2473 let hash_bytes = hash(message_digest, dg).map_err(EmrtdError::BoringErrorStack)?;
2474 let mut verified_hash = None;
2475 for dg_hash in verified_hashes {
2476 if dg_hash
2477 .data_group_number
2478 .eq(&rasn::types::Integer::from(dg_number))
2479 {
2480 verified_hash = Some(&dg_hash.data_group_hash_value);
2481 }
2482 }
2483 match verified_hash {
2484 Some(verified_hash) => {
2485 if !eq(verified_hash, &hash_bytes) {
2486 error!("Potentially cloned document, hashes do not match");
2487 return Err(EmrtdError::VerifyHashError(
2488 "Potentially cloned document, hashes do not match".to_owned(),
2489 ));
2490 }
2491 }
2492 None => {
2493 error!("Potentially cloned document, EF.DG{dg_number} file hash is not found inside verified hashes");
2494 return Err(EmrtdError::VerifyHashError(format!(
2495 "EF.DG{dg_number} file hash is not found inside verified hashes"
2496 )));
2497 }
2498 }
2499
2500 Ok(())
2501}
With all that, we are done. The library is still missing crucial features, especially PACE. There has been many ID cards issued since January 2018 that do not support BAC, and only support PACE. Active Authentication and Chip Authentication are also not supported yet. I plan to include them sometime in the future. Many of the eMRTDs also have quirks, this implementation does not care for any of those[3]. More tests must be written, testing using ICAO test vectors is also something to consider[4].
But I have some remarks about Rust to add here:
In Rust, the state of the cryptographic libraries is not really clean-cut. I was recommended the
RustCrypto
organization but I feel that some of the APIs are really messy. Encrypting a data block is supposed to be really simple but the wrapper functions I have made, shows otherwise. Some of the important functions/algorithms are not really a part of RustCrypto, for example theecb
crate. There is another well known crate calledring
and it is missing DES for example. At one point, I used theboring
crate which is BoringSSL bindings for Rust but that one failed during the parsing of the Master List. The signatures just couldn’t be verified. Then switching all my crypto needs to OpenSSL also failed, as that one fails during DES. So that resulted in this mix matched approach.Parsing ASN.1 is difficult if it is done manually. Imagine this, you have a
json
structure like the following{ "apple": "red", "banana": "yellow", }
and you want to get the information from this structure and use it in your app. Using a parsing library, you can just read the structure and get an object that you can directly use. However manually parsing would mean that you first would look for an open curly brace
{
and then a closing curly brace}
and a newline and two spaces and a double quote"
and the wordapple
and so on… After painstakingly doing this for both Rust and Python implementation I finally realized that there are some good libraries that can do this. I decided to userasn
for this. It also has a compiler that can generate Rust code from ASN.1 definitions. I used their onlinerasn_compiler
to generate the following structs:src/lib.rs#lds_security_object
257/// Generated and edited using `rasn_compiler` 258/// <https://librasn.github.io> 259/// <https://docs.rs/rasn-compiler/latest/rasn_compiler/> 260/// 261#[allow(clippy::doc_markdown)] 262/// LDSSecurityObjectV1 { joint-iso-itu-t(2) international(23) icao(136) mrtd(1) security(1) ldsSecurityObject(1)} 263/// 264/// DEFINITIONS IMPLICIT TAGS ::= BEGIN 265/// -- Constants 266/// ub-DataGroups INTEGER ::= 16 267/// -- Object Identifiers 268/// id-icao OBJECT IDENTIFIER::={joint-iso-itu-t(2) international(23) icao(136) } 269/// id-icao-mrtd OBJECT IDENTIFIER ::= {id-icao 1} 270/// id-icao-mrtd-security OBJECT IDENTIFIER ::= {id-icao-mrtd 1} 271/// id-icao-mrtd-security-ldsSecurityObject OBJECT IDENTIFIER ::= {id-icao-mrtd-security 1} 272/// 273/// -- LDS Security Object 274/// LDSSecurityObjectVersion ::= INTEGER {v0(0), v1(1) 275/// -- If LDSSecurityObjectVersion is V1, ldsVersionInfo MUST be present 276/// } 277/// DigestAlgorithmIdentifier ::= AlgorithmIdentifier 278/// 279/// LDSSecurityObject ::= SEQUENCE { 280/// version LDSSecurityObjectVersion, 281/// hashAlgorithm DigestAlgorithmIdentifier, 282/// dataGroupHashValues SEQUENCE SIZE (2..ub-DataGroups) OF 283/// DataGroupHash, 284/// ldsVersionInfo LDSVersionInfo OPTIONAL 285/// -- If present, version MUST be V1 286/// } 287/// DataGroupHash ::= SEQUENCE { 288/// dataGroupNumber DataGroupNumber, 289/// dataGroupHashValue OCTET STRING } 290/// 291/// DataGroupHash ::= SEQUENCE { 292/// dataGroupNumber DataGroupNumber, 293/// dataGroupHashValue OCTET STRING } 294/// DataGroupNumber ::= INTEGER { 295/// dataGroup1 (1), 296/// dataGroup2 (2), 297/// dataGroup3 (3), 298/// dataGroup4 (4), 299/// dataGroup5 (5), 300/// dataGroup6 (6), 301/// dataGroup7 (7), 302/// dataGroup8 (8), 303/// dataGroup9 (9), 304/// dataGroup10 (10), 305/// dataGroup11 (11), 306/// dataGroup12 (12), 307/// dataGroup13 (13), 308/// dataGroup14 (14), 309/// dataGroup15 (15), 310/// dataGroup16 (16)} 311/// 312/// LDSVersionInfo ::= SEQUENCE { 313/// ldsVersion PrintableString 314/// unicodeVersion PrintableString } 315/// 316/// END 317/// 318pub mod lds_security_object { 319 extern crate alloc; 320 use rasn::prelude::*; 321 use rasn_cms::AlgorithmIdentifier; 322 323 pub type DataGroupNumber = Integer; 324 pub type DigestAlgorithmIdentifier = AlgorithmIdentifier; 325 pub type LDSSecurityObjectVersion = Integer; 326 327 #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq)] 328 pub struct DataGroupHash { 329 pub data_group_number: DataGroupNumber, 330 pub data_group_hash_value: OctetString, 331 } 332 #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq)] 333 pub struct LDSSecurityObject { 334 pub version: LDSSecurityObjectVersion, 335 pub hash_algorithm: DigestAlgorithmIdentifier, 336 #[rasn(size("2..=16"))] 337 pub data_group_hash_values: SequenceOf<DataGroupHash>, 338 pub lds_version_info: Option<LDSVersionInfo>, 339 } 340 #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq)] 341 pub struct LDSVersionInfo { 342 pub lds_version: PrintableString, 343 pub unicode_version: PrintableString, 344 } 345}
src/lib.rs#csca_master_list
347/// Generated and edited using `rasn_compiler` 348/// <https://librasn.github.io> 349/// <https://docs.rs/rasn-compiler/latest/rasn_compiler/> 350/// 351#[allow(clippy::doc_markdown)] 352/// CscaMasterList { joint-iso-itu-t(2) international-organization(23) icao(136) mrtd(1) security(1) masterlist(2)} 353/// 354/// DEFINITIONS IMPLICIT TAGS ::= BEGIN 355/// IMPORTS 356/// Certificate FROM PKIX1Explicit88 { iso(1) identified-organization(3) dod(6) internet(1) security(5) mechanisms(5) pkix(7) mod(0) pkix1-explicit(18) }; 357/// 358/// -- CSCA Master List 359/// 360/// CscaMasterListVersion ::= INTEGER {v0(0)} 361/// CscaMasterList ::= SEQUENCE { 362/// version CscaMasterListVersion, 363/// certList SET OF Certificate } 364/// 365/// -- Object Identifiers 366/// 367/// id-icao-cscaMasterList OBJECT IDENTIFIER ::= {id-icao-mrtd-security 2} 368/// id-icao-cscaMasterListSigningKey OBJECT IDENTIFIER ::= {id-icao-mrtd-security 3} 369/// END 370/// 371pub mod csca_master_list { 372 extern crate alloc; 373 use rasn::prelude::*; 374 use rasn_pkix::Certificate; 375 376 pub type CscaMasterListCertList = SetOf<Certificate>; 377 pub type CscaMasterListVersion = Integer; 378 379 #[derive(AsnType, Debug, Clone, Decode, Encode, PartialEq, Eq)] 380 pub struct CscaMasterList { 381 pub version: CscaMasterListVersion, 382 pub cert_list: CscaMasterListCertList, 383 } 384}
Which is much cleaner than what I had previously.
Rust ecosystem is really nice. The Discord channel is quite active and people are always willing to help. Take a look at Rust Community.
Rust standard library is lacking. For example Go standard library have many high quality features already baked in, one of which is ASN.1 decoding. Check out Go Standard Crypto library and scroll down all the way to the end. This kind of standard library for Rust would be a blessing.
Well written guidelines matter. The Rust Book has a page that explains how publishing a crate (cargo reference) to crates.io works. It provides many recommendations. Following a checklist is much easier than trying to remember everything to do. It provides sane defaults and makes it very easy to follow. Don’t know what to license your project? You can be compatible with most of the Rust ecosystem by choosing
MIT
orApache 2.0
. Easy.
Footnotes:
[1]: ^ Smart card application protocol data unit
[2]: ^ https://en.wikipedia.org/wiki/Answer_to_reset
[3]: ^ For a well written post about eMRTD data quirks, see https://wf.lavatech.top/aves-tech-notes/emrtd-data-quirks
[4]: ^ For a great resource collecting all the test specifications, see https://blog.protocolbench.org/test-specifications/