uakep 0.1.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.
- uakep-0.1.0/LICENSE +21 -0
- uakep-0.1.0/NOTICE +58 -0
- uakep-0.1.0/PKG-INFO +106 -0
- uakep-0.1.0/README.md +86 -0
- uakep-0.1.0/pyproject.toml +88 -0
- uakep-0.1.0/pyproject.toml.orig +59 -0
- uakep-0.1.0/src/uakep/__init__.py +54 -0
- uakep-0.1.0/src/uakep/asn1.py +406 -0
- uakep-0.1.0/src/uakep/cades.py +400 -0
- uakep-0.1.0/src/uakep/certificate.py +210 -0
- uakep-0.1.0/src/uakep/curve.py +285 -0
- uakep-0.1.0/src/uakep/dstu4145.py +127 -0
- uakep-0.1.0/src/uakep/gf2m.py +161 -0
- uakep-0.1.0/src/uakep/gost28147.py +163 -0
- uakep-0.1.0/src/uakep/gost34311.py +188 -0
- uakep-0.1.0/src/uakep/keystore.py +377 -0
- uakep-0.1.0/src/uakep/kupyna.py +130 -0
- uakep-0.1.0/src/uakep/py.typed +0 -0
- uakep-0.1.0/src/uakep/tsp.py +105 -0
uakep-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anton Butsyk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
uakep-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
uakep
|
|
2
|
+
Copyright (c) 2026 Anton Butsyk. Licensed under the MIT License (see LICENSE).
|
|
3
|
+
|
|
4
|
+
uakep contains code ported from the projects below. Their notices are kept here.
|
|
5
|
+
|
|
6
|
+
-------------------------------------------------------------------------------
|
|
7
|
+
jkurwa — https://github.com/dstucrypt/jkurwa
|
|
8
|
+
Author: Ilya Petrov (Illya Petrov)
|
|
9
|
+
License: BSD, as declared in the project's package.json. The repository has no
|
|
10
|
+
LICENSE file; which BSD text applies is being confirmed with the author.
|
|
11
|
+
|
|
12
|
+
Ported into uakep: GF(2^m) arithmetic (lib/gf2m.js, lib/field.js), binary curves
|
|
13
|
+
and named curves (lib/curve.js, lib/point.js, lib/standard.js), ДСТУ 4145 signing
|
|
14
|
+
and verification including the fixed-length signing scalar (lib/models/Priv.js,
|
|
15
|
+
lib/models/Pub.js), key containers (lib/spec/keystore.js, lib/spec/pbes.js,
|
|
16
|
+
lib/spec/pfx.js), certificates and CMS/CAdES structures (lib/models/Certificate.js,
|
|
17
|
+
lib/models/Message.js, lib/spec/*.js).
|
|
18
|
+
|
|
19
|
+
-------------------------------------------------------------------------------
|
|
20
|
+
gost89 — https://github.com/dstucrypt/gost89
|
|
21
|
+
Author: Ilya Petrov (Illya Petrov)
|
|
22
|
+
License: BSD, as declared in the project's package.json.
|
|
23
|
+
|
|
24
|
+
Ported into uakep: the ГОСТ 28147-89 block cipher and its ECB, CFB and MAC modes
|
|
25
|
+
(lib/gost89.js), the ГОСТ 34.311-95 hash (lib/hash.js), the s-box tables
|
|
26
|
+
(lib/dstu.js) and the password-based key derivation (lib/util.js).
|
|
27
|
+
|
|
28
|
+
-------------------------------------------------------------------------------
|
|
29
|
+
ppolib — https://github.com/leon-mbs/ppolib
|
|
30
|
+
The four ДСТУ 7564:2014 substitution tables in src/uakep/kupyna.py were taken
|
|
31
|
+
from ppolib's src/algo/dstu7564.php. ppolib's license:
|
|
32
|
+
|
|
33
|
+
MIT License
|
|
34
|
+
|
|
35
|
+
Copyright (c) 2021 Leon
|
|
36
|
+
|
|
37
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
38
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
39
|
+
in the Software without restriction, including without limitation the rights
|
|
40
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
41
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
42
|
+
furnished to do so, subject to the following conditions:
|
|
43
|
+
|
|
44
|
+
The above copyright notice and this permission notice shall be included in
|
|
45
|
+
all copies or substantial portions of the Software.
|
|
46
|
+
|
|
47
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
48
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
49
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
50
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
51
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
52
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
53
|
+
SOFTWARE.
|
|
54
|
+
|
|
55
|
+
-------------------------------------------------------------------------------
|
|
56
|
+
Test data (repository only, not part of the package)
|
|
57
|
+
tests/data/*/ holds third-party test files. Each folder's README.md names the
|
|
58
|
+
source, version and license; tests/data/dstu-engine/LICENSE is Apache-2.0.
|
uakep-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: uakep
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Ukrainian qualified electronic signatures (КЕП, ДСТУ 4145-2002) in pure Python
|
|
5
|
+
Keywords: dstu4145,kep,cades,prro,ukraine
|
|
6
|
+
Author: Anton Butsyk
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: NOTICE
|
|
10
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Topic :: Security :: Cryptography
|
|
15
|
+
Requires-Dist: asn1crypto>=1.5.1
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Project-URL: Repository, https://github.com/butsyk/uakep
|
|
18
|
+
Project-URL: Changelog, https://github.com/butsyk/uakep/blob/main/CHANGELOG.md
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# uakep: Ukrainian КЕП signatures in pure Python
|
|
22
|
+
|
|
23
|
+
## What this is
|
|
24
|
+
|
|
25
|
+
uakep is a pure-Python library for Ukrainian qualified electronic signatures (КЕП) on ДСТУ 4145-2002, ported from [jkurwa](https://github.com/dstucrypt/jkurwa). Its first job is to sign documents for the State Tax Service's fiscal server (ПРРО) in the exact format that server accepts, using the key files sellers already have.
|
|
26
|
+
|
|
27
|
+
No native binaries, no Java, no Node: the goal is `pip install` and sign.
|
|
28
|
+
|
|
29
|
+
**Українською.** uakep — бібліотека на чистому Python для кваліфікованого електронного підпису за ДСТУ 4145-2002, порт бібліотеки jkurwa. Вона читатиме ключі (Key-6.dat, .jks, .pfx) і підписуватиме документи у форматі CAdES з позначкою часу, як цього вимагає фіскальний сервер ДПС для програмних РРО.
|
|
30
|
+
|
|
31
|
+
## Status
|
|
32
|
+
|
|
33
|
+
Pre-alpha. Milestone 0.1 is code-complete: uakep reads key files and certificates, and the tax service's samples and ЦЗО's examples verify in it. Milestone 0.2 is in progress: signature time-stamps are built and checked against the samples. Next is a document signed by uakep and accepted by the tax service's fiscal server in its test mode, the 0.2 gate (see the [roadmap](https://github.com/butsyk/uakep/blob/main/ROADMAP.md)).
|
|
34
|
+
|
|
35
|
+
## Why it exists
|
|
36
|
+
|
|
37
|
+
Python has no open-source library that signs the way the fiscal server requires. The tax service publishes the protocol, XSD schemas and examples, but no client library in any language ([official description](https://tax.gov.ua/baneryi/programni-rro/opis-ari-fiskalnogo-servera/opis-ari--kontrolyuyuchogo-organu/), [Markdown mirror](https://github.com/VSydorenko/prro_docs)). Sending a document is a plain HTTP POST; signing it is the hard part.
|
|
38
|
+
|
|
39
|
+
| Library | Language | Source | Notes |
|
|
40
|
+
| --- | --- | --- | --- |
|
|
41
|
+
| [IIT signing library](https://iit.com.ua/downloads) | C, with a Python interface | Closed, native binary | Its downloads page publishes no license terms |
|
|
42
|
+
| [jkurwa](https://github.com/dstucrypt/jkurwa) | JavaScript | Open, BSD | Maintained since 2014; reads Key-6.dat, .jks and .pfx; adds signature time-stamps |
|
|
43
|
+
| [ppolib](https://github.com/leon-mbs/ppolib) | PHP | Open, MIT | Port of jkurwa, written for a CRM's ПРРО |
|
|
44
|
+
| [uacrypto](https://github.com/h0rn3t/uacrypto) | Go | Open | First published in November 2026 |
|
|
45
|
+
|
|
46
|
+
A Python service today needs a native binary or a second runtime beside it. uakep removes both.
|
|
47
|
+
|
|
48
|
+
## First release: scope and non-goals
|
|
49
|
+
|
|
50
|
+
The first release does what the fiscal server needs and nothing more. The server's rules are in its [API description](https://github.com/VSydorenko/prro_docs), section «Порядок засвідчення повідомлень».
|
|
51
|
+
|
|
52
|
+
- **Keys:** read the private key from IIT Key-6.dat, PrivatBank .jks and PKCS#12 .pfx files, given the password.
|
|
53
|
+
- **Signature:** sign and verify with ДСТУ 4145-2002 on binary-field curves, named or given in the certificate, with the ГОСТ 34.311-95 hash (ДСТУ 7564 «Купина» where a certificate needs it).
|
|
54
|
+
- **Container:** build CAdES-E-T signed data with the document attached and the signer's certificate inside, and without issuer certificates, CRLs or OCSP answers.
|
|
55
|
+
- **Time-stamp:** add a signature time-stamp (RFC 3161) from the server named in the signer's certificate for online documents, and none for offline ones; never a content time-stamp, which the fiscal server refuses.
|
|
56
|
+
- **Certificates:** read owner name, РНОКПП or ЄДРПОУ, validity and key usage.
|
|
57
|
+
- **Answers:** unwrap the fiscal server's signed answers and verify its signature.
|
|
58
|
+
|
|
59
|
+
Not in the first release: encryption (EnvelopedData), XAdES, PAdES and ASiC, cloud signatures such as Дія.Підпис, hardware tokens, certificate lookup over CMP, OCSP and CRL checks, and a ПРРО client (receipt XML, shifts, offline mode).
|
|
60
|
+
|
|
61
|
+
## API
|
|
62
|
+
|
|
63
|
+
A sketch of the intended use; names and arguments may change before 1.0.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from uakep import load_key, sign, unwrap
|
|
67
|
+
|
|
68
|
+
# Key-6.dat holds no certificate, so pass the .cer file; .jks and .pfx carry their own
|
|
69
|
+
key = load_key("Key-6.dat", password="…", certificate="signer.cer")
|
|
70
|
+
|
|
71
|
+
# CAdES-E-T: document attached, signer's certificate inside, and a signature
|
|
72
|
+
# time-stamp from the server named in that certificate. uakep makes no network
|
|
73
|
+
# calls: your post(url, request) sends the request and returns the answer.
|
|
74
|
+
signed = sign(receipt_xml, key, timestamp=post)
|
|
75
|
+
# async code: signed = await sign_async(receipt_xml, key, timestamp=async_post)
|
|
76
|
+
# offline documents: sign(receipt_xml, key), without a time-stamp
|
|
77
|
+
|
|
78
|
+
# POST `signed` to https://fs.tax.gov.ua:8643/fs/doc as application/octet-stream
|
|
79
|
+
ticket_xml = unwrap(response_body) # checks the fiscal server's signature
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## How correctness is proven
|
|
83
|
+
|
|
84
|
+
Every layer is checked against ground truth that uakep did not produce, from the curve arithmetic up to the live fiscal server.
|
|
85
|
+
|
|
86
|
+
| Check | What it proves | Ground truth |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| Standard test vectors | Field and curve arithmetic, ДСТУ 4145 signatures, ГОСТ 34.311 and Купина hashes, ГОСТ 28147 match the standards | Worked examples in each standard |
|
|
89
|
+
| Official test data | Real certificates parse; real CAdES-BES, -T, -C and -X signatures verify; key files decrypt to the right keys | [ЦЗО test examples](https://czo.gov.ua/testexamples), CA certificates, key files with known passwords |
|
|
90
|
+
| The tax service's signed samples | A signed receipt, Z-report and command, as the fiscal server sees them, verify and unwrap | «Приклади з КЕП» published with the [API description](https://github.com/VSydorenko/prro_docs) |
|
|
91
|
+
| Fuzzing | Malformed key files, certificates and server answers raise clean errors, never crash or hang | Property-based and fuzz tests |
|
|
92
|
+
| Fiscal server, test mode | A whole shift (open, sale, Z-report, close) is accepted with a real key; test documents carry no tax effect | `<TESTING>true</TESTING>` documents on the live server |
|
|
93
|
+
| Real use | Keys from many CAs and real receipts, every day | Production users |
|
|
94
|
+
|
|
95
|
+
## Security and legal notes
|
|
96
|
+
|
|
97
|
+
- **Keys stay with the caller.** uakep never writes a key or password to disk or logs, and never sends either over the network.
|
|
98
|
+
- **Timing.** Python integers are not constant-time. uakep keeps jkurwa's countermeasure, a signing scalar of fixed bit length, and is meant for a server signing its own documents, not for hardware an attacker can measure.
|
|
99
|
+
- **Not a certified signature tool.** uakep has no expert conclusion from Держспецзв'язку. Nothing in the fiscal server's protocol names the software that made a signature, but whether your use needs certified software is a question for your lawyer.
|
|
100
|
+
- **Reporting a vulnerability.** Use GitHub's private vulnerability reporting on this repository, not a public issue.
|
|
101
|
+
|
|
102
|
+
## Credits, license and contributing
|
|
103
|
+
|
|
104
|
+
- **Credits.** The cryptography is ported from [jkurwa](https://github.com/dstucrypt/jkurwa) by Ilya Petrov. [ppolib](https://github.com/leon-mbs/ppolib) by leon-mbs is a second reference. The protocol notes come from the tax service's [published description](https://tax.gov.ua/baneryi/programni-rro/opis-ari-fiskalnogo-servera/opis-ari--kontrolyuyuchogo-organu/), mirrored as Markdown in [VSydorenko/prro_docs](https://github.com/VSydorenko/prro_docs).
|
|
105
|
+
- **License.** MIT for uakep's own code. Ported parts keep jkurwa's copyright notice and BSD terms, collected in NOTICE.
|
|
106
|
+
- **Contributing.** The most useful help is test data: a document signed with a key from a CA not yet covered, or one the fiscal server rejected, with its error. Never send a private key or its password. A signed document carries your certificate, so share only what you are fine making public.
|
uakep-0.1.0/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# uakep: Ukrainian КЕП signatures in pure Python
|
|
2
|
+
|
|
3
|
+
## What this is
|
|
4
|
+
|
|
5
|
+
uakep is a pure-Python library for Ukrainian qualified electronic signatures (КЕП) on ДСТУ 4145-2002, ported from [jkurwa](https://github.com/dstucrypt/jkurwa). Its first job is to sign documents for the State Tax Service's fiscal server (ПРРО) in the exact format that server accepts, using the key files sellers already have.
|
|
6
|
+
|
|
7
|
+
No native binaries, no Java, no Node: the goal is `pip install` and sign.
|
|
8
|
+
|
|
9
|
+
**Українською.** uakep — бібліотека на чистому Python для кваліфікованого електронного підпису за ДСТУ 4145-2002, порт бібліотеки jkurwa. Вона читатиме ключі (Key-6.dat, .jks, .pfx) і підписуватиме документи у форматі CAdES з позначкою часу, як цього вимагає фіскальний сервер ДПС для програмних РРО.
|
|
10
|
+
|
|
11
|
+
## Status
|
|
12
|
+
|
|
13
|
+
Pre-alpha. Milestone 0.1 is code-complete: uakep reads key files and certificates, and the tax service's samples and ЦЗО's examples verify in it. Milestone 0.2 is in progress: signature time-stamps are built and checked against the samples. Next is a document signed by uakep and accepted by the tax service's fiscal server in its test mode, the 0.2 gate (see the [roadmap](https://github.com/butsyk/uakep/blob/main/ROADMAP.md)).
|
|
14
|
+
|
|
15
|
+
## Why it exists
|
|
16
|
+
|
|
17
|
+
Python has no open-source library that signs the way the fiscal server requires. The tax service publishes the protocol, XSD schemas and examples, but no client library in any language ([official description](https://tax.gov.ua/baneryi/programni-rro/opis-ari-fiskalnogo-servera/opis-ari--kontrolyuyuchogo-organu/), [Markdown mirror](https://github.com/VSydorenko/prro_docs)). Sending a document is a plain HTTP POST; signing it is the hard part.
|
|
18
|
+
|
|
19
|
+
| Library | Language | Source | Notes |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| [IIT signing library](https://iit.com.ua/downloads) | C, with a Python interface | Closed, native binary | Its downloads page publishes no license terms |
|
|
22
|
+
| [jkurwa](https://github.com/dstucrypt/jkurwa) | JavaScript | Open, BSD | Maintained since 2014; reads Key-6.dat, .jks and .pfx; adds signature time-stamps |
|
|
23
|
+
| [ppolib](https://github.com/leon-mbs/ppolib) | PHP | Open, MIT | Port of jkurwa, written for a CRM's ПРРО |
|
|
24
|
+
| [uacrypto](https://github.com/h0rn3t/uacrypto) | Go | Open | First published in November 2026 |
|
|
25
|
+
|
|
26
|
+
A Python service today needs a native binary or a second runtime beside it. uakep removes both.
|
|
27
|
+
|
|
28
|
+
## First release: scope and non-goals
|
|
29
|
+
|
|
30
|
+
The first release does what the fiscal server needs and nothing more. The server's rules are in its [API description](https://github.com/VSydorenko/prro_docs), section «Порядок засвідчення повідомлень».
|
|
31
|
+
|
|
32
|
+
- **Keys:** read the private key from IIT Key-6.dat, PrivatBank .jks and PKCS#12 .pfx files, given the password.
|
|
33
|
+
- **Signature:** sign and verify with ДСТУ 4145-2002 on binary-field curves, named or given in the certificate, with the ГОСТ 34.311-95 hash (ДСТУ 7564 «Купина» where a certificate needs it).
|
|
34
|
+
- **Container:** build CAdES-E-T signed data with the document attached and the signer's certificate inside, and without issuer certificates, CRLs or OCSP answers.
|
|
35
|
+
- **Time-stamp:** add a signature time-stamp (RFC 3161) from the server named in the signer's certificate for online documents, and none for offline ones; never a content time-stamp, which the fiscal server refuses.
|
|
36
|
+
- **Certificates:** read owner name, РНОКПП or ЄДРПОУ, validity and key usage.
|
|
37
|
+
- **Answers:** unwrap the fiscal server's signed answers and verify its signature.
|
|
38
|
+
|
|
39
|
+
Not in the first release: encryption (EnvelopedData), XAdES, PAdES and ASiC, cloud signatures such as Дія.Підпис, hardware tokens, certificate lookup over CMP, OCSP and CRL checks, and a ПРРО client (receipt XML, shifts, offline mode).
|
|
40
|
+
|
|
41
|
+
## API
|
|
42
|
+
|
|
43
|
+
A sketch of the intended use; names and arguments may change before 1.0.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from uakep import load_key, sign, unwrap
|
|
47
|
+
|
|
48
|
+
# Key-6.dat holds no certificate, so pass the .cer file; .jks and .pfx carry their own
|
|
49
|
+
key = load_key("Key-6.dat", password="…", certificate="signer.cer")
|
|
50
|
+
|
|
51
|
+
# CAdES-E-T: document attached, signer's certificate inside, and a signature
|
|
52
|
+
# time-stamp from the server named in that certificate. uakep makes no network
|
|
53
|
+
# calls: your post(url, request) sends the request and returns the answer.
|
|
54
|
+
signed = sign(receipt_xml, key, timestamp=post)
|
|
55
|
+
# async code: signed = await sign_async(receipt_xml, key, timestamp=async_post)
|
|
56
|
+
# offline documents: sign(receipt_xml, key), without a time-stamp
|
|
57
|
+
|
|
58
|
+
# POST `signed` to https://fs.tax.gov.ua:8643/fs/doc as application/octet-stream
|
|
59
|
+
ticket_xml = unwrap(response_body) # checks the fiscal server's signature
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## How correctness is proven
|
|
63
|
+
|
|
64
|
+
Every layer is checked against ground truth that uakep did not produce, from the curve arithmetic up to the live fiscal server.
|
|
65
|
+
|
|
66
|
+
| Check | What it proves | Ground truth |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| Standard test vectors | Field and curve arithmetic, ДСТУ 4145 signatures, ГОСТ 34.311 and Купина hashes, ГОСТ 28147 match the standards | Worked examples in each standard |
|
|
69
|
+
| Official test data | Real certificates parse; real CAdES-BES, -T, -C and -X signatures verify; key files decrypt to the right keys | [ЦЗО test examples](https://czo.gov.ua/testexamples), CA certificates, key files with known passwords |
|
|
70
|
+
| The tax service's signed samples | A signed receipt, Z-report and command, as the fiscal server sees them, verify and unwrap | «Приклади з КЕП» published with the [API description](https://github.com/VSydorenko/prro_docs) |
|
|
71
|
+
| Fuzzing | Malformed key files, certificates and server answers raise clean errors, never crash or hang | Property-based and fuzz tests |
|
|
72
|
+
| Fiscal server, test mode | A whole shift (open, sale, Z-report, close) is accepted with a real key; test documents carry no tax effect | `<TESTING>true</TESTING>` documents on the live server |
|
|
73
|
+
| Real use | Keys from many CAs and real receipts, every day | Production users |
|
|
74
|
+
|
|
75
|
+
## Security and legal notes
|
|
76
|
+
|
|
77
|
+
- **Keys stay with the caller.** uakep never writes a key or password to disk or logs, and never sends either over the network.
|
|
78
|
+
- **Timing.** Python integers are not constant-time. uakep keeps jkurwa's countermeasure, a signing scalar of fixed bit length, and is meant for a server signing its own documents, not for hardware an attacker can measure.
|
|
79
|
+
- **Not a certified signature tool.** uakep has no expert conclusion from Держспецзв'язку. Nothing in the fiscal server's protocol names the software that made a signature, but whether your use needs certified software is a question for your lawyer.
|
|
80
|
+
- **Reporting a vulnerability.** Use GitHub's private vulnerability reporting on this repository, not a public issue.
|
|
81
|
+
|
|
82
|
+
## Credits, license and contributing
|
|
83
|
+
|
|
84
|
+
- **Credits.** The cryptography is ported from [jkurwa](https://github.com/dstucrypt/jkurwa) by Ilya Petrov. [ppolib](https://github.com/leon-mbs/ppolib) by leon-mbs is a second reference. The protocol notes come from the tax service's [published description](https://tax.gov.ua/baneryi/programni-rro/opis-ari-fiskalnogo-servera/opis-ari--kontrolyuyuchogo-organu/), mirrored as Markdown in [VSydorenko/prro_docs](https://github.com/VSydorenko/prro_docs).
|
|
85
|
+
- **License.** MIT for uakep's own code. Ported parts keep jkurwa's copyright notice and BSD terms, collected in NOTICE.
|
|
86
|
+
- **Contributing.** The most useful help is test data: a document signed with a key from a CA not yet covered, or one the fiscal server rejected, with its error. Never send a private key or its password. A signed document carries your certificate, so share only what you are fine making public.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "uakep"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Ukrainian qualified electronic signatures (КЕП, ДСТУ 4145-2002) in pure Python"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = [
|
|
8
|
+
"LICENSE",
|
|
9
|
+
"NOTICE",
|
|
10
|
+
]
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
dependencies = ["asn1crypto>=1.5.1"]
|
|
13
|
+
keywords = [
|
|
14
|
+
"dstu4145",
|
|
15
|
+
"kep",
|
|
16
|
+
"cades",
|
|
17
|
+
"prro",
|
|
18
|
+
"ukraine",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
25
|
+
"Topic :: Security :: Cryptography",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[[project.authors]]
|
|
29
|
+
name = "Anton Butsyk"
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Repository = "https://github.com/butsyk/uakep"
|
|
33
|
+
Changelog = "https://github.com/butsyk/uakep/blob/main/CHANGELOG.md"
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["uv_build>=0.13.7,<0.14.0"]
|
|
37
|
+
build-backend = "uv_build"
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = [
|
|
41
|
+
"pytest>=8",
|
|
42
|
+
"pytest-cov>=6",
|
|
43
|
+
"ruff>=0.14",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 100
|
|
48
|
+
target-version = "py311"
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
select = [
|
|
52
|
+
"E",
|
|
53
|
+
"F",
|
|
54
|
+
"W",
|
|
55
|
+
"I",
|
|
56
|
+
"B",
|
|
57
|
+
"UP",
|
|
58
|
+
"SIM",
|
|
59
|
+
"RUF",
|
|
60
|
+
]
|
|
61
|
+
ignore = [
|
|
62
|
+
"RUF001",
|
|
63
|
+
"RUF002",
|
|
64
|
+
"RUF003",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint.per-file-ignores]
|
|
68
|
+
"src/uakep/asn1.py" = ["RUF012"]
|
|
69
|
+
|
|
70
|
+
[tool.pytest.ini_options]
|
|
71
|
+
testpaths = ["tests"]
|
|
72
|
+
addopts = "-ra"
|
|
73
|
+
markers = [
|
|
74
|
+
"interop: compatibility cross-checks with the JavaScript peer in interop/",
|
|
75
|
+
"slow: long-running randomized tests",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[tool.coverage.run]
|
|
79
|
+
source = ["uakep"]
|
|
80
|
+
branch = true
|
|
81
|
+
|
|
82
|
+
[tool.coverage.report]
|
|
83
|
+
show_missing = true
|
|
84
|
+
skip_covered = false
|
|
85
|
+
exclude_also = [
|
|
86
|
+
"if TYPE_CHECKING:",
|
|
87
|
+
"raise NotImplementedError",
|
|
88
|
+
]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "uakep"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Ukrainian qualified electronic signatures (КЕП, ДСТУ 4145-2002) in pure Python"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE", "NOTICE"]
|
|
8
|
+
authors = [{ name = "Anton Butsyk" }]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = ["asn1crypto>=1.5.1"]
|
|
11
|
+
keywords = ["dstu4145", "kep", "cades", "prro", "ukraine"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Topic :: Security :: Cryptography",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Repository = "https://github.com/butsyk/uakep"
|
|
22
|
+
Changelog = "https://github.com/butsyk/uakep/blob/main/CHANGELOG.md"
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["uv_build>=0.13.7,<0.14.0"]
|
|
26
|
+
build-backend = "uv_build"
|
|
27
|
+
|
|
28
|
+
[dependency-groups]
|
|
29
|
+
dev = ["pytest>=8", "pytest-cov>=6", "ruff>=0.14"]
|
|
30
|
+
|
|
31
|
+
[tool.ruff]
|
|
32
|
+
line-length = 100
|
|
33
|
+
target-version = "py311"
|
|
34
|
+
|
|
35
|
+
[tool.ruff.lint]
|
|
36
|
+
select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF"]
|
|
37
|
+
# Ukrainian text in docstrings and messages is intentional.
|
|
38
|
+
ignore = ["RUF001", "RUF002", "RUF003"]
|
|
39
|
+
|
|
40
|
+
[tool.ruff.lint.per-file-ignores]
|
|
41
|
+
# asn1crypto declares structures as class-level lists.
|
|
42
|
+
"src/uakep/asn1.py" = ["RUF012"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
addopts = "-ra"
|
|
47
|
+
markers = [
|
|
48
|
+
"interop: compatibility cross-checks with the JavaScript peer in interop/",
|
|
49
|
+
"slow: long-running randomized tests",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[tool.coverage.run]
|
|
53
|
+
source = ["uakep"]
|
|
54
|
+
branch = true
|
|
55
|
+
|
|
56
|
+
[tool.coverage.report]
|
|
57
|
+
show_missing = true
|
|
58
|
+
skip_covered = false
|
|
59
|
+
exclude_also = ["if TYPE_CHECKING:", "raise NotImplementedError"]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""uakep: Ukrainian qualified electronic signatures (КЕП, ДСТУ 4145-2002) in pure Python.
|
|
2
|
+
|
|
3
|
+
from uakep import load_key, sign, unwrap
|
|
4
|
+
|
|
5
|
+
key = load_key("Key-6.dat", password="…", certificate="signer.cer")
|
|
6
|
+
signed = sign(receipt_xml_cp1251, key, timestamp=post) # CAdES with a signature time-stamp
|
|
7
|
+
content = unwrap(signed) # checks the signature, returns the data
|
|
8
|
+
|
|
9
|
+
``post(url, request) -> bytes`` is yours: it POSTs the time-stamp request to the TSA named in
|
|
10
|
+
the signer's certificate. Async callers use ``await sign_async(..., timestamp=async_post)``.
|
|
11
|
+
Offline documents are signed without ``timestamp``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
15
|
+
|
|
16
|
+
from .asn1 import UnsupportedError
|
|
17
|
+
from .cades import SignatureError, SigningKey, Verified, sign, sign_async, verify
|
|
18
|
+
from .certificate import Certificate, CertificateError
|
|
19
|
+
from .keystore import KeyFileError, WrongPasswordError, load_key
|
|
20
|
+
from .tsp import TimeStamp, TimeStampError
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
__version__ = version("uakep")
|
|
24
|
+
except PackageNotFoundError: # running from a source tree that is not installed
|
|
25
|
+
__version__ = "0+unknown"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def unwrap(signed: bytes, *, detached_content: bytes | None = None) -> bytes:
|
|
29
|
+
"""Check the ДСТУ 4145 signature on CAdES signed data and return the signed data.
|
|
30
|
+
|
|
31
|
+
Raises SignatureError when the signature, or a signature time-stamp, does not verify.
|
|
32
|
+
Certificate chains and revocation are not checked.
|
|
33
|
+
"""
|
|
34
|
+
return verify(signed, detached_content=detached_content).content
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"Certificate",
|
|
39
|
+
"CertificateError",
|
|
40
|
+
"KeyFileError",
|
|
41
|
+
"SignatureError",
|
|
42
|
+
"SigningKey",
|
|
43
|
+
"TimeStamp",
|
|
44
|
+
"TimeStampError",
|
|
45
|
+
"UnsupportedError",
|
|
46
|
+
"Verified",
|
|
47
|
+
"WrongPasswordError",
|
|
48
|
+
"__version__",
|
|
49
|
+
"load_key",
|
|
50
|
+
"sign",
|
|
51
|
+
"sign_async",
|
|
52
|
+
"unwrap",
|
|
53
|
+
"verify",
|
|
54
|
+
]
|