#iso8583 #mac #pin #crypto #parser #message-format #tcp-client

iso8583_rs

一个库,用于定义/解析/组装并通过TCP发送/接收(ISO8583)消息

10个版本

0.1.10 2020年8月24日
0.1.9 2020年8月23日
0.1.8 2020年7月7日
0.1.6 2020年6月28日

#1351 in 解析实现

每月32次下载

Apache-2.0

110KB
2K SLoC

iso8583_rs

用Rust编写的ISO8583库

Crates.io Crates.io Build Status

初期版本,v0.1.*版本不保证向后兼容性 :)

0.1.7新增

  • 支持在ISO0、ISO1、ISO2、ISO3格式中构建PIN块(F52)

0.1.8新增

  • 支持零售(X9.19或ISO9797算法-3)和CBC MAC(ISO9797算法-1)

功能

  • 在YAML文件中定义ISO规范
  • 定义一个消息处理器,可以对传入的消息进行操作并生成响应
  • 根据规范和消息处理器启动ISO8583服务器(请参阅以下示例)
  • 使用TCP客户端调用ISO服务器
  • 示例规范定义在sample_spec.yaml
  • 环境变量SPEC_FILE定义了YAML规范定义文件的位置
  • 支持ASCII、EBCDIC、BINARY/BCD编码
  • 支持在ISO0、ISO1、ISO2、ISO3格式中构建PIN块(F52)
  • 支持零售(X9.19或ISO9797算法-3)和CBC MAC(ISO9797算法-1)

注意

每个规范定义了一组头部字段(通常是MTI或消息类型),后面跟着任意数量的消息(授权/冲销等)。

对于每个传入请求(缓冲区),解析头部字段。解析后的头部字段的值与消息上定义的选择器匹配。

匹配成功后,将传入的数据与消息进行解析。一旦解析,将消息传递到服务器上定义的MsgProcessor。MsgProcessor应用其逻辑并生成一个响应,将其发送回客户端。

示例服务器应用程序

(改编自main.rs )

use hex;
use log::{info, debug, error, warn};
use simplelog;
use hex_literal::hex as hex_l;

use iso8583_rs::iso8583::iso_spec::{IsoMsg, new_msg};
use iso8583_rs::iso8583::IsoError;
use iso8583_rs::iso8583::mli::MLIType::MLI2E;
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
#[derive(Copy, Clone)]
pub struct SampleMsgProcessor {}


impl MsgProcessor for SampleMsgProcessor {
    fn process(&self, iso_server: &ISOServer, msg: &mut Vec<u8>) -> Result<(Vec<u8>, IsoMsg), IsoError> {
        match iso_server.spec.parse(msg) {
            Ok(iso_msg) => {
                debug!("parsed incoming request - message = \"{}\" successfully. \n : parsed message: \n --- \n {} \n ----\n",
                       iso_msg.msg.name(), iso_msg);

                let req_msg_type = iso_msg.get_field_value(&"message_type".to_string()).unwrap();
                let resp_msg_type = if req_msg_type == "1100" {
                    "1110"
                } else if req_msg_type == "1420" {
                    "1430"
                } else {
                    return Err(IsoError { msg: format!("unsupported msg_type {}", req_msg_type) });
                };


                let mut iso_resp_msg = new_msg(&iso_msg.spec, &iso_msg.spec.get_message_from_header(resp_msg_type).unwrap());

                if req_msg_type == "1420" {
                    iso_resp_msg.set("message_type", resp_msg_type).unwrap_or_default();
                    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, msg, &mut iso_resp_msg)?
                }


                match iso_resp_msg.assemble() {
                    Ok(resp_data) => Ok((resp_data, iso_resp_msg)),
                    Err(e) => {
                        error!("Failed to assemble response message, dropping message - {}", e.msg);
                        Err(IsoError { msg: format!("error: msg assembly failed..{} ", e.msg) })
                    }
                }
            }
            Err(e) => {
                Err(IsoError { msg: e.msg })
            }
        }
    }
}


