setup.rs (6178B)
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::path::Path; 21 22 use anyhow::{anyhow, bail}; 23 use compact_str::{CompactString, CompactStringExt}; 24 use tracing::{debug, info}; 25 26 use crate::{ 27 config::EbicsSetupCfg, 28 crypto::ebics_pub_key_hash, 29 ebics::{EbicsClient, administrative::VersionNumber, order::Order}, 30 keys::{ 31 BankKeys, ClientKeys, load_bank_keys, load_client_keys, persist_bank_keys, 32 persist_client_keys, 33 }, 34 pdf::generate_keys_pdf, 35 utils::hex_chunk_by_two, 36 }; 37 38 /** Load client private keys at or create new ones if missing */ 39 pub fn load_or_generate_client_keys(path: &Path) -> anyhow::Result<ClientKeys> { 40 // If exists load from disk 41 let current = load_client_keys(path)?; 42 if let Some(current) = current { 43 return Ok(current); 44 } 45 // Else create new keys 46 let new = ClientKeys::generate()?; 47 persist_client_keys(&new, path)?; 48 info!(target: "setup", 49 "New client private keys created at '{}'", 50 path.to_string_lossy() 51 ); 52 Ok(new) 53 } 54 55 pub async fn ebics_setup( 56 ebics: &EbicsClient<'_>, 57 cfg: &EbicsSetupCfg<'_>, 58 force_keys_resubmission: bool, 59 generate_registration_pdf: bool, 60 auto_accept_keys: bool, 61 ) -> anyhow::Result<(ClientKeys, BankKeys)> { 62 let mut client = load_or_generate_client_keys(cfg.keys.client.as_ref())?; 63 let bank = load_bank_keys(cfg.keys.bank.as_ref())?; 64 65 // Check EBICS 3 support 66 let versions = ebics.hev().await?; 67 debug!(target: "setup", 68 "HEV: {}", 69 versions 70 .iter() 71 .map(|v| v.to_string()) 72 .join_compact(", ") 73 ); 74 if !versions.contains(&VersionNumber { 75 number: "03.00".into(), 76 schema: "H005".into(), 77 }) && !versions.contains(&VersionNumber { 78 number: "03.02".into(), 79 schema: "H005".into(), 80 }) { 81 bail!("EBICS 3 is not supported by your bank"); 82 } 83 84 // Privs exist. Upload their pubs 85 let keys_not_sub = !client.submitted_ini; 86 if !client.submitted_ini || force_keys_resubmission { 87 ebics 88 .submit_client_keys(&cfg.keys, &mut client, Order::INI) 89 .await?; 90 } 91 // Eject PDF if the keys were submitted for the first time, or the user asked. 92 if keys_not_sub || generate_registration_pdf { 93 let pdf = generate_keys_pdf(&client, &cfg.host)?; 94 let path = "/tmp/libeufin-ebics-keys.pdf"; 95 std::fs::write("/tmp/libeufin-ebics-keys.pdf", &pdf) 96 .map_err(|e| anyhow!("Could not write PDF to '{path}': {}", e.kind()))?; 97 println!("PDF file with keys created at '{path}'"); 98 } 99 if !client.submitted_hia || force_keys_resubmission { 100 ebics 101 .submit_client_keys(&cfg.keys, &mut client, Order::HIA) 102 .await?; 103 } 104 105 let new = ebics.hpb(&client).await?; 106 let mut bank = if let Some(current) = bank { 107 // Check current bank keys 108 if current.enc.as_ref() != new.enc.as_ref() { 109 bail!( 110 "On disk bank encryption key stored at {} doesn't match server key\nDisk: {}\nServer: {}", 111 cfg.keys.bank, 112 hex_chunk_by_two(ebics_pub_key_hash(¤t.enc)), 113 hex_chunk_by_two(ebics_pub_key_hash(&new.enc)) 114 ) 115 } else if current.auth.as_ref() != new.auth.as_ref() { 116 bail!( 117 "On disk bank authentication key stored at {} doesn't match server key\nDisk: {}\nServer: {}", 118 cfg.keys.bank, 119 hex_chunk_by_two(ebics_pub_key_hash(¤t.auth)), 120 hex_chunk_by_two(ebics_pub_key_hash(&new.auth)) 121 ) 122 } 123 current 124 } else { 125 // Accept bank keys 126 info!("Bank keys stored at {}", cfg.keys.bank); 127 persist_bank_keys(&new, cfg.keys.bank.as_ref())?; 128 new 129 }; 130 if !bank.accepted { 131 // Finishing the setup by accepting the bank keys. 132 let enc_hash = ebics_pub_key_hash(&bank.enc); 133 let auth_hash = ebics_pub_key_hash(&bank.auth); 134 if auto_accept_keys { 135 bank.accepted = true 136 } else if let Some(enc) = cfg.enc 137 && let Some(auth) = cfg.auth 138 { 139 if enc == enc_hash.as_ref() && auth == auth_hash.as_ref() { 140 info!(target: "setup", "Accepting bank keys matching config hashes"); 141 bank.accepted = true 142 } else { 143 bail!( 144 "Bank keys does not match config hashes\nBank encryption key: {}\nConfig encryption key: {}\nBank authentication key: {}\nConfig authentication key: {}", 145 hex_chunk_by_two(enc_hash), 146 hex_chunk_by_two(enc), 147 hex_chunk_by_two(auth_hash), 148 hex_chunk_by_two(auth), 149 ) 150 } 151 } else { 152 println!( 153 "The bank has the following keys:\nEncryption key: {}\nAuthentication key: {}", 154 hex_chunk_by_two(enc_hash), 155 hex_chunk_by_two(auth_hash) 156 ); 157 bank.accepted = dialoguer::Input::<CompactString>::new() 158 .with_prompt("type 'yes, accept' to accept them") 159 .interact()? 160 == "yes, accept"; 161 } 162 if !bank.accepted { 163 bail!("Cannot successfully finish the setup without accepting the bank keys"); 164 } 165 persist_bank_keys(&bank, cfg.keys.bank.as_ref())?; 166 } 167 168 Ok((client, bank)) 169 }