From 14d2a257d9e239ea45b6a2350fec02700a35b38b Mon Sep 17 00:00:00 2001 From: Mika Rautio Date: Sat, 15 Jul 2023 18:54:03 +0300 Subject: [PATCH 1/3] Update Nix packages, use channel nixos-22.11 --- nix/sources.json | 22 ++------- nix/sources.nix | 118 ++++++++++++++++++++++++++++------------------- 2 files changed, 76 insertions(+), 64 deletions(-) diff --git a/nix/sources.json b/nix/sources.json index 68d9bd8..335b2a4 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -1,26 +1,14 @@ { - "niv": { - "branch": "master", - "description": "Easy dependency management for Nix projects", - "homepage": "https://github.com/nmattia/niv", - "owner": "nmattia", - "repo": "niv", - "rev": "5830a4dd348d77e39a0f3c4c762ff2663b602d4c", - "sha256": "1d3lsrqvci4qz2hwjrcnd8h5vfkg8aypq3sjd4g3izbc8frwz5sm", - "type": "tarball", - "url": "https://github.com/nmattia/niv/archive/5830a4dd348d77e39a0f3c4c762ff2663b602d4c.tar.gz", - "url_template": "https://github.com///archive/.tar.gz" - }, "nixpkgs": { - "branch": "nixos-21.05", + "branch": "nixos-22.11", "description": "Nix Packages collection", - "homepage": "", + "homepage": null, "owner": "NixOS", "repo": "nixpkgs", - "rev": "b86b6bc5e753919ab4d7c902210234f324d0efa4", - "sha256": "1hw9aq4qbv3afj7l0vjfk0wd2m5y7gxipc5bc90dmhlnr5bpjl5j", + "rev": "ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b", + "sha256": "1xi53rlslcprybsvrmipm69ypd3g3hr7wkxvzc73ag8296yclyll", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/b86b6bc5e753919ab4d7c902210234f324d0efa4.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/ea4c80b39be4c09702b0cb3b42eab59e2ba4f24b.tar.gz", "url_template": "https://github.com///archive/.tar.gz" } } diff --git a/nix/sources.nix b/nix/sources.nix index 1938409..fe3dadf 100644 --- a/nix/sources.nix +++ b/nix/sources.nix @@ -10,29 +10,50 @@ let let name' = sanitizeName name + "-src"; in - if spec.builtin or true then - builtins_fetchurl { inherit (spec) url sha256; name = name'; } - else - pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; name = name'; } + else + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; fetch_tarball = pkgs: name: spec: let name' = sanitizeName name + "-src"; in - if spec.builtin or true then - builtins_fetchTarball { name = name'; inherit (spec) url sha256; } - else - pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; + if spec.builtin or true then + builtins_fetchTarball { name = name'; inherit (spec) url sha256; } + else + pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; fetch_git = name: spec: let ref = - if spec ? ref then spec.ref else + spec.ref or ( if spec ? branch then "refs/heads/${spec.branch}" else - if spec ? tag then "refs/tags/${spec.tag}" else - abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; + if spec ? tag then "refs/tags/${spec.tag}" else + abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!" + ); + submodules = spec.submodules or false; + submoduleArg = + let + nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; + emptyArgWithWarning = + if submodules + then + builtins.trace + ( + "The niv input \"${name}\" uses submodules " + + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " + + "does not support them" + ) + { } + else { }; + in + if nixSupportsSubmodules + then { inherit submodules; } + else emptyArgWithWarning; in - builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; }; + builtins.fetchGit + ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); fetch_local = spec: spec.path; @@ -66,16 +87,16 @@ let hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; hasThisAsNixpkgsPath = == ./.; in - if builtins.hasAttr "nixpkgs" sources - then sourcesNixpkgs - else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then - import {} - else - abort - '' - Please specify either (through -I or NIX_PATH=nixpkgs=...) or - add a package called "nixpkgs" to your sources.json. - ''; + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import { } + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; # The actual fetching function. fetch = pkgs: name: spec: @@ -95,13 +116,13 @@ let # the path directly as opposed to the fetched source. replace = name: drv: let - saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; + saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name; ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; in - if ersatz == "" then drv else - # this turns the string into an actual Nix path (for both absolute and - # relative paths) - if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; + if ersatz == "" then drv else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; # Ports of functions for older nix versions @@ -112,7 +133,7 @@ let ); # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 - range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); + range = first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1); # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); @@ -123,43 +144,46 @@ let concatStrings = builtins.concatStringsSep ""; # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 - optionalAttrs = cond: as: if cond then as else {}; + optionalAttrs = cond: as: if cond then as else { }; # fetchTarball version that is compatible between all the versions of Nix builtins_fetchTarball = { url, name ? null, sha256 }@attrs: let inherit (builtins) lessThan nixVersion fetchTarball; in - if lessThan nixVersion "1.12" then - fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) - else - fetchTarball attrs; + if lessThan nixVersion "1.12" then + fetchTarball ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) + else + fetchTarball attrs; # fetchurl version that is compatible between all the versions of Nix builtins_fetchurl = { url, name ? null, sha256 }@attrs: let inherit (builtins) lessThan nixVersion fetchurl; in - if lessThan nixVersion "1.12" then - fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) - else - fetchurl attrs; + if lessThan nixVersion "1.12" then + fetchurl ({ inherit url; } // (optionalAttrs (name != null) { inherit name; })) + else + fetchurl attrs; # Create the final "sources" from the config mkSources = config: - mapAttrs ( - name: spec: - if builtins.hasAttr "outPath" spec - then abort - "The values in sources.json should not have an 'outPath' attribute" - else - spec // { outPath = replace name (fetch config.pkgs name spec); } - ) config.sources; + mapAttrs + ( + name: spec: + if builtins.hasAttr "outPath" spec + then + abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = replace name (fetch config.pkgs name spec); } + ) + config.sources; # The "config" used by the fetchers mkConfig = { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null - , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) + , sources ? if sourcesFile == null then { } else builtins.fromJSON (builtins.readFile sourcesFile) , system ? builtins.currentSystem , pkgs ? mkPkgs sources system }: rec { @@ -171,4 +195,4 @@ let }; in -mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } +mkSources (mkConfig { }) // { __functor = _: settings: mkSources (mkConfig settings); } From 77386b7fcf0572adc58c405b160e19493c3de614 Mon Sep 17 00:00:00 2001 From: Mika Rautio Date: Sat, 15 Jul 2023 18:54:54 +0300 Subject: [PATCH 2/3] Run everything through cargo fmt --- emvpt/src/bcdutil/mod.rs | 26 +- emvpt/src/lib.rs | 2123 ++++++++++++++++++++++----------- terminalsimulator/src/main.rs | 168 ++- 3 files changed, 1530 insertions(+), 787 deletions(-) diff --git a/emvpt/src/bcdutil/mod.rs b/emvpt/src/bcdutil/mod.rs index 1c334d7..57f7cfb 100644 --- a/emvpt/src/bcdutil/mod.rs +++ b/emvpt/src/bcdutil/mod.rs @@ -1,7 +1,7 @@ -pub fn bcd_to_ascii(bcd_data : &[u8]) -> Result, ()> { - let mut ascii_output : Vec = Vec::with_capacity(bcd_data.len() * 2); +pub fn bcd_to_ascii(bcd_data: &[u8]) -> Result, ()> { + let mut ascii_output: Vec = Vec::with_capacity(bcd_data.len() * 2); - const ASCII_CHARACTER_0 : u8 = 0x30; + const ASCII_CHARACTER_0: u8 = 0x30; for i in 0..bcd_data.len() { let byte = bcd_data[i]; @@ -28,12 +28,12 @@ pub fn bcd_to_ascii(bcd_data : &[u8]) -> Result, ()> { } //cn = 12 34 56 78 90 12 3F FF -pub fn ascii_to_bcd_cn(ascii_data : &[u8], size : usize) -> Result, ()> { - let mut bcd_output : Vec = Vec::with_capacity(size); +pub fn ascii_to_bcd_cn(ascii_data: &[u8], size: usize) -> Result, ()> { + let mut bcd_output: Vec = Vec::with_capacity(size); assert!(ascii_data.len() <= size * 2); - const ASCII_CHARACTER_0 : u8 = 0x30; + const ASCII_CHARACTER_0: u8 = 0x30; for i in (0..ascii_data.len()).step_by(2) { let b1 = ascii_data[i] - ASCII_CHARACTER_0; @@ -65,20 +65,20 @@ pub fn ascii_to_bcd_cn(ascii_data : &[u8], size : usize) -> Result, ()> } //n = 00 00 00 01 23 45 -pub fn ascii_to_bcd_n(ascii_data : &[u8], size : usize) -> Result, ()> { - let mut bcd_output : Vec = Vec::with_capacity(size); +pub fn ascii_to_bcd_n(ascii_data: &[u8], size: usize) -> Result, ()> { + let mut bcd_output: Vec = Vec::with_capacity(size); assert!(ascii_data.len() <= size * 2); - const ASCII_CHARACTER_0 : u8 = 0x30; + const ASCII_CHARACTER_0: u8 = 0x30; - let mut ascii_data_aligned : Vec = Vec::new(); + let mut ascii_data_aligned: Vec = Vec::new(); if ascii_data.len() % 2 == 1 { ascii_data_aligned.push(ASCII_CHARACTER_0); } ascii_data_aligned.extend_from_slice(&ascii_data[..]); - for _ in ascii_data_aligned.len()/2..size { + for _ in ascii_data_aligned.len() / 2..size { let bcd_byte = 0x00; bcd_output.push(bcd_byte); } @@ -89,7 +89,7 @@ pub fn ascii_to_bcd_n(ascii_data : &[u8], size : usize) -> Result, ()> { return Err(()); } - let b2 = ascii_data_aligned[i+1] - ASCII_CHARACTER_0; + let b2 = ascii_data_aligned[i + 1] - ASCII_CHARACTER_0; if b2 > 0x9 { return Err(()); } @@ -102,4 +102,4 @@ pub fn ascii_to_bcd_n(ascii_data : &[u8], size : usize) -> Result, ()> { assert_eq!(bcd_output.len(), size); Ok(bcd_output) -} \ No newline at end of file +} diff --git a/emvpt/src/lib.rs b/emvpt/src/lib.rs index 3219ef7..6bde0c9 100644 --- a/emvpt/src/lib.rs +++ b/emvpt/src/lib.rs @@ -1,52 +1,69 @@ +use chrono::{Datelike, NaiveDate, Timelike, Utc}; +use hex; use hexplay::HexViewBuilder; -use iso7816_tlv::ber::{Tlv, Tag, Value}; +use iso7816_tlv::ber::{Tag, Tlv, Value}; +use log::{debug, info, trace, warn}; +use openssl::bn::BigNum; +use openssl::rsa::{Padding, Rsa}; +use openssl::sha; +use rand::prelude::*; +use rand::Rng; +use rand_chacha::ChaCha20Rng; +use regex::Regex; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::str; use std::convert::TryFrom; use std::convert::TryInto; use std::error; use std::fmt; -use serde::{Deserialize, Serialize}; use std::fs::{self}; -use log::{info, warn, debug, trace}; -use rand::prelude::*; -use rand_chacha::ChaCha20Rng; -use rand::{Rng}; -use openssl::rsa::{Rsa, Padding}; -use openssl::bn::BigNum; -use openssl::sha; -use hex; -use chrono::{NaiveDate, Timelike, Datelike, Utc}; -use regex::Regex; +use std::str; pub mod bcdutil; macro_rules! get_bit { - ($byte:expr, $bit:expr) => (if $byte & (1 << $bit) != 0 { true } else { false }); + ($byte:expr, $bit:expr) => { + if $byte & (1 << $bit) != 0 { + true + } else { + false + } + }; } macro_rules! set_bit { - ($byte:expr, $bit:expr, $bit_value:expr) => (if $bit_value == true { $byte |= 1 << $bit; } else { $byte &= !(1 << $bit); }); + ($byte:expr, $bit:expr, $bit_value:expr) => { + if $bit_value == true { + $byte |= 1 << $bit; + } else { + $byte &= !(1 << $bit); + } + }; } macro_rules! serialize_yaml { - ($file:expr, $static_resource:expr) => { serde_yaml::from_str(&fs::read_to_string($file).unwrap_or(String::from_utf8_lossy(include_bytes!($static_resource)).to_string())).unwrap() } + ($file:expr, $static_resource:expr) => { + serde_yaml::from_str( + &fs::read_to_string($file) + .unwrap_or(String::from_utf8_lossy(include_bytes!($static_resource)).to_string()), + ) + .unwrap() + }; } - #[derive(Debug, Clone)] pub struct Track1 { - pub primary_account_number : String, - pub last_name : String, - pub first_name : String, - pub expiry_year : String, - pub expiry_month : String, - pub service_code : String, - pub discretionary_data : String + pub primary_account_number: String, + pub last_name: String, + pub first_name: String, + pub expiry_year: String, + pub expiry_month: String, + pub service_code: String, + pub discretionary_data: String, } impl Track1 { - pub fn new(track_data : &str) -> Track1 { + pub fn new(track_data: &str) -> Track1 { // %B4321432143214321^Mc'Doe/JOHN^2609101123456789012345678901234? println!("Track: {}", track_data); let re = Regex::new(r"^(%B)?(\d+)\^(.+)?/(.+)?\^(\d{2})(\d{2})(\d{3})(\d+)\??$").unwrap(); @@ -59,12 +76,23 @@ impl Track1 { expiry_year: cap.get(5).unwrap().as_str().to_string(), expiry_month: cap.get(6).unwrap().as_str().to_string(), service_code: cap.get(7).unwrap().as_str().to_string(), - discretionary_data: cap.get(8).unwrap().as_str().to_string() + discretionary_data: cap.get(8).unwrap().as_str().to_string(), } } pub fn censor(&mut self) { - self.primary_account_number = self.primary_account_number.chars().enumerate().map(|(i, c)| if i >= 6 && i < self.primary_account_number.len()-4 { '*' } else { c }).collect(); + self.primary_account_number = self + .primary_account_number + .chars() + .enumerate() + .map(|(i, c)| { + if i >= 6 && i < self.primary_account_number.len() - 4 { + '*' + } else { + c + } + }) + .collect(); self.last_name = self.last_name.replace(|_c: char| true, "*"); self.first_name = self.first_name.replace(|_c: char| true, "*"); self.discretionary_data = self.discretionary_data.replace(|_c: char| true, "*"); @@ -73,21 +101,31 @@ impl Track1 { impl fmt::Display for Track1 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "%B{}^{}/{}^{}{}{}{}?", self.primary_account_number, self.last_name, self.first_name, self.expiry_year, self.expiry_month, self.service_code, self.discretionary_data) + write!( + f, + "%B{}^{}/{}^{}{}{}{}?", + self.primary_account_number, + self.last_name, + self.first_name, + self.expiry_year, + self.expiry_month, + self.service_code, + self.discretionary_data + ) } } #[derive(Debug, Clone)] pub struct Track2 { - pub primary_account_number : String, - pub expiry_year : String, - pub expiry_month : String, - pub service_code : String, - pub discretionary_data : String + pub primary_account_number: String, + pub expiry_year: String, + pub expiry_month: String, + pub service_code: String, + pub discretionary_data: String, } impl Track2 { - pub fn new(track_data : &str) -> Track2 { + pub fn new(track_data: &str) -> Track2 { // ;4321432143214321=2612101123456789123? println!("Track: {}", track_data); let re = Regex::new(r"^;?(\d+)=(\d{2})(\d{2})(\d{3})(\d+)\??$").unwrap(); @@ -98,29 +136,48 @@ impl Track2 { expiry_year: cap.get(2).unwrap().as_str().to_string(), expiry_month: cap.get(3).unwrap().as_str().to_string(), service_code: cap.get(4).unwrap().as_str().to_string(), - discretionary_data: cap.get(5).unwrap().as_str().to_string() + discretionary_data: cap.get(5).unwrap().as_str().to_string(), } } pub fn censor(&mut self) { - self.primary_account_number = self.primary_account_number.chars().enumerate().map(|(i, c)| if i >= 6 && i < self.primary_account_number.len()-4 { '*' } else { c }).collect(); + self.primary_account_number = self + .primary_account_number + .chars() + .enumerate() + .map(|(i, c)| { + if i >= 6 && i < self.primary_account_number.len() - 4 { + '*' + } else { + c + } + }) + .collect(); self.discretionary_data = self.discretionary_data.replace(|_c: char| true, "*"); } } impl fmt::Display for Track2 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, ";{}={}{}{}{}?", self.primary_account_number, self.expiry_year, self.expiry_month, self.service_code, self.discretionary_data) + write!( + f, + ";{}={}{}{}{}?", + self.primary_account_number, + self.expiry_year, + self.expiry_month, + self.service_code, + self.discretionary_data + ) } -} +} #[repr(u8)] #[derive(Deserialize, Serialize, Debug, Copy, Clone)] pub enum CryptogramType { // bits 6-7 are relevant ApplicationAuthenticationCryptogram = 0b0000_0000, // AAC, transaction declined - AuthorisationRequestCryptogram = 0b1000_0000, // ARQC, online authorisation requested - TransactionCertificate = 0b0100_0000 // TC, transaction approved + AuthorisationRequestCryptogram = 0b1000_0000, // ARQC, online authorisation requested + TransactionCertificate = 0b0100_0000, // TC, transaction approved } impl From for u8 { @@ -128,7 +185,7 @@ impl From for u8 { match orig { CryptogramType::ApplicationAuthenticationCryptogram => 0b0000_0000, CryptogramType::AuthorisationRequestCryptogram => 0b1000_0000, - CryptogramType::TransactionCertificate => 0b0100_0000 + CryptogramType::TransactionCertificate => 0b0100_0000, } } } @@ -141,12 +198,11 @@ impl TryFrom for CryptogramType { 0b0000_0000 => Ok(CryptogramType::ApplicationAuthenticationCryptogram), 0b1000_0000 => Ok(CryptogramType::AuthorisationRequestCryptogram), 0b0100_0000 => Ok(CryptogramType::TransactionCertificate), - _ => Err("Unknown code!") + _ => Err("Unknown code!"), } } } - #[repr(u8)] #[derive(Debug, Copy, Clone)] pub enum CvmCode { @@ -157,7 +213,7 @@ pub enum CvmCode { EncipheredPinOffline = 0b0000_0100, EncipheredPinOfflineAndSignature = 0b0000_0101, Signature = 0b0001_1110, - NoCvm = 0b0001_1111 + NoCvm = 0b0001_1111, } impl From for u8 { @@ -170,7 +226,7 @@ impl From for u8 { CvmCode::EncipheredPinOffline => 0b0000_0100, CvmCode::EncipheredPinOfflineAndSignature => 0b0000_0101, CvmCode::Signature => 0b0001_1110, - CvmCode::NoCvm => 0b0001_1111 + CvmCode::NoCvm => 0b0001_1111, } } } @@ -188,7 +244,7 @@ impl TryFrom for CvmCode { 0b0000_0101 => Ok(CvmCode::EncipheredPinOfflineAndSignature), 0b0001_1110 => Ok(CvmCode::Signature), 0b0001_1111 => Ok(CvmCode::NoCvm), - _ => Err("Unknown code!") + _ => Err("Unknown code!"), } } } @@ -205,7 +261,7 @@ pub enum CvmConditionCode { IccCurrencyUnderX = 0x06, IccCurrencyOverX = 0x07, IccCurrencyUnderY = 0x08, - IccCurrencyOverY = 0x09 + IccCurrencyOverY = 0x09, } impl From for u8 { @@ -220,7 +276,7 @@ impl From for u8 { CvmConditionCode::IccCurrencyUnderX => 0x06, CvmConditionCode::IccCurrencyOverX => 0x07, CvmConditionCode::IccCurrencyUnderY => 0x08, - CvmConditionCode::IccCurrencyOverY => 0x09 + CvmConditionCode::IccCurrencyOverY => 0x09, } } } @@ -240,47 +296,46 @@ impl TryFrom for CvmConditionCode { 0x07 => Ok(CvmConditionCode::IccCurrencyOverX), 0x08 => Ok(CvmConditionCode::IccCurrencyUnderY), 0x09 => Ok(CvmConditionCode::IccCurrencyOverY), - _ => Err("Unknown condition!") + _ => Err("Unknown condition!"), } } } #[derive(Debug, Copy, Clone)] pub struct CvmRule { - pub amount_x : u32, - pub amount_y : u32, - pub fail_if_unsuccessful : bool, - pub code : CvmCode, - pub condition : CvmConditionCode + pub amount_x: u32, + pub amount_y: u32, + pub fail_if_unsuccessful: bool, + pub code: CvmCode, + pub condition: CvmConditionCode, } impl CvmRule { - pub fn into_9f34_value(rule : Result) -> Vec { + pub fn into_9f34_value(rule: Result) -> Vec { // EMV Book 4, A4 CVM Results let rule_unwrapped = match rule { Ok(rule) => rule, - Err(rule) => rule + Err(rule) => rule, }; - let mut c : u8 = rule_unwrapped.code.try_into().unwrap(); - if ! rule_unwrapped.fail_if_unsuccessful { + let mut c: u8 = rule_unwrapped.code.try_into().unwrap(); + if !rule_unwrapped.fail_if_unsuccessful { c += 0b0100_0000; } - - let mut value : Vec = Vec::new(); + let mut value: Vec = Vec::new(); value.push(c); value.push(rule_unwrapped.condition.try_into().unwrap()); - let result : u8 = match rule { + let result: u8 = match rule { Ok(rule) => { match rule.code { CvmCode::Signature => 0x00, // unknown - _ => 0x02 // successful + _ => 0x02, // successful } - }, - Err(_) => 0x01 // failed + } + Err(_) => 0x01, // failed }; value.push(result); @@ -293,33 +348,33 @@ impl CvmRule { #[derive(Serialize, Deserialize, Debug)] pub struct Capabilities { - pub sda : bool, - pub dda : bool, - pub cda : bool, - pub plaintext_pin : bool, - pub enciphered_pin : bool, - pub terminal_risk_management : bool, - pub issuer_authentication : bool + pub sda: bool, + pub dda: bool, + pub cda: bool, + pub plaintext_pin: bool, + pub enciphered_pin: bool, + pub terminal_risk_management: bool, + pub issuer_authentication: bool, } #[derive(Debug)] pub struct UsageControl { - pub domestic_cash_transactions : bool, - pub international_cash_transactions : bool, - pub domestic_goods : bool, - pub international_goods : bool, - pub domestic_services : bool, - pub international_services : bool, - pub atms : bool, - pub terminals_other_than_atms : bool, - pub domestic_cashback : bool, - pub international_cashback : bool + pub domestic_cash_transactions: bool, + pub international_cash_transactions: bool, + pub domestic_goods: bool, + pub international_goods: bool, + pub domestic_services: bool, + pub international_services: bool, + pub atms: bool, + pub terminals_other_than_atms: bool, + pub domestic_cashback: bool, + pub international_cashback: bool, } impl From> for UsageControl { fn from(data: Vec) -> Self { - let b1 : u8 = data[0]; - let b2 : u8 = data[1]; + let b1: u8 = data[0]; + let b2: u8 = data[1]; UsageControl { domestic_cash_transactions: get_bit!(b1, 7), @@ -331,21 +386,20 @@ impl From> for UsageControl { atms: get_bit!(b1, 1), terminals_other_than_atms: get_bit!(b1, 0), domestic_cashback: get_bit!(b2, 7), - international_cashback: get_bit!(b2, 6) + international_cashback: get_bit!(b2, 6), } } } - #[derive(Debug)] pub struct Icc { - pub capabilities : Capabilities, - pub usage : UsageControl, - pub cvm_rules : Vec, - pub issuer_pk : Option, - pub icc_pk : Option, - pub icc_pin_pk : Option, - pub data_authentication : Option> + pub capabilities: Capabilities, + pub usage: UsageControl, + pub cvm_rules: Vec, + pub issuer_pk: Option, + pub icc_pk: Option, + pub icc_pin_pk: Option, + pub data_authentication: Option>, } impl Icc { @@ -357,7 +411,7 @@ impl Icc { plaintext_pin: false, enciphered_pin: false, terminal_risk_management: false, - issuer_authentication: false + issuer_authentication: false, }; let usage = UsageControl { @@ -370,10 +424,18 @@ impl Icc { atms: false, terminals_other_than_atms: false, domestic_cashback: false, - international_cashback: false + international_cashback: false, }; - Icc { capabilities : capabilities, usage : usage, cvm_rules : Vec::new(), issuer_pk : None, icc_pk : None, icc_pin_pk : None, data_authentication : None } + Icc { + capabilities: capabilities, + usage: usage, + cvm_rules: Vec::new(), + issuer_pk: None, + icc_pk: None, + icc_pin_pk: None, + data_authentication: None, + } } } @@ -381,36 +443,35 @@ impl Icc { #[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub struct TerminalTransactionQualifiers { //byte 1 - pub mag_stripe_mode_supported : bool, + pub mag_stripe_mode_supported: bool, //7bit rfu - pub emv_mode_supported : bool, - pub emv_contact_chip_supported : bool, - pub offline_only_reader : bool, - pub online_pin_supported : bool, - pub signature_supported : bool, - pub offline_data_authentication_for_online_authorizations_supported : bool, + pub emv_mode_supported: bool, + pub emv_contact_chip_supported: bool, + pub offline_only_reader: bool, + pub online_pin_supported: bool, + pub signature_supported: bool, + pub offline_data_authentication_for_online_authorizations_supported: bool, //byte 2 - pub online_cryptogram_required : bool, // transient value - pub cvm_required : bool, // transient value - pub contact_chip_offline_pin_supported : bool, + pub online_cryptogram_required: bool, // transient value + pub cvm_required: bool, // transient value + pub contact_chip_offline_pin_supported: bool, //5-1 bits RFU //byte 3 - pub issuer_update_processing_supported : bool, - pub consumer_device_cvm_supported : bool - //6-1 bits RFU + pub issuer_update_processing_supported: bool, + pub consumer_device_cvm_supported: bool, //6-1 bits RFU - //byte 4 RFU + //byte 4 RFU } impl From for Vec { fn from(ttq: TerminalTransactionQualifiers) -> Self { // EMV Contactless Book A, Table 5-4: Terminal Transaction Qualifier (TTQ) - let mut b1 : u8 = 0b0000_0000; - let mut b2 : u8 = 0b0000_0000; - let mut b3 : u8 = 0b0000_0000; - let b4 : u8 = 0b0000_0000; //byte 4 RFU + let mut b1: u8 = 0b0000_0000; + let mut b2: u8 = 0b0000_0000; + let mut b3: u8 = 0b0000_0000; + let b4: u8 = 0b0000_0000; //byte 4 RFU set_bit!(b1, 7, ttq.mag_stripe_mode_supported); //7 bit RFU @@ -419,7 +480,11 @@ impl From for Vec { set_bit!(b1, 3, ttq.offline_only_reader); set_bit!(b1, 2, ttq.online_pin_supported); set_bit!(b1, 1, ttq.signature_supported); - set_bit!(b1, 0, ttq.offline_data_authentication_for_online_authorizations_supported); + set_bit!( + b1, + 0, + ttq.offline_data_authentication_for_online_authorizations_supported + ); set_bit!(b2, 7, ttq.online_cryptogram_required); set_bit!(b2, 6, ttq.cvm_required); @@ -430,7 +495,7 @@ impl From for Vec { set_bit!(b3, 6, ttq.consumer_device_cvm_supported); //6-1 bits RFU - let mut value : Vec = Vec::new(); + let mut value: Vec = Vec::new(); value.push(b1); value.push(b2); value.push(b3); @@ -440,101 +505,200 @@ impl From for Vec { } } - // TODO: support EMV Book 4, A2 Terminal Capabilities (a.k.a. 9F33) #[derive(Serialize, Deserialize)] pub struct Terminal { - pub use_random : bool, - pub capabilities : Capabilities, - pub tvr : TerminalVerificationResults, - pub tsi : TransactionStatusInformation, - pub cryptogram_type : CryptogramType, - pub cryptogram_type_arqc : CryptogramType, - pub terminal_transaction_qualifiers : TerminalTransactionQualifiers + pub use_random: bool, + pub capabilities: Capabilities, + pub tvr: TerminalVerificationResults, + pub tsi: TransactionStatusInformation, + pub cryptogram_type: CryptogramType, + pub cryptogram_type_arqc: CryptogramType, + pub terminal_transaction_qualifiers: TerminalTransactionQualifiers, } // EMV Book 3, C5 Terminal Verification Results (TVR) #[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub struct TerminalVerificationResults { //TVR byte 1 - pub offline_data_authentication_was_not_performed : bool, - pub sda_failed : bool, - pub icc_data_missing : bool, - pub card_appears_on_terminal_exception_file : bool, - pub dda_failed : bool, - pub cda_failed : bool, + pub offline_data_authentication_was_not_performed: bool, + pub sda_failed: bool, + pub icc_data_missing: bool, + pub card_appears_on_terminal_exception_file: bool, + pub dda_failed: bool, + pub cda_failed: bool, //RFU //RFU // TVR byte 2 - pub icc_and_terminal_have_different_application_versions : bool, - pub expired_application : bool, - pub application_not_yet_effective : bool, - pub requested_service_not_allowed_for_card_product : bool, - pub new_card : bool, + pub icc_and_terminal_have_different_application_versions: bool, + pub expired_application: bool, + pub application_not_yet_effective: bool, + pub requested_service_not_allowed_for_card_product: bool, + pub new_card: bool, //RFU //RFU //RFU //TVR byte 3 - pub cardholder_verification_was_not_successful : bool, - pub unrecognised_cvm : bool, - pub pin_try_limit_exceeded : bool, - pub pin_entry_required_and_pin_pad_not_present_or_not_working : bool, - pub pin_entry_required_pin_pad_present_but_pin_was_not_entered : bool, - pub online_pin_entered : bool, + pub cardholder_verification_was_not_successful: bool, + pub unrecognised_cvm: bool, + pub pin_try_limit_exceeded: bool, + pub pin_entry_required_and_pin_pad_not_present_or_not_working: bool, + pub pin_entry_required_pin_pad_present_but_pin_was_not_entered: bool, + pub online_pin_entered: bool, //RFU //RFU //TVR byte 4 - pub transaction_exceeds_floor_limit : bool, - pub lower_consecutive_offline_limit_exceeded : bool, - pub upper_consecutive_offline_limit_exceeded : bool, - pub transaction_selected_randomly_for_online_processing : bool, - pub merchant_forced_transaction_online : bool, + pub transaction_exceeds_floor_limit: bool, + pub lower_consecutive_offline_limit_exceeded: bool, + pub upper_consecutive_offline_limit_exceeded: bool, + pub transaction_selected_randomly_for_online_processing: bool, + pub merchant_forced_transaction_online: bool, //RFU //RFU //RFU //TVR byte 5 - pub default_tdol_used : bool, - pub issuer_authentication_failed : bool, - pub script_processing_failed_before_final_generate_ac : bool, - pub script_processing_failed_after_final_generate_ac : bool - //RFU - //RFU - //RFU - //RFU + pub default_tdol_used: bool, + pub issuer_authentication_failed: bool, + pub script_processing_failed_before_final_generate_ac: bool, + pub script_processing_failed_after_final_generate_ac: bool, //RFU + //RFU + //RFU + //RFU } impl TerminalVerificationResults { - pub fn action_code_matches(tvr : &TerminalVerificationResults, iac : &TerminalVerificationResults, tac : &TerminalVerificationResults) -> bool { - if tvr.offline_data_authentication_was_not_performed && (iac.offline_data_authentication_was_not_performed || tac.offline_data_authentication_was_not_performed) { return true; } - if tvr.sda_failed && (iac.sda_failed || tac.sda_failed) { return true; } - if tvr.icc_data_missing && (iac.icc_data_missing || tac.icc_data_missing) { return true; } - if tvr.card_appears_on_terminal_exception_file && (iac.card_appears_on_terminal_exception_file || tac.card_appears_on_terminal_exception_file) { return true; } - if tvr.dda_failed && (iac.dda_failed || tac.dda_failed) { return true; } - if tvr.cda_failed && (iac.cda_failed || tac.cda_failed) { return true; } - if tvr.icc_and_terminal_have_different_application_versions && (iac.icc_and_terminal_have_different_application_versions || tac.icc_and_terminal_have_different_application_versions) { return true; } - if tvr.expired_application && (iac.expired_application || tac.expired_application) { return true; } - if tvr.application_not_yet_effective && (iac.application_not_yet_effective || tac.application_not_yet_effective) { return true; } - if tvr.requested_service_not_allowed_for_card_product && (iac.requested_service_not_allowed_for_card_product || tac.requested_service_not_allowed_for_card_product) { return true; } - if tvr.new_card && (iac.new_card || tac.new_card) { return true; } - if tvr.cardholder_verification_was_not_successful && (iac.cardholder_verification_was_not_successful || tac.cardholder_verification_was_not_successful) { return true; } - if tvr.unrecognised_cvm && (iac.unrecognised_cvm || tac.unrecognised_cvm) { return true; } - if tvr.pin_try_limit_exceeded && (iac.pin_try_limit_exceeded || tac.pin_try_limit_exceeded) { return true; } - if tvr.pin_entry_required_and_pin_pad_not_present_or_not_working && (iac.pin_entry_required_and_pin_pad_not_present_or_not_working || tac.pin_entry_required_and_pin_pad_not_present_or_not_working) { return true; } - if tvr.pin_entry_required_pin_pad_present_but_pin_was_not_entered && (iac.pin_entry_required_pin_pad_present_but_pin_was_not_entered || tac.pin_entry_required_pin_pad_present_but_pin_was_not_entered) { return true; } - if tvr.online_pin_entered && (iac.online_pin_entered || tac.online_pin_entered) { return true; } - if tvr.transaction_exceeds_floor_limit && (iac.transaction_exceeds_floor_limit || tac.transaction_exceeds_floor_limit) { return true; } - if tvr.lower_consecutive_offline_limit_exceeded && (iac.lower_consecutive_offline_limit_exceeded || tac.lower_consecutive_offline_limit_exceeded) { return true; } - if tvr.upper_consecutive_offline_limit_exceeded && (iac.upper_consecutive_offline_limit_exceeded || tac.upper_consecutive_offline_limit_exceeded) { return true; } - if tvr.transaction_selected_randomly_for_online_processing && (iac.transaction_selected_randomly_for_online_processing || tac.transaction_selected_randomly_for_online_processing) { return true; } - if tvr.merchant_forced_transaction_online && (iac.merchant_forced_transaction_online || tac.merchant_forced_transaction_online) { return true; } - if tvr.default_tdol_used && (iac.default_tdol_used || tac.default_tdol_used) { return true; } - if tvr.issuer_authentication_failed && (iac.issuer_authentication_failed || tac.issuer_authentication_failed) { return true; } - if tvr.script_processing_failed_before_final_generate_ac && (iac.script_processing_failed_before_final_generate_ac || tac.script_processing_failed_before_final_generate_ac) { return true; } - if tvr.script_processing_failed_after_final_generate_ac && (iac.script_processing_failed_after_final_generate_ac || tac.script_processing_failed_after_final_generate_ac) { return true; } + pub fn action_code_matches( + tvr: &TerminalVerificationResults, + iac: &TerminalVerificationResults, + tac: &TerminalVerificationResults, + ) -> bool { + if tvr.offline_data_authentication_was_not_performed + && (iac.offline_data_authentication_was_not_performed + || tac.offline_data_authentication_was_not_performed) + { + return true; + } + if tvr.sda_failed && (iac.sda_failed || tac.sda_failed) { + return true; + } + if tvr.icc_data_missing && (iac.icc_data_missing || tac.icc_data_missing) { + return true; + } + if tvr.card_appears_on_terminal_exception_file + && (iac.card_appears_on_terminal_exception_file + || tac.card_appears_on_terminal_exception_file) + { + return true; + } + if tvr.dda_failed && (iac.dda_failed || tac.dda_failed) { + return true; + } + if tvr.cda_failed && (iac.cda_failed || tac.cda_failed) { + return true; + } + if tvr.icc_and_terminal_have_different_application_versions + && (iac.icc_and_terminal_have_different_application_versions + || tac.icc_and_terminal_have_different_application_versions) + { + return true; + } + if tvr.expired_application && (iac.expired_application || tac.expired_application) { + return true; + } + if tvr.application_not_yet_effective + && (iac.application_not_yet_effective || tac.application_not_yet_effective) + { + return true; + } + if tvr.requested_service_not_allowed_for_card_product + && (iac.requested_service_not_allowed_for_card_product + || tac.requested_service_not_allowed_for_card_product) + { + return true; + } + if tvr.new_card && (iac.new_card || tac.new_card) { + return true; + } + if tvr.cardholder_verification_was_not_successful + && (iac.cardholder_verification_was_not_successful + || tac.cardholder_verification_was_not_successful) + { + return true; + } + if tvr.unrecognised_cvm && (iac.unrecognised_cvm || tac.unrecognised_cvm) { + return true; + } + if tvr.pin_try_limit_exceeded && (iac.pin_try_limit_exceeded || tac.pin_try_limit_exceeded) + { + return true; + } + if tvr.pin_entry_required_and_pin_pad_not_present_or_not_working + && (iac.pin_entry_required_and_pin_pad_not_present_or_not_working + || tac.pin_entry_required_and_pin_pad_not_present_or_not_working) + { + return true; + } + if tvr.pin_entry_required_pin_pad_present_but_pin_was_not_entered + && (iac.pin_entry_required_pin_pad_present_but_pin_was_not_entered + || tac.pin_entry_required_pin_pad_present_but_pin_was_not_entered) + { + return true; + } + if tvr.online_pin_entered && (iac.online_pin_entered || tac.online_pin_entered) { + return true; + } + if tvr.transaction_exceeds_floor_limit + && (iac.transaction_exceeds_floor_limit || tac.transaction_exceeds_floor_limit) + { + return true; + } + if tvr.lower_consecutive_offline_limit_exceeded + && (iac.lower_consecutive_offline_limit_exceeded + || tac.lower_consecutive_offline_limit_exceeded) + { + return true; + } + if tvr.upper_consecutive_offline_limit_exceeded + && (iac.upper_consecutive_offline_limit_exceeded + || tac.upper_consecutive_offline_limit_exceeded) + { + return true; + } + if tvr.transaction_selected_randomly_for_online_processing + && (iac.transaction_selected_randomly_for_online_processing + || tac.transaction_selected_randomly_for_online_processing) + { + return true; + } + if tvr.merchant_forced_transaction_online + && (iac.merchant_forced_transaction_online || tac.merchant_forced_transaction_online) + { + return true; + } + if tvr.default_tdol_used && (iac.default_tdol_used || tac.default_tdol_used) { + return true; + } + if tvr.issuer_authentication_failed + && (iac.issuer_authentication_failed || tac.issuer_authentication_failed) + { + return true; + } + if tvr.script_processing_failed_before_final_generate_ac + && (iac.script_processing_failed_before_final_generate_ac + || tac.script_processing_failed_before_final_generate_ac) + { + return true; + } + if tvr.script_processing_failed_after_final_generate_ac + && (iac.script_processing_failed_after_final_generate_ac + || tac.script_processing_failed_after_final_generate_ac) + { + return true; + } false } @@ -542,11 +706,11 @@ impl TerminalVerificationResults { impl From> for TerminalVerificationResults { fn from(data: Vec) -> Self { - let b1 : u8 = data[0]; - let b2 : u8 = data[1]; - let b3 : u8 = data[2]; - let b4 : u8 = data[3]; - let b5 : u8 = data[4]; + let b1: u8 = data[0]; + let b2: u8 = data[1]; + let b3: u8 = data[2]; + let b4: u8 = data[3]; + let b5: u8 = data[4]; TerminalVerificationResults { offline_data_authentication_was_not_performed: get_bit!(b1, 7), @@ -574,19 +738,18 @@ impl From> for TerminalVerificationResults { default_tdol_used: get_bit!(b5, 7), issuer_authentication_failed: get_bit!(b5, 6), script_processing_failed_before_final_generate_ac: get_bit!(b5, 5), - script_processing_failed_after_final_generate_ac: get_bit!(b5, 4) + script_processing_failed_after_final_generate_ac: get_bit!(b5, 4), } } } - impl From for Vec { fn from(tvr: TerminalVerificationResults) -> Self { - let mut b1 : u8 = 0b0000_0000; - let mut b2 : u8 = 0b0000_0000; - let mut b3 : u8 = 0b0000_0000; - let mut b4 : u8 = 0b0000_0000; - let mut b5 : u8 = 0b0000_0000; + let mut b1: u8 = 0b0000_0000; + let mut b2: u8 = 0b0000_0000; + let mut b3: u8 = 0b0000_0000; + let mut b4: u8 = 0b0000_0000; + let mut b5: u8 = 0b0000_0000; set_bit!(b1, 7, tvr.offline_data_authentication_was_not_performed); set_bit!(b1, 6, tvr.sda_failed); @@ -595,7 +758,11 @@ impl From for Vec { set_bit!(b1, 3, tvr.dda_failed); set_bit!(b1, 2, tvr.cda_failed); - set_bit!(b2, 7, tvr.icc_and_terminal_have_different_application_versions); + set_bit!( + b2, + 7, + tvr.icc_and_terminal_have_different_application_versions + ); set_bit!(b2, 6, tvr.expired_application); set_bit!(b2, 5, tvr.application_not_yet_effective); set_bit!(b2, 4, tvr.requested_service_not_allowed_for_card_product); @@ -604,14 +771,26 @@ impl From for Vec { set_bit!(b3, 7, tvr.cardholder_verification_was_not_successful); set_bit!(b3, 6, tvr.unrecognised_cvm); set_bit!(b3, 5, tvr.pin_try_limit_exceeded); - set_bit!(b3, 4, tvr.pin_entry_required_and_pin_pad_not_present_or_not_working); - set_bit!(b3, 3, tvr.pin_entry_required_pin_pad_present_but_pin_was_not_entered); + set_bit!( + b3, + 4, + tvr.pin_entry_required_and_pin_pad_not_present_or_not_working + ); + set_bit!( + b3, + 3, + tvr.pin_entry_required_pin_pad_present_but_pin_was_not_entered + ); set_bit!(b3, 2, tvr.online_pin_entered); set_bit!(b4, 7, tvr.transaction_exceeds_floor_limit); set_bit!(b4, 6, tvr.lower_consecutive_offline_limit_exceeded); set_bit!(b4, 5, tvr.upper_consecutive_offline_limit_exceeded); - set_bit!(b4, 4, tvr.transaction_selected_randomly_for_online_processing); + set_bit!( + b4, + 4, + tvr.transaction_selected_randomly_for_online_processing + ); set_bit!(b4, 3, tvr.merchant_forced_transaction_online); set_bit!(b5, 7, tvr.default_tdol_used); @@ -619,7 +798,7 @@ impl From for Vec { set_bit!(b5, 5, tvr.script_processing_failed_before_final_generate_ac); set_bit!(b5, 4, tvr.script_processing_failed_after_final_generate_ac); - let mut output : Vec = Vec::new(); + let mut output: Vec = Vec::new(); output.push(b1); output.push(b2); output.push(b3); @@ -634,22 +813,21 @@ impl From for Vec { #[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub struct TransactionStatusInformation { //TSI byte 1 - pub offline_data_authentication_was_performed : bool, - pub cardholder_verification_was_performed : bool, - pub card_risk_management_was_performed : bool, - pub issuer_authentication_was_performed : bool, - pub terminal_risk_management_was_performed : bool, - pub script_processing_was_performed : bool - //RFU - //RFU - - //TSI byte 2 - RFU + pub offline_data_authentication_was_performed: bool, + pub cardholder_verification_was_performed: bool, + pub card_risk_management_was_performed: bool, + pub issuer_authentication_was_performed: bool, + pub terminal_risk_management_was_performed: bool, + pub script_processing_was_performed: bool, //RFU + //RFU + + //TSI byte 2 - RFU } impl From for Vec { fn from(tsi: TransactionStatusInformation) -> Self { - let mut b1 : u8 = 0b0000_0000; - let b2 : u8 = 0b0000_0000; + let mut b1: u8 = 0b0000_0000; + let b2: u8 = 0b0000_0000; set_bit!(b1, 7, tsi.offline_data_authentication_was_performed); set_bit!(b1, 6, tsi.cardholder_verification_was_performed); @@ -658,7 +836,7 @@ impl From for Vec { set_bit!(b1, 3, tsi.terminal_risk_management_was_performed); set_bit!(b1, 2, tsi.script_processing_was_performed); - let mut output : Vec = Vec::new(); + let mut output: Vec = Vec::new(); output.push(b1); output.push(b2); @@ -668,54 +846,60 @@ impl From for Vec { #[derive(Serialize, Deserialize)] pub struct ConfigurationFiles { - emv_tags : String, - scheme_ca_public_keys : String, - constants : String + emv_tags: String, + scheme_ca_public_keys: String, + constants: String, } #[derive(Serialize, Deserialize)] pub struct Settings { - pub censor_sensitive_fields : bool, - configuration_files : ConfigurationFiles, - pub terminal : Terminal, - default_tags : HashMap + pub censor_sensitive_fields: bool, + configuration_files: ConfigurationFiles, + pub terminal: Terminal, + default_tags: HashMap, } #[derive(Serialize, Deserialize)] pub struct Constants { - pub numeric_country_codes : HashMap, - pub numeric_currency_codes : HashMap, - pub apdu_status_codes : HashMap + pub numeric_country_codes: HashMap, + pub numeric_currency_codes: HashMap, + pub apdu_status_codes: HashMap, } pub trait ApduInterface { - fn send_apdu(&self, apdu : &[u8]) -> Result, ()>; + fn send_apdu(&self, apdu: &[u8]) -> Result, ()>; } pub struct DataObject { - pub emv_tag : EmvTag, - pub length : usize + pub emv_tag: EmvTag, + pub length: usize, } impl DataObject { - pub fn new(emv_connection : &EmvConnection, tag_name : &str, length : usize) -> DataObject { + pub fn new(emv_connection: &EmvConnection, tag_name: &str, length: usize) -> DataObject { DataObject { - emv_tag: emv_connection.get_emv_tag(tag_name) - .unwrap_or(&EmvTag::new(tag_name)).clone(), - length: length + emv_tag: emv_connection + .get_emv_tag(tag_name) + .unwrap_or(&EmvTag::new(tag_name)) + .clone(), + length: length, } } } pub struct DataObjectList { - data_objects : Vec + data_objects: Vec, } impl fmt::Display for DataObjectList { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for data_object in &self.data_objects { - if let Err(e) = write!(f, "{} - {} ({}b); ", data_object.emv_tag.tag, data_object.emv_tag.name, data_object.length) { - return Err(e) + if let Err(e) = write!( + f, + "{} - {} ({}b); ", + data_object.emv_tag.tag, data_object.emv_tag.name, data_object.length + ) { + return Err(e); } } @@ -726,7 +910,7 @@ impl fmt::Display for DataObjectList { // EMV Book 3, 5.4 Rules for Using a Data Object List (DOL) impl DataObjectList { // EMV has some tags that don't conform to ISO/IEC 7816 - fn is_non_conforming_one_byte_tag(tag : u8) -> bool { + fn is_non_conforming_one_byte_tag(tag: u8) -> bool { if tag == 0x95 { return true; } @@ -736,16 +920,19 @@ impl DataObjectList { fn new() -> DataObjectList { DataObjectList { - data_objects: Vec::new() + data_objects: Vec::new(), } } - fn push(&mut self, data_object : DataObject) { + fn push(&mut self, data_object: DataObject) { self.data_objects.push(data_object); } - pub fn process_data_object_list(emv_connection : &EmvConnection, tag_list : &[u8]) -> Result { - let mut dol : DataObjectList = DataObjectList::new(); + pub fn process_data_object_list( + emv_connection: &EmvConnection, + tag_list: &[u8], + ) -> Result { + let mut dol: DataObjectList = DataObjectList::new(); // FIXME: This parsing is complete BS // - Check if "Tag List" and "Data Object List" are same or different types @@ -756,17 +943,19 @@ impl DataObjectList { } else { let mut i = 0; loop { - let tag_value_length : usize; + let tag_value_length: usize; - let mut tag_name = hex::encode(&tag_list[i..i+1]).to_uppercase(); + let mut tag_name = hex::encode(&tag_list[i..i + 1]).to_uppercase(); - if Tag::try_from(tag_name.as_str()).is_ok() || DataObjectList::is_non_conforming_one_byte_tag(tag_list[i]) { - tag_value_length = tag_list[i+1] as usize; + if Tag::try_from(tag_name.as_str()).is_ok() + || DataObjectList::is_non_conforming_one_byte_tag(tag_list[i]) + { + tag_value_length = tag_list[i + 1] as usize; i += 2; } else { - tag_name = hex::encode(&tag_list[i..i+2]).to_uppercase(); + tag_name = hex::encode(&tag_list[i..i + 2]).to_uppercase(); if Tag::try_from(tag_name.as_str()).is_ok() { - tag_value_length = tag_list[i+2] as usize; + tag_value_length = tag_list[i + 2] as usize; i += 3; } else { warn!("Incorrect tag {:?}", tag_name); @@ -775,7 +964,7 @@ impl DataObjectList { } dol.push(DataObject::new(emv_connection, &tag_name, tag_value_length)); - + if i >= tag_list.len() { break; } @@ -785,71 +974,86 @@ impl DataObjectList { Ok(dol) } - pub fn get_tag_list_tag_values(&self, emv_connection : &EmvConnection) -> Vec { - let mut output : Vec = Vec::new(); + pub fn get_tag_list_tag_values(&self, emv_connection: &EmvConnection) -> Vec { + let mut output: Vec = Vec::new(); for data_object in &self.data_objects { - let default_value : Vec = vec![0; data_object.length]; + let default_value: Vec = vec![0; data_object.length]; let value = match emv_connection.get_tag_value(&data_object.emv_tag.tag) { Some(value) => value, None => { - debug!("tag {:?} has no value, filling with zeros", data_object.emv_tag.tag); - + debug!( + "tag {:?} has no value, filling with zeros", + data_object.emv_tag.tag + ); + &default_value } }; // TODO: we need to understand tag metadata (is tag "numeric" etc) in order to properly truncate/pad the tag if data_object.length > 0 && value.len() != data_object.length { - warn!("tag {:?} value length {:02X} does not match tag list value length {:02X}", data_object.emv_tag.tag, value.len(), data_object.length); + warn!( + "tag {:?} value length {:02X} does not match tag list value length {:02X}", + data_object.emv_tag.tag, + value.len(), + data_object.length + ); //return Err(()); } let mut len = data_object.length; - if len == 0 { // at least 9F4A does not provide length information + if len == 0 { + // at least 9F4A does not provide length information len = value.len(); } output.extend_from_slice(&value[..len]); } - output } } pub struct EmvConnection<'a> { - pub tags : HashMap>, - pub interface : Option<&'a dyn ApduInterface>, - pub contactless : bool, - emv_tags : HashMap, - constants : Constants, - pub settings : Settings, - pub icc : Icc, - pub pin_callback : Option<&'a dyn Fn() -> Result>, - pub amount_callback : Option<&'a dyn Fn() -> Result>, - pub pse_application_select_callback : Option<&'a dyn Fn(&Vec) -> Result>, - pub start_transaction_callback : Option<&'a dyn Fn(&mut EmvConnection) -> Result<(), ()>> + pub tags: HashMap>, + pub interface: Option<&'a dyn ApduInterface>, + pub contactless: bool, + emv_tags: HashMap, + constants: Constants, + pub settings: Settings, + pub icc: Icc, + pub pin_callback: Option<&'a dyn Fn() -> Result>, + pub amount_callback: Option<&'a dyn Fn() -> Result>, + pub pse_application_select_callback: + Option<&'a dyn Fn(&Vec) -> Result>, + pub start_transaction_callback: Option<&'a dyn Fn(&mut EmvConnection) -> Result<(), ()>>, } impl EmvConnection<'_> { - pub fn new(settings_file : &str) -> Result, String> { - - let settings : Settings = serialize_yaml!(settings_file, "config/settings.yaml"); - let emv_tags = serialize_yaml!(settings.configuration_files.emv_tags.clone(), "config/emv_tags.yaml"); - let constants = serialize_yaml!(settings.configuration_files.constants.clone(), "config/constants.yaml"); - - Ok ( EmvConnection { - tags : HashMap::new(), - emv_tags : emv_tags, - constants : constants, - settings : settings, - icc : Icc::new(), - interface : None, - contactless : false, - pin_callback : None, - amount_callback : None, - pse_application_select_callback : None, - start_transaction_callback : None } ) + pub fn new(settings_file: &str) -> Result, String> { + let settings: Settings = serialize_yaml!(settings_file, "config/settings.yaml"); + let emv_tags = serialize_yaml!( + settings.configuration_files.emv_tags.clone(), + "config/emv_tags.yaml" + ); + let constants = serialize_yaml!( + settings.configuration_files.constants.clone(), + "config/constants.yaml" + ); + + Ok(EmvConnection { + tags: HashMap::new(), + emv_tags: emv_tags, + constants: constants, + settings: settings, + icc: Icc::new(), + interface: None, + contactless: false, + pin_callback: None, + amount_callback: None, + pse_application_select_callback: None, + start_transaction_callback: None, + }) } pub fn print_tags(&self) { @@ -857,7 +1061,12 @@ impl EmvConnection<'_> { for (key, value) in &self.tags { i += 1; let emv_tag = self.emv_tags.get(key); - info!("{:02}. tag: {} - {}", i, key, emv_tag.unwrap_or(&EmvTag::new(&key.clone())).name); + info!( + "{:02}. tag: {} - {}", + i, + key, + emv_tag.unwrap_or(&EmvTag::new(&key.clone())).name + ); self.print_tag_value(&emv_tag, value, 0); } } @@ -870,21 +1079,31 @@ impl EmvConnection<'_> { self.tags.get(tag_name) } - pub fn add_tag(&mut self, tag_name : &str, value : Vec) { + pub fn add_tag(&mut self, tag_name: &str, value: Vec) { let old_tag = self.tags.get(tag_name); if old_tag.is_some() { if self.settings.censor_sensitive_fields { - trace!("Overriding tag {:?}. Old size: {}, new size: {}", tag_name, old_tag.unwrap().len(), value.len()); + trace!( + "Overriding tag {:?}. Old size: {}, new size: {}", + tag_name, + old_tag.unwrap().len(), + value.len() + ); } else { - trace!("Overriding tag {:?} from {:02X?} to {:02X?}", tag_name, old_tag.unwrap(), value); + trace!( + "Overriding tag {:?} from {:02X?} to {:02X?}", + tag_name, + old_tag.unwrap(), + value + ); } } self.tags.insert(tag_name.to_string(), value); } - pub fn process_tag_as_tlv(&mut self, tag_name : &str, value : Vec) { - let mut tlv : Vec = Vec::new(); + pub fn process_tag_as_tlv(&mut self, tag_name: &str, value: Vec) { + let mut tlv: Vec = Vec::new(); tlv.extend_from_slice(&hex::decode(&tag_name).unwrap()[..]); if value.len() >= 0x80 { tlv.push(0x81 as u8); @@ -895,13 +1114,13 @@ impl EmvConnection<'_> { self.process_tlv(&tlv[..], 1); } - fn send_apdu_select(&mut self, aid : &[u8]) -> (Vec, Vec) { + fn send_apdu_select(&mut self, aid: &[u8]) -> (Vec, Vec) { //ref. EMV Book 1, 11.3.2 Command message self.tags.clear(); let apdu_command_select = b"\x00\xA4"; - let p1_reference_control_parameter : u8 = 0b0000_0100; // "Select by name" - let p2_selection_options : u8 = 0b0000_0000; // "First or only occurrence" + let p1_reference_control_parameter: u8 = 0b0000_0100; // "Select by name" + let p2_selection_options: u8 = 0b0000_0000; // "First or only occurrence" let mut select_command = apdu_command_select.to_vec(); select_command.push(p1_reference_control_parameter); @@ -913,15 +1132,21 @@ impl EmvConnection<'_> { self.send_apdu(&select_command) } - fn get_apdu_response_localization(&self, apdu_status : &[u8]) -> String { + fn get_apdu_response_localization(&self, apdu_status: &[u8]) -> String { assert_eq!(apdu_status.len(), 2); let response_status_code = hex::encode_upper(apdu_status); - let response_localization : String; - if let Some(response_description) = self.constants.apdu_status_codes.get(&response_status_code) { + let response_localization: String; + if let Some(response_description) = + self.constants.apdu_status_codes.get(&response_status_code) + { response_localization = format!("{} - {}", response_status_code, response_description); - } else if let Some(response_description) = self.constants.apdu_status_codes.get(&response_status_code[0..2]) { + } else if let Some(response_description) = self + .constants + .apdu_status_codes + .get(&response_status_code[0..2]) + { response_localization = format!("{} - {}", response_status_code, response_description); } else { response_localization = format!("{}", response_status_code); @@ -930,10 +1155,9 @@ impl EmvConnection<'_> { response_localization } - pub fn send_apdu<'apdu>(&mut self, apdu : &'apdu [u8]) -> (Vec, Vec) { - - let mut response_data : Vec = Vec::new(); - let mut response_trailer : Vec; + pub fn send_apdu<'apdu>(&mut self, apdu: &'apdu [u8]) -> (Vec, Vec) { + let mut response_data: Vec = Vec::new(); + let mut response_trailer: Vec; let mut new_apdu_command; let mut apdu_command = apdu; @@ -941,26 +1165,43 @@ impl EmvConnection<'_> { loop { // Send an APDU command. if self.settings.censor_sensitive_fields { - debug!("Sending APDU: {:02X?}... ({} bytes)", &apdu_command[0..5], apdu_command.len()); + debug!( + "Sending APDU: {:02X?}... ({} bytes)", + &apdu_command[0..5], + apdu_command.len() + ); } else { - debug!("Sending APDU:\n{}", HexViewBuilder::new(&apdu_command).finish()); + debug!( + "Sending APDU:\n{}", + HexViewBuilder::new(&apdu_command).finish() + ); } let apdu_response = self.interface.unwrap().send_apdu(apdu_command).unwrap(); - response_data.extend_from_slice(&apdu_response[0..apdu_response.len()-2]); + response_data.extend_from_slice(&apdu_response[0..apdu_response.len() - 2]); // response codes: https://www.eftlab.com/knowledge-base/complete-list-of-apdu-responses/ - response_trailer = vec![apdu_response[apdu_response.len()-2], apdu_response[apdu_response.len()-1]]; - - debug!("APDU response status: {}", self.get_apdu_response_localization(&response_trailer[..])); + response_trailer = vec![ + apdu_response[apdu_response.len() - 2], + apdu_response[apdu_response.len() - 1], + ]; + + debug!( + "APDU response status: {}", + self.get_apdu_response_localization(&response_trailer[..]) + ); // Automatically query more data, if available from the ICC - const SW1_BYTES_AVAILABLE : u8 = 0x61; - const SW1_WRONG_LENGTH : u8 = 0x6C; + const SW1_BYTES_AVAILABLE: u8 = 0x61; + const SW1_WRONG_LENGTH: u8 = 0x6C; if response_trailer[0] == SW1_BYTES_AVAILABLE { - trace!("APDU response({} bytes):\n{}", response_data.len(), HexViewBuilder::new(&response_data).finish()); + trace!( + "APDU response({} bytes):\n{}", + response_data.len(), + HexViewBuilder::new(&response_data).finish() + ); let mut available_data_length = response_trailer[1]; @@ -976,7 +1217,11 @@ impl EmvConnection<'_> { apdu_command = &new_apdu_command[..]; } else if response_trailer[0] == SW1_WRONG_LENGTH { - trace!("APDU response({} bytes):\n{}", response_data.len(), HexViewBuilder::new(&response_data).finish()); + trace!( + "APDU response({} bytes):\n{}", + response_data.len(), + HexViewBuilder::new(&response_data).finish() + ); let available_data_length = response_trailer[1]; assert!(available_data_length > 0x00); @@ -994,7 +1239,11 @@ impl EmvConnection<'_> { if self.settings.censor_sensitive_fields { debug!("APDU response({} bytes)", response_data.len()); } else { - debug!("APDU response({} bytes):\n{}", response_data.len(), HexViewBuilder::new(&response_data).finish()); + debug!( + "APDU response({} bytes):\n{}", + response_data.len(), + HexViewBuilder::new(&response_data).finish() + ); } if !response_data.is_empty() { @@ -1006,60 +1255,69 @@ impl EmvConnection<'_> { (response_trailer, response_data) } - fn print_tag(&self, emv_tag : &EmvTag, level: u8) { + fn print_tag(&self, emv_tag: &EmvTag, level: u8) { let mut padding = String::with_capacity(level as usize); for _ in 0..level { padding.push(' '); } debug!("{}-{}: {}", padding, emv_tag.tag, emv_tag.name); } - fn print_tag_value(&self, emv_tag : &Option<&EmvTag>, v : &Vec, level: u8) { + fn print_tag_value(&self, emv_tag: &Option<&EmvTag>, v: &Vec, level: u8) { let mut padding = String::with_capacity(level as usize); for _ in 0..level { padding.push(' '); } - let mut value : String = String::from_utf8_lossy(&v).replace(|c: char| !(c.is_ascii_alphanumeric() || c.is_ascii_punctuation()), "."); + let mut value: String = String::from_utf8_lossy(&v).replace( + |c: char| !(c.is_ascii_alphanumeric() || c.is_ascii_punctuation()), + ".", + ); if let Some(tag) = emv_tag { match tag.format { Some(FieldFormat::CompressedNumeric) => { - value = format!("{:02X?}", v).replace(|c: char| !(c.is_ascii_alphanumeric()), "").trim_start_matches('0').to_string(); - }, + value = format!("{:02X?}", v) + .replace(|c: char| !(c.is_ascii_alphanumeric()), "") + .trim_start_matches('0') + .to_string(); + } Some(FieldFormat::Alphanumeric) | Some(FieldFormat::AlphanumericSpecial) => { value = String::from_utf8_lossy(&v).to_string(); - }, + } Some(FieldFormat::TerminalVerificationResults) => { - let tvr : TerminalVerificationResults = v.to_vec().into(); - value = format!("{:08b} {:08b} {:08b} {:08b} {:08b} => {:#?}", v[0], v[1], v[2], v[3], v[4], tvr); - }, + let tvr: TerminalVerificationResults = v.to_vec().into(); + value = format!( + "{:08b} {:08b} {:08b} {:08b} {:08b} => {:#?}", + v[0], v[1], v[2], v[3], v[4], tvr + ); + } Some(FieldFormat::ApplicationUsageControl) => { - let auc : UsageControl = v.to_vec().into(); + let auc: UsageControl = v.to_vec().into(); value = format!("{:08b} {:08b} => {:#?}", v[0], v[1], auc); - }, + } Some(FieldFormat::KeyCertificate) => { value = format!("{} bit key", v.len() * 8); - }, + } Some(FieldFormat::ServiceCodeIso7813) => { - let position_1_interchange : String = match v[0] { + let position_1_interchange: String = match v[0] { 1 => "International".to_string(), 2 => "International (prefer ICC)".to_string(), 5 => "National".to_string(), 6 => "National (prefer ICC)".to_string(), 7 => "Private".to_string(), 9 => "Test".to_string(), - _ => format!("N/A {}", v[0]) + _ => format!("N/A {}", v[0]), }; - let position_2 : u8 = v[1] >> 4; - let position_2_authorization_processing : String = match position_2 { + let position_2: u8 = v[1] >> 4; + let position_2_authorization_processing: String = match position_2 { 0 => "Normal".to_string(), 2 => "By Issuer".to_string(), 4 => "By Issuer (unless bileteral agreement exists)".to_string(), - _ => format!("N/A {}", position_2) + _ => format!("N/A {}", position_2), }; - let position_3 : u8 = v[1] & 0b0000_1111; - let position_3_allowed_services : String = match position_3 { + let position_3: u8 = v[1] & 0b0000_1111; + let position_3_allowed_services: String = match position_3 { 0 => "No restrictions (PIN required)".to_string(), 1 => "No restrictions".to_string(), 2 => "Goods and services only".to_string(), @@ -1068,45 +1326,66 @@ impl EmvConnection<'_> { 5 => "Goods and services only (PIN required)".to_string(), 6 => "No restrictions (PIN prompt if PED)".to_string(), 7 => "Goods and services only (PIN prompt if PED)".to_string(), - _ => format!("N/A {}", position_3) + _ => format!("N/A {}", position_3), }; - value = format!("{}{:02X} - {}, {}, {}", v[0], v[1], position_1_interchange, position_2_authorization_processing, position_3_allowed_services); - }, + value = format!( + "{}{:02X} - {}, {}, {}", + v[0], + v[1], + position_1_interchange, + position_2_authorization_processing, + position_3_allowed_services + ); + } Some(FieldFormat::NumericCountryCode) => { - let numeric_country_code : String = format!("{:02X?}", v).replace(|c: char| !(c.is_ascii_alphanumeric()), "")[1..].to_string(); - value = format!("{} - {}", numeric_country_code, self.constants.numeric_country_codes[&numeric_country_code]); - }, + let numeric_country_code: String = format!("{:02X?}", v) + .replace(|c: char| !(c.is_ascii_alphanumeric()), "")[1..] + .to_string(); + value = format!( + "{} - {}", + numeric_country_code, + self.constants.numeric_country_codes[&numeric_country_code] + ); + } Some(FieldFormat::NumericCurrencyCode) => { - let numeric_currency_code : String = format!("{:02X?}", v).replace(|c: char| !(c.is_ascii_alphanumeric()), "")[1..].to_string(); - value = format!("{} - {}", numeric_currency_code, self.constants.numeric_currency_codes[&numeric_currency_code]); - }, + let numeric_currency_code: String = format!("{:02X?}", v) + .replace(|c: char| !(c.is_ascii_alphanumeric()), "")[1..] + .to_string(); + value = format!( + "{} - {}", + numeric_currency_code, + self.constants.numeric_currency_codes[&numeric_currency_code] + ); + } Some(FieldFormat::DataObjectList) => { - let dol : DataObjectList = DataObjectList::process_data_object_list(self, &v[..]).unwrap(); + let dol: DataObjectList = + DataObjectList::process_data_object_list(self, &v[..]).unwrap(); value = format!("{}", dol); - }, + } Some(FieldFormat::Track2) => { - let track2 : Track2 = Track2::new(&String::from_utf8_lossy(&v).to_string()); + let track2: Track2 = Track2::new(&String::from_utf8_lossy(&v).to_string()); value = format!("{}", track2); - }, + } Some(FieldFormat::Date) => { - value = format!("{:02X?}", v).replace(|c: char| !(c.is_ascii_alphanumeric()), ""); + value = + format!("{:02X?}", v).replace(|c: char| !(c.is_ascii_alphanumeric()), ""); let yy = &value[0..2]; let mm = &value[2..4]; let dd = &value[4..6]; // FIXME: date format does not take into consideration pre 2000s dates value = format!("20{}-{}-{}", yy, mm, dd); - - }, + } Some(FieldFormat::Time) => { - value = format!("{:02X?}", v).replace(|c: char| !(c.is_ascii_alphanumeric()), ""); + value = + format!("{:02X?}", v).replace(|c: char| !(c.is_ascii_alphanumeric()), ""); let hh = &value[0..2]; let mm = &value[2..4]; let ss = &value[4..6]; value = format!("{}:{}:{}", hh, mm, ss); - }, + } _ => { /* NOP */ } } @@ -1115,7 +1394,7 @@ impl EmvConnection<'_> { "9F27" => { let icc_cryptogram_type = CryptogramType::try_from(v[0] as u8).unwrap(); value = format!("{:?}", icc_cryptogram_type); - }, + } _ => { /* NOP */ } } } @@ -1126,30 +1405,50 @@ impl EmvConnection<'_> { Some(FieldSensitivity::PrimaryAccountNumber) => { // PAN truncation rules ref. https://pcissc.secure.force.com/faq/articles/Frequently_Asked_Question/What-are-acceptable-formats-for-truncation-of-primary-account-numbers // Going with "First 6, last 4" as it is applicable for all schemes - let masked_pan : String = value.chars().enumerate().map(|(i, c)| if i >= 6 && i < value.len()-4 { '*' } else { c }).collect(); + let masked_pan: String = value + .chars() + .enumerate() + .map(|(i, c)| { + if i >= 6 && i < value.len() - 4 { + '*' + } else { + c + } + }) + .collect(); debug!("{}-data: {}", padding, masked_pan); - }, + } Some(FieldSensitivity::Track2) => { - let mut track2 : Track2 = Track2::new(&String::from_utf8_lossy(&v).to_string()); + let mut track2: Track2 = + Track2::new(&String::from_utf8_lossy(&v).to_string()); track2.censor(); value = format!("{}", track2); debug!("{}-data: {}", padding, value); - }, - Some(FieldSensitivity::SensitiveAuthenticationData | FieldSensitivity::Sensitive) => { + } + Some( + FieldSensitivity::SensitiveAuthenticationData | FieldSensitivity::Sensitive, + ) => { debug!("{}-data: censored {} bytes", padding, v.len()); - }, + } Some(FieldSensitivity::PersonallyIdentifiableInformation) => { // allowing punctuation is primarily to see cardholder name which is separated by '/' - debug!("{}-data: {}", padding, value.replace(|c: char| !(c.is_ascii_whitespace() || c.is_ascii_punctuation()), "*")); - }, + debug!( + "{}-data: {}", + padding, + value.replace( + |c: char| !(c.is_ascii_whitespace() || c.is_ascii_punctuation()), + "*" + ) + ); + } _ => { debug!("{}-data: {:02X?} = {}", padding, v, value); } } } else { debug!("{}-data: {:02X?} = {}", padding, v, value); - } + } } else { debug!("{}-data: {:02X?} = {}", padding, v, value); } @@ -1161,11 +1460,15 @@ impl EmvConnection<'_> { loop { let (tlv_data, leftover_buffer) = Tlv::parse(read_buffer); - let tlv_data : Tlv = match tlv_data { + let tlv_data: Tlv = match tlv_data { Ok(tlv) => tlv, Err(err) => { if leftover_buffer.len() > 0 { - trace!("Could not parse as TLV! error:{:?}, data: {:02X?}", err, read_buffer); + trace!( + "Could not parse as TLV! error:{:?}, data: {:02X?}", + err, + read_buffer + ); } break; @@ -1176,11 +1479,11 @@ impl EmvConnection<'_> { let tag_name = hex::encode_upper(tlv_data.tag().to_bytes()); - let emv_tag : Option<&EmvTag> = match self.emv_tags.get(tag_name.as_str()) { + let emv_tag: Option<&EmvTag> = match self.emv_tags.get(tag_name.as_str()) { Some(emv_tag) => { self.print_tag(&emv_tag, level); Some(emv_tag) - }, + } _ => { let unknown_tag = EmvTag::new(&tag_name.clone()); self.print_tag(&unknown_tag, level); @@ -1193,7 +1496,7 @@ impl EmvConnection<'_> { for tlv_tag in v { self.process_tlv(&tlv_tag.to_vec(), level + 1); } - }, + } Value::Primitive(v) => { self.print_tag_value(&emv_tag, v, level); self.add_tag(&tag_name, v.to_vec()); @@ -1216,15 +1519,17 @@ impl EmvConnection<'_> { match self.get_tag_value("9F38") { Some(tag_9f38_pdol) => { - let pdol_data = DataObjectList::process_data_object_list(self, &tag_9f38_pdol[..]).unwrap().get_tag_list_tag_values(self); + let pdol_data = DataObjectList::process_data_object_list(self, &tag_9f38_pdol[..]) + .unwrap() + .get_tag_list_tag_values(self); get_processing_options_command.push((pdol_data.len() + 2) as u8); // lc - // data + // data get_processing_options_command.push(0x83); // tag 83 get_processing_options_command.push(pdol_data.len() as u8); // tag 83 length get_processing_options_command.extend_from_slice(&pdol_data[..]); // pdol list get_processing_options_command.push(0x00); // le - }, + } None => { get_processing_options_command.push(0x02); // lc get_processing_options_command.push(0x83); // data @@ -1250,27 +1555,29 @@ impl EmvConnection<'_> { debug!("Read card Application File Locator (AFL) information:"); - let mut data_authentication : Vec = Vec::new(); + let mut data_authentication: Vec = Vec::new(); assert_eq!(tag_94_afl.len() % 4, 0); - let mut records : Vec = Vec::new(); + let mut records: Vec = Vec::new(); for i in (0..tag_94_afl.len()).step_by(4) { - let short_file_identifier : u8 = tag_94_afl[i] >> 3; - let record_index_start : u8 = tag_94_afl[i+1]; - let record_index_end : u8 = tag_94_afl[i+2]; - let mut data_authentication_records : u8 = tag_94_afl[i+3]; + let short_file_identifier: u8 = tag_94_afl[i] >> 3; + let record_index_start: u8 = tag_94_afl[i + 1]; + let record_index_end: u8 = tag_94_afl[i + 2]; + let mut data_authentication_records: u8 = tag_94_afl[i + 3]; - for record_index in record_index_start..record_index_end+1 { + for record_index in record_index_start..record_index_end + 1 { if let Some(data) = self.read_record(short_file_identifier, record_index) { assert_eq!(data[0], 0x70); records.extend(&data); - // Add data authentication input + // Add data authentication input // ref EMV Book 3, 10.3 Offline Data Authentication if data_authentication_records > 0 { data_authentication_records -= 1; if short_file_identifier <= 10 { - if let Value::Constructed(tag_70_tags) = parse_tlv(&data[..]).unwrap().value() { + if let Value::Constructed(tag_70_tags) = + parse_tlv(&data[..]).unwrap().value() + { for tag in tag_70_tags { data_authentication.extend(tag.to_vec()); } @@ -1278,7 +1585,7 @@ impl EmvConnection<'_> { } else { data_authentication.extend_from_slice(&data[..]); } - + if self.settings.censor_sensitive_fields { trace!("Data authentication building: short_file_identifier:{}, data_authentication_records:{}, record_index:{}/{}, data:{} bytes", short_file_identifier, data_authentication_records, record_index, record_index_end, data_authentication.len()); } else { @@ -1290,14 +1597,20 @@ impl EmvConnection<'_> { } if self.settings.censor_sensitive_fields { - debug!("AFL data authentication: {} bytes", data_authentication.len()); + debug!( + "AFL data authentication: {} bytes", + data_authentication.len() + ); } else { - debug!("AFL data authentication:\n{}", HexViewBuilder::new(&data_authentication).finish()); + debug!( + "AFL data authentication:\n{}", + HexViewBuilder::new(&data_authentication).finish() + ); } let tag_82_aip = self.get_tag_value("82").unwrap(); - let auc_b1 : u8 = tag_82_aip[0]; + let auc_b1: u8 = tag_82_aip[0]; // bit 7 = RFU self.icc.capabilities.sda = get_bit!(auc_b1, 6); self.icc.capabilities.dda = get_bit!(auc_b1, 5); @@ -1307,23 +1620,35 @@ impl EmvConnection<'_> { let tag_8e_cvm_list = self.get_tag_value("8E").unwrap().clone(); let amount1 = &tag_8e_cvm_list[0..4]; let amount2 = &tag_8e_cvm_list[4..8]; - let amount_x = str::from_utf8(&bcdutil::bcd_to_ascii(&amount1[..]).unwrap()[..]).unwrap().parse::().unwrap(); - let amount_y = str::from_utf8(&bcdutil::bcd_to_ascii(&amount2[..]).unwrap()[..]).unwrap().parse::().unwrap(); + let amount_x = str::from_utf8(&bcdutil::bcd_to_ascii(&amount1[..]).unwrap()[..]) + .unwrap() + .parse::() + .unwrap(); + let amount_y = str::from_utf8(&bcdutil::bcd_to_ascii(&amount2[..]).unwrap()[..]) + .unwrap() + .parse::() + .unwrap(); let tag_84_cvm_rules = &tag_8e_cvm_list[8..]; assert_eq!(tag_84_cvm_rules.len() % 2, 0); for i in (0..tag_84_cvm_rules.len()).step_by(2) { - let cvm_rule = &tag_84_cvm_rules[i..i+2]; + let cvm_rule = &tag_84_cvm_rules[i..i + 2]; let cvm_code = cvm_rule[0]; let cvm_condition_code = cvm_rule[1]; // bit 7 = RFU - let fail_if_unsuccessful = ! get_bit!(cvm_code, 6); + let fail_if_unsuccessful = !get_bit!(cvm_code, 6); let cvm_code = (cvm_code << 2) >> 2; - let code : CvmCode = cvm_code.try_into().unwrap(); - let condition : CvmConditionCode = cvm_condition_code.try_into().unwrap(); - - let rule = CvmRule { amount_x : amount_x, amount_y : amount_y, fail_if_unsuccessful : fail_if_unsuccessful, code : code, condition : condition }; + let code: CvmCode = cvm_code.try_into().unwrap(); + let condition: CvmConditionCode = cvm_condition_code.try_into().unwrap(); + + let rule = CvmRule { + amount_x: amount_x, + amount_y: amount_y, + fail_if_unsuccessful: fail_if_unsuccessful, + code: code, + condition: condition, + }; self.icc.cvm_rules.push(rule); } } @@ -1346,7 +1671,7 @@ impl EmvConnection<'_> { Ok(()) } - pub fn handle_verify_plaintext_pin(&mut self, ascii_pin : &[u8]) -> Result<(), ()> { + pub fn handle_verify_plaintext_pin(&mut self, ascii_pin: &[u8]) -> Result<(), ()> { debug!("Verify plaintext PIN:"); let pin_bcd_cn = bcdutil::ascii_to_bcd_cn(ascii_pin, 6).unwrap(); @@ -1371,19 +1696,19 @@ impl EmvConnection<'_> { Ok(()) } - fn fill_random(&self, data : &mut [u8]) { + fn fill_random(&self, data: &mut [u8]) { if self.settings.terminal.use_random { let mut rng = ChaCha20Rng::from_entropy(); rng.try_fill(data).unwrap(); } } - pub fn handle_verify_enciphered_pin(&mut self, ascii_pin : &[u8]) -> Result<(), ()> { + pub fn handle_verify_enciphered_pin(&mut self, ascii_pin: &[u8]) -> Result<(), ()> { debug!("Verify enciphered PIN:"); let pin_bcd_cn = bcdutil::ascii_to_bcd_cn(ascii_pin, 6).unwrap(); - const PK_MAX_SIZE : usize = 248; // ref. EMV Book 2, B2.1 RSA Algorithm + const PK_MAX_SIZE: usize = 248; // ref. EMV Book 2, B2.1 RSA Algorithm let mut random_padding = [0u8; PK_MAX_SIZE]; self.fill_random(&mut random_padding[..]); @@ -1400,9 +1725,17 @@ impl EmvConnection<'_> { // ICC Unpredictable Number plaintext_data.extend_from_slice(&icc_unpredictable_number[..]); // Random padding - plaintext_data.extend_from_slice(&random_padding[0..self.icc.icc_pin_pk.as_ref().unwrap().get_key_byte_size()-17]); - - let ciphered_pin_data = self.icc.icc_pin_pk.as_ref().unwrap().public_encrypt(&plaintext_data[..]).unwrap(); + plaintext_data.extend_from_slice( + &random_padding[0..self.icc.icc_pin_pk.as_ref().unwrap().get_key_byte_size() - 17], + ); + + let ciphered_pin_data = self + .icc + .icc_pin_pk + .as_ref() + .unwrap() + .public_encrypt(&plaintext_data[..]) + .unwrap(); let apdu_command_verify = b"\x00\x20\x00"; let mut verify_command = apdu_command_verify.to_vec(); @@ -1422,62 +1755,95 @@ impl EmvConnection<'_> { Ok(()) } - fn handle_application_cryptogram_card_authentication(&mut self, tag_77_data : &[u8], cdol_tag : &str) -> Result<(),()> { + fn handle_application_cryptogram_card_authentication( + &mut self, + tag_77_data: &[u8], + cdol_tag: &str, + ) -> Result<(), ()> { //ref. EMV Book 2, 6.6.2 Dynamic Signature Verification debug!("Perform Application Cryptogram Data Authentication (CDA):"); let tag_9f37_unpredictable_number = self.get_tag_value("9F37").unwrap(); - let tag_9f4b_signed_data_decrypted_dynamic_data = self.validate_signed_dynamic_application_data(&tag_9f37_unpredictable_number[..]).unwrap(); + let tag_9f4b_signed_data_decrypted_dynamic_data = self + .validate_signed_dynamic_application_data(&tag_9f37_unpredictable_number[..]) + .unwrap(); let mut i = 0; let icc_dynamic_number_length = tag_9f4b_signed_data_decrypted_dynamic_data[i] as usize; i += 1; - let _icc_dynamic_number = &tag_9f4b_signed_data_decrypted_dynamic_data[i..i + icc_dynamic_number_length]; + let _icc_dynamic_number = + &tag_9f4b_signed_data_decrypted_dynamic_data[i..i + icc_dynamic_number_length]; i += icc_dynamic_number_length; let cryptogram_information_data = &tag_9f4b_signed_data_decrypted_dynamic_data[i..i + 1]; i += 1; - let tag_9f26_application_cryptogram = &tag_9f4b_signed_data_decrypted_dynamic_data[i..i + 8]; + let tag_9f26_application_cryptogram = + &tag_9f4b_signed_data_decrypted_dynamic_data[i..i + 8]; i += 8; let transaction_data_hash_code = &tag_9f4b_signed_data_decrypted_dynamic_data[i..i + 20]; let tag_9f27_cryptogram_information_data = self.get_tag_value("9F27").unwrap(); if &tag_9f27_cryptogram_information_data[..] != cryptogram_information_data { - warn!("Cryptogram information data mismatch in CDE! 9F27:{:02X?}, 9F4B.CID:{:02X?}", &tag_9f27_cryptogram_information_data[..], cryptogram_information_data); + warn!( + "Cryptogram information data mismatch in CDE! 9F27:{:02X?}, 9F4B.CID:{:02X?}", + &tag_9f27_cryptogram_information_data[..], + cryptogram_information_data + ); return Err(()); } - let mut checksum_data : Vec = Vec::new(); + let mut checksum_data: Vec = Vec::new(); let tag_9f38_pdol = self.get_tag_value("9F38"); if tag_9f38_pdol.is_some() { - let pdol_data = DataObjectList::process_data_object_list(self, &tag_9f38_pdol.unwrap()[..]).unwrap().get_tag_list_tag_values(self); + let pdol_data = + DataObjectList::process_data_object_list(self, &tag_9f38_pdol.unwrap()[..]) + .unwrap() + .get_tag_list_tag_values(self); assert!(pdol_data.len() <= 0xFF); checksum_data.extend_from_slice(&pdol_data); } - let cdol1_data = DataObjectList::process_data_object_list(self, &self.get_tag_value("8C").unwrap()[..]).unwrap().get_tag_list_tag_values(self); + let cdol1_data = + DataObjectList::process_data_object_list(self, &self.get_tag_value("8C").unwrap()[..]) + .unwrap() + .get_tag_list_tag_values(self); assert!(cdol1_data.len() <= 0xFF); checksum_data.extend_from_slice(&cdol1_data); if cdol_tag == "8D" { - let cdol2_data = DataObjectList::process_data_object_list(self, &self.get_tag_value("8D").unwrap()[..]).unwrap().get_tag_list_tag_values(self); + let cdol2_data = DataObjectList::process_data_object_list( + self, + &self.get_tag_value("8D").unwrap()[..], + ) + .unwrap() + .get_tag_list_tag_values(self); assert!(cdol2_data.len() <= 0xFF); checksum_data.extend_from_slice(&cdol2_data); } let mut tag_77_hex_encoded = hex::encode_upper(tag_77_data); - let tag_9f4b_signed_dynamic_application_data_hex_encoded = hex::encode_upper(self.get_tag_value("9F4B").unwrap()); + let tag_9f4b_signed_dynamic_application_data_hex_encoded = + hex::encode_upper(self.get_tag_value("9F4B").unwrap()); let tag_9f4b_tlv_header_length = 4 /* tag */ + 2 /* tag length */; - let tag_77_part1 : String = tag_77_hex_encoded.drain(..tag_77_hex_encoded.find(&tag_9f4b_signed_dynamic_application_data_hex_encoded).unwrap() - tag_9f4b_tlv_header_length).collect(); + let tag_77_part1: String = tag_77_hex_encoded + .drain( + ..tag_77_hex_encoded + .find(&tag_9f4b_signed_dynamic_application_data_hex_encoded) + .unwrap() + - tag_9f4b_tlv_header_length, + ) + .collect(); checksum_data.extend_from_slice(&hex::decode(&tag_77_part1).unwrap()[..]); - let tag_9f4b_whole_size = tag_9f4b_signed_dynamic_application_data_hex_encoded.len() + tag_9f4b_tlv_header_length + 2; + let tag_9f4b_whole_size = tag_9f4b_signed_dynamic_application_data_hex_encoded.len() + + tag_9f4b_tlv_header_length + + 2; if tag_77_hex_encoded.len() > tag_9f4b_whole_size { - let tag_77_part2 : String = tag_77_hex_encoded.drain(tag_9f4b_whole_size..).collect(); + let tag_77_part2: String = tag_77_hex_encoded.drain(tag_9f4b_whole_size..).collect(); checksum_data.extend_from_slice(&hex::decode(&tag_77_part2).unwrap()[..]); } @@ -1485,9 +1851,18 @@ impl EmvConnection<'_> { if &transaction_data_hash_code_checksum[..] != &transaction_data_hash_code[..] { warn!("Transaction data hash code mismatch!"); - warn!("Calculated transaction data\n{}", HexViewBuilder::new(&checksum_data[..]).finish()); - warn!("Calculated transaction data hash code\n{}", HexViewBuilder::new(&transaction_data_hash_code_checksum[..]).finish()); - warn!("Transaction data hash code\n{}", HexViewBuilder::new(transaction_data_hash_code).finish()); + warn!( + "Calculated transaction data\n{}", + HexViewBuilder::new(&checksum_data[..]).finish() + ); + warn!( + "Calculated transaction data hash code\n{}", + HexViewBuilder::new(&transaction_data_hash_code_checksum[..]).finish() + ); + warn!( + "Transaction data hash code\n{}", + HexViewBuilder::new(transaction_data_hash_code).finish() + ); return Err(()); } @@ -1497,14 +1872,15 @@ impl EmvConnection<'_> { Ok(()) } - fn validate_ac(&self, requested_cryptogram_type : CryptogramType) -> Result { + fn validate_ac(&self, requested_cryptogram_type: CryptogramType) -> Result { if let CryptogramType::ApplicationAuthenticationCryptogram = requested_cryptogram_type { warn!("Transaction declined by terminal (AAC)"); return Err(()); } let tag_9f27_cryptogram_information_data = self.get_tag_value("9F27").unwrap(); - let icc_cryptogram_type = CryptogramType::try_from(tag_9f27_cryptogram_information_data[0] as u8).unwrap(); + let icc_cryptogram_type = + CryptogramType::try_from(tag_9f27_cryptogram_information_data[0] as u8).unwrap(); if let CryptogramType::ApplicationAuthenticationCryptogram = icc_cryptogram_type { warn!("Transaction declined by ICC (AAC)"); @@ -1520,11 +1896,18 @@ impl EmvConnection<'_> { // ref. EMV Book 3, 6.5.5 GENERATE APPLICATION CRYPTOGRAM // ref. EMV Contactless Book C-2, 7.6 Procedure – Prepare Generate AC Command - fn send_generate_ac(&mut self, requested_cryptogram_type : CryptogramType, cdol_tag : &str) -> Result { - - let mut p1_reference_control_parameter : u8 = requested_cryptogram_type.into(); + fn send_generate_ac( + &mut self, + requested_cryptogram_type: CryptogramType, + cdol_tag: &str, + ) -> Result { + let mut p1_reference_control_parameter: u8 = requested_cryptogram_type.into(); if self.icc.capabilities.cda { - set_bit!(p1_reference_control_parameter, 4, self.settings.terminal.capabilities.cda); + set_bit!( + p1_reference_control_parameter, + 4, + self.settings.terminal.capabilities.cda + ); // GET CHALLENGE might be needed to the 9F4C value if let None = self.get_tag_value("9F4C") { @@ -1533,10 +1916,14 @@ impl EmvConnection<'_> { } } - let cdol_data = DataObjectList::process_data_object_list(self, &self.get_tag_value(cdol_tag).unwrap()[..]).unwrap().get_tag_list_tag_values(self); + let cdol_data = DataObjectList::process_data_object_list( + self, + &self.get_tag_value(cdol_tag).unwrap()[..], + ) + .unwrap() + .get_tag_list_tag_values(self); assert!(cdol_data.len() <= 0xFF); - let apdu_command_generate_ac = b"\x80\xAE"; let mut generate_ac_command = apdu_command_generate_ac.to_vec(); generate_ac_command.push(p1_reference_control_parameter); @@ -1558,7 +1945,7 @@ impl EmvConnection<'_> { self.process_tag_as_tlv("9F26", response_data[5..13].to_vec()); if response_data.len() > 13 { self.process_tag_as_tlv("9F10", response_data[13..].to_vec()); - } + } } else if response_data[0] != 0x77 { warn!("Unrecognized response"); return Err(()); @@ -1566,12 +1953,17 @@ impl EmvConnection<'_> { if get_bit!(p1_reference_control_parameter, 4) { let tag_9f27_cryptogram_information_data = self.get_tag_value("9F27").unwrap(); - let icc_cryptogram_type = CryptogramType::try_from(tag_9f27_cryptogram_information_data[0] as u8).unwrap(); + let icc_cryptogram_type = + CryptogramType::try_from(tag_9f27_cryptogram_information_data[0] as u8).unwrap(); match icc_cryptogram_type { - CryptogramType::TransactionCertificate | CryptogramType::AuthorisationRequestCryptogram => { - self.handle_application_cryptogram_card_authentication(&response_data[3..], cdol_tag)?; - }, + CryptogramType::TransactionCertificate + | CryptogramType::AuthorisationRequestCryptogram => { + self.handle_application_cryptogram_card_authentication( + &response_data[3..], + cdol_tag, + )?; + } _ => {} } } @@ -1588,11 +1980,14 @@ impl EmvConnection<'_> { // ref. EMV Contactless Book C-3, A.2 Data Elements by Name - cryptogram returned in GET PROCESSING OPTIONS (Kernel 3, Visa) icc_cryptogram_type = self.validate_ac(self.settings.terminal.cryptogram_type)?; } else { - icc_cryptogram_type = self.send_generate_ac(self.settings.terminal.cryptogram_type, "8C")?; - + icc_cryptogram_type = + self.send_generate_ac(self.settings.terminal.cryptogram_type, "8C")?; + if let CryptogramType::AuthorisationRequestCryptogram = icc_cryptogram_type { // handle_2nd_generate_ac needed - } else if let CryptogramType::AuthorisationRequestCryptogram = self.settings.terminal.cryptogram_type { + } else if let CryptogramType::AuthorisationRequestCryptogram = + self.settings.terminal.cryptogram_type + { warn!("Transaction terminated by terminal - ARQC requested but got unexpected return type from ICC"); return Err(()); } @@ -1604,9 +1999,10 @@ impl EmvConnection<'_> { pub fn handle_2nd_generate_ac(&mut self) -> Result { debug!("Generate Application Cryptogram (GENERATE AC) - second issuance:"); - let icc_cryptogram_type = self.send_generate_ac(self.settings.terminal.cryptogram_type_arqc, "8D")?; + let icc_cryptogram_type = + self.send_generate_ac(self.settings.terminal.cryptogram_type_arqc, "8D")?; - if let CryptogramType::TransactionCertificate = icc_cryptogram_type { + if let CryptogramType::TransactionCertificate = icc_cryptogram_type { return Ok(icc_cryptogram_type); } @@ -1614,8 +2010,8 @@ impl EmvConnection<'_> { Err(()) } - fn read_record(&mut self, short_file_identifier : u8, record_index : u8) -> Option> { - let mut records : Vec = Vec::new(); + fn read_record(&mut self, short_file_identifier: u8, record_index: u8) -> Option> { + let mut records: Vec = Vec::new(); let apdu_command_read = b"\x00\xB2"; @@ -1623,7 +2019,7 @@ impl EmvConnection<'_> { read_record.push(record_index); read_record.push((short_file_identifier << 3) | 0x04); - const RECORD_LENGTH_DEFAULT : u8 = 0x00; + const RECORD_LENGTH_DEFAULT: u8 = 0x00; read_record.push(RECORD_LENGTH_DEFAULT); let (response_trailer, response_data) = self.send_apdu(&read_record); @@ -1657,7 +2053,7 @@ impl EmvConnection<'_> { return Err(()); } - let mut all_applications : Vec = Vec::new(); + let mut all_applications: Vec = Vec::new(); if self.contactless { //EMV Contactless Book B, Entry Point Specification v2.6, Table3-2: SELECT Response Message Data Field (FCI) of the PPSE @@ -1665,12 +2061,19 @@ impl EmvConnection<'_> { Some(tag_bf0c) => { if let Value::Constructed(application_templates) = tag_bf0c.value() { for tag_61_application_template in application_templates { - if let Value::Constructed(application_template) = tag_61_application_template.value() { + if let Value::Constructed(application_template) = + tag_61_application_template.value() + { self.tags.clear(); for application_template_child_tag in application_template { - if let Value::Primitive(value) = application_template_child_tag.value() { - let tag_name = hex::encode(application_template_child_tag.tag().to_bytes()).to_uppercase(); + if let Value::Primitive(value) = + application_template_child_tag.value() + { + let tag_name = hex::encode( + application_template_child_tag.tag().to_bytes(), + ) + .to_uppercase(); self.add_tag(&tag_name, value.to_vec()); } } @@ -1678,19 +2081,19 @@ impl EmvConnection<'_> { let tag_4f_aid = self.get_tag_value("4F").unwrap(); let tag_50_label = match self.get_tag_value("50") { Some(v) => v, - None => "UNKNOWN".as_bytes() + None => "UNKNOWN".as_bytes(), }; //EMV Contactless Book B, Entry Point Specification v2.6, Table3-3: Format of Application Priority Indicator let tag_87_priority = match self.get_tag_value("87") { Some(v) => v, - None => "01".as_bytes() + None => "01".as_bytes(), }; - + all_applications.push(EmvApplication { aid: tag_4f_aid.clone(), label: tag_50_label.to_vec(), - priority: tag_87_priority.to_vec() + priority: tag_87_priority.to_vec(), }); // TODO: Since in NFC we're interested only of a single application @@ -1700,13 +2103,12 @@ impl EmvConnection<'_> { } } } - }, + } None => { warn!("Expected tag BF0C not found! pse:{}", pse_name); return Err(()); } } - } else { let sfi_data = self.get_tag_value("88").unwrap().clone(); assert_eq!(sfi_data.len(), 1); @@ -1722,41 +2124,52 @@ impl EmvConnection<'_> { return Err(()); } - if let Value::Constructed(application_templates) = parse_tlv(&data).unwrap().value() { + if let Value::Constructed(application_templates) = + parse_tlv(&data).unwrap().value() + { for tag_61_application_template in application_templates { - if let Value::Constructed(application_template) = tag_61_application_template.value() { + if let Value::Constructed(application_template) = + tag_61_application_template.value() + { self.tags.clear(); for application_template_child_tag in application_template { - if let Value::Primitive(value) = application_template_child_tag.value() { - let tag_name = hex::encode(application_template_child_tag.tag().to_bytes()).to_uppercase(); + if let Value::Primitive(value) = + application_template_child_tag.value() + { + let tag_name = hex::encode( + application_template_child_tag.tag().to_bytes(), + ) + .to_uppercase(); self.add_tag(&tag_name, value.to_vec()); } } let tag_4f_aid = self.get_tag_value("4F").unwrap(); let default_label = "UNKNOWN".as_bytes().to_vec(); - let tag_50_label = self.get_tag_value("50").unwrap_or(&default_label); - + let tag_50_label = + self.get_tag_value("50").unwrap_or(&default_label); + if let Some(tag_87_priority) = self.get_tag_value("87") { all_applications.push(EmvApplication { aid: tag_4f_aid.clone(), label: tag_50_label.clone(), - priority: tag_87_priority.clone() + priority: tag_87_priority.clone(), }); } else { - debug!("Skipping application. AID:{:02X?}, label:{:?}", tag_4f_aid, str::from_utf8(&tag_50_label).unwrap()); + debug!( + "Skipping application. AID:{:02X?}, label:{:?}", + tag_4f_aid, + str::from_utf8(&tag_50_label).unwrap() + ); } - } } } - - }, - None => break + } + None => break, }; } - } if all_applications.is_empty() { @@ -1767,11 +2180,22 @@ impl EmvConnection<'_> { Ok(all_applications) } - pub fn handle_select_payment_application(&mut self, application : &EmvApplication) -> Result<(), ()> { - info!("Selecting application. AID:{:02X?}, label:{:?}, priority:{:02X?}", application.aid, str::from_utf8(&application.label).unwrap(), application.priority); + pub fn handle_select_payment_application( + &mut self, + application: &EmvApplication, + ) -> Result<(), ()> { + info!( + "Selecting application. AID:{:02X?}, label:{:?}, priority:{:02X?}", + application.aid, + str::from_utf8(&application.label).unwrap(), + application.priority + ); let (response_trailer, _) = self.send_apdu_select(&application.aid); if !is_success_response(&response_trailer) { - warn!("Could not select payment application! {:02X?}, {:?}", application.aid, application.label); + warn!( + "Could not select payment application! {:02X?}, {:?}", + application.aid, application.label + ); return Err(()); } @@ -1787,7 +2211,6 @@ impl EmvConnection<'_> { Ok(application) } - pub fn process_settings(&mut self) -> Result<(), Box> { let default_tags = self.settings.default_tags.clone(); for (tag_name, tag_value) in default_tags.iter() { @@ -1797,14 +2220,26 @@ impl EmvConnection<'_> { let now = Utc::now().naive_utc(); if !self.get_tag_value("9A").is_some() { let today = now.date(); - let transaction_date_ascii_yymmdd = format!("{:02}{:02}{:02}",today.year()-2000, today.month(), today.day()); - self.process_tag_as_tlv("9A", bcdutil::ascii_to_bcd_cn(transaction_date_ascii_yymmdd.as_bytes(), 3).unwrap()); + let transaction_date_ascii_yymmdd = format!( + "{:02}{:02}{:02}", + today.year() - 2000, + today.month(), + today.day() + ); + self.process_tag_as_tlv( + "9A", + bcdutil::ascii_to_bcd_cn(transaction_date_ascii_yymmdd.as_bytes(), 3).unwrap(), + ); } if !self.get_tag_value("9F21").is_some() { let time = now.time(); - let transaction_time_ascii_hhmmss = format!("{:02}{:02}{:02}",time.hour(), time.minute(), time.second()); - self.process_tag_as_tlv("9F21", bcdutil::ascii_to_bcd_cn(transaction_time_ascii_hhmmss.as_bytes(), 3).unwrap()); + let transaction_time_ascii_hhmmss = + format!("{:02}{:02}{:02}", time.hour(), time.minute(), time.second()); + self.process_tag_as_tlv( + "9F21", + bcdutil::ascii_to_bcd_cn(transaction_time_ascii_hhmmss.as_bytes(), 3).unwrap(), + ); } if !self.get_tag_value("9F37").is_some() { @@ -1820,14 +2255,18 @@ impl EmvConnection<'_> { } if !self.get_tag_value("9F66").is_some() { - let tag_9f66_ttq : Vec = self.settings.terminal.terminal_transaction_qualifiers.into(); + let tag_9f66_ttq: Vec = self + .settings + .terminal + .terminal_transaction_qualifiers + .into(); self.process_tag_as_tlv("9F66", tag_9f66_ttq); } Ok(()) } - pub fn handle_get_data(&mut self, tag : &[u8]) -> Result, ()> { + pub fn handle_get_data(&mut self, tag: &[u8]) -> Result, ()> { debug!("GET DATA:"); assert_eq!(tag.len(), 2); @@ -1847,7 +2286,7 @@ impl EmvConnection<'_> { return Err(()); } - let mut output : Vec = Vec::new(); + let mut output: Vec = Vec::new(); output.extend_from_slice(&response_data); Ok(output) @@ -1865,20 +2304,24 @@ impl EmvConnection<'_> { return Err(()); } - let mut output : Vec = Vec::new(); + let mut output: Vec = Vec::new(); output.extend_from_slice(&response_data); Ok(output) } - pub fn handle_public_keys(&mut self, application : &EmvApplication) -> Result<(), ()> { + pub fn handle_public_keys(&mut self, application: &EmvApplication) -> Result<(), ()> { if self.get_tag_value("8F").is_none() { debug!("Card does not support offline data authentication"); return Ok(()); } let (issuer_pk_modulus, issuer_pk_exponent) = self.get_issuer_public_key(application)?; - self.icc.issuer_pk = Some(RsaPublicKey::new(&issuer_pk_modulus[..], &issuer_pk_exponent[..], self.settings.censor_sensitive_fields)); + self.icc.issuer_pk = Some(RsaPublicKey::new( + &issuer_pk_modulus[..], + &issuer_pk_exponent[..], + self.settings.censor_sensitive_fields, + )); let data_authentication = &self.icc.data_authentication.as_ref().unwrap()[..]; @@ -1887,9 +2330,16 @@ impl EmvConnection<'_> { if tag_9f46_icc_pk_certificate.is_some() && tag_9f47_icc_pk_exponent.is_some() { let tag_9f48_icc_pk_remainder = self.get_tag_value("9F48"); let (icc_pk_modulus, icc_pk_exponent) = self.get_icc_public_key( - tag_9f46_icc_pk_certificate.unwrap(), tag_9f47_icc_pk_exponent.unwrap(), tag_9f48_icc_pk_remainder, - data_authentication)?; - self.icc.icc_pk = Some(RsaPublicKey::new(&icc_pk_modulus[..], &icc_pk_exponent[..], self.settings.censor_sensitive_fields)); + tag_9f46_icc_pk_certificate.unwrap(), + tag_9f47_icc_pk_exponent.unwrap(), + tag_9f48_icc_pk_remainder, + data_authentication, + )?; + self.icc.icc_pk = Some(RsaPublicKey::new( + &icc_pk_modulus[..], + &icc_pk_exponent[..], + self.settings.censor_sensitive_fields, + )); self.icc.icc_pin_pk = self.icc.icc_pk.clone(); } @@ -1900,19 +2350,31 @@ impl EmvConnection<'_> { // ICC has a separate ICC PIN Encipherement public key let (icc_pin_pk_modulus, icc_pin_pk_exponent) = self.get_icc_public_key( - tag_9f2d_icc_pin_pk_certificate.unwrap(), tag_9f2e_icc_pin_pk_exponent.unwrap(), tag_9f2f_icc_pin_pk_remainder, - data_authentication)?; + tag_9f2d_icc_pin_pk_certificate.unwrap(), + tag_9f2e_icc_pin_pk_exponent.unwrap(), + tag_9f2f_icc_pin_pk_remainder, + data_authentication, + )?; - self.icc.icc_pin_pk = Some(RsaPublicKey::new(&icc_pin_pk_modulus[..], &icc_pin_pk_exponent[..], self.settings.censor_sensitive_fields)); + self.icc.icc_pin_pk = Some(RsaPublicKey::new( + &icc_pin_pk_modulus[..], + &icc_pin_pk_exponent[..], + self.settings.censor_sensitive_fields, + )); } Ok(()) } - pub fn get_issuer_public_key(&self, application : &EmvApplication) -> Result<(Vec, Vec), ()> { - + pub fn get_issuer_public_key( + &self, + application: &EmvApplication, + ) -> Result<(Vec, Vec), ()> { // ref. https://www.emvco.com/wp-content/uploads/2017/05/EMV_v4.3_Book_2_Security_and_Key_Management_20120607061923900.pdf - 6.3 Retrieval of Issuer Public Key - let ca_data : HashMap = serialize_yaml!(&self.settings.configuration_files.scheme_ca_public_keys, "config/scheme_ca_public_keys_test.yaml"); + let ca_data: HashMap = serialize_yaml!( + &self.settings.configuration_files.scheme_ca_public_keys, + "config/scheme_ca_public_keys_test.yaml" + ); let tag_92_issuer_pk_remainder = self.get_tag_value("92"); let tag_9f32_issuer_pk_exponent = self.get_tag_value("9F32").unwrap(); @@ -1920,20 +2382,25 @@ impl EmvConnection<'_> { let rid = &application.aid[0..5]; let tag_8f_ca_pk_index = self.get_tag_value("8F").unwrap(); - + let ca_pk = get_ca_public_key(&ca_data, &rid[..], &tag_8f_ca_pk_index[..]).unwrap(); - let issuer_certificate = ca_pk.public_decrypt(&tag_90_issuer_public_key_certificate[..]).unwrap(); + let issuer_certificate = ca_pk + .public_decrypt(&tag_90_issuer_public_key_certificate[..]) + .unwrap(); let issuer_certificate_length = issuer_certificate.len(); if issuer_certificate[1] != 0x02 { - warn!("Incorrect issuer certificate type {:02X?}", issuer_certificate[1]); + warn!( + "Incorrect issuer certificate type {:02X?}", + issuer_certificate[1] + ); return Err(()); } let checksum_position = 15 + issuer_certificate_length - 36; - let issuer_certificate_iin = &issuer_certificate[2..6]; + let issuer_certificate_iin = &issuer_certificate[2..6]; let issuer_certificate_expiry = &issuer_certificate[6..8]; let issuer_certificate_serial = &issuer_certificate[8..11]; let issuer_certificate_hash_algorithm = &issuer_certificate[11..12]; @@ -1944,18 +2411,25 @@ impl EmvConnection<'_> { debug!("Issuer Identifier:{:02X?}", issuer_certificate_iin); debug!("Issuer expiry:{:02X?}", issuer_certificate_expiry); debug!("Issuer serial:{:02X?}", issuer_certificate_serial); - debug!("Issuer hash algo:{:02X?}", issuer_certificate_hash_algorithm); + debug!( + "Issuer hash algo:{:02X?}", + issuer_certificate_hash_algorithm + ); debug!("Issuer pk algo:{:02X?}", issuer_pk_algorithm); debug!("Issuer pk length:{:02X?}", issuer_pk_length); debug!("Issuer pk exp length:{:02X?}", issuer_pk_exponent_length); - debug!("Issuer pk leftmost digits:{:02X?}", issuer_pk_leftmost_digits); + debug!( + "Issuer pk leftmost digits:{:02X?}", + issuer_pk_leftmost_digits + ); assert_eq!(issuer_certificate_hash_algorithm[0], 0x01); // SHA-1 assert_eq!(issuer_pk_algorithm[0], 0x01); // RSA as defined in EMV Book 2, B2.1 RSA Algorihm - let issuer_certificate_checksum = &issuer_certificate[checksum_position..checksum_position + 20]; + let issuer_certificate_checksum = + &issuer_certificate[checksum_position..checksum_position + 20]; - let mut checksum_data : Vec = Vec::new(); + let mut checksum_data: Vec = Vec::new(); checksum_data.extend_from_slice(&issuer_certificate[1..checksum_position]); if tag_92_issuer_pk_remainder.is_some() { checksum_data.extend_from_slice(&tag_92_issuer_pk_remainder.unwrap()[..]); @@ -1966,8 +2440,14 @@ impl EmvConnection<'_> { if &cert_checksum[..] != &issuer_certificate_checksum[..] { warn!("Issuer cert checksum mismatch!"); - warn!("Calculated checksum\n{}", HexViewBuilder::new(&cert_checksum[..]).finish()); - warn!("Issuer provided checksum\n{}", HexViewBuilder::new(&issuer_certificate_checksum[..]).finish()); + warn!( + "Calculated checksum\n{}", + HexViewBuilder::new(&cert_checksum[..]).finish() + ); + warn!( + "Issuer provided checksum\n{}", + HexViewBuilder::new(&issuer_certificate_checksum[..]).finish() + ); return Err(()); } @@ -1976,41 +2456,67 @@ impl EmvConnection<'_> { let ascii_pan = bcdutil::bcd_to_ascii(&tag_5a_pan[..]).unwrap(); let ascii_iin = bcdutil::bcd_to_ascii(&issuer_certificate_iin).unwrap(); if ascii_iin != &ascii_pan[0..ascii_iin.len()] { - warn!("IIN mismatch! Cert IIN: {:02X?}, PAN IIN: {:02X?}", ascii_iin, &ascii_pan[0..ascii_iin.len()]); + warn!( + "IIN mismatch! Cert IIN: {:02X?}, PAN IIN: {:02X?}", + ascii_iin, + &ascii_pan[0..ascii_iin.len()] + ); return Err(()); } is_certificate_expired(&issuer_certificate_expiry[..]); - let issuer_pk_leftmost_digits_length = issuer_pk_leftmost_digits.iter() - .rev().position(|c| -> bool { *c != 0xBB }).map(|i| issuer_pk_leftmost_digits.len() - i).unwrap(); + let issuer_pk_leftmost_digits_length = issuer_pk_leftmost_digits + .iter() + .rev() + .position(|c| -> bool { *c != 0xBB }) + .map(|i| issuer_pk_leftmost_digits.len() - i) + .unwrap(); - let mut issuer_pk_modulus : Vec = Vec::new(); - issuer_pk_modulus.extend_from_slice(&issuer_pk_leftmost_digits[..issuer_pk_leftmost_digits_length]); + let mut issuer_pk_modulus: Vec = Vec::new(); + issuer_pk_modulus + .extend_from_slice(&issuer_pk_leftmost_digits[..issuer_pk_leftmost_digits_length]); if tag_92_issuer_pk_remainder.is_some() { issuer_pk_modulus.extend_from_slice(&tag_92_issuer_pk_remainder.unwrap()[..]); } - trace!("Issuer PK modulus:\n{}", HexViewBuilder::new(&issuer_pk_modulus[..]).finish()); + trace!( + "Issuer PK modulus:\n{}", + HexViewBuilder::new(&issuer_pk_modulus[..]).finish() + ); Ok((issuer_pk_modulus, tag_9f32_issuer_pk_exponent.to_vec())) } - - pub fn get_icc_public_key(&self, icc_pk_certificate : &Vec, icc_pk_exponent : &Vec, icc_pk_remainder : Option<&Vec>, data_authentication : &[u8]) -> Result<(Vec, Vec), ()> { + pub fn get_icc_public_key( + &self, + icc_pk_certificate: &Vec, + icc_pk_exponent: &Vec, + icc_pk_remainder: Option<&Vec>, + data_authentication: &[u8], + ) -> Result<(Vec, Vec), ()> { // ICC public key retrieval: EMV Book 2, 6.4 Retrieval of ICC Public Key - debug!("Retrieving ICC public key {:02X?}", &icc_pk_certificate[0..2]); + debug!( + "Retrieving ICC public key {:02X?}", + &icc_pk_certificate[0..2] + ); let tag_9f46_icc_pk_certificate = icc_pk_certificate; - let icc_certificate = self.icc.issuer_pk.as_ref().unwrap().public_decrypt(&tag_9f46_icc_pk_certificate[..]).unwrap(); + let icc_certificate = self + .icc + .issuer_pk + .as_ref() + .unwrap() + .public_decrypt(&tag_9f46_icc_pk_certificate[..]) + .unwrap(); let icc_certificate_length = icc_certificate.len(); if icc_certificate[1] != 0x04 { warn!("Incorrect ICC certificate type {:02X?}", icc_certificate[1]); return Err(()); } - let checksum_position = 21 + icc_certificate_length-42; + let checksum_position = 21 + icc_certificate_length - 42; let icc_certificate_pan = &icc_certificate[2..12]; let icc_certificate_expiry = &icc_certificate[12..14]; @@ -2021,10 +2527,15 @@ impl EmvConnection<'_> { let icc_certificate_pk_exp_length = &icc_certificate[20..21]; let icc_certificate_pk_leftmost_digits = &icc_certificate[21..checksum_position]; - if self.settings.censor_sensitive_fields { - let pan : String = String::from_utf8_lossy(&bcdutil::bcd_to_ascii(&icc_certificate_pan).unwrap()).to_string(); - let masked_pan: String = pan.chars().enumerate().map(|(i, c)| if i >= 6 && i < pan.len()-4 { '*' } else { c }).collect(); + let pan: String = + String::from_utf8_lossy(&bcdutil::bcd_to_ascii(&icc_certificate_pan).unwrap()) + .to_string(); + let masked_pan: String = pan + .chars() + .enumerate() + .map(|(i, c)| if i >= 6 && i < pan.len() - 4 { '*' } else { c }) + .collect(); debug!("ICC PAN:{}", masked_pan); } else { debug!("ICC PAN:{:02X?}", icc_certificate_pan); @@ -2035,14 +2546,17 @@ impl EmvConnection<'_> { debug!("ICC pk algo:{:02X?}", icc_certificate_pk_algo); debug!("ICC pk length:{:02X?}", icc_certificate_pk_length); debug!("ICC pk exp length:{:02X?}", icc_certificate_pk_exp_length); - debug!("ICC pk leftmost digits:{:02X?}", icc_certificate_pk_leftmost_digits); + debug!( + "ICC pk leftmost digits:{:02X?}", + icc_certificate_pk_leftmost_digits + ); assert_eq!(icc_certificate_hash_algo[0], 0x01); // SHA-1 assert_eq!(icc_certificate_pk_algo[0], 0x01); // RSA as defined in EMV Book 2, B2.1 RSA Algorihm let tag_9f47_icc_pk_exponent = icc_pk_exponent; - let mut checksum_data : Vec = Vec::new(); + let mut checksum_data: Vec = Vec::new(); checksum_data.extend_from_slice(&icc_certificate[1..checksum_position]); let tag_9f48_icc_pk_remainder = icc_pk_remainder; @@ -2055,13 +2569,19 @@ impl EmvConnection<'_> { checksum_data.extend_from_slice(data_authentication); if let Some(tag_9f4a_static_data_authentication_tag_list) = self.get_tag_value("9F4A") { - let static_data_authentication_tag_list_tag_values = DataObjectList::process_data_object_list(self, &tag_9f4a_static_data_authentication_tag_list[..]).unwrap().get_tag_list_tag_values(self); + let static_data_authentication_tag_list_tag_values = + DataObjectList::process_data_object_list( + self, + &tag_9f4a_static_data_authentication_tag_list[..], + ) + .unwrap() + .get_tag_list_tag_values(self); checksum_data.extend_from_slice(&static_data_authentication_tag_list_tag_values[..]); } let cert_checksum = sha::sha1(&checksum_data[..]); - let icc_certificate_checksum = &icc_certificate[checksum_position .. checksum_position + 20]; + let icc_certificate_checksum = &icc_certificate[checksum_position..checksum_position + 20]; if !self.settings.censor_sensitive_fields { trace!("Checksum data: {:02X?}", &checksum_data[..]); @@ -2074,39 +2594,65 @@ impl EmvConnection<'_> { let ascii_pan = bcdutil::bcd_to_ascii(&tag_5a_pan[..]).unwrap(); let icc_ascii_pan = bcdutil::bcd_to_ascii(&icc_certificate_pan).unwrap(); if icc_ascii_pan != ascii_pan { - warn!("PAN mismatch! Cert PAN: {:02X?}, PAN: {:02X?}", icc_ascii_pan, ascii_pan); + warn!( + "PAN mismatch! Cert PAN: {:02X?}, PAN: {:02X?}", + icc_ascii_pan, ascii_pan + ); return Err(()); } is_certificate_expired(&icc_certificate_expiry[..]); - let mut icc_pk_modulus : Vec = Vec::new(); - - let icc_certificate_pk_leftmost_digits_length = icc_certificate_pk_leftmost_digits.iter() - .rev().position(|c| -> bool { *c != 0xBB }).map(|i| icc_certificate_pk_leftmost_digits.len() - i).unwrap(); + let mut icc_pk_modulus: Vec = Vec::new(); + + let icc_certificate_pk_leftmost_digits_length = icc_certificate_pk_leftmost_digits + .iter() + .rev() + .position(|c| -> bool { *c != 0xBB }) + .map(|i| icc_certificate_pk_leftmost_digits.len() - i) + .unwrap(); - icc_pk_modulus.extend_from_slice(&icc_certificate_pk_leftmost_digits[..icc_certificate_pk_leftmost_digits_length]); + icc_pk_modulus.extend_from_slice( + &icc_certificate_pk_leftmost_digits[..icc_certificate_pk_leftmost_digits_length], + ); if let Some(tag_9f48_icc_pk_remainder) = tag_9f48_icc_pk_remainder { icc_pk_modulus.extend_from_slice(&tag_9f48_icc_pk_remainder[..]); } - trace!("ICC PK modulus ({} bytes):\n{}", icc_pk_modulus.len(), HexViewBuilder::new(&icc_pk_modulus[..]).finish()); + trace!( + "ICC PK modulus ({} bytes):\n{}", + icc_pk_modulus.len(), + HexViewBuilder::new(&icc_pk_modulus[..]).finish() + ); Ok((icc_pk_modulus, tag_9f47_icc_pk_exponent.to_vec())) } - pub fn validate_signed_dynamic_application_data(&self, auth_data : &[u8]) -> Result,()> { + pub fn validate_signed_dynamic_application_data( + &self, + auth_data: &[u8], + ) -> Result, ()> { let tag_9f4b_signed_data = self.get_tag_value("9F4B").unwrap(); - trace!("9F4B signed data result moduluslength: ({} bytes):\n{}", tag_9f4b_signed_data.len(), HexViewBuilder::new(&tag_9f4b_signed_data[..]).finish()); + trace!( + "9F4B signed data result moduluslength: ({} bytes):\n{}", + tag_9f4b_signed_data.len(), + HexViewBuilder::new(&tag_9f4b_signed_data[..]).finish() + ); if self.icc.icc_pk.is_none() { warn!("ICC PK missing, can't validate"); return Err(()); } - let tag_9f4b_signed_data_decrypted = self.icc.icc_pk.as_ref().unwrap().public_decrypt(&tag_9f4b_signed_data[..]).unwrap(); + let tag_9f4b_signed_data_decrypted = self + .icc + .icc_pk + .as_ref() + .unwrap() + .public_decrypt(&tag_9f4b_signed_data[..]) + .unwrap(); let tag_9f4b_signed_data_decrypted_length = tag_9f4b_signed_data_decrypted.len(); if tag_9f4b_signed_data_decrypted[1] != 0x05 { warn!("Unrecognized format"); @@ -2116,23 +2662,32 @@ impl EmvConnection<'_> { let tag_9f4b_signed_data_decrypted_hash_algo = tag_9f4b_signed_data_decrypted[2]; assert_eq!(tag_9f4b_signed_data_decrypted_hash_algo, 0x01); - let tag_9f4b_signed_data_decrypted_dynamic_data_length = tag_9f4b_signed_data_decrypted[3] as usize; - - let tag_9f4b_signed_data_decrypted_dynamic_data = &tag_9f4b_signed_data_decrypted[4..4+tag_9f4b_signed_data_decrypted_dynamic_data_length]; + let tag_9f4b_signed_data_decrypted_dynamic_data_length = + tag_9f4b_signed_data_decrypted[3] as usize; + + let tag_9f4b_signed_data_decrypted_dynamic_data = &tag_9f4b_signed_data_decrypted + [4..4 + tag_9f4b_signed_data_decrypted_dynamic_data_length]; let checksum_position = tag_9f4b_signed_data_decrypted_length - 21; - let mut checksum_data : Vec = Vec::new(); + let mut checksum_data: Vec = Vec::new(); checksum_data.extend_from_slice(&tag_9f4b_signed_data_decrypted[1..checksum_position]); checksum_data.extend_from_slice(&auth_data[..]); let signed_data_checksum = sha::sha1(&checksum_data[..]); - let tag_9f4b_signed_data_decrypted_checksum = &tag_9f4b_signed_data_decrypted[checksum_position..checksum_position+20]; + let tag_9f4b_signed_data_decrypted_checksum = + &tag_9f4b_signed_data_decrypted[checksum_position..checksum_position + 20]; if &signed_data_checksum[..] != &tag_9f4b_signed_data_decrypted_checksum[..] { warn!("Signed data checksum mismatch!"); - warn!("Calculated checksum\n{}", HexViewBuilder::new(&signed_data_checksum[..]).finish()); - warn!("Signed data checksum\n{}", HexViewBuilder::new(&tag_9f4b_signed_data_decrypted_checksum[..]).finish()); + warn!( + "Calculated checksum\n{}", + HexViewBuilder::new(&signed_data_checksum[..]).finish() + ); + warn!( + "Signed data checksum\n{}", + HexViewBuilder::new(&tag_9f4b_signed_data_decrypted_checksum[..]).finish() + ); return Err(()); } @@ -2140,7 +2695,10 @@ impl EmvConnection<'_> { Ok(tag_9f4b_signed_data_decrypted_dynamic_data.to_vec()) } - pub fn handle_signed_static_application_data(&mut self, data_authentication : &[u8]) -> Result<(),()> { + pub fn handle_signed_static_application_data( + &mut self, + data_authentication: &[u8], + ) -> Result<(), ()> { debug!("Validate Signed Static Application Data (SDA):"); if self.icc.issuer_pk.is_none() { @@ -2155,25 +2713,48 @@ impl EmvConnection<'_> { return Err(()); } - let tag_93_ssad_decrypted = self.icc.issuer_pk.as_ref().unwrap().public_decrypt(&tag_93_ssad[..]).unwrap(); + let tag_93_ssad_decrypted = self + .icc + .issuer_pk + .as_ref() + .unwrap() + .public_decrypt(&tag_93_ssad[..]) + .unwrap(); assert_eq!(tag_93_ssad_decrypted[1], 0x03); - let mut checksum_data : Vec = Vec::new(); - checksum_data.extend_from_slice(&tag_93_ssad_decrypted[1 .. tag_93_ssad_decrypted.len() - 22]); + let mut checksum_data: Vec = Vec::new(); + checksum_data + .extend_from_slice(&tag_93_ssad_decrypted[1..tag_93_ssad_decrypted.len() - 22]); checksum_data.extend_from_slice(data_authentication); - let static_data_authentication_tag_list_tag_values = DataObjectList::process_data_object_list(self, &self.get_tag_value("9F4A").unwrap()[..]).unwrap().get_tag_list_tag_values(self); + let static_data_authentication_tag_list_tag_values = + DataObjectList::process_data_object_list( + self, + &self.get_tag_value("9F4A").unwrap()[..], + ) + .unwrap() + .get_tag_list_tag_values(self); checksum_data.extend_from_slice(&static_data_authentication_tag_list_tag_values[..]); let ssad_checksum_calculated = sha::sha1(&checksum_data[..]); - let ssad_checksum = &tag_93_ssad_decrypted[tag_93_ssad_decrypted.len() - 22 .. tag_93_ssad_decrypted.len() - 1]; + let ssad_checksum = &tag_93_ssad_decrypted + [tag_93_ssad_decrypted.len() - 22..tag_93_ssad_decrypted.len() - 1]; if &ssad_checksum_calculated[..] != ssad_checksum { warn!("SDA verification mismatch!"); - warn!("Checksum input\n{}", HexViewBuilder::new(&checksum_data[..]).finish()); - warn!("Calculated checksum\n{}", HexViewBuilder::new(&ssad_checksum_calculated[..]).finish()); - warn!("Stored checksum\n{}", HexViewBuilder::new(&ssad_checksum[..]).finish()); + warn!( + "Checksum input\n{}", + HexViewBuilder::new(&checksum_data[..]).finish() + ); + warn!( + "Calculated checksum\n{}", + HexViewBuilder::new(&ssad_checksum_calculated[..]).finish() + ); + warn!( + "Stored checksum\n{}", + HexViewBuilder::new(&ssad_checksum[..]).finish() + ); return Err(()); } @@ -2183,9 +2764,8 @@ impl EmvConnection<'_> { Ok(()) } - pub fn handle_dynamic_data_authentication(&mut self) -> Result<(),()> { - - let mut auth_data : Vec = Vec::new(); + pub fn handle_dynamic_data_authentication(&mut self) -> Result<(), ()> { + let mut auth_data: Vec = Vec::new(); if let Some(tag_9f69_card_authentication_related_data) = self.get_tag_value("9F69") { // ref. EMV Contactless Book C-3, Annex C Fast Dynamic Data Authentication (fDDA) @@ -2199,7 +2779,10 @@ impl EmvConnection<'_> { } if tag_9f69_card_authentication_related_data[0] != 0x01 { - warn!("fDDA version not recognized:{}",tag_9f69_card_authentication_related_data[0]); + warn!( + "fDDA version not recognized:{}", + tag_9f69_card_authentication_related_data[0] + ); return Err(()); } @@ -2207,7 +2790,6 @@ impl EmvConnection<'_> { auth_data.extend_from_slice(&self.get_tag_value("9F02").unwrap()[..]); auth_data.extend_from_slice(&self.get_tag_value("5F2A").unwrap()[..]); auth_data.extend_from_slice(&tag_9f69_card_authentication_related_data[..]); - } else { debug!("Perform Dynamic Data Authentication (DDA):"); @@ -2215,10 +2797,12 @@ impl EmvConnection<'_> { let tag_9f49_ddol = match self.get_tag_value("9F49") { Some(ddol) => ddol, // fall-back to a default DDOL - None => &ddol_default_value + None => &ddol_default_value, }; - let ddol_data = DataObjectList::process_data_object_list(self, &tag_9f49_ddol[..]).unwrap().get_tag_list_tag_values(self); + let ddol_data = DataObjectList::process_data_object_list(self, &tag_9f49_ddol[..]) + .unwrap() + .get_tag_list_tag_values(self); auth_data.extend_from_slice(&ddol_data[..]); @@ -2242,7 +2826,9 @@ impl EmvConnection<'_> { } } - let tag_9f4b_signed_data_decrypted_dynamic_data = self.validate_signed_dynamic_application_data(&auth_data[..]).unwrap(); + let tag_9f4b_signed_data_decrypted_dynamic_data = self + .validate_signed_dynamic_application_data(&auth_data[..]) + .unwrap(); let tag_9f4c_icc_dynamic_number = &tag_9f4b_signed_data_decrypted_dynamic_data[1..]; self.process_tag_as_tlv("9F4C", tag_9f4c_icc_dynamic_number.to_vec()); @@ -2250,7 +2836,7 @@ impl EmvConnection<'_> { Ok(()) } - pub fn start_transaction(&mut self, application : &EmvApplication) -> Result<(),()> { + pub fn start_transaction(&mut self, application: &EmvApplication) -> Result<(), ()> { self.process_settings().unwrap(); self.start_transaction_callback.unwrap()(self)?; @@ -2263,7 +2849,12 @@ impl EmvConnection<'_> { } pub fn handle_card_verification_methods(&mut self) -> Result<(), ()> { - let purchase_amount = str::from_utf8(&bcdutil::bcd_to_ascii(&self.get_tag_value("9F02").unwrap()[..]).unwrap()[..]).unwrap().parse::().unwrap(); + let purchase_amount = str::from_utf8( + &bcdutil::bcd_to_ascii(&self.get_tag_value("9F02").unwrap()[..]).unwrap()[..], + ) + .unwrap() + .parse::() + .unwrap(); let cvm_rules = self.icc.cvm_rules.clone(); for rule in cvm_rules { @@ -2271,10 +2862,12 @@ impl EmvConnection<'_> { let mut success = false; match rule.condition { - CvmConditionCode::UnattendedCash | CvmConditionCode::ManualCash | CvmConditionCode::PurchaseWithCashback => { + CvmConditionCode::UnattendedCash + | CvmConditionCode::ManualCash + | CvmConditionCode::PurchaseWithCashback => { // TODO: conditions currently never supported, maybe should implement it more flexible continue; - }, + } CvmConditionCode::CvmSupported => { skip_if_not_supported = true; } @@ -2283,7 +2876,7 @@ impl EmvConnection<'_> { if purchase_amount >= rule.amount_x { continue; } - }, + } CvmConditionCode::IccCurrencyOverX => { if purchase_amount <= rule.amount_x { continue; @@ -2293,13 +2886,13 @@ impl EmvConnection<'_> { if purchase_amount >= rule.amount_y { continue; } - }, + } CvmConditionCode::IccCurrencyOverY => { if purchase_amount <= rule.amount_y { continue; } - }, - _ => () + } + _ => (), } match rule.code { @@ -2312,11 +2905,15 @@ impl EmvConnection<'_> { } success = false; - }, - CvmCode::PlaintextPin | CvmCode::PlaintextPinAndSignature | CvmCode::EncipheredPinOffline | CvmCode::EncipheredPinOfflineAndSignature => { + } + CvmCode::PlaintextPin + | CvmCode::PlaintextPinAndSignature + | CvmCode::EncipheredPinOffline + | CvmCode::EncipheredPinOfflineAndSignature => { let enciphered_pin = match rule.code { - CvmCode::EncipheredPinOffline | CvmCode::EncipheredPinOfflineAndSignature => true, - _ => false + CvmCode::EncipheredPinOffline + | CvmCode::EncipheredPinOfflineAndSignature => true, + _ => false, }; let ascii_pin = self.pin_callback.unwrap()()?; @@ -2324,40 +2921,52 @@ impl EmvConnection<'_> { if enciphered_pin && self.settings.terminal.capabilities.enciphered_pin { success = match self.handle_verify_enciphered_pin(ascii_pin.as_bytes()) { Ok(_) => true, - Err(_) => false + Err(_) => false, }; } else if self.settings.terminal.capabilities.plaintext_pin { success = match self.handle_verify_plaintext_pin(ascii_pin.as_bytes()) { Ok(_) => true, - Err(_) => false + Err(_) => false, }; } else if skip_if_not_supported { continue; } - }, + } CvmCode::Signature | CvmCode::NoCvm => { success = true; } } if success { - self.settings.terminal.tvr.cardholder_verification_was_not_successful = false; + self.settings + .terminal + .tvr + .cardholder_verification_was_not_successful = false; self.process_tag_as_tlv("9F34", CvmRule::into_9f34_value(Ok(rule))); break; } else { - self.settings.terminal.tvr.cardholder_verification_was_not_successful = true; + self.settings + .terminal + .tvr + .cardholder_verification_was_not_successful = true; self.process_tag_as_tlv("9F34", CvmRule::into_9f34_value(Err(rule))); - if ! skip_if_not_supported { + if !skip_if_not_supported { break; } } } - self.settings.terminal.tsi.cardholder_verification_was_performed = true; + self.settings + .terminal + .tsi + .cardholder_verification_was_performed = true; - if ! self.get_tag_value("9F34").is_some() { - self.settings.terminal.tvr.cardholder_verification_was_not_successful = true; + if !self.get_tag_value("9F34").is_some() { + self.settings + .terminal + .tvr + .cardholder_verification_was_not_successful = true; // "no CVM performed" self.process_tag_as_tlv("9F34", b"\x3F\x00\x01".to_vec()); @@ -2366,8 +2975,7 @@ impl EmvConnection<'_> { Ok(()) } - pub fn handle_terminal_risk_management(&mut self) -> Result<(),()> { - + pub fn handle_terminal_risk_management(&mut self) -> Result<(), ()> { //ref. EMV 4.3 Book 3 - 10.6 Terminal Risk Management //risk management for online transaction: //- check terminal floor limit @@ -2377,7 +2985,7 @@ impl EmvConnection<'_> { Ok(()) } - pub fn handle_offline_data_authentication(&mut self) -> Result<(),()> { + pub fn handle_offline_data_authentication(&mut self) -> Result<(), ()> { //ref. EMV 4.3 Book 3 - 10.3 Offline Data Authentication if !(self.settings.terminal.capabilities.cda && self.icc.capabilities.cda) { @@ -2386,18 +2994,23 @@ impl EmvConnection<'_> { self.settings.terminal.tvr.dda_failed = true; } } else if self.settings.terminal.capabilities.sda && self.icc.capabilities.sda { - if let Err(_) = self.handle_signed_static_application_data(&self.icc.data_authentication.as_ref().unwrap().clone()[..]) { + if let Err(_) = self.handle_signed_static_application_data( + &self.icc.data_authentication.as_ref().unwrap().clone()[..], + ) { self.settings.terminal.tvr.sda_failed = true; } } } - self.settings.terminal.tsi.offline_data_authentication_was_performed = true; + self.settings + .terminal + .tsi + .offline_data_authentication_was_performed = true; Ok(()) } - pub fn handle_terminal_action_analysis(&mut self) -> Result<(),()> { + pub fn handle_terminal_action_analysis(&mut self) -> Result<(), ()> { // ref. EMV 4.3 Book 3 - 10.7 Terminal Action Analysis // Terminal & Issuer Action Code - Denial => default bits 0 // For each bit in the TVR that has a value of 1, the terminal shall check the corresponding bits in @@ -2414,51 +3027,65 @@ impl EmvConnection<'_> { //used (for example, in case of an offline-only terminal) or indicated a desire on the part of the issuer or the acquirer //to process the transaction online but the terminal was unable to go online. - let tag_95_tvr : Vec = self.settings.terminal.tvr.into(); + let tag_95_tvr: Vec = self.settings.terminal.tvr.into(); let tvr_len = tag_95_tvr.len(); self.process_tag_as_tlv("95", tag_95_tvr); debug!("{:?}", self.settings.terminal.tvr); - let action_zero : TerminalVerificationResults = vec![0; tvr_len].into(); - let action_one : TerminalVerificationResults = vec![1; tvr_len].into(); + let action_zero: TerminalVerificationResults = vec![0; tvr_len].into(); + let action_one: TerminalVerificationResults = vec![1; tvr_len].into(); - let tag_9f0e_issuer_action_code_denial : TerminalVerificationResults = match self.get_tag_value("9F0E") { - Some(iac) => { - let ac : TerminalVerificationResults = iac.to_vec().into(); - debug!("Action Code - Denial: {:?}", ac); - ac - }, - None => action_zero.clone() - }; - let tag_9f0f_issuer_action_code_online : TerminalVerificationResults = match self.get_tag_value("9F0F") { - Some(iac) => { - let ac : TerminalVerificationResults = iac.to_vec().into(); - debug!("Action Code - Online: {:?}", ac); - ac - }, - None => action_one.clone() - }; + let tag_9f0e_issuer_action_code_denial: TerminalVerificationResults = + match self.get_tag_value("9F0E") { + Some(iac) => { + let ac: TerminalVerificationResults = iac.to_vec().into(); + debug!("Action Code - Denial: {:?}", ac); + ac + } + None => action_zero.clone(), + }; + let tag_9f0f_issuer_action_code_online: TerminalVerificationResults = + match self.get_tag_value("9F0F") { + Some(iac) => { + let ac: TerminalVerificationResults = iac.to_vec().into(); + debug!("Action Code - Online: {:?}", ac); + ac + } + None => action_one.clone(), + }; let tag_9f0d_issuer_action_code_default = match self.get_tag_value("9F0D") { Some(iac) => { - let ac : TerminalVerificationResults = iac.to_vec().into(); + let ac: TerminalVerificationResults = iac.to_vec().into(); debug!("Action Code - Default: {:?}", ac); ac - }, - None => action_one.clone() + } + None => action_one.clone(), }; - let terminal_action_code_denial : TerminalVerificationResults = action_zero.clone(); - let terminal_action_code_online : TerminalVerificationResults = action_zero.clone(); - let terminal_action_code_default : TerminalVerificationResults = action_zero.clone(); + let terminal_action_code_denial: TerminalVerificationResults = action_zero.clone(); + let terminal_action_code_online: TerminalVerificationResults = action_zero.clone(); + let terminal_action_code_default: TerminalVerificationResults = action_zero.clone(); // TODO: actually make the GENERATE AC happen - if TerminalVerificationResults::action_code_matches(&self.settings.terminal.tvr, &tag_9f0e_issuer_action_code_denial, &terminal_action_code_denial) { + if TerminalVerificationResults::action_code_matches( + &self.settings.terminal.tvr, + &tag_9f0e_issuer_action_code_denial, + &terminal_action_code_denial, + ) { debug!("Action Code - Denial matches => GENERATE AC AAC needed"); - } else if TerminalVerificationResults::action_code_matches(&self.settings.terminal.tvr, &tag_9f0f_issuer_action_code_online, &terminal_action_code_online) { + } else if TerminalVerificationResults::action_code_matches( + &self.settings.terminal.tvr, + &tag_9f0f_issuer_action_code_online, + &terminal_action_code_online, + ) { // online action codes for online capable terminals debug!("Action Code - Online matches => GENERATE AC ARQC needed"); - } else if TerminalVerificationResults::action_code_matches(&self.settings.terminal.tvr, &tag_9f0d_issuer_action_code_default, &terminal_action_code_default) { + } else if TerminalVerificationResults::action_code_matches( + &self.settings.terminal.tvr, + &tag_9f0d_issuer_action_code_default, + &terminal_action_code_default, + ) { // TODO: offline-only terminals or if online authorization is not possible this is to be done debug!("Action Code - Default matches => GENERATE AC AAC needed"); } else { @@ -2468,12 +3095,14 @@ impl EmvConnection<'_> { Ok(()) } - pub fn handle_issuer_authentication_data(&mut self) -> Result<(),()> { + pub fn handle_issuer_authentication_data(&mut self) -> Result<(), ()> { // ref. EMV 4.3 Book 3 - 10.9 Online Processing // ref. EMV 4.3 Book 3 - 6.5.4 EXTERNAL AUTHENTICATE Command-Response APDUs let tag_91_issuer_authentication_data = self.get_tag_value("91"); - if !self.icc.capabilities.issuer_authentication && tag_91_issuer_authentication_data.is_none() { + if !self.icc.capabilities.issuer_authentication + && tag_91_issuer_authentication_data.is_none() + { return Ok(()); } @@ -2482,23 +3111,23 @@ impl EmvConnection<'_> { let apdu_command_external_authenticate = b"\x00\x82\x00\x00"; // EXTERNAL AUTHENTICATE let mut external_authenticate_command = apdu_command_external_authenticate.to_vec(); external_authenticate_command.push(tag_91_issuer_authentication_data.unwrap().len() as u8); - external_authenticate_command.extend_from_slice(&tag_91_issuer_authentication_data.unwrap()[..]); + external_authenticate_command + .extend_from_slice(&tag_91_issuer_authentication_data.unwrap()[..]); let (response_trailer, _response_data) = self.send_apdu(&external_authenticate_command); if !is_success_response(&response_trailer) { self.settings.terminal.tvr.issuer_authentication_failed = true; } - Ok(()) } } #[derive(Clone, Debug)] pub struct EmvApplication { - pub aid : Vec, - pub label : Vec, - pub priority : Vec + pub aid: Vec, + pub label: Vec, + pub priority: Vec, } #[derive(Deserialize, Serialize, Debug, Copy, Clone)] @@ -2508,7 +3137,7 @@ pub enum FieldSensitivity { Sensitive, Track2, PrimaryAccountNumber, - PersonallyIdentifiableInformation + PersonallyIdentifiableInformation, } #[derive(Deserialize, Serialize, Debug, Copy, Clone)] @@ -2526,7 +3155,7 @@ pub enum FieldFormat { DataObjectList, Track2, Date, - Time + Time, } #[derive(Deserialize, Serialize, Debug, Copy, Clone)] @@ -2534,7 +3163,7 @@ pub enum FieldSource { Icc, Terminal, Issuer, - IssuerOrTerminal + IssuerOrTerminal, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -2545,11 +3174,11 @@ pub struct EmvTag { pub format: Option, pub min: Option, pub max: Option, - pub source: Option + pub source: Option, } impl EmvTag { - pub fn new(tag_name : &str) -> EmvTag { + pub fn new(tag_name: &str) -> EmvTag { EmvTag { tag: tag_name.to_string(), name: "Unknown tag".to_string(), @@ -2557,7 +3186,7 @@ impl EmvTag { format: None, min: None, max: None, - source: None + source: None, } } } @@ -2566,19 +3195,23 @@ impl EmvTag { pub struct RsaPublicKey { pub modulus: String, pub exponent: String, - sensitive: Option + sensitive: Option, } impl RsaPublicKey { - pub fn new(modulus : &[u8], exponent : &[u8], sensitive : bool) -> RsaPublicKey { - RsaPublicKey { modulus: hex::encode_upper(modulus), exponent: hex::encode_upper(exponent), sensitive: Some(sensitive) } + pub fn new(modulus: &[u8], exponent: &[u8], sensitive: bool) -> RsaPublicKey { + RsaPublicKey { + modulus: hex::encode_upper(modulus), + exponent: hex::encode_upper(exponent), + sensitive: Some(sensitive), + } } pub fn get_key_byte_size(&self) -> usize { return self.modulus.len() / 2; } - pub fn public_encrypt(&self, plaintext_data : &[u8]) -> Result, ()> { + pub fn public_encrypt(&self, plaintext_data: &[u8]) -> Result, ()> { let pk_modulus_raw = hex::decode(&self.modulus).unwrap(); let pk_modulus = BigNum::from_slice(&pk_modulus_raw[..]).unwrap(); let pk_exponent = BigNum::from_slice(&(hex::decode(&self.exponent).unwrap())[..]).unwrap(); @@ -2587,13 +3220,14 @@ impl RsaPublicKey { let mut encrypt_output = [0u8; 4096]; - let length = match rsa.public_encrypt(plaintext_data, &mut encrypt_output[..], Padding::NONE) { - Ok(length) => length, - Err(_) => { - warn!("Could not encrypt data"); - return Err(()); - } - }; + let length = + match rsa.public_encrypt(plaintext_data, &mut encrypt_output[..], Padding::NONE) { + Ok(length) => length, + Err(_) => { + warn!("Could not encrypt data"); + return Err(()); + } + }; let mut data = Vec::new(); data.extend_from_slice(&encrypt_output[..length]); @@ -2601,7 +3235,11 @@ impl RsaPublicKey { if let Some(true) = self.sensitive { trace!("Encrypt result ({} bytes)", data.len()); } else { - trace!("Encrypt result ({} bytes):\n{}", data.len(), HexViewBuilder::new(&data[..]).finish()); + trace!( + "Encrypt result ({} bytes):\n{}", + data.len(), + HexViewBuilder::new(&data[..]).finish() + ); } if data.len() != pk_modulus_raw.len() { @@ -2612,7 +3250,7 @@ impl RsaPublicKey { Ok(data) } - pub fn public_decrypt(&self, cipher_data : &[u8]) -> Result, ()> { + pub fn public_decrypt(&self, cipher_data: &[u8]) -> Result, ()> { let pk_modulus_raw = hex::decode(&self.modulus).unwrap(); let pk_modulus = BigNum::from_slice(&pk_modulus_raw[..]).unwrap(); let pk_exponent = BigNum::from_slice(&(hex::decode(&self.exponent).unwrap())[..]).unwrap(); @@ -2635,7 +3273,11 @@ impl RsaPublicKey { if let Some(true) = self.sensitive { trace!("Decrypt result ({} bytes)", data.len()); } else { - trace!("Decrypt result ({} bytes):\n{}", data.len(), HexViewBuilder::new(&data[..]).finish()); + trace!( + "Decrypt result ({} bytes):\n{}", + data.len(), + HexViewBuilder::new(&data[..]).finish() + ); } if data.len() != pk_modulus_raw.len() { @@ -2658,30 +3300,28 @@ impl RsaPublicKey { #[derive(Serialize, Deserialize)] pub struct CertificateAuthority { issuer: String, - certificates: HashMap + certificates: HashMap, } -pub fn is_success_response(response_trailer : &Vec) -> bool { +pub fn is_success_response(response_trailer: &Vec) -> bool { let mut success = false; - if response_trailer.len() >= 2 - && response_trailer[0] == 0x90 && response_trailer[1] == 0x00 { + if response_trailer.len() >= 2 && response_trailer[0] == 0x90 && response_trailer[1] == 0x00 { success = true; } success } -fn parse_tlv(raw_data : &[u8]) -> Option { +fn parse_tlv(raw_data: &[u8]) -> Option { let (tlv_data, leftover_buffer) = Tlv::parse(raw_data); if leftover_buffer.len() > 0 { trace!("Could not parse as TLV: {:02X?}", leftover_buffer); } - let tlv_data : Option = match tlv_data { + let tlv_data: Option = match tlv_data { Ok(tlv) => Some(tlv), - Err(_) => None - + Err(_) => None, }; return tlv_data; @@ -2693,16 +3333,19 @@ fn find_tlv_tag(buf: &[u8], tag: &str) -> Option { loop { let (tlv_data, leftover_buffer) = Tlv::parse(read_buffer); - let tlv_data : Tlv = match tlv_data { + let tlv_data: Tlv = match tlv_data { Ok(tlv) => tlv, Err(err) => { if leftover_buffer.len() > 0 { - trace!("Could not parse as TLV! error:{:?}, data: {:02X?}", err, read_buffer); + trace!( + "Could not parse as TLV! error:{:?}, data: {:02X?}", + err, + read_buffer + ); } break; } - }; read_buffer = leftover_buffer; @@ -2715,7 +3358,7 @@ fn find_tlv_tag(buf: &[u8], tag: &str) -> Option { if let Value::Constructed(v) = tlv_data.value() { for tlv_tag in v { - let child_tlv : Option = find_tlv_tag(&tlv_tag.to_vec(), tag); + let child_tlv: Option = find_tlv_tag(&tlv_tag.to_vec(), tag); if child_tlv.is_some() { return child_tlv; } @@ -2730,29 +3373,35 @@ fn find_tlv_tag(buf: &[u8], tag: &str) -> Option { None } - -pub fn get_ca_public_key<'a>(ca_data : &'a HashMap, rid : &[u8], index : &[u8]) -> Option<&'a RsaPublicKey> { +pub fn get_ca_public_key<'a>( + ca_data: &'a HashMap, + rid: &[u8], + index: &[u8], +) -> Option<&'a RsaPublicKey> { match ca_data.get(&hex::encode_upper(&rid)) { - Some(ca) => { - match ca.certificates.get(&hex::encode_upper(&index)) { - Some(pk) => Some(pk), - _ => { - warn!("No CA key defined! rid:{:02X?}, index:{:02X?}", rid, index); - return None - } + Some(ca) => match ca.certificates.get(&hex::encode_upper(&index)) { + Some(pk) => Some(pk), + _ => { + warn!("No CA key defined! rid:{:02X?}, index:{:02X?}", rid, index); + return None; } }, - _ => None + _ => None, } } -pub fn is_certificate_expired(date_bcd : &[u8]) -> bool { +pub fn is_certificate_expired(date_bcd: &[u8]) -> bool { let today = Utc::today().naive_utc(); - let expiry_date = NaiveDate::parse_from_str(&format!("01{:02X?}", date_bcd), "%d[%m, %y]").unwrap(); + let expiry_date = + NaiveDate::parse_from_str(&format!("01{:02X?}", date_bcd), "%d[%m, %y]").unwrap(); let duration = today.signed_duration_since(expiry_date).num_days(); if duration > 30 { - warn!("Certificate expiry date (MMYY) {:02X?} is {} days in the past", date_bcd, duration.to_string()); + warn!( + "Certificate expiry date (MMYY) {:02X?} is {} days in the past", + date_bcd, + duration.to_string() + ); return true; } @@ -2762,44 +3411,47 @@ pub fn is_certificate_expired(date_bcd : &[u8]) -> bool { #[cfg(test)] mod tests { - use super::*; use super::bcdutil::*; - use hexplay::HexViewBuilder; - use std::str; - use std::sync::Once; - use serde::{Deserialize, Serialize}; - use log4rs; - use openssl::rsa::{Rsa, Padding}; + use super::*; use hex; - use std::fs::{self}; + use hexplay::HexViewBuilder; use log::{debug, LevelFilter}; + use log4rs; use log4rs::{ append::console::ConsoleAppender, - config::{Appender, Root} + config::{Appender, Root}, }; + use openssl::rsa::{Padding, Rsa}; + use serde::{Deserialize, Serialize}; + use std::fs::{self}; + use std::str; + use std::sync::Once; static LOGGING: Once = Once::new(); - static SETTINGS_FILE : &str = "config/settings.yaml"; + static SETTINGS_FILE: &str = "config/settings.yaml"; #[derive(Serialize, Deserialize, Clone)] struct ApduRequestResponse { - req : String, - res : String + req: String, + res: String, } impl ApduRequestResponse { - fn to_raw_vec(s : &String) -> Vec { + fn to_raw_vec(s: &String) -> Vec { hex::decode(s.replace(" ", "")).unwrap() } } struct DummySmartCardConnection { - test_data_file : String + test_data_file: String, } impl DummySmartCardConnection { - fn find_dummy_apdu<'a>(test_data : &'a Vec, apdu : &[u8]) -> Option<&'a ApduRequestResponse> { + fn find_dummy_apdu<'a>( + test_data: &'a Vec, + apdu: &[u8], + ) -> Option<&'a ApduRequestResponse> { for data in test_data { if &apdu[..] == &ApduRequestResponse::to_raw_vec(&data.req)[..] { return Some(data); @@ -2811,12 +3463,13 @@ mod tests { } impl ApduInterface for DummySmartCardConnection { - fn send_apdu(&self, apdu : &[u8]) -> Result, ()> { - let mut output : Vec = Vec::new(); + fn send_apdu(&self, apdu: &[u8]) -> Result, ()> { + let mut output: Vec = Vec::new(); let mut response = b"\x6A\x82".to_vec(); // file not found error - let test_data : Vec = serde_yaml::from_str(&fs::read_to_string(&self.test_data_file).unwrap()).unwrap(); + let test_data: Vec = + serde_yaml::from_str(&fs::read_to_string(&self.test_data_file).unwrap()).unwrap(); if let Some(req) = DummySmartCardConnection::find_dummy_apdu(&test_data, &apdu[..]) { response = ApduRequestResponse::to_raw_vec(&req.res); @@ -2842,8 +3495,8 @@ mod tests { fn test_rsa_key() -> Result<(), String> { init_logging(); - const KEY_SIZE : u32 = 1408; - const KEY_BYTE_SIZE : usize = KEY_SIZE as usize / 8; + const KEY_SIZE: u32 = 1408; + const KEY_BYTE_SIZE: usize = KEY_SIZE as usize / 8; // openssl key generation: // openssl genrsa -out icc_1234560012345608_e_3_private_key.pem -3 1024 @@ -2851,26 +3504,40 @@ mod tests { // openssl genrsa -out AFFFFFFFFF_92_ca_private_key.pem -3 1408 // openssl rsa -in AFFFFFFFFF_92_ca_private_key.pem -outform PEM -pubout -out AFFFFFFFFF_92_ca_key.pem - let rsa = Rsa::private_key_from_pem(&fs::read_to_string("config/AFFFFFFFFF_92_ca_private_key.pem").unwrap().as_bytes()).unwrap(); + let rsa = Rsa::private_key_from_pem( + &fs::read_to_string("config/AFFFFFFFFF_92_ca_private_key.pem") + .unwrap() + .as_bytes(), + ) + .unwrap(); //let rsa = Rsa::private_key_from_pem(&fs::read_to_string("config/iin_313233343536_e_3_private_key.pem").unwrap().as_bytes()).unwrap(); //let rsa = Rsa::private_key_from_pem(&fs::read_to_string("config/icc_1234560012345608_e_3_private_key.pem").unwrap().as_bytes()).unwrap(); - - let public_key_modulus = &rsa.n().to_vec()[..]; + + let public_key_modulus = &rsa.n().to_vec()[..]; let public_key_exponent = &rsa.e().to_vec()[..]; let private_key_exponent = &rsa.d().to_vec()[..]; let pk = RsaPublicKey::new(public_key_modulus, public_key_exponent, false); - debug!("modulus: {:02X?}, exponent: {:02X?}, private_exponent: {:02X?}", public_key_modulus, public_key_exponent, private_key_exponent); + debug!( + "modulus: {:02X?}, exponent: {:02X?}, private_exponent: {:02X?}", + public_key_modulus, public_key_exponent, private_key_exponent + ); let mut encrypt_output = [0u8; KEY_BYTE_SIZE]; let mut plaintext_data = [0u8; KEY_BYTE_SIZE]; plaintext_data[0] = 0x6A; plaintext_data[1] = 0xFF; - plaintext_data[KEY_BYTE_SIZE-1] = 0xBC; + plaintext_data[KEY_BYTE_SIZE - 1] = 0xBC; - let encrypt_size = rsa.private_encrypt(&plaintext_data[..], &mut encrypt_output[..], Padding::NONE).unwrap(); + let encrypt_size = rsa + .private_encrypt(&plaintext_data[..], &mut encrypt_output[..], Padding::NONE) + .unwrap(); - debug!("Encrypt result ({} bytes):\n{}", encrypt_output.len(), HexViewBuilder::new(&encrypt_output[..]).finish()); + debug!( + "Encrypt result ({} bytes):\n{}", + encrypt_output.len(), + HexViewBuilder::new(&encrypt_output[..]).finish() + ); let decrypted_data = pk.public_decrypt(&encrypt_output[0..encrypt_size]).unwrap(); @@ -2879,7 +3546,7 @@ mod tests { Ok(()) } - fn pse_application_select(applications : &Vec) -> Result { + fn pse_application_select(applications: &Vec) -> Result { Ok(applications[0].clone()) } @@ -2891,7 +3558,7 @@ mod tests { Ok(1) } - fn start_transaction(connection : &mut EmvConnection) -> Result<(), ()> { + fn start_transaction(connection: &mut EmvConnection) -> Result<(), ()> { // force transaction date as 24.07.2020 connection.process_tag_as_tlv("9A", b"\x20\x07\x24".to_vec()); @@ -2905,7 +3572,7 @@ mod tests { Ok(()) } - fn setup_connection(connection : &mut EmvConnection) -> Result<(), ()> { + fn setup_connection(connection: &mut EmvConnection) -> Result<(), ()> { connection.contactless = false; connection.pse_application_select_callback = Some(&pse_application_select); connection.pin_callback = Some(&pin_entry); @@ -2920,7 +3587,9 @@ mod tests { init_logging(); let mut connection = EmvConnection::new(SETTINGS_FILE).unwrap(); - let smart_card_connection = DummySmartCardConnection { test_data_file: "test_data.yaml".to_string()}; + let smart_card_connection = DummySmartCardConnection { + test_data_file: "test_data.yaml".to_string(), + }; connection.interface = Some(&smart_card_connection); setup_connection(&mut connection)?; @@ -2937,7 +3606,9 @@ mod tests { init_logging(); let mut connection = EmvConnection::new(SETTINGS_FILE).unwrap(); - let smart_card_connection = DummySmartCardConnection { test_data_file: "test_data.yaml".to_string()}; + let smart_card_connection = DummySmartCardConnection { + test_data_file: "test_data.yaml".to_string(), + }; connection.interface = Some(&smart_card_connection); setup_connection(&mut connection)?; @@ -2958,7 +3629,9 @@ mod tests { init_logging(); let mut connection = EmvConnection::new(SETTINGS_FILE).unwrap(); - let smart_card_connection = DummySmartCardConnection { test_data_file: "test_data.yaml".to_string()}; + let smart_card_connection = DummySmartCardConnection { + test_data_file: "test_data.yaml".to_string(), + }; connection.interface = Some(&smart_card_connection); setup_connection(&mut connection)?; @@ -2968,7 +3641,10 @@ mod tests { connection.start_transaction(&application)?; - connection.process_tag_as_tlv("9F02", ascii_to_bcd_n(format!("{}", amount).as_bytes(), 6).unwrap()); + connection.process_tag_as_tlv( + "9F02", + ascii_to_bcd_n(format!("{}", amount).as_bytes(), 6).unwrap(), + ); connection.handle_card_verification_methods()?; @@ -2981,16 +3657,30 @@ mod tests { match connection.handle_1st_generate_ac()? { CryptogramType::AuthorisationRequestCryptogram => { connection.handle_issuer_authentication_data()?; - assert!(!connection.settings.terminal.tvr.issuer_authentication_failed); + assert!( + !connection + .settings + .terminal + .tvr + .issuer_authentication_failed + ); match connection.handle_2nd_generate_ac()? { - CryptogramType::AuthorisationRequestCryptogram => { return Err(()); }, - CryptogramType::TransactionCertificate => { /* Expected */ }, - CryptogramType::ApplicationAuthenticationCryptogram => { return Err(()); } + CryptogramType::AuthorisationRequestCryptogram => { + return Err(()); + } + CryptogramType::TransactionCertificate => { /* Expected */ } + CryptogramType::ApplicationAuthenticationCryptogram => { + return Err(()); + } } - }, - CryptogramType::TransactionCertificate => { return Err(()); /* For test case 2ND GEN AC TC is expected */ }, - CryptogramType::ApplicationAuthenticationCryptogram => { return Err(()); } + } + CryptogramType::TransactionCertificate => { + return Err(()); /* For test case 2ND GEN AC TC is expected */ + } + CryptogramType::ApplicationAuthenticationCryptogram => { + return Err(()); + } } Ok(()) @@ -3000,45 +3690,48 @@ mod tests { fn test_data_object_list_processing() -> Result<(), ()> { let mut connection = EmvConnection::new(SETTINGS_FILE).unwrap(); - let cdol1 : [u8; 39] = [ + let cdol1: [u8; 39] = [ //tag length - 0x9F,0x02, 0x06, - 0x9F,0x03, 0x06, - 0x9F,0x1A, 0x02, - 0x95, 0x05, - 0x5F,0x2A, 0x02, - 0x9A, 0x03, - 0x9C, 0x01, - 0x9F,0x37, 0x04, - 0x9F,0x35, 0x01, - 0x9F,0x45, 0x02, - 0x9F,0x4C, 0x08, - 0x9F,0x34, 0x03, - 0x9F,0x21, 0x03, - 0x9F,0x7C, 0x14]; - - let dol1 : DataObjectList = DataObjectList::process_data_object_list(&connection, &cdol1).unwrap(); + 0x9F, 0x02, 0x06, 0x9F, 0x03, 0x06, 0x9F, 0x1A, 0x02, 0x95, 0x05, 0x5F, 0x2A, 0x02, + 0x9A, 0x03, 0x9C, 0x01, 0x9F, 0x37, 0x04, 0x9F, 0x35, 0x01, 0x9F, 0x45, 0x02, 0x9F, + 0x4C, 0x08, 0x9F, 0x34, 0x03, 0x9F, 0x21, 0x03, 0x9F, 0x7C, 0x14, + ]; + let dol1: DataObjectList = + DataObjectList::process_data_object_list(&connection, &cdol1).unwrap(); // Check zero padded DOL - let dol1_output : Vec = dol1.get_tag_list_tag_values(&connection); + let dol1_output: Vec = dol1.get_tag_list_tag_values(&connection); assert_eq!(&dol1_output[..], [0; 66]); // Fill couple of tags with information - connection.process_tag_as_tlv("9F02", ascii_to_bcd_n(format!("{}", 123456).as_bytes(), 6).unwrap()); + connection.process_tag_as_tlv( + "9F02", + ascii_to_bcd_n(format!("{}", 123456).as_bytes(), 6).unwrap(), + ); connection.process_tag_as_tlv("9C", [0xFF].to_vec()); - let tag_82_data : [u8; 2] = [0x39, 0x00]; + let tag_82_data: [u8; 2] = [0x39, 0x00]; connection.process_tag_as_tlv("82", tag_82_data.to_vec()); - let dol1_output2 : Vec = dol1.get_tag_list_tag_values(&connection); - assert_eq!(&dol1_output2[..], [0, 0, 0, 18, 52, 86, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let dol1_output2: Vec = dol1.get_tag_list_tag_values(&connection); + assert_eq!( + &dol1_output2[..], + [ + 0, 0, 0, 18, 52, 86, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); // Curious case of static data authentication list (9F4A) which slightly differs from regular DOL by not providing tag length - let static_data_authentication_list : [u8; 1] = [0x82]; - let static_dol1 : DataObjectList = DataObjectList::process_data_object_list(&connection, &static_data_authentication_list).unwrap(); - let static_data_authentication_list_output : Vec = static_dol1.get_tag_list_tag_values(&connection); + let static_data_authentication_list: [u8; 1] = [0x82]; + let static_dol1: DataObjectList = + DataObjectList::process_data_object_list(&connection, &static_data_authentication_list) + .unwrap(); + let static_data_authentication_list_output: Vec = + static_dol1.get_tag_list_tag_values(&connection); assert_eq!(&static_data_authentication_list_output[..], tag_82_data); Ok(()) @@ -3046,10 +3739,10 @@ mod tests { #[test] fn test_track2() -> Result<(), ()> { - let track2_data = ";4321432143214321=2612101123456789123?"; + let track2_data = ";4321432143214321=2612101123456789123?"; let track2_data_censored = ";432143******4321=2612101************?"; - let mut track2 : Track2 = Track2::new(track2_data); + let mut track2: Track2 = Track2::new(track2_data); assert_eq!(format!("{}", track2), track2_data); assert_eq!(track2.primary_account_number, "4321432143214321"); @@ -3065,13 +3758,14 @@ mod tests { Ok(()) } - + #[test] fn test_track1() -> Result<(), ()> { - let track1_data = "%B4321432143214321^Mc'Doe/JOHN^2609101123456789012345678901234?"; - let track1_data_censored = "%B432143******4321^******/****^2609101************************?"; + let track1_data = "%B4321432143214321^Mc'Doe/JOHN^2609101123456789012345678901234?"; + let track1_data_censored = + "%B432143******4321^******/****^2609101************************?"; - let mut track1 : Track1 = Track1::new(track1_data); + let mut track1: Track1 = Track1::new(track1_data); assert_eq!(format!("{}", track1), track1_data); assert_eq!(track1.primary_account_number, "4321432143214321"); @@ -3091,5 +3785,4 @@ mod tests { Ok(()) } - } diff --git a/terminalsimulator/src/main.rs b/terminalsimulator/src/main.rs index 7b2b265..11c6a97 100644 --- a/terminalsimulator/src/main.rs +++ b/terminalsimulator/src/main.rs @@ -1,38 +1,44 @@ -use pcsc::{Context, Card, Scope, ShareMode, Protocols, MAX_BUFFER_SIZE, MAX_ATR_SIZE}; -use regex::Regex; -use log::{error, warn, info, debug}; -use log4rs; use clap::Parser; +use hex; +use log::{debug, error, info, warn}; +use log4rs; +use pcsc::{Card, Context, Protocols, Scope, ShareMode, MAX_ATR_SIZE, MAX_BUFFER_SIZE}; +use regex::Regex; use std::io::{self}; -use std::{thread, time}; -use std::str; use std::path::PathBuf; -use hex; +use std::str; +use std::{thread, time}; use emvpt::*; -static mut INTERACTIVE : bool = false; -static mut PIN_OPTION : Option = None; +static mut INTERACTIVE: bool = false; +static mut PIN_OPTION: Option = None; pub enum ReaderError { ReaderConnectionFailed(String), ReaderNotFound, CardConnectionFailed(String), - CardNotFound + CardNotFound, } pub struct SmartCardConnection { - ctx : Option, - card : Option, - pub contactless : bool + ctx: Option, + card: Option, + pub contactless: bool, } impl ApduInterface for SmartCardConnection { - fn send_apdu(&self, apdu : &[u8]) -> Result, ()> { - let mut output : Vec = Vec::new(); + fn send_apdu(&self, apdu: &[u8]) -> Result, ()> { + let mut output: Vec = Vec::new(); let mut apdu_response_buffer = [0; MAX_BUFFER_SIZE]; - output.extend_from_slice(self.card.as_ref().unwrap().transmit(apdu, &mut apdu_response_buffer).unwrap()); + output.extend_from_slice( + self.card + .as_ref() + .unwrap() + .transmit(apdu, &mut apdu_response_buffer) + .unwrap(), + ); Ok(output) } @@ -41,13 +47,13 @@ impl ApduInterface for SmartCardConnection { impl SmartCardConnection { pub fn new() -> SmartCardConnection { SmartCardConnection { - ctx : None, - card : None, - contactless : false + ctx: None, + card: None, + contactless: false, } } - fn is_contactless_reader(&self, reader_name : &str) -> bool { + fn is_contactless_reader(&self, reader_name: &str) -> bool { if Regex::new(r"^ACS ACR12").unwrap().is_match(reader_name) { debug!("Card reader is deemed contactless"); return true; @@ -61,7 +67,10 @@ impl SmartCardConnection { self.ctx = match Context::establish(Scope::User) { Ok(ctx) => Some(ctx), Err(err) => { - return Err(ReaderError::ReaderConnectionFailed(format!("Failed to establish context: {}", err))); + return Err(ReaderError::ReaderConnectionFailed(format!( + "Failed to establish context: {}", + err + ))); } }; } @@ -70,7 +79,10 @@ impl SmartCardConnection { let readers_size = match ctx.list_readers_len() { Ok(readers_size) => readers_size, Err(err) => { - return Err(ReaderError::ReaderConnectionFailed(format!("Failed to list readers size: {}", err))); + return Err(ReaderError::ReaderConnectionFailed(format!( + "Failed to list readers size: {}", + err + ))); } }; @@ -78,7 +90,10 @@ impl SmartCardConnection { let readers = match ctx.list_readers(&mut readers_buf) { Ok(readers) => readers, Err(err) => { - return Err(ReaderError::ReaderConnectionFailed(format!("Failed to list readers: {}", err))); + return Err(ReaderError::ReaderConnectionFailed(format!( + "Failed to list readers: {}", + err + ))); } }; @@ -87,11 +102,14 @@ impl SmartCardConnection { Ok(card) => { self.contactless = self.is_contactless_reader(reader.to_str().unwrap()); - debug!("Card reader: {:?}, contactless:{}", reader, self.contactless); + debug!( + "Card reader: {:?}, contactless:{}", + reader, self.contactless + ); Some(card) - }, - _ => None + } + _ => None, }; if self.card.is_some() { @@ -100,13 +118,24 @@ impl SmartCardConnection { } if self.card.is_some() { - const MAX_NAME_SIZE : usize = 2048; + const MAX_NAME_SIZE: usize = 2048; let mut names_buffer = [0; MAX_NAME_SIZE]; let mut atr_buffer = [0; MAX_ATR_SIZE]; - let card_status = self.card.as_ref().unwrap().status2(&mut names_buffer, &mut atr_buffer).unwrap(); + let card_status = self + .card + .as_ref() + .unwrap() + .status2(&mut names_buffer, &mut atr_buffer) + .unwrap(); // https://www.eftlab.com/knowledge-base/171-atr-list-full/ - debug!("Card ATR:\n{}", format!("{:02X?}", card_status.atr()).replace(|c: char| !(c.is_ascii_alphanumeric() || c.is_ascii_whitespace()), "")); + debug!( + "Card ATR:\n{}", + format!("{:02X?}", card_status.atr()).replace( + |c: char| !(c.is_ascii_alphanumeric() || c.is_ascii_whitespace()), + "" + ) + ); debug!("Card protocol: {:?}", card_status.protocol2().unwrap()); } else { return Err(ReaderError::CardNotFound); @@ -116,13 +145,17 @@ impl SmartCardConnection { } } -fn pse_application_select(applications : &Vec) -> Result { +fn pse_application_select(applications: &Vec) -> Result { let user_interactive = unsafe { INTERACTIVE }; if user_interactive && applications.len() > 1 { println!("Select payment application:"); for i in 0..applications.len() { - println!("{:02}. {}", i+1, str::from_utf8(&applications[i].label).unwrap()); + println!( + "{:02}. {}", + i + 1, + str::from_utf8(&applications[i].label).unwrap() + ); } print!("> "); @@ -144,7 +177,6 @@ fn pin_entry() -> Result { } } - if user_interactive { println!("Enter PIN:"); print!("> "); @@ -164,7 +196,11 @@ fn amount_entry() -> Result { let mut stdin_buffer = String::new(); io::stdin().read_line(&mut stdin_buffer).unwrap(); - return Ok(format!("{:.0}", stdin_buffer.trim().parse::().unwrap() * 100.0).parse::().unwrap()); + return Ok( + format!("{:.0}", stdin_buffer.trim().parse::().unwrap() * 100.0) + .parse::() + .unwrap(), + ); } Ok(1) @@ -179,32 +215,37 @@ struct Args { interactive: bool, /// Print all read or generated tags - #[arg(long="print-tags", default_value_t = false)] + #[arg(long = "print-tags", default_value_t = false)] print_tags: bool, /// Censor sensitive data from the output - #[arg(long="censor-sensitive-fields", default_value_t = false)] + #[arg(long = "censor-sensitive-fields", default_value_t = false)] censor_sensitive_fields: bool, - /// Exit after connecting the card - #[arg(long="stop-after-connect", default_value_t = false)] + /// Exit after connecting the card + #[arg(long = "stop-after-connect", default_value_t = false)] stop_after_connect: bool, /// Stop processing transaction after card data has been read - #[arg(long="stop-after-read", default_value_t = false)] + #[arg(long = "stop-after-read", default_value_t = false)] stop_after_read: bool, /// Card PIN code to be used when PIN code is required - #[arg(short, long, value_name="PIN CODE")] + #[arg(short, long, value_name = "PIN CODE")] pin: Option, /// Card PIN code to be used when PIN code is required - #[arg(short, long, value_name="settings file", default_value = "config/settings.yaml")] + #[arg( + short, + long, + value_name = "settings file", + default_value = "config/settings.yaml" + )] settings: PathBuf, /// Print TLV data in human readable form - #[arg(long, value_name="TLV")] - print_tlv: Option + #[arg(long, value_name = "TLV")] + print_tlv: Option, } fn run() -> Result, String> { @@ -231,7 +272,9 @@ fn run() -> Result, String> { connection.amount_callback = Some(&amount_entry); if print_tlv.is_some() { - let tlv_hex_data = print_tlv.unwrap().replace(|c: char| !(c.is_ascii_alphanumeric()), ""); + let tlv_hex_data = print_tlv + .unwrap() + .replace(|c: char| !(c.is_ascii_alphanumeric()), ""); info!("input TLV: {}", tlv_hex_data); connection.process_tlv(&hex::decode(&tlv_hex_data).unwrap()[..], 0); return Ok(None); @@ -250,21 +293,19 @@ fn run() -> Result, String> { loop { match smart_card_connection.connect_to_card() { Ok(_) => break, - Err(err) => { - match err { - ReaderError::CardNotFound => { - thread::sleep(time::Duration::from_millis(250)); - }, - _ => return Err("Could not connect to the reader".to_string()) + Err(err) => match err { + ReaderError::CardNotFound => { + thread::sleep(time::Duration::from_millis(250)); } - } + _ => return Err("Could not connect to the reader".to_string()), + }, } } } else { return Err("Card not found.".to_string()); } - }, - _ => return Err("Could not connect to the reader".to_string()) + } + _ => return Err("Could not connect to the reader".to_string()), } } @@ -278,11 +319,14 @@ fn run() -> Result, String> { let application = connection.select_payment_application().unwrap(); connection.process_settings().unwrap(); - connection.add_tag("9F02", bcdutil::ascii_to_bcd_n(format!("{}", purchase_amount).as_bytes(), 6).unwrap()); + connection.add_tag( + "9F02", + bcdutil::ascii_to_bcd_n(format!("{}", purchase_amount).as_bytes(), 6).unwrap(), + ); connection.handle_get_processing_options().unwrap(); - if ! stop_after_read { + if !stop_after_read { connection.handle_public_keys(&application).unwrap(); connection.handle_card_verification_methods().unwrap(); @@ -295,12 +339,18 @@ fn run() -> Result, String> { match connection.handle_1st_generate_ac().unwrap() { CryptogramType::AuthorisationRequestCryptogram => { - if let CryptogramType::TransactionCertificate = connection.handle_2nd_generate_ac().unwrap() { + if let CryptogramType::TransactionCertificate = + connection.handle_2nd_generate_ac().unwrap() + { purchase_successful = true; } - }, - CryptogramType::TransactionCertificate => { purchase_successful = true; }, - CryptogramType::ApplicationAuthenticationCryptogram => { purchase_successful = false; } + } + CryptogramType::TransactionCertificate => { + purchase_successful = true; + } + CryptogramType::ApplicationAuthenticationCryptogram => { + purchase_successful = false; + } } if purchase_successful { @@ -323,7 +373,7 @@ fn main() { Ok(msg) => { warn!("{:?}", msg); 0 - }, + } Err(err) => { error!("{:?}", err); 1 From 1b90d7c0e397901668273a284548b3ac413c1fb7 Mon Sep 17 00:00:00 2001 From: Mika Rautio Date: Sat, 15 Jul 2023 19:11:43 +0300 Subject: [PATCH 3/3] Fix chrono::Utc::today deprecation warning warning: use of deprecated associated function `chrono::Utc::today`: use `Utc::now()` instead, potentially with `.date_naive()` --- emvpt/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emvpt/src/lib.rs b/emvpt/src/lib.rs index 6bde0c9..869ff93 100644 --- a/emvpt/src/lib.rs +++ b/emvpt/src/lib.rs @@ -3391,7 +3391,7 @@ pub fn get_ca_public_key<'a>( } pub fn is_certificate_expired(date_bcd: &[u8]) -> bool { - let today = Utc::today().naive_utc(); + let today = Utc::now().date_naive(); let expiry_date = NaiveDate::parse_from_str(&format!("01{:02X?}", date_bcd), "%d[%m, %y]").unwrap(); let duration = today.signed_duration_since(expiry_date).num_days();