# pyasice - ASiC-E (BDOC) and XAdES Manipulation Library
[![pypi Package](https://badge.fury.io/py/pyasice.png)](https://badge.fury.io/py/pyasice)
[![Build Status](https://app.travis-ci.com/thorgate/pyasice.svg?branch=main)](https://app.travis-ci.com/thorgate/pyasice)
[![Coverage Status](https://coveralls.io/repos/github/thorgate/pyasice/badge.svg?branch=main)](https://coveralls.io/github/thorgate/pyasice?branch=main)
The `pyasice` library is designed to:
* create, read, and verify XAdES/XMLDsig/eIDAS electronic signatures,
* validate signers' certificates with OCSP,
* confirm these signatures with TimeStamping,
* create and manipulate [ASiC-E](https://en.wikipedia.org/wiki/Associated_Signature_Containers) or BDoc 2.1 containers,
which are based on the XAdES/eIDAS stack.
## Contents
* [Quickstart](#quickstart)
* * [ASiC-E/BDOC Container File Manipulation](#asic-ebdoc-container-file-manipulation)
* * [Signing Flow Utilities](#signing-flow-utilities)
* [Normative References](#normative-references)
* [Module Layout](#module-layout)
* [Technology Stack](#technology-stack)
* [Build the XAdES XML Signature meta-file](#build-the-xades-xml-signature-meta-file)
* * [SignedInfo](#signedinfo)
* * [SignatureValue](#signaturevalue)
* * [KeyInfo](#keyinfo)
* * [SignedProperties](#signedproperties)
* [Secondary Services](#secondary-services)
* * [OCSP](#ocsp)
* * [Timestamping Service](#timestamping-service)
## Quickstart
### ASiC-E/BDOC Container File Manipulation
Create a new container:
```python
from pyasice import Container, XmlSignature
xmlsig = XmlSignature.create().add_document('test.txt', b'Test data', 'application/pdf')
# ... here goes the signing, confirming and timestamping part ...
container = Container()
container\
.add_file('test.txt', b'Test data', 'application/pdf')\
.add_signature(xmlsig)\
.save('test.asice')
# container is a context manager:
with Container() as container:
container.add_file('a', b'b', 'c').save('path/to')
# Open an existing container:
container = Container.open('test.asice')
# Verify container. Raises pyasice.SignatureVerificationError on failure
container.verify_signatures()
# Read files in the container
with container.open_file('test.txt') as f:
assert f.read() == b'Test data'
# Iterate over signatures
for xmlsig in container.iter_signatures():
xmlsig.get_signing_time()
```
### Signing Flow Utilities
```python
from pyasice import Container, finalize_signature
# get this from an external service, ID card, or elsewhere
user_certificate = b'user certificate in DER/PEM format'
container = Container()
container.add_file("test.txt", b'Test', "text/plain")
xml_sig = container.prepare_signature(user_certificate)
# Use an external service, or ID card, or a private key from elsewhere
# to sign the XML signature structure
signature_value = externally.sign(xml_sig.signed_data())
xml_sig.set_signature_value(signature_value)
# Get issuer certificate from the ID service provider, e.g. sk.ee.
# Here we use the user certificate's `issuer.common_name` field to identify the issuer cert,
# and find the cert in the `esteid-certificates` PyPI package.
issuer_cert_name = xml_sig.get_certificate_issuer_common_name()
import esteid_certificates
issuer_certificate = esteid_certificates.get_certificate(issuer_cert_name)
# Complete the XML signature with OCSP and optionally Timestamping
finalize_signature(xml_sig, ocsp_url="https://ocsp.server.url", tsa_url="https://tsa.server.url")
container.add_signature(xml_sig)
container.save("path/to/file.asice")
```
## Normative References
The main document this library is based on:
the [BDOC 2.1.2 spec](https://www.id.ee/wp-content/uploads/2020/06/bdoc-spec212-eng.pdf).
The specific standards outlined in that document:
* [ETSI TS 101 903 v1.4.2](https://www.etsi.org/deliver/etsi_ts/101900_101999/101903/01.04.02_60/ts_101903v010402p.pdf)
– XML Advanced Electronic Signatures (XAdES) and its Baseline Profile ETSI TS 103 171;
* ITU-T Recommendation X.509;
* [RFC 3161](https://tools.ietf.org/html/rfc3161) – PKIX Time-Stamp protocol;
* [RFC 6960](https://tools.ietf.org/html/rfc6960) – Online Certificate Status Protocol;
* ETSI TS 102 918 v1.2.1 - Associated Signature Containers (ASiC) and its
Baseline Profile ETSI TS 103 174.
The difference between ASiC-E and BDOC is almost exclusively in terminology.
The [BDOC 2.1.2 spec](https://www.id.ee/wp-content/uploads/2020/06/bdoc-spec212-eng.pdf) states:
> The BDOC file format is based on ASiC standard which is in turn profiled by ASiC BP.
> BDOC packaging is a ASiC-E XAdES type ZIP container ...
So with a moderate risk of confusion, we can accept that ASiC-E and BDOC refer to the same thing.
## Module Layout
* [container.py](container.py) -- the `Container` class, that deals with ASiC-E (BDOC v.2.1) container format
* [xmlsig.py](xmlsig.py) -- the `XmlSignature` class, that deals with XAdES/XMLDSig XML structures
* [ocsp.py](ocsp.py) -- the `OCSP` class that deals with OCSP requests and responses
* [tsa.py](tsa.py) -- the `TSA` class that deals with TimeStamping service requests and responses
* [signature_verifier.py](signature_verifier.py) -- the `verify` function, to verify signatures against a certificate.
## Technology Stack
Dealing with the subject involves, at least:
* public key cryptography (RSA, ECDSA);
* ASN.1 encoding;
* XML processing;
* Zip archives;
* and also requests to various services (obtaining signer's certificate and the signature,
validating the certificate through OCSP, time-stamping the signature).
The [asn1crypto](https://github.com/wbond/asn1crypto) library and its higher-level complement
[oscrypto](https://github.com/wbond/oscrypto)
allow handling certificates and ASN.1 structures quite easily.
The [cryptography](https://cryptography.io/en/latest) library is by far the most powerful python library
for dealing with public key cryptography algorithms.
## Build the XAdES XML Signature meta-file
The structure of the XAdES XML signature file looks like this:
```xml
<asic:XAdESSignatures xmlns:asic="http://uri.etsi.org/02918/v1.2.1#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:xades="http://uri.etsi.org/01903/v1.3.2#">
<ds:Signature Id="S0">
<ds:SignedInfo Id="S0-SignedInfo">...</ds:SignedInfo>
<ds:SignatureValue Id="S0-SIG">...</ds:SignatureValue>
<ds:KeyInfo Id="S0-KeyInfo">...</ds:KeyInfo>
<ds:Object Id="S0-object-xades">
<xades:QualifyingProperties Id="S0-QualifyingProperties" Target="#S0">
<xades:SignedProperties Id="S0-SignedProperties">
<xades:SignedSignatureProperties Id="S0-SignedSignatureProperties">
<xades:SigningTime>2019-06-07T14:03:50Z</xades:SigningTime>
<xades:SigningCertificate>...</xades:SigningCertificate>
<xades:SignaturePolicyIdentifer>...</xades:SignaturePolicyIdentifer>
</xades:SignedSignatureProperties>
</xades:SignedProperties>
</xades:QualifyingProperties>
</ds:Object>
</ds:Signature>
</asic:XAdESSignatures>
```
We'll go over each section below.
* [SignedInfo](#signedinfo)
* [SignatureValue](#signaturevalue)
* [KeyInfo](#keyinfo)
* [SignedProperties](#signedproperties)
### SignedInfo
The `SignedInfo` node is the source of the data being signed. The XML content of the node, canonicalized
using the `CanonicalizationMethod` as per the respective child node, is hashed using an algorithm defined in
the `SignatureMethod` child node, and this hash is fed to a signing service (ID card, SmartID etc.)
```xml
<ds:SignedInfo Id="S0-SignedInfo">
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2006/12/xml-c14n11"></ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"></ds:SignatureMethod>
<ds:Reference Id="S0-ref-0" URI="test.pdf">
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod>
<ds:DigestValue>...</ds:DigestValue>
</ds:Reference>
<ds:Reference Id="S0-ref-sp" Type="http://uri.etsi.org/01903#SignedProperties" URI="#S0-SignedProperties">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod>
<ds:DigestValue>...</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
```
The `Reference` fields are different in purpose and formation.
The first `Reference` field is about the signed document and as such, has an `URI` attribute of the document's file name.
Its child `DigestValue` element is the SHA256 hash of the document, it is, incidentally, the very hash that is sent to the SmartID API for signing.
The second `Reference` is built on the basis of some fields defined later in the [SignedProperties](#SignedProperties) section.
Its child `DigestValue` is calculated as a SHA256 hash of the canonicalized XML output of the `SignedProperties` tag, after that one is formed:
The `URI` attribute of this `Reference` tag is the `#`-prefixed `Id` attribute of the `SignedProperties` tag.
```python
import base64
import hashlib
from lxml import etree
buf = etree.tostring(el, method='c14n', exclusive=True or False) # NOTE below
digest_value = base64.b64encode(hashlib.sha256(buf).digest())
```
(Assuming the `el` here to be the XML `<SignedProperties>` element)
#### Canonicalization
The `exclusive` kwarg controls whether the namespace declarations of ancestor tags should be included in the resulting canonical representation, or _excluded_.
Whether to use `exclusive=True` depends on the canonicalization tag's `Algorithm` attribute:
* `http://www.w3.org/2001/10/xml-exc-c14n#`, uses `exclusive=True`,
* the two others, the required `http://www.w3.org/TR/2001/REC-xml-c14n-20010315`, or `http://www.w3.org/2006/12/xml-c14n11`, are not exclusive.
The aforementioned `<ds:CanonicalizationMethod>` tag controls the c14n of the `SignedInfo` node before feeding its digest to the signature service.
The c14n of `SignedProperties` prior to getting its digest is determined by the `ds:Transform` tag within this `ds:Reference` node.
If it's not present, then the default, ie. not exclusive, c14n is used.
### KeyInfo
This section contains the base64-encoded user certificate value, e.g. the SmartID API response's `cert.value`,
or the certificate obtained from an ID card:
```xml
<ds:KeyInfo Id="S0-KeyInfo">
<ds:X509Data>
<ds:X509Certificate>MIIGJDCCBAygAwIBAgIQBNsLtTIpnmNbbE4+laSLaTANBgkqhkiG9w0BAQsFADBr...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
```
More details about the certificate in the [SigningCertificate](#SigningCertificate) subsection.
### SignedProperties
The XML section of `SignedProperties` consists of, [at least](https://www.w3.org/TR/XAdES/#IDAEAD1B),
the `SigningTime`, `SigningCertificate` and `SignaturePolicyIdentifer` elements.
:question: The signatures returned by e.g. [Dokobit](https://dokobit.ee),
do not contain the `SignaturePolicyIdentifer` node.
#### SigningTime
A timestamp in ISO 8601 format.
#### SignaturePolicyIdentifier
This appears to be a static^1 XML chunk referencing the BDOC 2.1 Specifications document:
```xml
<xades:SignaturePolicyIdentifier>
<xades:SignaturePolicyId>
<xades:SigPolicyId>
<xades:Identifier Qualifier="OIDAsURN">urn:oid:1.3.6.1.4.1.10015.1000.3.2.1</xades:Identifier>
</xades:SigPolicyId>
<xades:SigPolicyHash>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256">
</ds:DigestMethod>
<ds:DigestValue>3Tl1oILSvOAWomdI9VeWV6IA/32eSXRUri9kPEz1IVs=</ds:DigestValue>
</xades:SigPolicyHash>
<xades:SigPolicyQualifiers>
<xades:SigPolicyQualifier>
<xades:SPURI>https://www.sk.ee/repository/bdoc-spec21.pdf</xades:SPURI>
</xades:SigPolicyQualifier>
</xades:SigPolicyQualifiers>
</xades:SignaturePolicyId>
</xades:SignaturePolicyIdentifier>
```
[1] The DigestValue is the hash value of the document referenced by `SPURI`, encoded in base64.
Refer to [BDOC 2.1:2014 Specification](https://www.id.ee/public/bdoc-spec212-eng.pdf) for more information.
#### SigningCertificate
The user certificate is a base64-encoded DER certificate which can be loaded as follows:
```python
import base64
from cryptography import x509
from cryptography.hazmat.backends import default_backend
cert_asn1 = base64.b64decode(cert_value)
cert = x509.load_der_x509_certificate(base64.b64decode(cert_asn1), default_backend())
```
or with `pyopenssl`:
```python
import base64
from OpenSSL.crypto import load_certificate, FILETYPE_ASN1
cert_asn1 = base64.b64decode(cert_value)
openssl_cert = load_certificate(FILETYPE_ASN1, base64.b64decode(cert_asn1))
```
These objects expose a slightly different but similar API.
What we need is the issuer name and certificate serial number:
```python
assert openssl_cert.get_serial_number() == cert.sertial_number == '6454262457486410408874311107672836969'
assert cert.issuer.rfc4514_string() == 'C=EE,O=AS Sertifitseerimiskeskus,2.5.4.97=NTREE-10747013,CN=TEST of ESTEID-SK 2015'
assert openssl_cert.issuer.get_components() == [(b'C', b'EE'), (b'O', b'AS Sertifitseerimiskeskus'), (b'organizationIdentifier', b'NTREE-10747013'), (b'CN', b'ESTEID-SK 2015')]
```
Also we need a SHA256 digest value of the certificate:
```python
cert_digest = base64.b64encode(hashlib.sha256(cert_asn1).digest())
```
With these values we can build the certificate information entry of the SignedProperties:
```xml
<xades:SigningCertificate>
<xades:Cert>
<xades:CertDigest>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod>
<ds:DigestValue>hdsLTm4aaFKaGMwF6fvH5vWmiMBBnTCH3kba+TjY+pE=</ds:DigestValue>
</xades:CertDigest>
<xades:IssuerSerial>
<ds:X509IssuerName>C=EE,O=AS Sertifitseerimiskeskus,2.5.4.97=NTREE-10747013,CN=TEST of EID-SK 2016</ds:X509IssuerName>
<ds:X509SerialNumber>98652662091042892833248946646759285960</ds:X509SerialNumber>
</xades:IssuerSerial>
</xades:Cert>
</xades:SigningCertificate>
```
:question: Does `X509IssuerName` content need to be a `cert.issuer.rfc4514_string()` or can it be anything else?
So, in the end, we get a `<xades:SignedProperties>` element which we then canonicalize and calculate a sha256 hash of this string,
to place it in the appropriate `<ds:Reference>` element.
### SignatureValue
```xml
<ds:SignatureValue Id="SIG-{SIGNATURE_ID}"><!-- Base64-encoded SIGNATURE_VALUE, gotten externally --></ds:SignatureValue>
```
A base64-encoded value of the signature calculated over the signed data.
The signed data is the `ds:SignedInfo` section, as [described above](#signedinfo).
When using SmartID/MobileID, this is taken from the `signature.value` field of the response.
### KeyInfo
Contains the base64-encoded certificate, as gotten from the SmartID response.
```xml
<ds:KeyInfo Id="S0-KeyInfo">
<ds:X509Data>
<ds:X509Certificate>...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
```
## Secondary Services
### OCSP
OCSP ([Online Certificate Status Protocol](https://en.wikipedia.org/wiki/Online_Certificate_Status_Protocol))
is designed to check that the signing certificate is valid at the point of signing. It is a binary protocol, and uses ASN.1 encoding in both request and response payload.
To deal with it, we're using the `asn1crypto` library.
The OCSP request should be made immediately after signing, and the base64-encoded response is embedded in the XAdES signature as a `xades:UnsignedSignatureProperties` descendant node,
namely `xades:EncapsulatedOCSPValue`.
#### Estonian eID
URLs for OCSP services:
* Demo: `http://demo.sk.ee/ocsp`
* Production: `http://ocsp.sk.ee/`
More detail on the [sk.ee OCSP page](https://www.sk.ee/en/services/validity-confirmation-services/technical-information/)
### Timestamping service
The [TimeStamp protocol](https://en.wikipedia.org/wiki/Time_stamp_protocol) is also a binary protocol, for getting a Long-Term Validity Timestamp for a signature.
Also handled with the help of the `asn1crypto` library.
The TSA request should be made immediately after OCSP validity confirmation, and the base64-encoded response is embedded in the XAdES signature as a `xades:UnsignedSignatureProperties` descendant node,
namely `xades:EncapsulatedTimeStamp`.
#### Estonian eID
URLs for timestamping services:
* Demo: `http://demo.sk.ee/tsa/`
* Production: `http://tsa.sk.ee`
More detail on the [sk.ee TSA page](https://www.sk.ee/en/services/time-stamping-service/)
Raw data
{
"_id": null,
"home_page": "https://github.com/thorgate/pyasice",
"name": "pyasice",
"maintainer": "Jyrno Ader",
"docs_url": null,
"requires_python": ">=3.6,<4.0",
"maintainer_email": "jyrno42@gmail.com",
"keywords": "esteid,asice,smartid,mobile-id,idcard",
"author": "Thorgate",
"author_email": "info@thorgate.eu",
"download_url": "https://files.pythonhosted.org/packages/0b/b9/92619402d76c4f912f65f3e7236eaee5ccc4dfcc8d04cb4fb81559004e60/pyasice-1.1.0.tar.gz",
"platform": null,
"description": "# pyasice - ASiC-E (BDOC) and XAdES Manipulation Library\n\n[![pypi Package](https://badge.fury.io/py/pyasice.png)](https://badge.fury.io/py/pyasice)\n[![Build Status](https://app.travis-ci.com/thorgate/pyasice.svg?branch=main)](https://app.travis-ci.com/thorgate/pyasice)\n[![Coverage Status](https://coveralls.io/repos/github/thorgate/pyasice/badge.svg?branch=main)](https://coveralls.io/github/thorgate/pyasice?branch=main)\n\nThe `pyasice` library is designed to:\n* create, read, and verify XAdES/XMLDsig/eIDAS electronic signatures,\n* validate signers' certificates with OCSP,\n* confirm these signatures with TimeStamping, \n* create and manipulate [ASiC-E](https://en.wikipedia.org/wiki/Associated_Signature_Containers) or BDoc 2.1 containers, \nwhich are based on the XAdES/eIDAS stack. \n\n## Contents\n\n* [Quickstart](#quickstart)\n* * [ASiC-E/BDOC Container File Manipulation](#asic-ebdoc-container-file-manipulation)\n* * [Signing Flow Utilities](#signing-flow-utilities)\n* [Normative References](#normative-references)\n* [Module Layout](#module-layout)\n* [Technology Stack](#technology-stack)\n* [Build the XAdES XML Signature meta-file](#build-the-xades-xml-signature-meta-file)\n* * [SignedInfo](#signedinfo)\n* * [SignatureValue](#signaturevalue)\n* * [KeyInfo](#keyinfo)\n* * [SignedProperties](#signedproperties)\n* [Secondary Services](#secondary-services)\n* * [OCSP](#ocsp)\n* * [Timestamping Service](#timestamping-service)\n\n## Quickstart\n\n### ASiC-E/BDOC Container File Manipulation\n\nCreate a new container:\n```python\nfrom pyasice import Container, XmlSignature\n\nxmlsig = XmlSignature.create().add_document('test.txt', b'Test data', 'application/pdf')\n# ... here goes the signing, confirming and timestamping part ... \n\ncontainer = Container()\ncontainer\\\n .add_file('test.txt', b'Test data', 'application/pdf')\\\n .add_signature(xmlsig)\\\n .save('test.asice')\n\n# container is a context manager:\nwith Container() as container:\n container.add_file('a', b'b', 'c').save('path/to')\n\n# Open an existing container:\ncontainer = Container.open('test.asice')\n\n# Verify container. Raises pyasice.SignatureVerificationError on failure\ncontainer.verify_signatures()\n\n# Read files in the container\nwith container.open_file('test.txt') as f:\n assert f.read() == b'Test data'\n\n# Iterate over signatures\nfor xmlsig in container.iter_signatures():\n xmlsig.get_signing_time()\n```\n\n### Signing Flow Utilities\n\n```python\nfrom pyasice import Container, finalize_signature\n\n# get this from an external service, ID card, or elsewhere\nuser_certificate = b'user certificate in DER/PEM format'\n\ncontainer = Container()\ncontainer.add_file(\"test.txt\", b'Test', \"text/plain\")\n\nxml_sig = container.prepare_signature(user_certificate)\n\n# Use an external service, or ID card, or a private key from elsewhere\n# to sign the XML signature structure\nsignature_value = externally.sign(xml_sig.signed_data())\nxml_sig.set_signature_value(signature_value)\n\n# Get issuer certificate from the ID service provider, e.g. sk.ee. \n# Here we use the user certificate's `issuer.common_name` field to identify the issuer cert,\n# and find the cert in the `esteid-certificates` PyPI package. \nissuer_cert_name = xml_sig.get_certificate_issuer_common_name()\nimport esteid_certificates\nissuer_certificate = esteid_certificates.get_certificate(issuer_cert_name)\n\n# Complete the XML signature with OCSP and optionally Timestamping\nfinalize_signature(xml_sig, ocsp_url=\"https://ocsp.server.url\", tsa_url=\"https://tsa.server.url\")\n\ncontainer.add_signature(xml_sig)\n\ncontainer.save(\"path/to/file.asice\")\n```\n\n\n## Normative References\n\nThe main document this library is based on:\nthe [BDOC 2.1.2 spec](https://www.id.ee/wp-content/uploads/2020/06/bdoc-spec212-eng.pdf).\n\nThe specific standards outlined in that document:\n\n* [ETSI TS 101 903 v1.4.2](https://www.etsi.org/deliver/etsi_ts/101900_101999/101903/01.04.02_60/ts_101903v010402p.pdf) \n \u2013 XML Advanced Electronic Signatures (XAdES) and its Baseline Profile ETSI TS 103 171;\n* ITU-T Recommendation X.509;\n* [RFC 3161](https://tools.ietf.org/html/rfc3161) \u2013 PKIX Time-Stamp protocol;\n* [RFC 6960](https://tools.ietf.org/html/rfc6960) \u2013 Online Certificate Status Protocol;\n* ETSI TS 102 918 v1.2.1 - Associated Signature Containers (ASiC) and its\n Baseline Profile ETSI TS 103 174.\n\nThe difference between ASiC-E and BDOC is almost exclusively in terminology.\n\nThe [BDOC 2.1.2 spec](https://www.id.ee/wp-content/uploads/2020/06/bdoc-spec212-eng.pdf) states:\n\n> The BDOC file format is based on ASiC standard which is in turn profiled by ASiC BP.\n> BDOC packaging is a ASiC-E XAdES type ZIP container ...\n\nSo with a moderate risk of confusion, we can accept that ASiC-E and BDOC refer to the same thing.\n\n## Module Layout\n\n* [container.py](container.py) -- the `Container` class, that deals with ASiC-E (BDOC v.2.1) container format \n* [xmlsig.py](xmlsig.py) -- the `XmlSignature` class, that deals with XAdES/XMLDSig XML structures\n* [ocsp.py](ocsp.py) -- the `OCSP` class that deals with OCSP requests and responses\n* [tsa.py](tsa.py) -- the `TSA` class that deals with TimeStamping service requests and responses\n* [signature_verifier.py](signature_verifier.py) -- the `verify` function, to verify signatures against a certificate.\n\n## Technology Stack\n\nDealing with the subject involves, at least:\n* public key cryptography (RSA, ECDSA);\n* ASN.1 encoding;\n* XML processing;\n* Zip archives;\n* and also requests to various services (obtaining signer's certificate and the signature,\n validating the certificate through OCSP, time-stamping the signature).\n\nThe [asn1crypto](https://github.com/wbond/asn1crypto) library and its higher-level complement \n[oscrypto](https://github.com/wbond/oscrypto)\nallow handling certificates and ASN.1 structures quite easily.\n\nThe [cryptography](https://cryptography.io/en/latest) library is by far the most powerful python library \nfor dealing with public key cryptography algorithms.\n\n\n## Build the XAdES XML Signature meta-file\n\nThe structure of the XAdES XML signature file looks like this:\n```xml\n<asic:XAdESSignatures xmlns:asic=\"http://uri.etsi.org/02918/v1.2.1#\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"\n xmlns:xades=\"http://uri.etsi.org/01903/v1.3.2#\">\n <ds:Signature Id=\"S0\">\n <ds:SignedInfo Id=\"S0-SignedInfo\">...</ds:SignedInfo>\n <ds:SignatureValue Id=\"S0-SIG\">...</ds:SignatureValue>\n <ds:KeyInfo Id=\"S0-KeyInfo\">...</ds:KeyInfo>\n <ds:Object Id=\"S0-object-xades\">\n <xades:QualifyingProperties Id=\"S0-QualifyingProperties\" Target=\"#S0\">\n <xades:SignedProperties Id=\"S0-SignedProperties\">\n <xades:SignedSignatureProperties Id=\"S0-SignedSignatureProperties\">\n <xades:SigningTime>2019-06-07T14:03:50Z</xades:SigningTime>\n <xades:SigningCertificate>...</xades:SigningCertificate>\n <xades:SignaturePolicyIdentifer>...</xades:SignaturePolicyIdentifer>\n </xades:SignedSignatureProperties>\n </xades:SignedProperties>\n </xades:QualifyingProperties>\n </ds:Object>\n </ds:Signature>\n</asic:XAdESSignatures>\n```\n\nWe'll go over each section below.\n\n* [SignedInfo](#signedinfo)\n* [SignatureValue](#signaturevalue)\n* [KeyInfo](#keyinfo)\n* [SignedProperties](#signedproperties)\n\n### SignedInfo\n\nThe `SignedInfo` node is the source of the data being signed. The XML content of the node, canonicalized\nusing the `CanonicalizationMethod` as per the respective child node, is hashed using an algorithm defined in\nthe `SignatureMethod` child node, and this hash is fed to a signing service (ID card, SmartID etc.) \n\n```xml\n<ds:SignedInfo Id=\"S0-SignedInfo\">\n <ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2006/12/xml-c14n11\"></ds:CanonicalizationMethod>\n <ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256\"></ds:SignatureMethod>\n <ds:Reference Id=\"S0-ref-0\" URI=\"test.pdf\">\n <ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"></ds:DigestMethod>\n <ds:DigestValue>...</ds:DigestValue>\n </ds:Reference>\n <ds:Reference Id=\"S0-ref-sp\" Type=\"http://uri.etsi.org/01903#SignedProperties\" URI=\"#S0-SignedProperties\">\n <ds:Transforms>\n <ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n </ds:Transforms>\n <ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"></ds:DigestMethod>\n <ds:DigestValue>...</ds:DigestValue>\n </ds:Reference>\n</ds:SignedInfo>\n```\n\nThe `Reference` fields are different in purpose and formation.\n\nThe first `Reference` field is about the signed document and as such, has an `URI` attribute of the document's file name.\nIts child `DigestValue` element is the SHA256 hash of the document, it is, incidentally, the very hash that is sent to the SmartID API for signing.\n\nThe second `Reference` is built on the basis of some fields defined later in the [SignedProperties](#SignedProperties) section.\nIts child `DigestValue` is calculated as a SHA256 hash of the canonicalized XML output of the `SignedProperties` tag, after that one is formed:\nThe `URI` attribute of this `Reference` tag is the `#`-prefixed `Id` attribute of the `SignedProperties` tag.\n\n```python\nimport base64\nimport hashlib\nfrom lxml import etree\n\nbuf = etree.tostring(el, method='c14n', exclusive=True or False) # NOTE below\ndigest_value = base64.b64encode(hashlib.sha256(buf).digest())\n```\n(Assuming the `el` here to be the XML `<SignedProperties>` element)\n\n#### Canonicalization\n\nThe `exclusive` kwarg controls whether the namespace declarations of ancestor tags should be included in the resulting canonical representation, or _excluded_.\nWhether to use `exclusive=True` depends on the canonicalization tag's `Algorithm` attribute: \n* `http://www.w3.org/2001/10/xml-exc-c14n#`, uses `exclusive=True`, \n* the two others, the required `http://www.w3.org/TR/2001/REC-xml-c14n-20010315`, or `http://www.w3.org/2006/12/xml-c14n11`, are not exclusive.\n\nThe aforementioned `<ds:CanonicalizationMethod>` tag controls the c14n of the `SignedInfo` node before feeding its digest to the signature service. \nThe c14n of `SignedProperties` prior to getting its digest is determined by the `ds:Transform` tag within this `ds:Reference` node. \nIf it's not present, then the default, ie. not exclusive, c14n is used. \n\n### KeyInfo\n\nThis section contains the base64-encoded user certificate value, e.g. the SmartID API response's `cert.value`,\nor the certificate obtained from an ID card:\n```xml\n<ds:KeyInfo Id=\"S0-KeyInfo\">\n <ds:X509Data>\n <ds:X509Certificate>MIIGJDCCBAygAwIBAgIQBNsLtTIpnmNbbE4+laSLaTANBgkqhkiG9w0BAQsFADBr...</ds:X509Certificate>\n </ds:X509Data>\n</ds:KeyInfo>\n```\nMore details about the certificate in the [SigningCertificate](#SigningCertificate) subsection.\n \n### SignedProperties\n\nThe XML section of `SignedProperties` consists of, [at least](https://www.w3.org/TR/XAdES/#IDAEAD1B), \nthe `SigningTime`, `SigningCertificate` and `SignaturePolicyIdentifer` elements.\n\n:question: The signatures returned by e.g. [Dokobit](https://dokobit.ee), \ndo not contain the `SignaturePolicyIdentifer` node.\n\n#### SigningTime\n\nA timestamp in ISO 8601 format.\n\n#### SignaturePolicyIdentifier \n\nThis appears to be a static^1 XML chunk referencing the BDOC 2.1 Specifications document:\n```xml\n<xades:SignaturePolicyIdentifier>\n <xades:SignaturePolicyId>\n <xades:SigPolicyId>\n <xades:Identifier Qualifier=\"OIDAsURN\">urn:oid:1.3.6.1.4.1.10015.1000.3.2.1</xades:Identifier>\n </xades:SigPolicyId>\n <xades:SigPolicyHash>\n <ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\">\n </ds:DigestMethod>\n <ds:DigestValue>3Tl1oILSvOAWomdI9VeWV6IA/32eSXRUri9kPEz1IVs=</ds:DigestValue>\n </xades:SigPolicyHash>\n <xades:SigPolicyQualifiers>\n <xades:SigPolicyQualifier>\n <xades:SPURI>https://www.sk.ee/repository/bdoc-spec21.pdf</xades:SPURI>\n </xades:SigPolicyQualifier>\n </xades:SigPolicyQualifiers>\n </xades:SignaturePolicyId>\n</xades:SignaturePolicyIdentifier>\n```\n\n[1] The DigestValue is the hash value of the document referenced by `SPURI`, encoded in base64. \nRefer to [BDOC 2.1:2014 Specification](https://www.id.ee/public/bdoc-spec212-eng.pdf) for more information.\n\n#### SigningCertificate\n\nThe user certificate is a base64-encoded DER certificate which can be loaded as follows:\n```python\nimport base64\nfrom cryptography import x509\nfrom cryptography.hazmat.backends import default_backend\ncert_asn1 = base64.b64decode(cert_value)\ncert = x509.load_der_x509_certificate(base64.b64decode(cert_asn1), default_backend())\n```\nor with `pyopenssl`:\n```python\nimport base64\nfrom OpenSSL.crypto import load_certificate, FILETYPE_ASN1\ncert_asn1 = base64.b64decode(cert_value)\nopenssl_cert = load_certificate(FILETYPE_ASN1, base64.b64decode(cert_asn1))\n```\nThese objects expose a slightly different but similar API.\n\nWhat we need is the issuer name and certificate serial number:\n```python\nassert openssl_cert.get_serial_number() == cert.sertial_number == '6454262457486410408874311107672836969'\nassert cert.issuer.rfc4514_string() == 'C=EE,O=AS Sertifitseerimiskeskus,2.5.4.97=NTREE-10747013,CN=TEST of ESTEID-SK 2015'\nassert openssl_cert.issuer.get_components() == [(b'C', b'EE'), (b'O', b'AS Sertifitseerimiskeskus'), (b'organizationIdentifier', b'NTREE-10747013'), (b'CN', b'ESTEID-SK 2015')] \n```\n\nAlso we need a SHA256 digest value of the certificate:\n```python\ncert_digest = base64.b64encode(hashlib.sha256(cert_asn1).digest())\n```\n\nWith these values we can build the certificate information entry of the SignedProperties:\n```xml\n<xades:SigningCertificate>\n <xades:Cert>\n <xades:CertDigest>\n <ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"></ds:DigestMethod>\n <ds:DigestValue>hdsLTm4aaFKaGMwF6fvH5vWmiMBBnTCH3kba+TjY+pE=</ds:DigestValue>\n </xades:CertDigest>\n <xades:IssuerSerial>\n <ds:X509IssuerName>C=EE,O=AS Sertifitseerimiskeskus,2.5.4.97=NTREE-10747013,CN=TEST of EID-SK 2016</ds:X509IssuerName>\n <ds:X509SerialNumber>98652662091042892833248946646759285960</ds:X509SerialNumber>\n </xades:IssuerSerial>\n </xades:Cert>\n</xades:SigningCertificate>\n```\n\n:question: Does `X509IssuerName` content need to be a `cert.issuer.rfc4514_string()` or can it be anything else?\n\nSo, in the end, we get a `<xades:SignedProperties>` element which we then canonicalize and calculate a sha256 hash of this string,\nto place it in the appropriate `<ds:Reference>` element.\n\n### SignatureValue\n\n```xml\n<ds:SignatureValue Id=\"SIG-{SIGNATURE_ID}\"><!-- Base64-encoded SIGNATURE_VALUE, gotten externally --></ds:SignatureValue>\n```\n\nA base64-encoded value of the signature calculated over the signed data. \nThe signed data is the `ds:SignedInfo` section, as [described above](#signedinfo).\n\nWhen using SmartID/MobileID, this is taken from the `signature.value` field of the response.\n\n### KeyInfo\n\nContains the base64-encoded certificate, as gotten from the SmartID response.\n```xml\n<ds:KeyInfo Id=\"S0-KeyInfo\">\n <ds:X509Data>\n <ds:X509Certificate>...</ds:X509Certificate>\n </ds:X509Data>\n</ds:KeyInfo>\n```\n\n## Secondary Services\n\n### OCSP\n\nOCSP ([Online Certificate Status Protocol](https://en.wikipedia.org/wiki/Online_Certificate_Status_Protocol)) \nis designed to check that the signing certificate is valid at the point of signing. It is a binary protocol, and uses ASN.1 encoding in both request and response payload.\nTo deal with it, we're using the `asn1crypto` library.\n\nThe OCSP request should be made immediately after signing, and the base64-encoded response is embedded in the XAdES signature as a `xades:UnsignedSignatureProperties` descendant node,\nnamely `xades:EncapsulatedOCSPValue`.\n\n#### Estonian eID\n\nURLs for OCSP services:\n* Demo: `http://demo.sk.ee/ocsp`\n* Production: `http://ocsp.sk.ee/`\n\nMore detail on the [sk.ee OCSP page](https://www.sk.ee/en/services/validity-confirmation-services/technical-information/)\n\n\n### Timestamping service\n\nThe [TimeStamp protocol](https://en.wikipedia.org/wiki/Time_stamp_protocol) is also a binary protocol, for getting a Long-Term Validity Timestamp for a signature. \nAlso handled with the help of the `asn1crypto` library.\n\nThe TSA request should be made immediately after OCSP validity confirmation, and the base64-encoded response is embedded in the XAdES signature as a `xades:UnsignedSignatureProperties` descendant node,\nnamely `xades:EncapsulatedTimeStamp`.\n\n#### Estonian eID\n\nURLs for timestamping services:\n* Demo: `http://demo.sk.ee/tsa/`\n* Production: `http://tsa.sk.ee`\n\nMore detail on the [sk.ee TSA page](https://www.sk.ee/en/services/time-stamping-service/)\n",
"bugtrack_url": null,
"license": "ISC",
"summary": "Manipulate ASiC-E containers and XAdES/eIDAS signatures for Estonian e-identity services",
"version": "1.1.0",
"project_urls": {
"Homepage": "https://github.com/thorgate/pyasice",
"Repository": "https://github.com/thorgate/pyasice"
},
"split_keywords": [
"esteid",
"asice",
"smartid",
"mobile-id",
"idcard"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "91293c33af3b72529b167784c8c2ec4bec4b82ef7c5a46e31773bf5048a35600",
"md5": "a5863d8f717b5ed2754bf8761672c51a",
"sha256": "8e8471ca06f54f8af8fc25e9477f283842c8a014d88b3678c6c43a00c61fdfd5"
},
"downloads": -1,
"filename": "pyasice-1.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a5863d8f717b5ed2754bf8761672c51a",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6,<4.0",
"size": 27555,
"upload_time": "2024-02-28T09:33:22",
"upload_time_iso_8601": "2024-02-28T09:33:22.607491Z",
"url": "https://files.pythonhosted.org/packages/91/29/3c33af3b72529b167784c8c2ec4bec4b82ef7c5a46e31773bf5048a35600/pyasice-1.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "0bb992619402d76c4f912f65f3e7236eaee5ccc4dfcc8d04cb4fb81559004e60",
"md5": "d01b42e76fc2126f3f8e3c642137aa4a",
"sha256": "0ffdf23629aab8d8ba85186feab9dbd0e4be5b920039ae7a01da62cd7dda796b"
},
"downloads": -1,
"filename": "pyasice-1.1.0.tar.gz",
"has_sig": false,
"md5_digest": "d01b42e76fc2126f3f8e3c642137aa4a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.6,<4.0",
"size": 29209,
"upload_time": "2024-02-28T09:33:24",
"upload_time_iso_8601": "2024-02-28T09:33:24.901355Z",
"url": "https://files.pythonhosted.org/packages/0b/b9/92619402d76c4f912f65f3e7236eaee5ccc4dfcc8d04cb4fb81559004e60/pyasice-1.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-02-28 09:33:24",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "thorgate",
"github_project": "pyasice",
"travis_ci": true,
"coveralls": false,
"github_actions": false,
"lcname": "pyasice"
}