diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 9e26dfe..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index dc64d61..279b7b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -36,15 +36,15 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", @@ -89,6 +89,15 @@ dependencies = [ "nom", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "block-padding" version = "0.3.3" @@ -115,9 +124,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.94" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" [[package]] name = "cfg-if" @@ -127,9 +136,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "num-traits", ] @@ -144,12 +153,24 @@ dependencies = [ "inout", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const_panic" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b" +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "cpufeatures" version = "0.2.12" @@ -180,9 +201,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -203,6 +224,17 @@ dependencies = [ "cipher", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -220,25 +252,29 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "emrtd" -version = "0.0.1" +version = "0.0.2" dependencies = [ "aes", "cbc", "cipher", + "constant_time_eq", "des", "ecb", "hex-literal", "openssl", "pcsc", + "rand", "rasn", "rasn-cms", "rasn-pkix", + "sha1-checked", + "sha2", "tracing", "tracing-subscriber", ] @@ -276,9 +312,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -287,9 +323,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "heck" @@ -330,9 +366,9 @@ checksum = "17ab85f84ca42c5ec520e6f3c9966ba1fd62909ce260f8837e248857d2560509" [[package]] name = "konst" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d712a8c49d4274f8d8a5cf61368cb5f3c143d149882b1a2918129e53395fdb0" +checksum = "50a0ba6de5f7af397afff922f22c149ff605c766cd3269cf6c1cd5e466dbe3b9" dependencies = [ "const_panic", "konst_kernel", @@ -341,9 +377,9 @@ dependencies = [ [[package]] name = "konst_kernel" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac6ea8c376b6e208a81cf39b8e82bebf49652454d98a4829e907dac16ef1790" +checksum = "be0a455a1719220fd6adf756088e1c69a85bf14b6a9e24537a5cc04f503edb2b" dependencies = [ "typewit", ] @@ -356,9 +392,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "log" @@ -380,9 +416,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] @@ -409,11 +445,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -429,18 +464,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "object" -version = "0.32.2" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "memchr", ] @@ -474,14 +509,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[package]] name = "openssl-src" -version = "300.2.3+3.2.1" +version = "300.3.1+3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" +checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" dependencies = [ "cc", ] @@ -517,9 +552,9 @@ dependencies = [ [[package]] name = "pcsc-sys" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b7bfecba2c0f1b5efb0e7caf7533ab1c295024165bcbb066231f60d33e23ea" +checksum = "b09e9ba80f2c4d167f936d27594f7248bca3295921ffbfa44a24b339b6cb7403" dependencies = [ "pkg-config", ] @@ -536,11 +571,17 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" -version = "1.0.80" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -560,6 +601,36 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rasn" version = "0.14.0" @@ -639,9 +710,41 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1-checked" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest", + "sha1", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] [[package]] name = "sharded-slab" @@ -694,9 +797,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.58" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -738,7 +841,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.66", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 09bb150..aec5bdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "emrtd" -version = "0.0.1" +version = "0.0.2" authors = ["Burak Can Kus"] edition = "2021" rust-version = "1.66.1" @@ -13,17 +13,25 @@ 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" +sha1-checked = "0.10.0" +sha2 = "0.10.8" +constant_time_eq = "0.3.0" +rand = { version = "0.8.5", features = ["getrandom"] } + +openssl = { version = "0.10.64", features = ["vendored"], optional = true } +rasn = { version = "0.14.0", optional = true} +rasn-cms = { version = "0.14.0", optional = true} +rasn-pkix = { version = "0.14.0", optional = true} [dev-dependencies] hex-literal = "0.4.1" + +[features] +passive_auth = ["dep:openssl", "dep:rasn", "dep:rasn-cms", "dep:rasn-pkix"] diff --git a/README.md b/README.md index 3a585a5..dd0acc4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A library that can read an eMRTD (Electronic Machine Readable Travel Document). The `emrtd` crate provides a simple API that can be used to communicate with -eMRTDs and read the data that resides within them. With the help of openssl, +eMRTDs and read the data that resides within them. With the help of `openssl`, it can perform Passive Authentication. **NOTE:** @@ -14,6 +14,8 @@ Please note that this crate is provided 'as is' and is not considered production **WARNING:** Currently Active Authentication (AA), Chip Authentication (CA), PACE or EAC are **not** supported. +Enable the `passive_auth` feature for Passive Authentication (PA), but note that it depends on [`openssl`](https://docs.rs/openssl/latest/openssl/) crate. + ## License Licensed under either of diff --git a/examples/read_emrtd.rs b/examples/read_emrtd.rs index 4497b64..d9bd82d 100644 --- a/examples/read_emrtd.rs +++ b/examples/read_emrtd.rs @@ -1,11 +1,11 @@ use std::env; -use emrtd::{ - bytes2hex, get_jpeg_from_ef_dg2, other_mrz, parse_master_list, passive_authentication, - validate_dg, EmrtdComms, EmrtdError, -}; +use emrtd::{bytes2hex, get_jpeg_from_ef_dg2, other_mrz, EmrtdComms, EmrtdError}; use tracing::{error, info}; +#[cfg(feature = "passive_auth")] +use emrtd::{parse_master_list, passive_authentication, validate_dg}; + fn main() -> Result<(), EmrtdError> { tracing_subscriber::fmt() .with_max_level(tracing::Level::TRACE) @@ -89,25 +89,30 @@ fn main() -> Result<(), EmrtdError> { let ef_sod = sm_object.read_data_from_ef(true)?; info!("Data from the EF.SOD: {}", bytes2hex(&ef_sod)); - let master_list = include_bytes!("../data/DE_ML_2024-04-10-10-54-13.ml"); - - let csca_cert_store = parse_master_list(master_list)?; - - info!("Number of certificates parse from the Master List in the store {}", csca_cert_store.all_certificates().len()); - - let result = passive_authentication(&ef_sod, &csca_cert_store).unwrap(); - info!("{:?} {:?} {:?}", result.0.type_(), result.1, result.2); + #[cfg(feature = "passive_auth")] + { + let master_list: &[u8; 0] = include_bytes!("../data/DE_ML_2024-04-10-10-54-13.ml"); + let csca_cert_store = parse_master_list(master_list)?; + info!( + "Number of certificates parse from the Master List in the store {}", + csca_cert_store.all_certificates().len() + ); + let result = passive_authentication(&ef_sod, &csca_cert_store).unwrap(); + info!("{:?} {:?} {:?}", result.0.type_(), result.1, result.2); + } // Read EF.DG1 sm_object.select_ef(b"\x01\x01", "EF.DG1", true)?; let ef_dg1 = sm_object.read_data_from_ef(true)?; info!("Data from the EF.DG1: {}", bytes2hex(&ef_dg1)); + #[cfg(feature = "passive_auth")] validate_dg(&ef_dg1, 1, result.0, &result.1)?; // Read EF.DG2 sm_object.select_ef(b"\x01\x02", "EF.DG2", true)?; let ef_dg2 = sm_object.read_data_from_ef(true)?; info!("Data from the EF.DG2: {}", bytes2hex(&ef_dg2)); + #[cfg(feature = "passive_auth")] validate_dg(&ef_dg2, 2, result.0, &result.1)?; let jpeg = get_jpeg_from_ef_dg2(&ef_dg2)?; diff --git a/src/lib.rs b/src/lib.rs index c77f236..c112608 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,26 +1,29 @@ //! A library that can read an eMRTD. //! //! A library that can read an eMRTD (Electronic Machine Readable Travel Document). -//! +//! //! The `emrtd` crate provides a simple API that can be used to communicate with -//! eMRTDs and read the data that resides within them. With the help of openssl, +//! eMRTDs and read the data that resides within them. With the help of `openssl`, //! it can perform Passive Authentication. -//! +//! //! **NOTE:** //! Please note that this crate is provided 'as is' and is not considered production-ready. Use at your own risk. -//! +//! //! Currently Active Authentication (AA), Chip Authentication (CA), PACE or EAC //! are **not** supported. //! +//! Enable the `passive_auth` feature for Passive Authentication (PA), but note +//! that it depends on [`openssl`](https://docs.rs/openssl/latest/openssl/) crate. +//! //! # Quick Start //! //! ``` -//! use emrtd::{ -//! bytes2hex, get_jpeg_from_ef_dg2, other_mrz, parse_master_list, -//! passive_authentication, validate_dg, EmrtdComms, EmrtdError, -//! }; +//! use emrtd::{bytes2hex, get_jpeg_from_ef_dg2, other_mrz, EmrtdComms, EmrtdError}; //! use tracing::{error, info}; //! +//! #[cfg(feature = "passive_auth")] +//! use emrtd::{parse_master_list, passive_authentication, validate_dg}; +//! //! fn main() -> Result<(), EmrtdError> { //! tracing_subscriber::fmt() //! .with_max_level(tracing::Level::TRACE) @@ -89,7 +92,13 @@ //! // Select eMRTD application //! sm_object.select_emrtd_application()?; //! -//! let secret = other_mrz(&doc_no, &birthdate, &expirydate)?; +//! let secret = match other_mrz(&doc_no, &birthdate, &expirydate) { +//! Ok(secret) => secret, +//! Err(EmrtdError) => { +//! error!("Invalid MRZ string."); +//! return Ok(()); +//! } +//! }; //! //! sm_object.establish_bac_session_keys(secret.as_bytes())?; //! @@ -103,23 +112,26 @@ //! let ef_sod = sm_object.read_data_from_ef(true)?; //! info!("Data from the EF.SOD: {}", bytes2hex(&ef_sod)); //! -//! let master_list = include_bytes!("../data/DE_ML_2024-04-10-10-54-13.ml"); -//! -//! let csca_cert_store = parse_master_list(master_list)?; -//! -//! let result = passive_authentication(&ef_sod, &csca_cert_store).unwrap(); -//! info!("{:?} {:?} {:?}", result.0.type_(), result.1, result.2); +//! #[cfg(feature = "passive_auth")] +//! { +//! let master_list = include_bytes!("../data/DE_ML_2024-04-10-10-54-13.ml"); +//! let csca_cert_store = parse_master_list(master_list)?; +//! let result = passive_authentication(&ef_sod, &csca_cert_store).unwrap(); +//! info!("{:?} {:?} {:?}", result.0.type_(), result.1, result.2); +//! } //! //! // Read EF.DG1 //! sm_object.select_ef(b"\x01\x01", "EF.DG1", true)?; //! let ef_dg1 = sm_object.read_data_from_ef(true)?; //! info!("Data from the EF.DG1: {}", bytes2hex(&ef_dg1)); +//! #[cfg(feature = "passive_auth")] //! validate_dg(&ef_dg1, 1, result.0, &result.1)?; //! //! // Read EF.DG2 //! sm_object.select_ef(b"\x01\x02", "EF.DG2", true)?; //! let ef_dg2 = sm_object.read_data_from_ef(true)?; //! info!("Data from the EF.DG2: {}", bytes2hex(&ef_dg2)); +//! #[cfg(feature = "passive_auth")] //! validate_dg(&ef_dg2, 2, result.0, &result.1)?; //! //! let jpeg = get_jpeg_from_ef_dg2(&ef_dg2)?; @@ -134,14 +146,14 @@ extern crate alloc; use alloc::{borrow::ToOwned, collections::BTreeMap, format, string::String, vec, vec::Vec}; use cipher::{BlockDecryptMut, BlockEncryptMut, KeyInit, KeyIvInit}; +use constant_time_eq::constant_time_eq; use core::{ fmt::{self, Debug, Write}, iter, mem, }; +#[cfg(feature = "passive_auth")] use openssl::{ hash::{hash, MessageDigest}, - memcmp::eq, - rand::rand_bytes, sign::Verifier, stack::Stack, x509::{ @@ -150,9 +162,17 @@ use openssl::{ }, }; use pcsc::{Attribute::AtrString, Card}; +use rand::{rngs::OsRng, RngCore}; +#[cfg(feature = "passive_auth")] use rasn::{der, types::Oid}; +#[cfg(feature = "passive_auth")] use rasn_cms::{CertificateChoices, RevocationInfoChoice}; -use tracing::{error, info, trace, warn}; +use sha1_checked::Sha1; +use sha2::{Digest, Sha256}; +use std::num::TryFromIntError; +#[cfg(feature = "passive_auth")] +use tracing::warn; +use tracing::{error, info, trace}; #[derive(Debug)] #[non_exhaustive] @@ -172,12 +192,17 @@ pub enum EmrtdError { InvalidFileStructure(&'static str), VerifySignatureError(&'static str), VerifyHashError(String), + CalculateHashError(&'static str), PcscError(pcsc::Error), - BoringErrorStack(openssl::error::ErrorStack), + #[cfg(feature = "passive_auth")] + OpensslErrorStack(openssl::error::ErrorStack), + #[cfg(feature = "passive_auth")] RasnEncodeError(rasn::error::EncodeError), + #[cfg(feature = "passive_auth")] RasnDecodeError(rasn::error::DecodeError), PadError(cipher::inout::PadError), UnpadError(cipher::block_padding::UnpadError), + IntCastError(TryFromIntError), } impl fmt::Display for EmrtdError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -222,12 +247,19 @@ impl fmt::Display for EmrtdError { Self::VerifyHashError(ref error_msg) => { write!(f, "Failure during comparison of hashes: {error_msg}") } + Self::CalculateHashError(error_msg) => { + write!(f, "Failure during calculation of hashes: {error_msg}") + } Self::PcscError(ref e) => fmt::Display::fmt(&e, f), - Self::BoringErrorStack(ref e) => fmt::Display::fmt(&e, f), + #[cfg(feature = "passive_auth")] + Self::OpensslErrorStack(ref e) => fmt::Display::fmt(&e, f), + #[cfg(feature = "passive_auth")] Self::RasnEncodeError(ref e) => fmt::Display::fmt(&e, f), + #[cfg(feature = "passive_auth")] Self::RasnDecodeError(ref e) => fmt::Display::fmt(&e, f), Self::PadError(ref e) => fmt::Display::fmt(&e, f), Self::UnpadError(ref e) => fmt::Display::fmt(&e, f), + Self::IntCastError(ref e) => fmt::Display::fmt(&e, f), } } } @@ -315,6 +347,7 @@ pub enum MacAlgorithm { /// /// END /// +#[cfg(feature = "passive_auth")] pub mod lds_security_object { extern crate alloc; use rasn::prelude::*; @@ -368,6 +401,7 @@ pub mod lds_security_object { /// id-icao-cscaMasterListSigningKey OBJECT IDENTIFIER ::= {id-icao-mrtd-security 3} /// END /// +#[cfg(feature = "passive_auth")] pub mod csca_master_list { extern crate alloc; use rasn::prelude::*; @@ -686,8 +720,14 @@ pub fn int2asn1len(length: usize) -> Vec { /// /// `EmrtdError` if 'SHA1' fails. fn generate_key_seed(secret: &[u8]) -> Result, EmrtdError> { - let hash_bytes = hash(MessageDigest::sha1(), secret).map_err(EmrtdError::BoringErrorStack)?; - Ok(hash_bytes.to_vec()) + let hash_result = Sha1::try_digest(secret); + if hash_result.has_collision() { + error!("SHA1 hash calculation during generate_key_seed had collision"); + return Err(EmrtdError::CalculateHashError( + "SHA1 hash calculation during generate_key_seed had collision", + )); + } + Ok(hash_result.hash().as_slice().to_vec()) } /// Encrypts data using the specified block cipher and mode. @@ -922,8 +962,14 @@ fn compute_key( match alg { EncryptionAlgorithm::DES3 => { - let hash_bytes = - hash(MessageDigest::sha1(), &d).map_err(EmrtdError::BoringErrorStack)?; + let hash_result = Sha1::try_digest(&d); + if hash_result.has_collision() { + error!("SHA1 hash calculation during 3DES compute_key had collision"); + return Err(EmrtdError::CalculateHashError( + "SHA1 hash calculation during 3DES compute_key had collision", + )); + } + let hash_bytes = hash_result.hash().as_slice().to_vec(); let key_1_2 = des3_adjust_parity_bits(hash_bytes.iter().copied().take(16).collect()); match *key_type { KeyType::Encryption => Ok([&key_1_2[..], &key_1_2[..8]].concat()), @@ -931,19 +977,25 @@ fn compute_key( } } EncryptionAlgorithm::AES128 => { - let hash_bytes = - hash(MessageDigest::sha1(), &d).map_err(EmrtdError::BoringErrorStack)?; + let hash_result = Sha1::try_digest(&d); + if hash_result.has_collision() { + error!("SHA1 hash calculation during AES-128 compute_key had collision"); + return Err(EmrtdError::CalculateHashError( + "SHA1 hash calculation during AES-128 compute_key had collision", + )); + } + let hash_bytes = hash_result.hash().as_slice().to_vec(); Ok(hash_bytes.iter().copied().take(16).collect()) } EncryptionAlgorithm::AES192 => { - let hash_bytes = - hash(MessageDigest::sha256(), &d).map_err(EmrtdError::BoringErrorStack)?; + let hash_result = Sha256::digest(&d); + let hash_bytes = hash_result.as_slice().to_vec(); Ok(hash_bytes.iter().copied().take(24).collect()) } EncryptionAlgorithm::AES256 => { - let hash_bytes = - hash(MessageDigest::sha256(), &d).map_err(EmrtdError::BoringErrorStack)?; - Ok(hash_bytes.to_vec()) + let hash_result = Sha256::digest(&d); + let hash_bytes = hash_result.as_slice().to_vec(); + Ok(hash_bytes) } } } @@ -1121,6 +1173,7 @@ fn des3_adjust_parity_bits(mut key: Vec) -> Vec { /// # Errors /// /// * `EmrtdError` if an unsupported OID is given. +#[cfg(feature = "passive_auth")] fn oid2digestalg(oid: &rasn::types::ObjectIdentifier) -> Result { let digest_alg_oid_dict: [(&Oid, MessageDigest); 6] = [ ( @@ -1169,24 +1222,28 @@ fn oid2digestalg(oid: &rasn::types::ObjectIdentifier) -> Result Result<(), EmrtdError> { - match data.get(..tag.len()) { - Some(d) => { - if !d.starts_with(tag) { + data.get(..tag.len()).map_or_else( + || { + error!( + "Error while validating ASN1 tag, `data.len()`: `{}` is less than `tag.len()`: `{}`", + data.len(), + tag.len() + ); + Err(EmrtdError::ParseAsn1DataError(tag.len(), data.len())) + }, + |d| { + if d.starts_with(tag) { + Ok(()) + } else { error!( "Error while validating ASN1 tag, expected: {}, found {}", bytes2hex(tag), bytes2hex(d) ); Err(EmrtdError::ParseAsn1TagError(bytes2hex(tag), bytes2hex(d))) - } else { - Ok(()) } - } - None => { - error!("Error while validating ASN1 tag, `data.len()`: `{}` is less than `tag.len()`: `{}`", data.len(), tag.len()); - Err(EmrtdError::ParseAsn1DataError(tag.len(), data.len())) - } - } + }, + ) } /// Retrieve the ASN.1 child from the provided data. @@ -1265,6 +1322,7 @@ fn get_asn1_child(data: &[u8], tag_len: usize) -> Result<(&[u8], &[u8]), EmrtdEr /// # Ok(()) /// # } /// ``` +#[cfg(feature = "passive_auth")] pub fn parse_master_list(master_list: &[u8]) -> Result { // We expect a Master List as specified in // ICAO Doc 9303-12 Section 9 @@ -1393,7 +1451,7 @@ pub fn parse_master_list(master_list: &[u8]) -> Result { // extension for Master List Signer certificates is 2.23.136.1.1.3. if ext.extn_id.eq(Oid::const_new(&[2, 5, 29, 37])) && ext.extn_value.len() == 10 - && eq( + && constant_time_eq( &ext.extn_value, b"\x30\x08\x06\x06\x67\x81\x08\x01\x01\x03", ) @@ -1402,7 +1460,7 @@ pub fn parse_master_list(master_list: &[u8]) -> Result { der::encode(&c).map_err(EmrtdError::RasnEncodeError)?; let master_list_signer = X509::from_der(&master_list_signer_bytes) - .map_err(EmrtdError::BoringErrorStack)?; + .map_err(EmrtdError::OpensslErrorStack)?; possible_master_list_signer = Some(master_list_signer); break; // It is mandatory by ICAO Doc 9303-12 Table 6 @@ -1412,12 +1470,15 @@ pub fn parse_master_list(master_list: &[u8]) -> Result { // > PathLenConstraint must always be '0' } else if ext.extn_id.eq(Oid::const_new(&[2, 5, 29, 19])) && ext.extn_value.len() == 8 - && eq(&ext.extn_value, b"\x30\x06\x01\x01\xFF\x02\x01\x00") + && constant_time_eq( + &ext.extn_value, + b"\x30\x06\x01\x01\xFF\x02\x01\x00", + ) { let csca_cert_bytes = der::encode(&c).map_err(EmrtdError::RasnEncodeError)?; let csca_cert = X509::from_der(&csca_cert_bytes) - .map_err(EmrtdError::BoringErrorStack)?; + .map_err(EmrtdError::OpensslErrorStack)?; possible_csca_cert = Some(csca_cert); break; } @@ -1439,16 +1500,16 @@ pub fn parse_master_list(master_list: &[u8]) -> Result { // And verify that the Master List Signer was issued by CSCA certificate if it exists match possible_csca_cert { Some(csca_cert) => { - let chain = Stack::new().map_err(EmrtdError::BoringErrorStack)?; + let chain = Stack::new().map_err(EmrtdError::OpensslErrorStack)?; let mut store_bldr = - X509StoreBuilder::new().map_err(EmrtdError::BoringErrorStack)?; + X509StoreBuilder::new().map_err(EmrtdError::OpensslErrorStack)?; store_bldr .add_cert(csca_cert) - .map_err(EmrtdError::BoringErrorStack)?; + .map_err(EmrtdError::OpensslErrorStack)?; let store = store_bldr.build(); let mut context = - X509StoreContext::new().map_err(EmrtdError::BoringErrorStack)?; + X509StoreContext::new().map_err(EmrtdError::OpensslErrorStack)?; let master_list_verification = context .init(&store, &c, &chain, |c| { let verification = c.verify_cert()?; @@ -1458,7 +1519,7 @@ pub fn parse_master_list(master_list: &[u8]) -> Result { Ok((verification, c.error().error_string())) } }) - .map_err(EmrtdError::BoringErrorStack)?; + .map_err(EmrtdError::OpensslErrorStack)?; if !master_list_verification.0 { warn!("Error while verifying Master List Signer Certificate signature: {}", master_list_verification.1); } @@ -1685,7 +1746,7 @@ pub fn parse_master_list(master_list: &[u8]) -> Result { // // Message Digest Calculation Process as specified in RFC 5652 let csca_master_list_hash = - hash(digest_algorithm, &csca_master_list_bytes).map_err(EmrtdError::BoringErrorStack)?; + hash(digest_algorithm, &csca_master_list_bytes).map_err(EmrtdError::OpensslErrorStack)?; if csca_master_list_hash.ne(&message_digest) { error!("Digest of cscaMasterList does not match with the digest in SignedAttributes"); @@ -1718,15 +1779,15 @@ pub fn parse_master_list(master_list: &[u8]) -> Result { info!("{:?}", master_list_signer); let pub_key = master_list_signer .public_key() - .map_err(EmrtdError::BoringErrorStack)?; + .map_err(EmrtdError::OpensslErrorStack)?; let mut verifier = - Verifier::new(digest_algorithm, &pub_key).map_err(EmrtdError::BoringErrorStack)?; + Verifier::new(digest_algorithm, &pub_key).map_err(EmrtdError::OpensslErrorStack)?; verifier .update(&signed_attrs_bytes) - .map_err(EmrtdError::BoringErrorStack)?; + .map_err(EmrtdError::OpensslErrorStack)?; let sig_verified = verifier .verify(signature) - .map_err(EmrtdError::BoringErrorStack)?; + .map_err(EmrtdError::OpensslErrorStack)?; info!("Signature verification: {sig_verified}"); if !sig_verified { @@ -1748,15 +1809,15 @@ pub fn parse_master_list(master_list: &[u8]) -> Result { } // Create a store that can be used to verify DSC certificate during passive authentication - let mut store_bldr = X509StoreBuilder::new().map_err(EmrtdError::BoringErrorStack)?; + let mut store_bldr = X509StoreBuilder::new().map_err(EmrtdError::OpensslErrorStack)?; for csca_cert in csca_master_list.cert_list { match der::encode(&csca_cert).map_err(EmrtdError::RasnEncodeError) { Ok(c) => { - let x509cert = X509::from_der(&c).map_err(EmrtdError::BoringErrorStack)?; + let x509cert = X509::from_der(&c).map_err(EmrtdError::OpensslErrorStack)?; store_bldr .add_cert(x509cert) - .map_err(EmrtdError::BoringErrorStack)?; + .map_err(EmrtdError::OpensslErrorStack)?; } Err(e) => return Err(e), } @@ -1825,6 +1886,7 @@ pub fn parse_master_list(master_list: &[u8]) -> Result { /// /// * Instead of returning error immediately after an error, collect all errors and return at the end /// of the function, i.e. in case of signature verification failure, maybe the user can still get the DG hashes +#[cfg(feature = "passive_auth")] pub fn passive_authentication( ef_sod: &[u8], cert_store: &X509Store, @@ -1947,7 +2009,7 @@ pub fn passive_authentication( for cert in signed_data.certificates.iter().flatten() { if let CertificateChoices::Certificate(c) = cert { let dsc_bytes = der::encode(&c).map_err(EmrtdError::RasnEncodeError)?; - let dsc = X509::from_der(&dsc_bytes).map_err(EmrtdError::BoringErrorStack)?; + let dsc = X509::from_der(&dsc_bytes).map_err(EmrtdError::OpensslErrorStack)?; possible_dsc = Some(dsc); break; } @@ -1955,8 +2017,8 @@ pub fn passive_authentication( // Make sure we got a possible certificate match possible_dsc { Some(c) => { - let chain = Stack::new().map_err(EmrtdError::BoringErrorStack)?; - let mut context = X509StoreContext::new().map_err(EmrtdError::BoringErrorStack)?; + let chain = Stack::new().map_err(EmrtdError::OpensslErrorStack)?; + let mut context = X509StoreContext::new().map_err(EmrtdError::OpensslErrorStack)?; let dsc_verification = context.init(cert_store, &c, &chain, |c| { let verification = c.verify_cert()?; if verification { @@ -1964,7 +2026,7 @@ pub fn passive_authentication( } else { Ok((verification, c.error().error_string())) } - }).map_err(EmrtdError::BoringErrorStack)?; + }).map_err(EmrtdError::OpensslErrorStack)?; if !dsc_verification.0 { error!("Error while verifying Document Signer Certificate signature: {}", dsc_verification.1); return Err(EmrtdError::InvalidFileStructure("DSC certificate verification using CSCA store failed")); @@ -2194,8 +2256,8 @@ pub fn passive_authentication( // // // Message Digest Calculation Process as specified in RFC 3369 - let lds_security_object_hash = - hash(digest_algorithm, &lds_security_object_bytes).map_err(EmrtdError::BoringErrorStack)?; + let lds_security_object_hash = hash(digest_algorithm, &lds_security_object_bytes) + .map_err(EmrtdError::OpensslErrorStack)?; if lds_security_object_hash.ne(&message_digest) { error!("Digest of LDSSecurityObject does not match with the digest in SignedAttributes"); @@ -2225,15 +2287,15 @@ pub fn passive_authentication( // let _signature_algorithm = &signer_info.signature_algorithm; let signature = &signer_info.signature; - let pub_key = dsc.public_key().map_err(EmrtdError::BoringErrorStack)?; + let pub_key = dsc.public_key().map_err(EmrtdError::OpensslErrorStack)?; let mut verifier = - Verifier::new(digest_algorithm, &pub_key).map_err(EmrtdError::BoringErrorStack)?; + Verifier::new(digest_algorithm, &pub_key).map_err(EmrtdError::OpensslErrorStack)?; verifier .update(&signed_attrs_bytes) - .map_err(EmrtdError::BoringErrorStack)?; + .map_err(EmrtdError::OpensslErrorStack)?; let sig_verified = verifier .verify(signature) - .map_err(EmrtdError::BoringErrorStack)?; + .map_err(EmrtdError::OpensslErrorStack)?; info!("Signature verification: {sig_verified}"); if !sig_verified { @@ -2459,6 +2521,7 @@ pub fn get_jpeg_from_ef_dg2(ef_dg2: &[u8]) -> Result<&[u8], EmrtdError> { /// # Ok(()) /// # } /// ``` +#[cfg(feature = "passive_auth")] pub fn validate_dg( dg: &[u8], dg_number: i32, @@ -2470,7 +2533,7 @@ pub fn validate_dg( return Err(EmrtdError::InvalidArgument("Invalid Data Group number")); } - let hash_bytes = hash(message_digest, dg).map_err(EmrtdError::BoringErrorStack)?; + let hash_bytes = hash(message_digest, dg).map_err(EmrtdError::OpensslErrorStack)?; let mut verified_hash = None; for dg_hash in verified_hashes { if dg_hash @@ -2828,7 +2891,7 @@ impl EmrtdComms { let protected_apdu = [ apdu.get_command_header(), - [payload.len() as u8].to_vec(), + [u8::try_from(payload.len()).map_err(EmrtdError::IntCastError)?].to_vec(), payload, b"\x00".to_vec(), ] @@ -2969,7 +3032,7 @@ impl EmrtdComms { pad_len, )?; let cc = compute_mac(ks_mac, &k, mac_alg)?; - if !eq(&cc, do8e.unwrap_or_default()) { + if !constant_time_eq(&cc, do8e.unwrap_or_default()) { error!("MAC verification failed"); return Err(EmrtdError::VerifyMacError()); } @@ -3154,13 +3217,16 @@ impl EmrtdComms { trace!("Reading {data_len} bytes from EF..."); while offset < data_len { let le = if data_len - offset < 0xFA { - [((data_len - offset) & 0xFF) as u8] + [u8::try_from((data_len - offset) & 0xFF).map_err(EmrtdError::IntCastError)?] } else { [0x00] }; // Send "Read Binary" APDU for the next chunk - let offset_bytes = [(offset >> 8) as u8, (offset & 0xFF) as u8]; + let offset_bytes = [ + u8::try_from(offset >> 8).map_err(EmrtdError::IntCastError)?, + u8::try_from(offset & 0xFF).map_err(EmrtdError::IntCastError)?, + ]; let read_apdu = APDU::new( b'\x00', b'\xB0', @@ -3259,9 +3325,9 @@ impl EmrtdComms { }; let mut rnd_ifd: [u8; 8] = [0; 8]; - rand_bytes(&mut rnd_ifd).map_err(EmrtdError::BoringErrorStack)?; + OsRng.fill_bytes(&mut rnd_ifd); let mut k_ifd: [u8; 16] = [0; 16]; - rand_bytes(&mut k_ifd).map_err(EmrtdError::BoringErrorStack)?; + OsRng.fill_bytes(&mut k_ifd); let e_ifd = encrypt::>( ba_key_enc, @@ -3304,7 +3370,7 @@ impl EmrtdComms { &padding_method_2(&resp_data_enc[..resp_data_enc.len() - 8], 8)?, &MacAlgorithm::DES, )?; - if !eq(&m_ic, &resp_data_enc[resp_data_enc.len() - 8..]) { + if !constant_time_eq(&m_ic, &resp_data_enc[resp_data_enc.len() - 8..]) { error!("MAC verification failed"); return Err(EmrtdError::VerifyMacError()); } @@ -3315,12 +3381,12 @@ impl EmrtdComms { &resp_data_enc[..resp_data_enc.len() - 8], )?; - if !eq(&resp_data[..8], &rnd_ic[..]) { + if !constant_time_eq(&resp_data[..8], &rnd_ic[..]) { error!("Error while establishing BAC session keys."); return Err(EmrtdError::InvalidResponseError()); } - if !eq(&resp_data[8..16], &rnd_ifd[..]) { + if !constant_time_eq(&resp_data[8..16], &rnd_ifd[..]) { error!("Error while establishing BAC session keys."); return Err(EmrtdError::InvalidResponseError()); } @@ -3727,6 +3793,7 @@ fn test_compute_mac() -> Result<(), EmrtdError> { Ok(()) } +#[cfg(feature = "passive_auth")] #[test] fn test_oid2digestalg_known_oid() -> Result<(), EmrtdError> { let result = oid2digestalg( @@ -3737,6 +3804,7 @@ fn test_oid2digestalg_known_oid() -> Result<(), EmrtdError> { Ok(()) } +#[cfg(feature = "passive_auth")] #[test] fn test_oid2digestalg_unknown_oid() -> Result<(), EmrtdError> { // ripemd256