ebicsclient 1.3.3__tar.gz → 1.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/PKG-INFO +10 -5
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/README.md +9 -4
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/docs/05-zkb-onboarding.md +22 -5
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/docs/11-certificate-profiles.md +8 -1
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/pyproject.toml +1 -1
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/__init__.py +2 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/client.py +10 -5
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/keys.py +60 -8
- ebicsclient-1.4.0/src/ebicsclient/letter.py +411 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_h005.py +12 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_keys.py +52 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_letter.py +61 -10
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/uv.lock +1 -1
- ebicsclient-1.3.3/src/ebicsclient/letter.py +0 -344
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/.github/workflows/ci.yml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/.github/workflows/release.yml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/.gitignore +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/CLAUDE.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/CONTRIBUTING.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/LICENSE.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/docs/01-protocol-and-formats.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/docs/02-licensing-strategy.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/docs/03-library-landscape.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/docs/04-implementation-plan.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/docs/06-engineering-conventions.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/docs/07-handshake-testing.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/docs/08-parity-and-xsd-findings.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/docs/09-zkb-test-platform-settings.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/docs/10-btf-order-types.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/examples/zkb_handshake.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/certificates.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/crypto.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/errors.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/formats/__init__.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/formats/camt.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/formats/camt052.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/formats/camt053.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/formats/camt054.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/formats/container.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/formats/pain002.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/models.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/protocol/__init__.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/protocol/h005.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/py.typed +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/src/ebicsclient/transport.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/crypto_helpers.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/data/camt052_zkb_sample.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/data/camt053_zkb_sample.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/data/camt054_collective_zkb_sample.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/data/camt054_credit_zkb_sample.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/data/camt054_debit_zkb_sample.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/data/htd_zkb_sample.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/data/pain002_accp.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/data/pain002_actc.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/data/pain002_part.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/data/pain002_rjct.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/fixtures/parity/hia.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/fixtures/parity/hpb.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/fixtures/parity/ini.xml +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/fixtures/parity/meta.json +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_c14n_vectors.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_camt052_054.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_camt053.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_certificates.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_client.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_container.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_crypto.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_errors.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_input_validation.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_pain002.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_php_parity.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_schema_validation.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tests/test_transport.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tools/fetch-schemas.py +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tools/php-parity/README.md +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tools/php-parity/composer.json +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tools/php-parity/composer.lock +0 -0
- {ebicsclient-1.3.3 → ebicsclient-1.4.0}/tools/php-parity/generate.php +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ebicsclient
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Pure-Python client for the EBICS 3.0 (H005) banking protocol
|
|
5
5
|
Project-URL: Homepage, https://github.com/schulluk/ebicsclient
|
|
6
6
|
Project-URL: Repository, https://github.com/schulluk/ebicsclient
|
|
@@ -124,8 +124,9 @@ validated live against Zürcher Kantonalbank (ZKB).
|
|
|
124
124
|
**What it provides today:**
|
|
125
125
|
|
|
126
126
|
- **Key ceremony** — INI/HIA/HPB with X.509 key transmission (self-signed *mit Schlüsseln* or
|
|
127
|
-
CA-issued *mit Zertifikaten*), the printable initialisation
|
|
128
|
-
|
|
127
|
+
CA-issued *mit Zertifikaten*), the printable EBICS 3.0 initialisation letters (INI + HIA,
|
|
128
|
+
HTML/PDF, carrying the certificates and their SHA-256 DER fingerprints per spec 4.4.1.2.3),
|
|
129
|
+
and bank-key **pinning** across sessions
|
|
129
130
|
- **Read** — statement/report downloads with parsers into typed models: **camt.053** end-of-day
|
|
130
131
|
statements, **camt.052** intraday reports, **camt.054** booking advices (incl. QRR/SCOR/LSV),
|
|
131
132
|
and **pain.002** payment status reports
|
|
@@ -237,8 +238,12 @@ a real bank statement.
|
|
|
237
238
|
- [x] Authentication signature (inclusive Canonical XML 1.0 + RSA-SHA256)
|
|
238
239
|
- [x] HTTPS transport (TLS 1.2 floor, certifi fallback via the optional `tls` extra)
|
|
239
240
|
- [x] INI/HIA/HPB handshake
|
|
240
|
-
- [x] X.509 key transmission: **mit Schlüsseln** (self-signed) and
|
|
241
|
-
|
|
241
|
+
- [x] X.509 key transmission: **mit Schlüsseln** (self-signed, deterministic certificates) and
|
|
242
|
+
**mit Zertifikaten** (CA certs)
|
|
243
|
+
- [x] Initialisation letters (INI + HIA) with certificate SHA-256 DER fingerprints per the
|
|
244
|
+
EBICS 3.0 spec (HTML, or PDF via the optional `pdf` extra)
|
|
245
|
+
- [ ] Production activation on the printed letters (in progress — the pre-1.4.0 letter carried
|
|
246
|
+
the EBICS 2.x public-key hash, which banks no longer match; see the 1.4.0 notes)
|
|
242
247
|
- [x] Bank-key pinning across sessions (`hpb(pinned=...)`)
|
|
243
248
|
|
|
244
249
|
**Milestone 2 — Read** (validated live on ZKB)
|
|
@@ -6,8 +6,9 @@ validated live against Zürcher Kantonalbank (ZKB).
|
|
|
6
6
|
**What it provides today:**
|
|
7
7
|
|
|
8
8
|
- **Key ceremony** — INI/HIA/HPB with X.509 key transmission (self-signed *mit Schlüsseln* or
|
|
9
|
-
CA-issued *mit Zertifikaten*), the printable initialisation
|
|
10
|
-
|
|
9
|
+
CA-issued *mit Zertifikaten*), the printable EBICS 3.0 initialisation letters (INI + HIA,
|
|
10
|
+
HTML/PDF, carrying the certificates and their SHA-256 DER fingerprints per spec 4.4.1.2.3),
|
|
11
|
+
and bank-key **pinning** across sessions
|
|
11
12
|
- **Read** — statement/report downloads with parsers into typed models: **camt.053** end-of-day
|
|
12
13
|
statements, **camt.052** intraday reports, **camt.054** booking advices (incl. QRR/SCOR/LSV),
|
|
13
14
|
and **pain.002** payment status reports
|
|
@@ -119,8 +120,12 @@ a real bank statement.
|
|
|
119
120
|
- [x] Authentication signature (inclusive Canonical XML 1.0 + RSA-SHA256)
|
|
120
121
|
- [x] HTTPS transport (TLS 1.2 floor, certifi fallback via the optional `tls` extra)
|
|
121
122
|
- [x] INI/HIA/HPB handshake
|
|
122
|
-
- [x] X.509 key transmission: **mit Schlüsseln** (self-signed) and
|
|
123
|
-
|
|
123
|
+
- [x] X.509 key transmission: **mit Schlüsseln** (self-signed, deterministic certificates) and
|
|
124
|
+
**mit Zertifikaten** (CA certs)
|
|
125
|
+
- [x] Initialisation letters (INI + HIA) with certificate SHA-256 DER fingerprints per the
|
|
126
|
+
EBICS 3.0 spec (HTML, or PDF via the optional `pdf` extra)
|
|
127
|
+
- [ ] Production activation on the printed letters (in progress — the pre-1.4.0 letter carried
|
|
128
|
+
the EBICS 2.x public-key hash, which banks no longer match; see the 1.4.0 notes)
|
|
124
129
|
- [x] Bank-key pinning across sessions (`hpb(pinned=...)`)
|
|
125
130
|
|
|
126
131
|
**Milestone 2 — Read** (validated live on ZKB)
|
|
@@ -12,11 +12,17 @@ activation PIN**.
|
|
|
12
12
|
1. **Generate** your three RSA keypairs (A006 sig, X002 auth, E002 enc) → encrypted keyring.
|
|
13
13
|
2. **INI** — send your signature public key (`ebicsUnsecuredRequest`, order type `INI`).
|
|
14
14
|
3. **HIA** — send your authentication + encryption public keys (order type `HIA`).
|
|
15
|
-
4. **Initialisierungsbriefe** — `make_ini_letter()` renders
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
4. **Initialisierungsbriefe** — `make_ini_letter()` renders **both** EBICS 3.0 letters (the INI
|
|
16
|
+
letter with the A006 signature certificate; the HIA letter with the X002 and E002 certificates,
|
|
17
|
+
on its own page) as **printable HTML** by default (no dependency) or as **PDF** with the optional
|
|
18
|
+
`pdf` extra; `AUTO` picks PDF when that extra is installed, else HTML. Per the EBICS 3.0 spec
|
|
19
|
+
(section 4.4.1.2.3) each letter shows the certificate in PEM **and the SHA-256 hash of its DER
|
|
20
|
+
encoding** in uppercase hex — *not* the EBICS 2.x public-key (`exponent modulus`) hash; the bank
|
|
21
|
+
compares those fingerprints against the certificates it received over INI/HIA. The default
|
|
22
|
+
self-signed certificates are **deterministic**, so the letter always reproduces exactly the
|
|
23
|
+
certificates the requests transmitted, from the keyring alone. **Sign each page by hand (legally
|
|
24
|
+
valid signature)** and mail to ZKB Kompetenzcenter Services. ZKB verifies the signature against
|
|
25
|
+
documents on file, then activates and confirms your access. See
|
|
20
26
|
[doc 07](07-handshake-testing.md) for the end-to-end test walkthrough.
|
|
21
27
|
5. **HPB** — download ZKB's public keys and **verify them against the hashes printed on p.2 of the
|
|
22
28
|
Bankparameterdaten letter** (in `../local/`).
|
|
@@ -48,3 +54,14 @@ Contact: **support.epayment@zkb.ch / +41 44 293 99 15** (Kompetenzcenter Service
|
|
|
48
54
|
|
|
49
55
|
- Confirm **plain keys** ("mit Schlüsseln") are fine for download-only — assume yes, proceed.
|
|
50
56
|
- Profile **T** (no distributed signature) — irrelevant since download-only.
|
|
57
|
+
|
|
58
|
+
## Production watch-item: `BankPubKeyDigests` hash format
|
|
59
|
+
|
|
60
|
+
The EBICS 3.0 spec (section 5.5.1.1) defines the `BankPubKeyDigests` values in requests as the
|
|
61
|
+
SHA-256 over the bank **certificate's DER** — but this client sends the EBICS 2.x public-key
|
|
62
|
+
(`exponent modulus`) digest, and the ZKB **test platform accepted that in every live
|
|
63
|
+
transaction** (downloads and uploads). ZKB's published Bankparameterdaten hashes are also
|
|
64
|
+
public-key hashes, so HPB pinning stays in the `e m` format. If ZKB **production** ever rejects
|
|
65
|
+
requests with `EBICS_BANK_PUBKEY_UPDATE_REQUIRED` despite fresh HPB keys, this mismatch is the
|
|
66
|
+
first suspect: the fix is to retain the bank *certificates* from the HPB response and send
|
|
67
|
+
their DER SHA-256 fingerprints instead.
|
|
@@ -6,7 +6,14 @@ subscriber's certificate comes from and how the bank's certificate is trusted:
|
|
|
6
6
|
|
|
7
7
|
- **mit Schlüsseln** (key-based) — the certificate is **self-signed** by the key it carries.
|
|
8
8
|
Identity is established out of band: the signed initialisation letter, or online activation.
|
|
9
|
-
This is the default and what the ZKB validation used.
|
|
9
|
+
This is the default and what the ZKB validation used. The self-signed certificates are
|
|
10
|
+
**deterministic** (fixed validity 2020-01-01 → 9999-12-31, key-derived serial, deterministic
|
|
11
|
+
PKCS#1 v1.5 signature): regenerating one for the same key yields **byte-identical DER**. This
|
|
12
|
+
matters because the EBICS 3.0 initialisation letters print the SHA-256 hash of the DER-encoded
|
|
13
|
+
certificate (spec section 4.4.1.2.3) and the bank compares it against the certificate INI/HIA
|
|
14
|
+
transmitted — determinism lets any later session reproduce that certificate from the keyring
|
|
15
|
+
alone, without persisting certificates. (The Swiss Market Practice Guidelines section 6.1
|
|
16
|
+
explicitly allow unlimited certificate validity.)
|
|
10
17
|
- **mit Zertifikaten** (certificate-based) — the certificate is **issued by a CA the bank
|
|
11
18
|
trusts**, and the bank validates the chain. Common at German and French banks.
|
|
12
19
|
|
|
@@ -39,6 +39,7 @@ from ebicsclient.errors import (
|
|
|
39
39
|
from ebicsclient.keys import (
|
|
40
40
|
CertificateUsage,
|
|
41
41
|
bank_key_hashes,
|
|
42
|
+
certificate_fingerprint,
|
|
42
43
|
deserialize_keyring,
|
|
43
44
|
generate_keyring,
|
|
44
45
|
generate_self_signed_certificate,
|
|
@@ -139,6 +140,7 @@ __all__ = [
|
|
|
139
140
|
"User",
|
|
140
141
|
"__version__",
|
|
141
142
|
"bank_key_hashes",
|
|
143
|
+
"certificate_fingerprint",
|
|
142
144
|
"deserialize_keyring",
|
|
143
145
|
"generate_keyring",
|
|
144
146
|
"generate_self_signed_certificate",
|
|
@@ -93,18 +93,22 @@ class Client:
|
|
|
93
93
|
output_format: OutputFormat = OutputFormat.AUTO,
|
|
94
94
|
branding: str = "ebicsClient",
|
|
95
95
|
) -> Letter:
|
|
96
|
-
"""Render the initialisation
|
|
96
|
+
"""Render the initialisation letters (INI and HIA) to print, sign, and send.
|
|
97
97
|
|
|
98
|
-
The
|
|
99
|
-
|
|
98
|
+
The letters carry the subscriber's certificates and their SHA-256 DER
|
|
99
|
+
fingerprints (EBICS 3.0 spec, section 4.4.1.2.3) so the bank can verify, out of
|
|
100
|
+
band, the certificates it received electronically over INI and HIA. They are
|
|
101
|
+
rendered from this client's certificate provider, so the printed fingerprints
|
|
102
|
+
match the certificates :meth:`ini` and :meth:`hia` transmitted.
|
|
100
103
|
|
|
101
104
|
Args:
|
|
102
105
|
output_format: The output format. ``AUTO`` renders PDF when the optional
|
|
103
106
|
``pdf`` extra is installed, otherwise HTML.
|
|
104
|
-
branding: A name shown in the
|
|
107
|
+
branding: A name shown in the letters' footer; defaults to ``"ebicsClient"``.
|
|
105
108
|
|
|
106
109
|
Returns:
|
|
107
|
-
The rendered
|
|
110
|
+
The rendered letters (format, media type, and content bytes) — the INI
|
|
111
|
+
letter and the HIA letter, each on its own page.
|
|
108
112
|
|
|
109
113
|
Raises:
|
|
110
114
|
MissingDependencyError: PDF output was requested without the ``pdf`` extra.
|
|
@@ -113,6 +117,7 @@ class Client:
|
|
|
113
117
|
self._bank,
|
|
114
118
|
self._user,
|
|
115
119
|
self._keyring,
|
|
120
|
+
certificate_provider=self._certificate_provider,
|
|
116
121
|
output_format=output_format,
|
|
117
122
|
branding=branding,
|
|
118
123
|
)
|
|
@@ -38,9 +38,19 @@ _PUBLIC_EXPONENT = 65537
|
|
|
38
38
|
_KEYRING_FORMAT_VERSION = 1
|
|
39
39
|
_KEY_FIELDS = ("signature", "authentication", "encryption")
|
|
40
40
|
|
|
41
|
-
# Self-signed certificates for the key-based ("mit Schlüsseln") profile are
|
|
42
|
-
#
|
|
43
|
-
|
|
41
|
+
# Self-signed certificates for the key-based ("mit Schlüsseln") profile are DETERMINISTIC:
|
|
42
|
+
# regenerating one for the same key always yields byte-identical DER. That matters because
|
|
43
|
+
# EBICS 3.0 initialisation letters carry the SHA-256 hash of the DER-encoded certificate
|
|
44
|
+
# (EBICS 3.0 spec, section 4.4.1.2.3), and the bank compares it against the certificate it
|
|
45
|
+
# received over INI/HIA — so the letter must be able to reproduce that exact certificate
|
|
46
|
+
# from the keyring alone, in any later session. Determinism comes from fixed validity
|
|
47
|
+
# dates, a serial derived from the key, and RSA PKCS#1 v1.5 signing (which is itself
|
|
48
|
+
# deterministic). The Swiss Market Practice Guidelines EBICS 3.0 (section 6.1) explicitly
|
|
49
|
+
# allow any validity value including unlimited (9999-12-31).
|
|
50
|
+
_CERTIFICATE_NOT_BEFORE = datetime.datetime(2020, 1, 1, tzinfo=datetime.UTC)
|
|
51
|
+
_CERTIFICATE_NOT_AFTER = datetime.datetime(9999, 12, 31, 23, 59, 59, tzinfo=datetime.UTC)
|
|
52
|
+
# RFC 5280 caps serial numbers at 20 octets; 16 digest bytes stay comfortably below.
|
|
53
|
+
_CERTIFICATE_SERIAL_BYTES = 16
|
|
44
54
|
|
|
45
55
|
|
|
46
56
|
def _require_passphrase(passphrase: object) -> None:
|
|
@@ -109,13 +119,20 @@ def _key_usage(usage: CertificateUsage) -> x509.KeyUsage:
|
|
|
109
119
|
def generate_self_signed_certificate(
|
|
110
120
|
private_key: rsa.RSAPrivateKey, common_name: str, usage: CertificateUsage
|
|
111
121
|
) -> x509.Certificate:
|
|
112
|
-
"""Generate a self-signed X.509 certificate carrying an EBICS public key.
|
|
122
|
+
"""Generate a deterministic self-signed X.509 certificate carrying an EBICS public key.
|
|
113
123
|
|
|
114
124
|
EBICS 3.0 (H005) transmits public keys as X.509 certificates (``ds:X509Data``). In the
|
|
115
125
|
key-based ("mit Schlüsseln") profile the certificate is self-signed by the very key it
|
|
116
126
|
carries, and the bank extracts the public key from it. The bank *does* validate the
|
|
117
127
|
certificate's Key Usage against its EBICS role, so it is set from ``usage``.
|
|
118
128
|
|
|
129
|
+
The certificate is **deterministic**: the same key, name, and usage always produce
|
|
130
|
+
byte-identical DER (fixed validity, key-derived serial, deterministic PKCS#1 v1.5
|
|
131
|
+
signature). The EBICS 3.0 initialisation letters carry the SHA-256 hash of this DER
|
|
132
|
+
(see :func:`certificate_fingerprint`), and the bank compares it against the certificate
|
|
133
|
+
delivered over INI/HIA — determinism lets the letter reproduce that certificate from
|
|
134
|
+
the keyring alone, without persisting certificates.
|
|
135
|
+
|
|
119
136
|
Args:
|
|
120
137
|
private_key: The key pair to certify; its public half is embedded and it self-signs.
|
|
121
138
|
common_name: The subject/issuer common name (e.g. the subscriber's User ID).
|
|
@@ -125,20 +142,55 @@ def generate_self_signed_certificate(
|
|
|
125
142
|
The self-signed certificate.
|
|
126
143
|
"""
|
|
127
144
|
name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, common_name)])
|
|
128
|
-
now = datetime.datetime.now(datetime.UTC)
|
|
129
145
|
return (
|
|
130
146
|
x509.CertificateBuilder()
|
|
131
147
|
.subject_name(name)
|
|
132
148
|
.issuer_name(name)
|
|
133
149
|
.public_key(private_key.public_key())
|
|
134
|
-
.serial_number(
|
|
135
|
-
.not_valid_before(
|
|
136
|
-
.not_valid_after(
|
|
150
|
+
.serial_number(_deterministic_serial(private_key.public_key(), common_name, usage))
|
|
151
|
+
.not_valid_before(_CERTIFICATE_NOT_BEFORE)
|
|
152
|
+
.not_valid_after(_CERTIFICATE_NOT_AFTER)
|
|
137
153
|
.add_extension(_key_usage(usage), critical=True)
|
|
138
154
|
.sign(private_key, hashes.SHA256())
|
|
139
155
|
)
|
|
140
156
|
|
|
141
157
|
|
|
158
|
+
def _deterministic_serial(
|
|
159
|
+
public_key: rsa.RSAPublicKey, common_name: str, usage: CertificateUsage
|
|
160
|
+
) -> int:
|
|
161
|
+
# A serial derived from the certified key (plus name and role) keeps regeneration
|
|
162
|
+
# byte-identical while staying unique per certificate. RFC 5280 requires a positive
|
|
163
|
+
# serial of at most 20 octets; 16 digest bytes always satisfy both (`or 1` covers the
|
|
164
|
+
# astronomically unlikely all-zero digest).
|
|
165
|
+
digest = hashlib.sha256(
|
|
166
|
+
b"|".join(
|
|
167
|
+
(
|
|
168
|
+
_fingerprint_data(public_key.public_numbers()),
|
|
169
|
+
common_name.encode("utf-8"),
|
|
170
|
+
usage.value.encode("ascii"),
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
).digest()
|
|
174
|
+
return int.from_bytes(digest[:_CERTIFICATE_SERIAL_BYTES], "big") or 1
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def certificate_fingerprint(certificate: x509.Certificate) -> bytes:
|
|
178
|
+
"""Compute the SHA-256 fingerprint of a certificate's DER encoding.
|
|
179
|
+
|
|
180
|
+
This is the hash the EBICS 3.0 initialisation letters print (spec section 4.4.1.2.3):
|
|
181
|
+
the SHA-256 of the certificate in DER binary format, shown as uppercase hexadecimal.
|
|
182
|
+
The bank compares it against the certificate received over INI/HIA before activating
|
|
183
|
+
the subscriber.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
certificate: The certificate to fingerprint.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
The 32-byte SHA-256 digest of the DER-encoded certificate.
|
|
190
|
+
"""
|
|
191
|
+
return certificate.fingerprint(hashes.SHA256())
|
|
192
|
+
|
|
193
|
+
|
|
142
194
|
def bank_key_hashes(bank_keys: BankKeys) -> BankKeyHashes:
|
|
143
195
|
"""Compute the pinning hashes for a bank's public keys.
|
|
144
196
|
|