key_management.rs (10985B)
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::{borrow::Cow, io::Write as _}; 21 22 use anyhow::bail; 23 use aws_lc_rs::{ 24 encoding::{AsDer, Pkcs8V1Der}, 25 rsa::PublicKey, 26 }; 27 use compact_str::CompactStringExt; 28 use flate2::{Compression, write::ZlibEncoder}; 29 use taler_common::encoding::base64; 30 use tracing::info; 31 32 use crate::{ 33 config::{EbicsHostCfg, EbicsKeysCfg}, 34 crypto::{rsa_private_from_b64_x509_certificate, x509_certificate_from_rsa_private}, 35 ebics::{ 36 EbicsClient, EbicsCtx, EbicsErrKind, EbicsError, EbicsErrorHelper, EbicsResponse, 37 bts::DataEncryptionInfo, decrypt_and_decompress_payload, ebics_code::EbicsReturnCode, 38 order::Order, 39 }, 40 keys::{self, BankKeys, ClientKeys}, 41 xml, 42 xml::{Xml, XmlAccess as _, XmlWriter}, 43 xml_sign::sign_ebics, 44 }; 45 46 impl EbicsClient<'_> { 47 /** Perform an EBICS public key management [order] using [client] and update on disk state */ 48 pub async fn submit_client_keys( 49 &self, 50 cfg: &EbicsKeysCfg<'_>, 51 client: &mut ClientKeys, 52 order: Order, 53 ) -> Result<(), EbicsError> { 54 let ctx = EbicsCtx::new(&order); 55 if !matches!(order, Order::INI | Order::HIA) { 56 unreachable!("Only INI & HIA are supported for client keys"); 57 } 58 let res = self.key_management(client, &order, &ctx).await?; 59 60 if res.technical_code == EbicsReturnCode::EBICS_INVALID_USER_STATE 61 || res.technical_code == EbicsReturnCode::EBICS_INVALID_USER_OR_USER_STATE 62 { 63 return Err(EbicsErrKind::Custom(Cow::Owned(format!( 64 "status code {}: either your IDs are incorrect, or you already have keys registered with this bank", 65 res.technical_code 66 ))).ctx(&ctx)); 67 } 68 res.ok_or_fail().ctx(&ctx)?; 69 match order { 70 Order::INI => client.submitted_ini = true, 71 Order::HIA => client.submitted_hia = true, 72 _ => unreachable!("Only INI & HIA are supported for client keys"), 73 } 74 keys::persist_client_keys(client, cfg.client.as_ref()) 75 .map_err(|e| EbicsErrKind::Custom(e.to_string().into())) 76 .ctx(&ctx)?; 77 Ok(()) 78 } 79 80 /** Perform an EBICS private key management HPB using [client] */ 81 pub async fn hpb(&self, client: &ClientKeys) -> anyhow::Result<BankKeys> { 82 let order = Order::HPB; 83 let ctx = EbicsCtx::new(&order); 84 let res = self.key_management(client, &order, &ctx).await?; 85 if res.technical_code == EbicsReturnCode::EBICS_AUTHENTICATION_FAILED { 86 bail!( 87 "{order} status code {}: could not download bank keys, send client keys (and/or related PDF document with --generate-registration-pdf) to the bank", 88 res.technical_code 89 ) 90 } 91 let order_data = res.ok_or_fail()?.expect("{order}: missing order data"); 92 self.logger.log_payload(&ctx, &order_data, "xml")?; 93 Ok(Xml::parse(&order_data, "HPBResponseOrderData", |root| { 94 let auth_pub = root.one("AuthenticationPubKeyInfo")?; 95 let version = auth_pub.one("AuthenticationVersion")?.text(); 96 assert_eq!( 97 version, "X002", 98 "Expected authentication version X002 got unsupported {version}" 99 ); 100 let auth_pub = rsa_pub_key(auth_pub)?; 101 102 let enc_pub = root.one("EncryptionPubKeyInfo")?; 103 let version = enc_pub.one("EncryptionVersion")?.text(); 104 assert_eq!( 105 version, "E002", 106 "Expected encryption version E002 got unsupported {version}" 107 ); 108 let enc_pub = rsa_pub_key(enc_pub)?; 109 110 Ok(BankKeys { 111 auth: auth_pub, 112 enc: enc_pub, 113 accepted: false, 114 }) 115 })?) 116 } 117 118 async fn key_management( 119 &self, 120 client: &ClientKeys, 121 order: &Order, 122 ctx: &EbicsCtx<'_>, 123 ) -> Result<EbicsResponse<Option<Vec<u8>>>, EbicsError> { 124 let EbicsHostCfg { 125 host_id, 126 user_id, 127 partner_id, 128 .. 129 } = &self.cfg; 130 info!(target: "ebics", "Doing key request {order}"); 131 132 let (name, security_medium) = match order { 133 Order::INI | Order::HIA => ("ebicsUnsecuredRequest", "0200"), 134 Order::HPB => ("ebicsNoPubKeyDigestsRequest", "0000"), 135 _ => unreachable!(), 136 }; 137 138 fn xml_order_data( 139 cfg: &EbicsHostCfg, 140 name: &str, 141 schema: &str, 142 build: impl FnOnce(&mut XmlWriter), 143 ) -> String { 144 let xml = xml!(name "xmlns"=schema "xmlns:ds"="http://www.w3.org/2000/09/xmldsig#" { 145 @ build, 146 "PartnerID": &cfg.partner_id, 147 "UserID": &cfg.user_id 148 }); 149 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); 150 encoder.write_all(xml.as_bytes()).unwrap(); 151 let compressed = encoder.finish().unwrap(); 152 base64::encode(&compressed) 153 } 154 155 let data = match order { 156 Order::INI => Some(xml_order_data( 157 &self.cfg, 158 "SignaturePubKeyOrderData", 159 "http://www.ebics.org/S002", 160 |w| { 161 xml!(w => "SignaturePubKeyInfo" { 162 @ |w| rsa_key_xml(w, &client.sign), 163 "SignatureVersion": "A006" 164 }) 165 }, 166 )), 167 Order::HIA => Some(xml_order_data( 168 &self.cfg, 169 "HIARequestOrderData", 170 "urn:org:ebics:H005", 171 |w| { 172 xml!(w => 173 "AuthenticationPubKeyInfo" { 174 @ |w| rsa_key_xml(w, &client.auth), 175 "AuthenticationVersion": "X002" 176 }, 177 "EncryptionPubKeyInfo" { 178 @ |w| rsa_key_xml(w, &client.enc), 179 "EncryptionVersion": "E002" 180 } 181 ) 182 }, 183 )), 184 Order::HPB => None, 185 _ => unreachable!(), 186 }; 187 let sign = matches!(order, Order::HPB); 188 let msg = xml!( 189 name 190 "xmlns"="urn:org:ebics:H005" 191 "xmlns:ds"="http://www.w3.org/2000/09/xmldsig#" 192 "Version"="H005" 193 "Revision"="1" 194 { 195 "header" "authenticate"="true" { 196 "static" { 197 "HostID": host_id, 198 @ |w: &mut XmlWriter| if *order == Order::HPB { 199 let nonce: u128 = rand::random(); 200 xml!(w => 201 "Nonce": format_args!("{:032x}", nonce), 202 "Timestamp": jiff::Timestamp::now() 203 ) 204 }, 205 "PartnerID": partner_id, 206 "UserID": user_id, 207 "OrderDetails" { 208 "AdminOrderType": order 209 }, 210 "SecurityMedium": security_medium 211 }, 212 "mutable" 213 }, 214 @ |w: &mut XmlWriter| if sign { 215 xml!(w => "AuthSignature") 216 }, 217 "body" { 218 @ |w: &mut XmlWriter| if let Some(data) = data { 219 xml!(w => "DataTransfer" { 220 "OrderData": data 221 }) 222 } 223 } 224 } 225 ); 226 let signed = if sign { 227 sign_ebics(msg, &client.auth).expect("EBICS signature never fails") 228 } else { 229 msg 230 }; 231 let res = self.post_to_bank(signed, ctx).await?; 232 let parsed = Xml::parse(&res, "ebicsKeyManagementResponse", |root| { 233 let body = root.one("body")?; 234 let mutable = root.one_signed("header").one("mutable")?; 235 Ok(EbicsResponse { 236 technical_code: mutable.one("ReturnCode").parse()?, 237 technical_text: mutable.one("ReportText").parse()?, 238 bank_code: body.one_signed("ReturnCode").parse()?, 239 content: Some(if let Some(data) = body.opt("DataTransfer")? { 240 let info = data.one_signed("DataEncryptionInfo")?; 241 let info = DataEncryptionInfo { 242 tx_key: info.one("TransactionKey").b64()?, 243 enc_pub_digest: info.one("EncryptionPubKeyDigest").b64()?, 244 }; 245 let chunk = data.one("OrderData").b64()?; 246 Some((info, chunk)) 247 } else { 248 None 249 }), 250 }) 251 }) 252 .ctx(ctx)?; 253 let decoded = match parsed.content { 254 Some(Some((info, chunk))) => Some(Some( 255 decrypt_and_decompress_payload(&client.enc, info, chunk).ctx(ctx)?, 256 )), 257 Some(None) => Some(None), 258 None => None, 259 }; 260 Ok(EbicsResponse { 261 content: decoded, 262 technical_code: parsed.technical_code, 263 bank_code: parsed.bank_code, 264 technical_text: parsed.technical_text, 265 }) 266 } 267 } 268 269 pub fn rsa_pub_key(xml: Xml) -> xml::Result<PublicKey> { 270 xml.one("X509Data") 271 .one("X509Certificate") 272 .decode(rsa_private_from_b64_x509_certificate) 273 } 274 275 pub fn rsa_key_xml<K>(w: &mut XmlWriter, key: &K) 276 where 277 K: AsDer<Pkcs8V1Der<'static>>, 278 { 279 let der = key.as_der().unwrap(); 280 let b64 = base64::encode(der.as_ref()); 281 let lines = b64 282 .as_bytes() 283 .chunks(64) 284 .map(|c| std::str::from_utf8(c).unwrap()) 285 .join_compact("\n"); 286 let pem = format!("-----BEGIN RSA PRIVATE KEY-----\n{lines}\n-----END RSA PRIVATE KEY-----\n"); 287 let cert = x509_certificate_from_rsa_private(&pem, "LibEuFin EBICS").unwrap(); 288 let der = cert.der(); 289 290 xml!(w => "ds:X509Data" { 291 "ds:X509Certificate": base64::fmt(der) 292 }) 293 }