diff --git a/Cargo.lock b/Cargo.lock index a4c1a16..2e63dbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,6 +25,31 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-modes" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc7143fa8aadf0a3d8140f3f558063dcfc1b3205e5e837ad59485140f1575ae8" +dependencies = [ + "block-cipher", + "block-padding", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "byteorder" version = "1.3.4" @@ -98,6 +123,25 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" +[[package]] +name = "hex-literal" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "961de220ec9a91af2e1e5bd80d02109155695e516771762381ef8581317066e0" +dependencies = [ + "hex-literal-impl", + "proc-macro-hack", +] + +[[package]] +name = "hex-literal-impl" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "853f769599eb31de176303197b7ba4973299c38c7a7604a6bc88c3eef05b9b46" +dependencies = [ + "proc-macro-hack", +] + [[package]] name = "hexdump" version = "0.1.0" @@ -112,11 +156,13 @@ dependencies = [ name = "iso8583_rs" version = "0.1.7" dependencies = [ + "block-modes", "byteorder", "des", "encoding8", "generic-array", "hex", + "hex-literal", "hexdump", "lazy_static", "log", @@ -214,6 +260,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +[[package]] +name = "proc-macro-hack" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" + [[package]] name = "proc-macro2" version = "1.0.18" diff --git a/Cargo.toml b/Cargo.toml index c745c6d..cedda1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,4 +24,6 @@ hexdump = "0.1.0" rand = "0.7.3" des = "0.4.0" generic-array = "0.14.2" -odds = "0.4.0" \ No newline at end of file +odds = "0.4.0" +block-modes = "0.5.0" +hex-literal = "0.2.1" \ No newline at end of file diff --git a/README.md b/README.md index 214446d..20cb500 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ ISO8583 library written in Rust __Early days., No promise of backward compatibility for v0.1.* :)__ -## New in 0.1.7 +### New in 0.1.7 * Support for building PIN blocks (F52) in ISO0,ISO1,ISO2,ISO3 formats - +### New in 0.1.8 +* Support for Retail (X9.19 or ISO9797 Algorithm-3) and CBC MAC (ISO9797 Algorithm-1) ## Features * Define a ISO specification in a YAML file @@ -43,6 +44,8 @@ extern crate lazy_static; #[macro_use] extern crate log; extern crate simplelog; +#[macro_use] +extern crate hex_literal; use iso8583_rs::iso8583::iso_spec::{IsoMsg, new_msg}; use iso8583_rs::iso8583::IsoError; @@ -51,6 +54,11 @@ use iso8583_rs::iso8583::server::ISOServer; use iso8583_rs::iso8583::server::MsgProcessor; use iso8583_rs::crypto::pin::verify_pin; use iso8583_rs::crypto::pin::PinFormat::ISO0; +use std::path::Path; +use iso8583_rs::crypto::mac::MacAlgo::RetailMac; +use iso8583_rs::crypto::mac::PaddingType::Type1; +use iso8583_rs::crypto::mac::verify_mac; + // Below is an example implementation of a MsgProcessor i.e the entity responsible for handling incoming messages // at the server @@ -82,7 +90,7 @@ impl MsgProcessor for SampleMsgProcessor { iso_resp_msg.echo_from(&iso_msg, &[2, 3, 4, 11, 14, 19, 96])?; iso_resp_msg.set_on(39, "400").unwrap_or_default(); } else if req_msg_type == "1100" { - handle_1100(&iso_msg, &mut iso_resp_msg)? + handle_1100(&iso_msg, msg, &mut iso_resp_msg)? } @@ -110,8 +118,35 @@ impl MsgProcessor for SampleMsgProcessor { // F39 = 100; // // -fn handle_1100(iso_msg: &IsoMsg, iso_resp_msg: &mut IsoMsg) -> Result<(), IsoError> { +fn handle_1100(iso_msg: &IsoMsg, raw_msg: &Vec, iso_resp_msg: &mut IsoMsg) -> Result<(), IsoError> { iso_resp_msg.set("message_type", "1110").unwrap_or_default(); + //validate the mac + if iso_msg.bmp.is_on(64) || iso_msg.bmp.is_on(128) { + + let key=hex!("e0f4543f3e2a2c5ffc7e5e5a222e3e4d").to_vec(); + let expected_mac = match iso_msg.bmp.is_on(64) { + true => { + iso_msg.bmp_child_value(64) + } + false => { + iso_msg.bmp_child_value(128) + } + }; + let mac_data=&raw_msg.as_slice()[0..raw_msg.len() - 8]; + match verify_mac(&RetailMac, &Type1, mac_data, &key, &hex::decode(expected_mac.unwrap()).unwrap()) { + Ok(_) => { + debug!("mac verified OK!"); + } + Err(e) => { + error!("failed to verify mac. Reason: {}", e.msg); + iso_resp_msg.set("message_type", "1110").unwrap_or_default(); + iso_resp_msg.set_on(39, "916").unwrap_or_default(); + iso_resp_msg.echo_from(&iso_msg, &[2, 3, 4, 11, 14, 19, 96]); + return Ok(()); + } + } + } + if !iso_msg.bmp.is_on(4) { error!("No amount in request, responding with F39 = 115 "); @@ -187,7 +222,9 @@ fn handle_1100(iso_msg: &IsoMsg, iso_resp_msg: &mut IsoMsg) -> Result<(), IsoErr fn main() { - std::env::set_var("SPEC_FILE", "sample_spec\\sample_spec.yaml"); + let path = Path::new(".").join("sample_spec").join("sample_spec.yaml"); + let spec_file = path.to_str().unwrap(); + std::env::set_var("SPEC_FILE", spec_file); let _ = simplelog::SimpleLogger::init(simplelog::LevelFilter::Debug, simplelog::Config::default()); @@ -211,15 +248,15 @@ fn main() { - ``` ## Sample TCP client ```rust - fn test_send_recv_iso_1100() -> Result<(), IsoError> { - std::env::set_var("SPEC_FILE", "sample_spec/sample_spec.yaml"); +fn test_send_recv_iso_1100() -> Result<(), IsoError> { + let path = Path::new(".").join("sample_spec").join("sample_spec.yaml"); + std::env::set_var("SPEC_FILE", path.to_str().unwrap()); let spec = crate::iso8583::iso_spec::spec(""); let msg_seg = spec.get_message_from_header("1100").unwrap(); @@ -236,9 +273,12 @@ fn main() { iso_msg.set_on(19, "840").unwrap(); - //--------- set pin - F52 let mut cfg = Config::new(); - cfg.with_pin(ISO0, String::from("e0f4543f3e2a2c5ffc7e5e5a222e3e4d")); + cfg.with_pin(ISO0, String::from("e0f4543f3e2a2c5ffc7e5e5a222e3e4d")) + .with_mac(RetailMac, Type1, String::from("e0f4543f3e2a2c5ffc7e5e5a222e3e4d")); + + + //--------- set pin - F52 //this will compute a pin based on cfg and the supplied pan and set bit position 52 iso_msg.set_pin("1234", iso_msg.bmp_child_value(2).unwrap().as_str(), &cfg).unwrap(); @@ -252,7 +292,12 @@ fn main() { iso_msg.set_on(62, "reserved-2").unwrap(); iso_msg.set_on(63, "87877622525").unwrap(); iso_msg.set_on(96, "1234").unwrap(); - iso_msg.set_on(160, "5678").unwrap(); + + + //--------- set mac - either F64 or F128 + iso_msg.set_mac(&cfg); + //--------- set mac + let mut client = ISOTcpClient::new("localhost:6666", &spec, MLI2E); @@ -277,72 +322,74 @@ C:/Users/rkbal/.cargo/bin/cargo.exe run --color=always --package iso8583_rs --bi Compiling iso8583_rs v0.1.6 (C:\Users\rkbal\IdeaProjects\iso8583_rs) Finished dev [unoptimized + debuginfo] target(s) in 2.97s Running `target\debug\iso8583_rs.exe` -current-dir: C:\Users\rkbal\IdeaProjects\iso8583_rs -spec-file: sample_spec\sample_spec.yaml -16:54:50 [INFO] starting iso server for spec SampleSpec at port 6666 -16:54:59 [DEBUG] (2) iso8583_rs::iso8583::server: Accepted new connection .. Ok(V4(127.0.0.1:57338)) -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::server: received request: -|31313030 f0242000 0000100e 80000001| 1100.$ ......... 00000000 -|00000000 00000001 00000000 31363435| ............1645 00000010 -|36373930 39383435 36373132 33353030| 6790984567123500 00000020 -|34303030 30303030 30303030 30303239| 4000000000000029 00000030 -|37373935 38313232 3034f8f4 f0268977| 7795812204...&.w 00000040 -|50330a80 a7001072 65736572 7665645f| P3.....reserved_ 00000050 -|310a9985 a28599a5 858460f2 f0f1f138| 1.........`....8 00000060 -|37383737 36323235 32353132 33343536| 7877622525123456 00000070 -|3738| 78 00000080 - 00000082 - - len = 130 -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: computed header value for incoming message = 1100 -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: parsing field : message_type -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: parsing field : bitmap -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - pan -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - proc_code -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - amount -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - stan -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - expiration_date -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - country_code -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - pin_data -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - private_1 -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - private_2 -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - private_3 -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - key_mgmt_data -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - reserved_data -16:54:59 [DEBUG] (3) iso8583_rs: parsed incoming request - message = "1100 - Authorization" successfully. +current-dir: C:\Users\rkbal\IdeaProjects\iso8583_rs +spec-file: .\sample_spec\sample_spec.yaml +15:54:37 [INFO] starting iso server for spec SampleSpec at port 6666 +15:54:47 [DEBUG] (2) iso8583_rs::iso8583::server: Accepted new connection .. Ok(V4(127.0.0.1:56307)) +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::server: received request: + +|31313030 f0242000 0000100e 00000001| 1100.$ ......... 00000000 +|00000001 31363435 36373930 39383435| ....164567909845 00000010 +|36373132 33353030 34303030 30303030| 6712350040000000 00000020 +|30303030 30303239 37373935 38313232| 0000002977958122 00000030 +|3034f8f4 f077fcbd 9ffc0dfa 6f001072| 04...w......o..r 00000040 +|65736572 7665645f 310a9985 a28599a5| eserved_1....... 00000050 +|858460f2 f0f1f138 37383737 36323235| ..`....878776225 00000060 +|32353132 3334e470 06f5de8c 70b9| 251234.p....p. 00000070 + 0000007e + + len = 126 +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: computed header value for incoming message = 1100 +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: parsing field : message_type +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: parsing field : bitmap +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - pan +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - proc_code +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - amount +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - stan +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - expiration_date +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - country_code +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - pin_data +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - private_1 +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - private_2 +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - private_3 +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - key_mgmt_data +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::bitmap: parsing field - mac_2 +15:54:47 [DEBUG] (3) iso8583_rs: parsed incoming request - message = "1100 - Authorization" successfully. : parsed message: --- -Field- : -Position- : -Field Value- message_type : : 1100 -bitmap : : f02420000000100e80000001000000000000000100000000 +bitmap : : f02420000000100e0000000100000001 pan : 002 : 4567909845671235 proc_code : 003 : 004000 amount : 004 : 000000000029 stan : 011 : 779581 expiration_date : 014 : 2204 country_code : 019 : 840 -pin_data : 052 : 26897750330a80a7 +pin_data : 052 : 77fcbd9ffc0dfa6f private_1 : 061 : reserved_1 private_2 : 062 : reserved-2 private_3 : 063 : 87877622525 key_mgmt_data : 096 : 1234 -reserved_data : 160 : 5678 +mac_2 : 128 : e47006f5de8c70b9 ---- -16:54:59 [DEBUG] (3) iso8583_rs: amount = 29 -16:54:59 [DEBUG] (3) iso8583_rs: verifying pin ... -16:54:59 [DEBUG] (3) iso8583_rs::crypto::pin: verifying pin - expected_pin: 1234, block: 26897750330a80a7, pan:4567909845671235, key:e0f4543f3e2a2c5ffc7e5e5a222e3e4d -16:54:59 [DEBUG] (3) iso8583_rs: PIN verified OK. -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 2: 4567909845671235 -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 3: 004000 -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 4: 000000000029 -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 11: 779581 -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 14: 2204 -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 19: 840 -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 96: 1234 -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::server: iso_response : +generating mac on 31313030f02420000000100e000000010000000131363435363739303938343536373132333530303430303030303030303030303030323937373935383132323034f8f4f077fcbd9ffc0dfa6f001072657365727665645f310a9985a28599a5858460f2f0f1f1383738373736323235323531323334 +15:54:47 [DEBUG] (3) iso8583_rs: mac verified OK! +15:54:47 [DEBUG] (3) iso8583_rs: amount = 29 +15:54:47 [DEBUG] (3) iso8583_rs: verifying pin ... +15:54:47 [DEBUG] (3) iso8583_rs::crypto::pin: verifying pin - expected_pin: 1234, block: 77fcbd9ffc0dfa6f, pan:4567909845671235, key:e0f4543f3e2a2c5ffc7e5e5a222e3e4d +15:54:47 [DEBUG] (3) iso8583_rs: PIN verified OK. +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 2: 4567909845671235 +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 3: 004000 +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 4: 000000000029 +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 11: 779581 +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 14: 2204 +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 19: 840 +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::iso_spec: echoing .. 96: 1234 +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::server: iso_response : |31313130 f0242000 0600000e 80000001| 1110.$ ......... 00000000 |00000000 00000001 00000000 31363435| ............1645 00000010 |36373930 39383435 36373132 33353030| 6790984567123500 00000020 @@ -374,8 +421,8 @@ key_mgmt_data : 096 : 1234 reserved_data : 160 : F160 --- -16:54:59 [DEBUG] (3) iso8583_rs::iso8583::server: request processing time = 5 millis -16:54:59 [ERROR] client socket_err: 127.0.0.1:57338 failed to fill whole buffer +15:54:47 [DEBUG] (3) iso8583_rs::iso8583::server: request processing time = 9 millis +15:54:47 [ERROR] client socket_err: 127.0.0.1:56307 failed to fill whole buffer ``` @@ -385,12 +432,13 @@ reserved_data : 160 : F160 Now run src/iso8583/test.rs:test_send_recv_iso_1100(..) ``` -Testing started at 22:24 ... -current-dir: C:\Users\rkbal\IdeaProjects\iso8583_rs -spec-file: sample_spec/sample_spec.yaml -= 041234bea870366b -raw iso msg = 008231313030f02420000000100e8000000100000000000000010000000031363435363739303938343536373132333530303430303030303030303030303030323937373935383132323034f8f4f026897750330a80a7001072657365727665645f310a9985a28599a5858460f2f0f1f138373837373632323532353132333435363738 -connected to server @ Ok(V4(127.0.0.1:57338)) +Testing started at 21:24 ... +kbalIdeaProjectsiso8583_rs +spec-file: .sample_specsample_spec.yaml += 04123423142edc39 +generating mac on 31313030f02420000000100e000000010000000131363435363739303938343536373132333530303430303030303030303030303030323937373935383132323034f8f4f077fcbd9ffc0dfa6f001072657365727665645f310a9985a28599a5858460f2f0f1f1383738373736323235323531323334 +raw iso msg = 007e31313030f02420000000100e000000010000000131363435363739303938343536373132333530303430303030303030303030303030323937373935383132323034f8f4f077fcbd9ffc0dfa6f001072657365727665645f310a9985a28599a5858460f2f0f1f1383738373736323235323531323334e47006f5de8c70b9 +connected to server @ Ok(V4(127.0.0.1:56307)) received response: with 129 bytes. |31313130 f0242000 0600000e 80000001| 1110.$ ......... 00000000 @@ -424,5 +472,6 @@ private_3 : 063 : 007 key_mgmt_data : 096 : 1234 reserved_data : 160 : F160 + ``` diff --git a/sample_spec/sample_spec.yaml b/sample_spec/sample_spec.yaml index eb1cafd..36c3eff 100644 --- a/sample_spec/sample_spec.yaml +++ b/sample_spec/sample_spec.yaml @@ -115,6 +115,13 @@ messages: data_encoding: ASCII position: 63 + - name: "mac_1" + id: 21 + type: Fixed + len: 8 + data_encoding: BINARY + position: 64 + - name: "key_mgmt_data" id: 14 type: Fixed @@ -122,6 +129,13 @@ messages: data_encoding: ASCII position: 96 + - name: "mac_2" + id: 22 + type: Fixed + len: 8 + data_encoding: BINARY + position: 128 + - name: "reserved_data" id: 14 type: Fixed diff --git a/src/crypto/mac.rs b/src/crypto/mac.rs index 7a971b2..ffe40e1 100644 --- a/src/crypto/mac.rs +++ b/src/crypto/mac.rs @@ -1,3 +1,238 @@ -pub struct MacAlgo{ +//! This module provides implementation of MAC algorithms +//https://en.wikipedia.org/wiki/ISO/IEC_9797-1#Complete_specification_of_the_MAC_calculation + +use crate::crypto::{tdes_encrypt_cbc, des_encrypt_cbc}; + +/// This enum defines various supported algorithms +pub enum MacAlgo { + //ISO9797 - algo 1 + CbcMac, + // ISO9797 - algo 3 + RetailMac, +} + +/// This enum defines all supported padding types +pub enum PaddingType { + /// Adding 0 bits + Type1, + /// Adding a single 1 bit followed by 0 bits + Type2, +} + +pub struct MacError { + pub msg: String +} + + +pub fn verify_mac(algo: &MacAlgo, padding_type: &PaddingType, data: &[u8], key: &Vec, expected_mac: &Vec) -> Result<(), MacError> { + let mac = generate_mac(algo, padding_type, &data.to_vec(), key)?; + if mac.eq(expected_mac) { + Ok(()) + } else { + Err(MacError { msg: format!("computed mac: {} doesn't match expected_mac: {}", hex::encode(mac), hex::encode(expected_mac)) }) + } +} + +pub fn generate_mac(algo: &MacAlgo, padding_type: &PaddingType, data: &Vec, key: &Vec) -> Result, MacError> { + let new_data = apply_padding(padding_type, data); + let mut iv = Vec::::new(); + iv.extend_from_slice(hex::decode("0000000000000000").unwrap().as_slice()); + + println!("generating mac on {}", hex::encode(data)); + + match algo { + MacAlgo::CbcMac => { + let res = tdes_encrypt_cbc(&new_data, key, &iv); + Ok(res[res.len() - 8..].to_vec()) + } + MacAlgo::RetailMac => { + let k = key.as_slice()[0..8].to_vec(); + + //if there is a single block + if data.len() == 8 { + Ok(tdes_encrypt_cbc(&data, key, &iv)) + } else { + + //else, all but the last block DES and the last block TDES + let d1 = &new_data[0..new_data.len() - 8].to_vec(); + let d2 = &new_data[new_data.len() - 8..].to_vec(); + + let res1 = des_encrypt_cbc(&d1, &k, &iv); + Ok(tdes_encrypt_cbc(&d2, key, &res1[(res1.len() - 8)..].to_vec())) + } + } + } +} + +fn apply_padding(padding_type: &PaddingType, data: &Vec) -> Vec { + let mut new_data = data.clone(); + match padding_type { + PaddingType::Type1 => {} + PaddingType::Type2 => { + new_data.push(0x80); + } + }; + + while new_data.len() < 8 { + new_data.push(0x00); + } + + while new_data.len() % 8 != 0 { + new_data.push(0x00); + } + + new_data +} + + +#[cfg(test)] +mod tests { + use crate::crypto::mac::{apply_padding, PaddingType, generate_mac, MacAlgo}; + use hex_literal::hex; + + #[test] + fn test_padding1_shortof8() { + let data = hex::decode("0102030405").unwrap(); + assert_eq!(hex::encode(apply_padding(&PaddingType::Type1, &data)), "0102030405000000"); + } + + #[test] + fn test_padding1_exact() { + let data = hex::decode("0102030405060708").unwrap(); + assert_eq!(hex::encode(apply_padding(&PaddingType::Type1, &data)), "0102030405060708"); + } + + #[test] + fn test_padding1_typical_short() { + let data = hex::decode("0102030405060708090a").unwrap(); + assert_eq!(hex::encode(apply_padding(&PaddingType::Type1, &data)), "0102030405060708090a000000000000"); + } + + + #[test] + fn test_padding2_shortof8() { + let data = hex::decode("0102030405").unwrap(); + assert_eq!(hex::encode(apply_padding(&PaddingType::Type2, &data)), "0102030405800000"); + } + + #[test] + fn test_padding2_exact() { + let data = hex::decode("0102030405060708").unwrap(); + assert_eq!(hex::encode(apply_padding(&PaddingType::Type2, &data)), "01020304050607088000000000000000"); + } + + #[test] + fn test_padding2_typical_short() { + let data = hex::decode("0102030405060708090a").unwrap(); + assert_eq!(hex::encode(apply_padding(&PaddingType::Type2, &data)), "0102030405060708090a800000000000"); + } + + + #[test] + fn test_gen_mac_cbc_nopads() { + let res = generate_mac(&MacAlgo::CbcMac, &PaddingType::Type1, + &Vec::from(hex!("0102030405060708")), &Vec::from(hex!("e0f4543f3e2a2c5ffc7e5e5a222e3e4d"))); + match res { + Ok(m) => { + println!("mac = {}", hex::encode(m.as_slice())); + assert_eq!("7d34c3071da931b9", hex::encode(m)); + } + Err(e) => { + assert!(false, e.msg) + } + } + } + + #[test] + fn test_gen_mac_cbc_2() { + let res = generate_mac(&MacAlgo::CbcMac, &PaddingType::Type1, + &Vec::from(hex!("01020304050607080102030405060708")), &Vec::from(hex!("e0f4543f3e2a2c5ffc7e5e5a222e3e4d"))); + match res { + Ok(m) => { + println!("mac = {}", hex::encode(m.as_slice())); + assert_eq!("0fe28f4b5537ee79", hex::encode(m)); + } + Err(e) => { + assert!(false, e.msg) + } + } + } + + + #[test] + fn test_gen_mac_cbc_3() { + let res = generate_mac(&MacAlgo::CbcMac, &PaddingType::Type1, + &Vec::from(hex!("01020304050607080102030405")), &Vec::from(hex!("e0f4543f3e2a2c5ffc7e5e5a222e3e4d"))); + match res { + Ok(m) => { + println!("mac = {}", hex::encode(m.as_slice())); + assert_eq!("8fb12963d5661a22", hex::encode(m)); + } + Err(e) => { + assert!(false, e.msg) + } + } + } + + #[test] + fn test_gen_mac_cbc_2_paddingtype2() { + let res = generate_mac(&MacAlgo::CbcMac, &PaddingType::Type2, + &Vec::from(hex!("01020304050607080102030405")), &Vec::from(hex!("e0f4543f3e2a2c5ffc7e5e5a222e3e4d"))); + match res { + Ok(m) => { + println!("mac = {}", hex::encode(m.as_slice())); + assert_eq!("8568cd2b7698605f", hex::encode(m)); + } + Err(e) => { + assert!(false, e.msg) + } + } + } + + + #[test] + fn test_gen_mac_retail1_nopads() { + let res = generate_mac(&MacAlgo::RetailMac, &PaddingType::Type1, + &Vec::from(hex!("0102030405060708")), &Vec::from(hex!("e0f4543f3e2a2c5ffc7e5e5a222e3e4d"))); + match res { + Ok(m) => { + println!("mac = {}", hex::encode(m.as_slice())); + assert_eq!("7d34c3071da931b9", hex::encode(m)); + } + Err(e) => { + assert!(false, e.msg) + } + } + } + + #[test] + fn test_gen_mac_retail2_padtype1() { + let res = generate_mac(&MacAlgo::RetailMac, &PaddingType::Type1, + &Vec::from(hex!("0102030405060708010203040506070801020304050607080000")), &Vec::from(hex!("e0f4543f3e2a2c5ffc7e5e5a222e3e4d"))); + match res { + Ok(m) => { + println!("mac = {}", hex::encode(m.as_slice())); + assert_eq!(hex::encode(m), "149f99288681d292"); + } + Err(e) => { + assert!(false, e.msg) + } + } + } + + #[test] + fn test_gen_mac_retail_padtype2() { + let res = generate_mac(&MacAlgo::RetailMac, &PaddingType::Type2, + &Vec::from(hex!("0102030405060708010203040506070801020304050607080000")), &Vec::from(hex!("e0f4543f3e2a2c5ffc7e5e5a222e3e4d"))); + match res { + Ok(m) => { + println!("mac = {}", hex::encode(m.as_slice())); + assert_eq!(hex::encode(m), "4689dd5a87015394"); + } + Err(e) => { + assert!(false, e.msg) + } + } + } } \ No newline at end of file diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 3f223c4..9628e1a 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,4 +1,69 @@ pub mod mac; pub mod pin; + extern crate rand; -extern crate des; \ No newline at end of file +extern crate des; +extern crate block_modes; +extern crate hex_literal; + + +use generic_array::{GenericArray}; +use des::block_cipher::NewBlockCipher; +use des::block_cipher::BlockCipher; + + +use self::block_modes::{BlockMode}; + + +/// CryptoError is a generic error in processing within this crate +#[allow(unused)] +pub(crate) struct CryptoError { + pub(crate) msg: String +} + +pub(crate) fn tdes_ede2_encrypt(data: &Vec, key: &Vec) -> Vec { + let block_cipher = des::TdesEde2::new(GenericArray::from_slice(key.as_slice())); + + let mut cp_data = data.clone(); + block_cipher.encrypt_block(GenericArray::from_mut_slice(&mut cp_data)); + cp_data +} + +pub(crate) fn tdes_ede2_decrypt(data: &Vec, key: &Vec) -> Vec { + let block_cipher = des::TdesEde2::new(GenericArray::from_slice(key.as_slice())); + + let mut cp_data = data.clone(); + block_cipher.decrypt_block(GenericArray::from_mut_slice(&mut cp_data)); + cp_data +} + +type TripleDESCBC = block_modes::Cbc::; + +pub(crate) fn tdes_encrypt_cbc(data: &Vec, key: &Vec, iv: &Vec) -> Vec { + let block_cipher = TripleDESCBC::new_var(key.as_slice(), &iv.as_slice()).unwrap(); + + let encrypted_data = block_cipher.encrypt_vec(data.as_slice()); + encrypted_data +} + + +pub(crate) fn des_encrypt_cbc(data: &Vec, key: &Vec, iv: &Vec) -> Vec { + let block_cipher = block_modes::Cbc::::new_var(key.as_slice(), iv.as_slice()).unwrap(); + block_cipher.encrypt_vec(data) +} + +type DesCbc = block_modes::Cbc::; + +#[allow(unused)] +pub(crate) fn des_decrypt_cbc(data: &Vec, key: &Vec, iv: &Vec) -> Result, CryptoError> { + let block_cipher = DesCbc::new_var(key.as_slice(), iv.as_slice()).unwrap(); + + match block_cipher.decrypt_vec(data) { + Ok(d) => { + Ok(d) + } + Err(e) => { + Err(CryptoError { msg: e.to_string() }) + } + } +} \ No newline at end of file diff --git a/src/crypto/pin.rs b/src/crypto/pin.rs index 922850c..bfb3299 100644 --- a/src/crypto/pin.rs +++ b/src/crypto/pin.rs @@ -4,9 +4,7 @@ use rand; use super::rand::Rng; -use generic_array::GenericArray; -use des::block_cipher::NewBlockCipher; -use des::block_cipher::BlockCipher; +use crate::crypto::{tdes_ede2_decrypt, tdes_ede2_encrypt}; #[derive(Debug)] @@ -35,7 +33,7 @@ pub fn generate_pin_block(fmt: &PinFormat, c_pin: &str, pan: &str, key: &str) -> b2.push_str(&pan[pan.len() - 13..pan.len() - 1]); let res = xor_hexstr(b1.as_str(), b2.as_str()); - let res = des_ede2_encrypt(&res, &hex::decode(key).unwrap().to_vec()); + let res = tdes_ede2_encrypt(&res, &hex::decode(key).unwrap().to_vec()); Ok(res.to_vec()) } @@ -44,7 +42,7 @@ pub fn generate_pin_block(fmt: &PinFormat, c_pin: &str, pan: &str, key: &str) -> pad_8(&mut b1); match hex::decode(b1) { Ok(res) => { - let res = des_ede2_encrypt(&res, &hex::decode(key).unwrap().to_vec()); + let res = tdes_ede2_encrypt(&res, &hex::decode(key).unwrap().to_vec()); Ok(res) } Err(e) => { @@ -61,7 +59,7 @@ pub fn generate_pin_block(fmt: &PinFormat, c_pin: &str, pan: &str, key: &str) -> println!("= {}", b1); match hex::decode(b1) { Ok(res) => { - let res = des_ede2_encrypt(&res, &hex::decode(key).unwrap().to_vec()); + let res = tdes_ede2_encrypt(&res, &hex::decode(key).unwrap().to_vec()); Ok(res) } Err(e) => { @@ -80,7 +78,7 @@ pub fn generate_pin_block(fmt: &PinFormat, c_pin: &str, pan: &str, key: &str) -> b2.push_str(&pan[pan.len() - 13..pan.len() - 1]); let res = xor_hexstr(b1.as_str(), b2.as_str()); - let res = des_ede2_encrypt(&res, &hex::decode(key).unwrap().to_vec()); + let res = tdes_ede2_encrypt(&res, &hex::decode(key).unwrap().to_vec()); Ok(res.to_vec()) } @@ -100,7 +98,7 @@ pub fn verify_pin(fmt: &PinFormat, expected_pin: &str, pin_block: &Vec, pan: let mut b2 = String::from("0000"); b2.push_str(&pan[pan.len() - 13..pan.len() - 1]); - let res = des_ede2_decrypt(&pin_block, &hex::decode(key).unwrap().to_vec()); + let res = tdes_ede2_decrypt(&pin_block, &hex::decode(key).unwrap().to_vec()); let res = xor_hexstr(hex::encode(res.as_slice()).as_str(), b2.as_str()); let pin_len = res.get(0).unwrap(); let b1 = hex::encode(&res); @@ -113,7 +111,7 @@ pub fn verify_pin(fmt: &PinFormat, expected_pin: &str, pin_block: &Vec, pan: } PinFormat::ISO1 => { - let res = des_ede2_decrypt(&pin_block, &hex::decode(key).unwrap().to_vec()); + let res = tdes_ede2_decrypt(&pin_block, &hex::decode(key).unwrap().to_vec()); let pin_len = res.get(0).unwrap(); let b1 = hex::encode(&res); @@ -125,7 +123,7 @@ pub fn verify_pin(fmt: &PinFormat, expected_pin: &str, pin_block: &Vec, pan: } } PinFormat::ISO2 => { - let res = des_ede2_decrypt(&pin_block, &hex::decode(key).unwrap().to_vec()); + let res = tdes_ede2_decrypt(&pin_block, &hex::decode(key).unwrap().to_vec()); let pin_len = res.get(0).unwrap(); let b1 = hex::encode(&res); @@ -140,7 +138,7 @@ pub fn verify_pin(fmt: &PinFormat, expected_pin: &str, pin_block: &Vec, pan: let mut b2 = String::from("0000"); b2.push_str(&pan[pan.len() - 13..pan.len() - 1]); - let res = des_ede2_decrypt(&pin_block, &hex::decode(key).unwrap().to_vec()); + let res = tdes_ede2_decrypt(&pin_block, &hex::decode(key).unwrap().to_vec()); let res = xor_hexstr(hex::encode(res.as_slice()).as_str(), b2.as_str()); let pin_len = res.get(0).unwrap(); let b1 = hex::encode(&res); @@ -157,21 +155,7 @@ pub fn verify_pin(fmt: &PinFormat, expected_pin: &str, pin_block: &Vec, pan: } } -fn des_ede2_encrypt(data: &Vec, key: &Vec) -> Vec { - let block_cipher = des::TdesEde2::new(GenericArray::from_slice(key.as_slice())); - let mut cp_data = data.clone(); - block_cipher.encrypt_block(GenericArray::from_mut_slice(&mut cp_data)); - cp_data -} - -fn des_ede2_decrypt(data: &Vec, key: &Vec) -> Vec { - let block_cipher = des::TdesEde2::new(GenericArray::from_slice(key.as_slice())); - - let mut cp_data = data.clone(); - block_cipher.decrypt_block(GenericArray::from_mut_slice(&mut cp_data)); - cp_data -} /// XOR the contents of 2 hex string (of equal length) and return the result /// as a Vec diff --git a/src/iso8583/config.rs b/src/iso8583/config.rs index abaa704..7d05b66 100644 --- a/src/iso8583/config.rs +++ b/src/iso8583/config.rs @@ -2,12 +2,13 @@ //! crypto field F52, F64/128 etc use crate::crypto::pin::PinFormat; -use crate::crypto::mac::MacAlgo; +use crate::crypto::mac::{MacAlgo, PaddingType}; pub struct Config { pin_format: Option, pin_key: Option, mac_algo: Option, + mac_padding: Option, mac_key: Option, } @@ -20,6 +21,7 @@ impl Config { pin_key: None, mac_algo: None, mac_key: None, + mac_padding: None, } } @@ -33,6 +35,22 @@ impl Config { &self.pin_key } + /// Returns the MAC key associated with this config + pub fn get_mac_key(&self) -> &Option { + &self.mac_key + } + + /// Returns the MAC'ing algorithm associated with this config + pub fn get_mac_algo(&self) -> &Option { + &self.mac_algo + } + + /// Returns the MAC padding scheme associated with this config + pub fn get_mac_padding(&self) -> &Option { + &self.mac_padding + } + + /// Use the Config with a builder pattern pub fn with_pin(&mut self, fmt: PinFormat, key: String) -> &mut Config { self.pin_format = Some(fmt); @@ -41,9 +59,10 @@ impl Config { } /// Use the Config with a builder pattern - pub fn with_mac(&mut self, algo: MacAlgo, key: String) -> &mut Config { + pub fn with_mac(&mut self, algo: MacAlgo, mac_padding: PaddingType, key: String) -> &mut Config { self.mac_algo = Some(algo); self.mac_key = Some(key); + self.mac_padding = Some(mac_padding); self } } \ No newline at end of file diff --git a/src/iso8583/iso_spec.rs b/src/iso8583/iso_spec.rs index 9a7ebc4..5da1b6e 100644 --- a/src/iso8583/iso_spec.rs +++ b/src/iso8583/iso_spec.rs @@ -10,6 +10,7 @@ use crate::iso8583::yaml_de::YMessageSegment; use crate::iso8583::bitmap::Bitmap; use crate::iso8583::config::Config; use crate::crypto::pin::generate_pin_block; +use crate::crypto::mac::generate_mac; // Reads the spec definitions from YAML file lazy_static! { @@ -279,7 +280,6 @@ impl IsoMsg { /// Sets F52 based on provided clear pin, and format, key provided via cfg pub fn set_pin(&mut self, pin: &str, pan: &str, cfg: &Config) -> Result<(), IsoError> { - if cfg.get_pin_fmt().is_none() || cfg.get_pin_key().is_none() { return Err(IsoError { msg: format!("missing pin_format or key in call to set_pin") }); } @@ -293,6 +293,47 @@ impl IsoMsg { } } } + + /// Sets F64 or F128 based on algo, padding and key provided via cfg + pub fn set_mac(&mut self, cfg: &Config) -> Result<(), IsoError> { + if cfg.get_mac_algo().is_none() || cfg.get_mac_padding().is_none() || cfg.get_mac_key().is_none() { + return Err(IsoError { msg: format!("missing mac_algo or padding or key in call to set_mac") }); + } + + + if self.bmp.is_on(1) { + self.set_on(128,"0000000000000000"); + } else { + self.set_on(64,"0000000000000000"); + } + + + let data: Vec = match self.assemble() { + Ok(v) => { + v + } + Err(e) => { + return Err(e); + } + }; + + debug!("generating mac on: {}", hex::encode(&data)); + + match generate_mac(&cfg.get_mac_algo().as_ref().unwrap(), &cfg.get_mac_padding().as_ref().unwrap(), + &data[0..data.len()-8].to_vec(), &hex::decode(cfg.get_mac_key().as_ref().unwrap()).unwrap()) { + Ok(v) => { + if self.bmp.is_on(1) { + self.set_on(128, hex::encode(v).as_str()); + } else { + self.set_on(64, hex::encode(v).as_str()); + } + Ok(()) + } + Err(e) => { + Err(IsoError { msg: e.msg }) + } + } + } } fn collect_children(f: &dyn Field, ordered_fields: &mut Vec) { diff --git a/src/iso8583/mod.rs b/src/iso8583/mod.rs index 6e68596..e1c023d 100644 --- a/src/iso8583/mod.rs +++ b/src/iso8583/mod.rs @@ -1,3 +1,4 @@ +//! This module contains functions related to ISO8583 specifications, message, parsers etc pub mod client; pub mod bitmap; pub mod field; @@ -10,6 +11,6 @@ pub mod config; /// IsoError represents a generic error throughout this and dependent sub-modules #[derive(Debug)] -pub struct IsoError{ +pub struct IsoError { pub msg: String, } diff --git a/src/iso8583/test.rs b/src/iso8583/test.rs index 9a92fec..3deb54c 100644 --- a/src/iso8583/test.rs +++ b/src/iso8583/test.rs @@ -5,11 +5,19 @@ mod tests { use crate::iso8583::mli::MLIType::MLI2E; use crate::crypto::pin::PinFormat::ISO0; use crate::iso8583::config::Config; + use crate::crypto::mac::MacAlgo::RetailMac; + use crate::crypto::mac::PaddingType::Type1; + + use log; + use simplelog; + use std::env::join_paths; + use std::path::Path; #[test] #[ignore] fn test_send_recv_iso_1100() -> Result<(), IsoError> { - std::env::set_var("SPEC_FILE", "sample_spec/sample_spec.yaml"); + let path = Path::new(".").join("sample_spec").join("sample_spec.yaml"); + std::env::set_var("SPEC_FILE", path.to_str().unwrap()); let spec = crate::iso8583::iso_spec::spec(""); let msg_seg = spec.get_message_from_header("1100").unwrap(); @@ -26,9 +34,12 @@ mod tests { iso_msg.set_on(19, "840").unwrap(); - //--------- set pin - F52 let mut cfg = Config::new(); - cfg.with_pin(ISO0, String::from("e0f4543f3e2a2c5ffc7e5e5a222e3e4d")); + cfg.with_pin(ISO0, String::from("e0f4543f3e2a2c5ffc7e5e5a222e3e4d")) + .with_mac(RetailMac, Type1, String::from("e0f4543f3e2a2c5ffc7e5e5a222e3e4d")); + + + //--------- set pin - F52 //this will compute a pin based on cfg and the supplied pan and set bit position 52 iso_msg.set_pin("1234", iso_msg.bmp_child_value(2).unwrap().as_str(), &cfg).unwrap(); @@ -42,7 +53,12 @@ mod tests { iso_msg.set_on(62, "reserved-2").unwrap(); iso_msg.set_on(63, "87877622525").unwrap(); iso_msg.set_on(96, "1234").unwrap(); - iso_msg.set_on(160, "5678").unwrap(); + + + //--------- set mac - either F64 or F128 + iso_msg.set_mac(&cfg); + //--------- set mac + let mut client = ISOTcpClient::new("localhost:6666", &spec, MLI2E); @@ -61,7 +77,9 @@ mod tests { #[test] #[ignore] fn test_send_recv_iso_1420() -> Result<(), IsoError> { - std::env::set_var("SPEC_FILE", "sample_spec/sample_spec.yaml"); + + let path = Path::new(".").join("sample_spec").join("sample_spec.yaml"); + std::env::set_var("SPEC_FILE", path.to_str().unwrap()); let spec = crate::iso8583::iso_spec::spec(""); let msg_seg = spec.get_message_from_header("1420").unwrap(); diff --git a/src/iso8583/yaml_de.rs b/src/iso8583/yaml_de.rs index 41158b9..4b5d4aa 100644 --- a/src/iso8583/yaml_de.rs +++ b/src/iso8583/yaml_de.rs @@ -140,7 +140,6 @@ mod tests { fn test_deserialize_yaml_spec() { let path = Path::new(".").join("sample_spec").join("sample_spec.yaml"); - println!("path is {}", path.to_str().unwrap()); match read_spec(path.to_str().unwrap()) { Ok(spec) => { diff --git a/src/main.rs b/src/main.rs index 3205ea1..1b8ffcd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ extern crate lazy_static; #[macro_use] extern crate log; extern crate simplelog; +#[macro_use] +extern crate hex_literal; use iso8583_rs::iso8583::iso_spec::{IsoMsg, new_msg}; use iso8583_rs::iso8583::IsoError; @@ -12,6 +14,11 @@ use iso8583_rs::iso8583::server::ISOServer; use iso8583_rs::iso8583::server::MsgProcessor; use iso8583_rs::crypto::pin::verify_pin; use iso8583_rs::crypto::pin::PinFormat::ISO0; +use std::path::Path; +use iso8583_rs::crypto::mac::MacAlgo::RetailMac; +use iso8583_rs::crypto::mac::PaddingType::Type1; +use iso8583_rs::crypto::mac::verify_mac; + // Below is an example implementation of a MsgProcessor i.e the entity responsible for handling incoming messages // at the server @@ -43,7 +50,7 @@ impl MsgProcessor for SampleMsgProcessor { iso_resp_msg.echo_from(&iso_msg, &[2, 3, 4, 11, 14, 19, 96])?; iso_resp_msg.set_on(39, "400").unwrap_or_default(); } else if req_msg_type == "1100" { - handle_1100(&iso_msg, &mut iso_resp_msg)? + handle_1100(&iso_msg, msg, &mut iso_resp_msg)? } @@ -71,8 +78,35 @@ impl MsgProcessor for SampleMsgProcessor { // F39 = 100; // // -fn handle_1100(iso_msg: &IsoMsg, iso_resp_msg: &mut IsoMsg) -> Result<(), IsoError> { +fn handle_1100(iso_msg: &IsoMsg, raw_msg: &Vec, iso_resp_msg: &mut IsoMsg) -> Result<(), IsoError> { iso_resp_msg.set("message_type", "1110").unwrap_or_default(); + //validate the mac + if iso_msg.bmp.is_on(64) || iso_msg.bmp.is_on(128) { + + let key=hex!("e0f4543f3e2a2c5ffc7e5e5a222e3e4d").to_vec(); + let expected_mac = match iso_msg.bmp.is_on(64) { + true => { + iso_msg.bmp_child_value(64) + } + false => { + iso_msg.bmp_child_value(128) + } + }; + let mac_data=&raw_msg.as_slice()[0..raw_msg.len() - 8]; + match verify_mac(&RetailMac, &Type1, mac_data, &key, &hex::decode(expected_mac.unwrap()).unwrap()) { + Ok(_) => { + debug!("mac verified OK!"); + } + Err(e) => { + error!("failed to verify mac. Reason: {}", e.msg); + iso_resp_msg.set("message_type", "1110").unwrap_or_default(); + iso_resp_msg.set_on(39, "916").unwrap_or_default(); + iso_resp_msg.echo_from(&iso_msg, &[2, 3, 4, 11, 14, 19, 96]); + return Ok(()); + } + } + } + if !iso_msg.bmp.is_on(4) { error!("No amount in request, responding with F39 = 115 "); @@ -148,7 +182,9 @@ fn handle_1100(iso_msg: &IsoMsg, iso_resp_msg: &mut IsoMsg) -> Result<(), IsoErr fn main() { - std::env::set_var("SPEC_FILE", "sample_spec\\sample_spec.yaml"); + let path = Path::new(".").join("sample_spec").join("sample_spec.yaml"); + let spec_file = path.to_str().unwrap(); + std::env::set_var("SPEC_FILE", spec_file); let _ = simplelog::SimpleLogger::init(simplelog::LevelFilter::Debug, simplelog::Config::default());