test.rs (19784B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2026 Taler Systems S.A. 4 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 use std::{ 21 fs::Permissions, 22 os::unix::fs::PermissionsExt as _, 23 sync::{Arc, Mutex}, 24 time::Duration, 25 }; 26 27 use aws_lc_rs::rsa::{KeyPair, KeySize, PublicKey}; 28 use axum::{body::Bytes, response::IntoResponse as _, routing::post}; 29 use compact_str::CompactString; 30 use jiff::{ 31 Timestamp, Zoned, 32 civil::{Date, date}, 33 tz::TimeZone, 34 }; 35 use reqwest::StatusCode; 36 use taler_api::{Serve, api::TalerRouter as _}; 37 use taler_common::encoding::base64; 38 use tempfile::{TempDir, tempdir}; 39 use tokio::net::UnixStream; 40 41 use crate::{ 42 crypto::{ebics_pub_key_hash, encrypt_ebics_e002, gen_ebics_e002_key}, 43 ebics::{ 44 key_management::{rsa_key_xml, rsa_pub_key}, 45 rand_ebics_id, 46 }, 47 utils::{deflate, inflate}, 48 xml, 49 xml::{Xml, XmlAccess}, 50 xml_sign::sign_ebics, 51 }; 52 53 pub async fn wait_for_unix_socket(path: &str) { 54 for _ in 0..100 { 55 if UnixStream::connect(path).await.is_ok() { 56 return; 57 } 58 tokio::time::sleep(Duration::from_millis(10)).await; 59 } 60 panic!("{path} never becomed active") 61 } 62 63 pub type Sequence = fn(&mut EbicsState, body: &[u8]) -> EbicsRes; 64 65 pub enum EbicsRes { 66 Ok(String), 67 BadRequest, 68 Failure, 69 } 70 pub struct EbicsState { 71 bank: KeyPair, 72 73 client_sign: Option<PublicKey>, 74 client_enc: Option<PublicKey>, 75 client_auth: Option<PublicKey>, 76 77 tx_id: Option<CompactString>, 78 order_id: Option<CompactString>, 79 } 80 81 impl EbicsState { 82 pub fn new() -> Self { 83 Self { 84 bank: KeyPair::generate(KeySize::Rsa2048).unwrap(), 85 client_sign: None, 86 client_enc: None, 87 client_auth: None, 88 tx_id: None, 89 order_id: None, 90 } 91 } 92 93 fn parse_unsecure_request( 94 body: &[u8], 95 order: &str, 96 root: &str, 97 parse: impl FnOnce(Xml) -> xml::Result<()>, 98 ) { 99 Xml::parse(body, "ebicsUnsecuredRequest", |n| { 100 let admin_order = n 101 .one("header") 102 .one("static") 103 .one("OrderDetails") 104 .one("AdminOrderType")? 105 .text(); 106 assert_eq!(admin_order, order); 107 let chunk = n.one("body").one("DataTransfer").one("OrderData").b64()?; 108 let inflated = inflate(&chunk); 109 Xml::parse(&inflated, root, parse) 110 }) 111 .unwrap() 112 } 113 114 fn parse_download_init(body: &[u8], order: &str) { 115 Xml::parse(body, "ebicsRequest", |root| { 116 let header = root.one("header")?; 117 let admin_order = header 118 .one("static") 119 .one("OrderDetails") 120 .one("AdminOrderType")? 121 .text(); 122 assert_eq!(admin_order, order); 123 let phase = header.one("mutable").one("TransactionPhase")?.text(); 124 assert_eq!(phase, "Initialisation"); 125 Ok(()) 126 }) 127 .unwrap(); 128 } 129 130 fn signed_response(&self, xml: String) -> EbicsRes { 131 EbicsRes::Ok(sign_ebics(xml, &self.bank).expect("EBICS signature never fails")) 132 } 133 134 fn ebics_response_payload(&mut self, payload: &str, last: bool) -> EbicsRes { 135 let tx_id = self.tx_id.insert(rand_ebics_id()); 136 let deflated = deflate(payload.as_bytes()); 137 let (tx_key, encrypted_key) = gen_ebics_e002_key(self.client_enc.as_ref().unwrap()); 138 let encrypted = encrypt_ebics_e002(&tx_key, deflated); 139 let xml = xml!("ebicsResponse" "xmlns"="http://www.ebics.org/H005" "xmlns:ds"="http://www.w3.org/2000/09/xmldsig#" { 140 "header" "authenticate"="true" { 141 "static" { 142 "TransactionID": tx_id, 143 "NumSegments": "1" 144 }, 145 "mutable" { 146 "TransactionPhase": "Initialisation", 147 "SegmentNumber" "lastSegment"=last : 1, 148 "ReturnCode": "000000", 149 "ReportText": "[EBICS_OK] OK" 150 } 151 }, 152 "AuthSignature", 153 "body" { 154 "DataTransfer" { 155 "DataEncryptionInfo" "authenticate"="true" { 156 "EncryptionPubKeyDigest" "Version"="E002" "Algorithm"="http://www.w3.org/2001/04/xmlenc#sha256": base64::fmt(ebics_pub_key_hash(self.client_enc.as_ref().unwrap())), 157 "TransactionKey": base64::fmt(encrypted_key) 158 }, 159 "OrderData": base64::fmt(encrypted) 160 }, 161 "ReturnCode" "authenticate"="true": "000000" 162 } 163 }); 164 self.signed_response(xml) 165 } 166 167 fn ebics_response_no_data(&self) -> EbicsRes { 168 let xml = xml!("ebicsResponse" "xmlns"="http://www.ebics.org/H005" "xmlns:ds"="http://www.w3.org/2000/09/xmldsig#" { 169 "header" "authenticate"="true" { 170 "static", 171 "mutable" { 172 "TransactionPhase": "Initialisation", 173 "ReturnCode": "000000", 174 "ReportText": "[EBICS_OK] OK" 175 } 176 }, 177 "AuthSignature", 178 "body" { 179 "ReturnCode" "authenticate"="true": "090005" 180 } 181 }); 182 self.signed_response(xml) 183 } 184 185 pub fn hev(&mut self, body: &[u8]) -> EbicsRes { 186 Xml::parse(body, "ebicsHEVRequest", |root| { 187 root.one("HostID")?; 188 Ok(()) 189 }) 190 .unwrap(); 191 EbicsRes::Ok( 192 xml!("ebicsHEVResponse" "xmlns"="http://www.ebics.org/H000" { 193 "SystemReturnCode" { 194 "ReturnCode": "000000", 195 "ReportText": "[EBICS_OK] OK" 196 }, 197 "VersionNumber" "ProtocolVersion"="H005" : "03.00" 198 }), 199 ) 200 } 201 202 pub fn ini(&mut self, body: &[u8]) -> EbicsRes { 203 Self::parse_unsecure_request(body, "INI", "SignaturePubKeyOrderData", |root| { 204 let n = root.one("SignaturePubKeyInfo")?; 205 assert_eq!(n.one("SignatureVersion")?.text(), "A006"); 206 self.client_sign = Some(rsa_pub_key(n)?); 207 Ok(()) 208 }); 209 EbicsRes::Ok( 210 xml!("ebicsKeyManagementResponse" "xmlns"="http://www.ebics.org/H000" { 211 "header" "authenticate"="true" { 212 "mutable" { 213 "ReturnCode": "000000", 214 "ReportText": "[EBICS_OK] OK" 215 } 216 }, 217 "body" { 218 "ReturnCode" "authenticate"="true" : "000000" 219 } 220 }), 221 ) 222 } 223 224 pub fn hia(&mut self, body: &[u8]) -> EbicsRes { 225 Self::parse_unsecure_request(body, "HIA", "HIARequestOrderData", |root| { 226 let n = root.one("AuthenticationPubKeyInfo")?; 227 assert_eq!(n.one("AuthenticationVersion")?.text(), "X002"); 228 self.client_auth = Some(rsa_pub_key(n)?); 229 230 let n = root.one("EncryptionPubKeyInfo")?; 231 assert_eq!(n.one("EncryptionVersion")?.text(), "E002"); 232 self.client_enc = Some(rsa_pub_key(n)?); 233 Ok(()) 234 }); 235 EbicsRes::Ok( 236 xml!("ebicsKeyManagementResponse" "xmlns"="http://www.ebics.org/H000" { 237 "header" "authenticate"="true" { 238 "mutable" { 239 "ReturnCode": "000000", 240 "ReportText": "[EBICS_OK] OK" 241 } 242 }, 243 "body" { 244 "ReturnCode" "authenticate"="true" : "000000" 245 } 246 }), 247 ) 248 } 249 250 pub fn hpb(&mut self, body: &[u8]) -> EbicsRes { 251 // Parse HPB request 252 Xml::parse(body, "ebicsNoPubKeyDigestsRequest", |root| { 253 let order = root 254 .one("header") 255 .one("static") 256 .one("OrderDetails") 257 .one("AdminOrderType")? 258 .text(); 259 assert_eq!(order, "HPB"); 260 Ok(()) 261 }) 262 .unwrap(); 263 264 let payload = xml!("HPBResponseOrderData" "xmlns:ds"="http://www.w3.org/2000/09/xmldsig#" { 265 "AuthenticationPubKeyInfo" { 266 @ |w| rsa_key_xml(w, &self.bank), 267 "AuthenticationVersion": "X002" 268 }, 269 "EncryptionPubKeyInfo" { 270 @ |w| rsa_key_xml(w, &self.bank), 271 "EncryptionVersion": "E002" 272 } 273 }); 274 let deflated = deflate(payload.as_bytes()); 275 let (tx_key, encrypted_key) = gen_ebics_e002_key(self.client_enc.as_ref().unwrap()); 276 let encrypted = encrypt_ebics_e002(&tx_key, deflated); 277 EbicsRes::Ok( 278 xml!("ebicsKeyManagementResponse" "xmlns:ds"="http://www.w3.org/2000/09/xmldsig#" "xmlns"="http://www.ebics.org/H005" { 279 "header" "authenticate"="true"{ 280 "mutable" { 281 "ReturnCode": "000000", 282 "ReportText": "[EBICS_OK] OK" 283 } 284 }, 285 "body" { 286 "DataTransfer" { 287 "DataEncryptionInfo" "authenticate"="true" { 288 "EncryptionPubKeyDigest" "Version"="E002" "Algorithm"="http://www.w3.org/2001/04/xmlenc#sha256": base64::fmt(ebics_pub_key_hash(self.client_enc.as_ref().unwrap())), 289 "TransactionKey": base64::fmt(encrypted_key) 290 }, 291 "OrderData": base64::fmt(encrypted) 292 }, 293 "ReturnCode" "authenticate"="true": "000000" 294 } 295 }), 296 ) 297 } 298 299 pub fn hkd(&mut self, body: &[u8]) -> EbicsRes { 300 Self::parse_download_init(body, "HKD"); 301 self.ebics_response_payload( 302 &xml!("HKDResponseOrderData" { 303 "PartnerInfo" { 304 "AddressInfo", 305 "OrderInfo" { 306 "AdminOrderType": "BTD", 307 "Service" { 308 "ServiceName": "STM", 309 "Scope": "CH", 310 "Container" "containerType"="ZIP", 311 "MsgName" "version"="08": "camt.052" 312 }, 313 "Description" 314 }, 315 "OrderInfo" { 316 "AdminOrderType": "BTU", 317 "Service" { 318 "ServiceName": "SCT", 319 "MsgName": "pain.001" 320 }, 321 "Description": "Direct Debit" 322 }, 323 "OrderInfo" { 324 "AdminOrderType": "BTU", 325 "Service" { 326 "ServiceName": "SCI", 327 "Scope": "DE", 328 "MsgName": "pain.001" 329 }, 330 "Description": "Instant Direct Debit" 331 } 332 } 333 }), 334 true, 335 ) 336 } 337 338 pub fn haa(&mut self, body: &[u8]) -> EbicsRes { 339 Self::parse_download_init(body, "HAA"); 340 self.ebics_response_payload( 341 &xml!("HAAResponseOrderData" { 342 "Service" { 343 "ServiceName": "STM", 344 "Scope": "CH", 345 "Container" "containerType"="ZIP", 346 "MsgName" "version"="08": "camt.052" 347 } 348 }), 349 true, 350 ) 351 } 352 353 fn receipt(&mut self, body: &[u8], ok: bool) -> EbicsRes { 354 Xml::parse(body, "ebicsRequest", |root| { 355 let header = root.one("header")?; 356 let tx_id = header.one("static").one("TransactionID")?.text(); 357 assert_eq!(tx_id, self.tx_id.as_deref().unwrap()); 358 let phase = header.one("mutable").one("TransactionPhase")?.text(); 359 assert_eq!(phase, "Receipt"); 360 let code = root 361 .one("body") 362 .one("TransferReceipt") 363 .one("ReceiptCode")? 364 .text(); 365 if ok { 366 assert_eq!(code, "0") 367 } else { 368 assert_eq!(code, "1") 369 } 370 Ok(()) 371 }) 372 .unwrap(); 373 let tx_id = self.tx_id.take().unwrap(); 374 self.signed_response(xml!("ebicsResponse" "xmlns"="http://www.ebics.org/H005" "xmlns:ds"="http://www.w3.org/2000/09/xmldsig#" { 375 "header" "authenticate"="true" { 376 "static" { 377 "TransactionID": tx_id 378 }, 379 "mutable" { 380 "TransactionPhase": "Receipt", 381 "ReturnCode": "000000", 382 "ReportText": "[EBICS_OK] OK", 383 } 384 }, 385 "AuthSignature", 386 "body" { 387 "ReturnCode" "authenticate"="true": "000000" 388 } 389 })) 390 } 391 392 pub fn receipt_ok(&mut self, body: &[u8]) -> EbicsRes { 393 self.receipt(body, true) 394 } 395 396 pub fn receipt_err(&mut self, body: &[u8]) -> EbicsRes { 397 self.receipt(body, false) 398 } 399 400 fn btd_date_check(&self, body: &[u8], pinned: Option<Date>) -> EbicsRes { 401 Xml::parse(body, "ebicsRequest", |root| { 402 let header = root.one("header")?; 403 let details = header.one("static").one("OrderDetails")?; 404 let admin_order = details.one("AdminOrderType")?.text(); 405 assert_eq!(admin_order, "BTD"); 406 let start = details 407 .one("BTDOrderParams") 408 .opt("DateRange") 409 .opt("Start") 410 .parse()?; 411 assert_eq!(start, pinned); 412 let phase = header.one("mutable").one("TransactionPhase")?.text(); 413 assert_eq!(phase, "Initialisation"); 414 Ok(()) 415 }) 416 .unwrap(); 417 self.ebics_response_no_data() 418 } 419 420 pub fn btd_no_data(&mut self, body: &[u8]) -> EbicsRes { 421 self.btd_date_check(body, None) 422 } 423 424 pub fn btd_no_data_now(&mut self, body: &[u8]) -> EbicsRes { 425 self.btd_date_check( 426 body, 427 Some(Zoned::new(Timestamp::now(), TimeZone::UTC).date()), 428 ) 429 } 430 431 pub fn btd_no_data_pinned(&mut self, body: &[u8]) -> EbicsRes { 432 self.btd_date_check(body, Some(date(2024, 6, 5))) 433 } 434 435 pub fn btu_init(&mut self, body: &[u8]) -> EbicsRes { 436 Self::parse_download_init(body, "BTU"); 437 let tx_id = self.tx_id.insert(rand_ebics_id()); 438 let order_id = self.order_id.insert(rand_ebics_id()); 439 let xml = xml!("ebicsResponse" "xmlns"="http://www.ebics.org/H005" "xmlns:ds"="http://www.w3.org/2000/09/xmldsig#" { 440 "header" "authenticate"="true" { 441 "static" { 442 "TransactionID": tx_id 443 }, 444 "mutable" { 445 "TransactionPhase": "Initialisation", 446 "OrderID": order_id, 447 "ReturnCode": "000000", 448 "ReportText": "[EBICS_OK] OK", 449 } 450 }, 451 "AuthSignature", 452 "body" { 453 "ReturnCode" "authenticate"="true": "000000" 454 } 455 }); 456 self.signed_response(xml) 457 } 458 459 pub fn btu_payload(&mut self, body: &[u8]) -> EbicsRes { 460 let tx_id = self.tx_id.as_ref().unwrap(); 461 let order_id = self.order_id.as_ref().unwrap(); 462 let segment_nb: CompactString = Xml::parse(body, "ebicsRequest", |root| { 463 let header = root.one("header")?; 464 let txid = header.one("static").one("TransactionID")?.text(); 465 assert_eq!(txid, tx_id); 466 let mutable = header.one("mutable")?; 467 let phase = mutable.one("TransactionPhase")?.text(); 468 assert_eq!(phase, "Transfer"); 469 mutable.one("SegmentNumber").parse() 470 }) 471 .unwrap(); 472 self.signed_response(xml!("ebicsResponse" "xmlns"="http://www.ebics.org/H005" "xmlns:ds"="http://www.w3.org/2000/09/xmldsig#" { 473 "header" "authenticate"="true" { 474 "static" { 475 "TransactionID": tx_id 476 }, 477 "mutable" { 478 "TransactionPhase": "Transfer", 479 "SegmentNumber": segment_nb, 480 "OrderID": order_id, 481 "ReturnCode": "000000", 482 "ReportText": "[EBICS_OK] OK", 483 } 484 }, 485 "AuthSignature", 486 "body" { 487 "ReturnCode" "authenticate"="true": "000000" 488 } 489 })) 490 } 491 492 pub fn init_tx(&mut self, _: &[u8]) -> EbicsRes { 493 self.ebics_response_payload("", false) 494 } 495 496 pub fn failure(&mut self, _: &[u8]) -> EbicsRes { 497 EbicsRes::Failure 498 } 499 500 pub fn bad_request(&mut self, _: &[u8]) -> EbicsRes { 501 EbicsRes::BadRequest 502 } 503 } 504 505 pub struct TestBank { 506 pub dir: TempDir, 507 pub sock_path: String, 508 pub sequence: Arc<Mutex<Vec<Sequence>>>, 509 } 510 511 impl TestBank { 512 pub async fn new() -> Self { 513 let dir = tempdir().unwrap(); 514 let sock_path = dir.path().join("bank.sock").to_str().unwrap().to_string(); 515 let sequence = Arc::new(Mutex::new(Vec::new())); 516 let server_sequence = sequence.clone(); 517 let bank = Arc::new(Mutex::new(EbicsState::new())); 518 let serve = Serve::Unix { 519 path: sock_path.clone(), 520 permission: Permissions::from_mode(0o660), 521 }; 522 tokio::spawn(async move { 523 axum::Router::new() 524 .route( 525 "/", 526 post(async move |body: Bytes| { 527 let sequence: Sequence = server_sequence.lock().unwrap().pop().unwrap(); 528 let mut bank = bank.lock().unwrap(); 529 let res = sequence(&mut bank, &body); 530 match res { 531 EbicsRes::Ok(xml) => xml.into_response(), 532 EbicsRes::BadRequest => StatusCode::BAD_REQUEST.into_response(), 533 EbicsRes::Failure => StatusCode::SERVICE_UNAVAILABLE.into_response(), 534 } 535 }), 536 ) 537 .serve(&serve, None) 538 .await 539 .unwrap() 540 }); 541 wait_for_unix_socket(&sock_path).await; 542 Self { 543 dir, 544 sock_path, 545 sequence, 546 } 547 } 548 549 pub fn sequences(&self, sequences: &[Sequence]) { 550 let mut state = self.sequence.lock().unwrap(); 551 assert_eq!(state.len(), 0); 552 state.extend(sequences.iter().rev()); 553 } 554 } 555 556 impl Drop for TestBank { 557 fn drop(&mut self) { 558 if let Ok(sequences) = self.sequence.lock() { 559 assert_eq!(sequences.len(), 0); 560 } 561 } 562 }