// Handle the incoming 1100 message based on amount
// if amount (F4) <100 then
//   F38 = APPR01;
//   F39 = 000;
// else
//   F39 = 100;
//
//
fn handle_1100(iso_msg: &IsoMsg, raw_msg: &Vec<u8>, iso_resp_msg: &mut IsoMsg) -> Result<(), IsoError> {

    let key = hex_l!("e0f4543f3e2a2c5ffc7e5e5a222e3e4d").to_vec();

    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 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]).unwrap_or_default();
                return Ok(());
            }
        }
    }


    if !iso_msg.bmp.is_on(4) {
        error!("No amount in request, responding with F39 = 115 ");
        iso_resp_msg.set("message_type", "1110").unwrap_or_default();
        iso_resp_msg.set_on(39, "115").unwrap_or_default();
        iso_resp_msg.echo_from(&iso_msg, &[2, 3, 4, 11, 14, 19, 96])
    } else {
        // process the incoming request based on amount
        let amt = iso_msg.bmp_child_value(4).unwrap();
        match amt.parse::<u32>() {
            Ok(i_amt) => {
                debug!("amount = {}", i_amt);
                if i_amt < 100 {
                    iso_resp_msg.set_on(39, "000").unwrap_or_default();
                } else {
                    iso_resp_msg.set_on(39, "100").unwrap_or_default();
                }


                if iso_msg.bmp.is_on(52) {
                    //validate the pin
                    let f52 = iso_msg.bmp_child_value(52).unwrap();
                    debug!("{}", "verifying pin ... ");
                    match verify_pin(&ISO0, "1234", &hex::decode(f52).unwrap(),
                                     iso_msg.bmp_child_value(2).unwrap().as_str(), &key) {
                        Ok(res) => {
                            if res {
                                debug!("{}", "PIN verified OK.");
                            } else {
                                warn!("{}", "PIN verified Failed!!");
                                iso_resp_msg.set_on(39, "117").unwrap_or_default();
                            }
                        }
                        Err(e) => {
                            error!("failed to verify PIN, {}", e.msg);
                            iso_resp_msg.set_on(39, "126").unwrap_or_default();
                        }
                    };
                }

                if iso_msg.bmp.is_on(61) {
                    let mut val = iso_msg.bmp_child_value(61).unwrap();
                    val += "-OK";
                    iso_resp_msg.set_on(61, val.as_str()).unwrap();
                }

                if iso_msg.bmp.is_on(62) {
                    let mut val = iso_msg.bmp_child_value(62).unwrap();
                    val += "-OK";
                    iso_resp_msg.set_on(62, val.as_str()).unwrap();
                }

                iso_resp_msg.set_on(63, "007").unwrap_or_default();
                iso_resp_msg.set_on(160, "F160").unwrap_or_default();


                if iso_resp_msg.bmp_child_value(39).unwrap() == "000" {
                    // generate a approval code
                    iso_resp_msg.set_on(38, "APPR01").unwrap_or_default();
                }
            }
            Err(_e) => {
                iso_resp_msg.set_on(39, "107").unwrap_or_default();
            }
        };

        iso_resp_msg.echo_from(&iso_msg, &[2, 3, 4, 11, 14, 19, 96])?;
        iso_resp_msg.fd_map.insert("bitmap".to_string(), iso_resp_msg.bmp.as_vec());

        Ok(())
    }
}


fn main() {
    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());

    let iso_spec = iso8583_rs::iso8583::iso_spec::spec("");

    info!("starting iso server for spec {} at port {}", iso_spec.name(), 6666);
    let server = match ISOServer::new("127.0.0.1:6666".to_string(),
                                      iso_spec,
                                      MLI2E,
                                      Box::new(SampleMsgProcessor {})) {
        Ok(server) => {
            server
        }
        Err(e) => {
            error!("failed to start ISO server - {}", e.msg);
            panic!(e)
        }
    };
    server.start().join().unwrap()
}





示例TCP客户端

    
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();


        let mut iso_msg = iso_spec::new_msg(spec, msg_seg);

        iso_msg.set("message_type", "1100").unwrap();
        iso_msg.set_on(2, "4567909845671235").unwrap();
        iso_msg.set_on(3, "004000").unwrap();
        iso_msg.set_on(4, "000000000029").unwrap();
        iso_msg.set_on(11, "779581").unwrap();
        iso_msg.set_on(14, "2204").unwrap();
        iso_msg.set_on(19, "840").unwrap();


        let mut cfg = Config::new();
        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();

        // You can also directly set this if there are other means of computing the pin block
        // iso_msg.set_on(52, "0102030405060708").unwrap(); //binary field are represented in their hex encoded format

        //--------- set pin - F52

        iso_msg.set_on(61, "reserved_1").unwrap();
        iso_msg.set_on(62, "reserved-2").unwrap();
        iso_msg.set_on(63, "87877622525").unwrap();
        iso_msg.set_on(96, "1234").unwrap();


        //--------- set mac  - either F64 or F128
        iso_msg.set_mac(&cfg);
        //--------- set mac


        let mut client = ISOTcpClient::new("localhost:6666", &spec, MLI2E);

        match client.send(&iso_msg) {
            Ok(resp_iso_msg) => {
                println!("Received {} \n {}", resp_iso_msg.msg.name(), resp_iso_msg);
            }
            Err(e) => {
                eprintln!("{:?}", e)
            }
        }
        Ok(())
    }

运行ISO服务器

  • 运行main.rs以启动ISO服务器(由上述规范支持)
C:/Users/rkbal/.cargo/bin/cargo.exe run --color=always --package iso8583_rs --bin iso8583_rs
   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
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               :             : 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      : 77fcbd9ffc0dfa6f 
private_1            :    061      : reserved_1 
private_2            :    062      : reserved-2 
private_3            :    063      : 87877622525 
key_mgmt_data        :    096      : 1234 
mac_2                :    128      : e47006f5de8c70b9  
 ----

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
|34303030 30303030 30303030 30303239| 4000000000000029 00000030
|37373935 38313232 3034f8f4 f0415050| 7795812204...APP 00000040
|52303130 30300013 72657365 72766564| R01000..reserved 00000050
|5f312d4f 4b0d9985 a28599a5 858460f2| _1-OK.........`. 00000060
|60d6d2f0 f0f33030 37313233 34463136| `.....0071234F16 00000070
|30|                                  0                00000080
                                                       00000081
 
 parsed :
 --- 
-Field-              : -Position-  : -Field Value- 
message_type         :             : 1110 
bitmap               :             : f02420000600000e80000001000000000000000100000000 
pan                  :    002      : 4567909845671235 
proc_code            :    003      : 004000 
amount               :    004      : 000000000029 
stan                 :    011      : 779581 
expiration_date      :    014      : 2204 
country_code         :    019      : 840 
approval_code        :    038      : APPR01 
action_code          :    039      : 000 
private_1            :    061      : reserved_1-OK 
private_2            :    062      : reserved-2-OK 
private_3            :    063      : 007 
key_mgmt_data        :    096      : 1234 
reserved_data        :    160      : F160  
 --- 

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


ISO TCP客户端

现在运行src/iso8583/test.rs:test_send_recv_iso_1100(..)

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
|00000000 00000001 00000000 31363435| ............1645 00000010
|36373930 39383435 36373132 33353030| 6790984567123500 00000020
|34303030 30303030 30303030 30303239| 4000000000000029 00000030
|37373935 38313232 3034f8f4 f0415050| 7795812204...APP 00000040
|52303130 30300013 72657365 72766564| R01000..reserved 00000050
|5f312d4f 4b0d9985 a28599a5 858460f2| _1-OK.........`. 00000060
|60d6d2f0 f0f33030 37313233 34463136| `.....0071234F16 00000070
|30|                                  0                00000080
                                                       00000081


Received 1100 - Authorization 
 
-Field-              : -Position-  : -Field Value- 
message_type         :             : 1110 
bitmap               :             : f02420000600000e80000001000000000000000100000000 
pan                  :    002      : 4567909845671235 
proc_code            :    003      : 004000 
amount               :    004      : 000000000029 
stan                 :    011      : 779581 
expiration_date      :    014      : 2204 
country_code         :    019      : 840 
approval_code        :    038      : APPR01 
action_code          :    039      : 000 
private_1            :    061      : reserved_1-OK 
private_2            :    062      : reserved-2-OK 
private_3            :    063      : 007 
key_mgmt_data        :    096      : 1234 
reserved_data        :    160      : F160 


依赖项

~5–14MB
~160K SLoC