Skip to content

Commit e069964

Browse files
authored
Merge pull request #779 from cordada/task/add-crypto-util-get-x509-cert-from-pkcs12
libs: Add utility to get X.509 certificate from PKCS12 (PFX) file
2 parents 98cd6da + f5cfec6 commit e069964

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

src/cl_sii/libs/crypto_utils.py

+44-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@
3636
> In the case that it encodes a certificate it would simply contain the
3737
> base64 encoding of the DER certificate [plus the header and footer].
3838
39+
PKCS12
40+
--------
41+
42+
PKCS12 stands for "Public Key Cryptography Standard #12".
43+
44+
> […] a binary format described in RFC 7292. It can contain certificates, keys, and more.
45+
> PKCS12 files commonly have a `pfx` or `p12` file suffix.
3946
"""
4047

4148
__all__ = [
@@ -44,9 +51,10 @@
4451
]
4552

4653
import base64
47-
from typing import Union
54+
from typing import Optional, Union
4855

4956
import cryptography.hazmat.backends.openssl.backend as _crypto_x509_backend
57+
import cryptography.hazmat.primitives.serialization.pkcs12
5058
import cryptography.x509
5159
import signxml.util
5260
from cryptography.x509 import Certificate as X509Cert
@@ -170,3 +178,38 @@ def remove_pem_cert_header_footer(pem_cert: bytes) -> bytes:
170178
mod_pem_value_str = signxml.util.strip_pem_header(pem_value_str)
171179
mod_pem_value: bytes = mod_pem_value_str.encode('ascii').strip()
172180
return mod_pem_value
181+
182+
183+
def load_pfx_x509_cert(pfx_value: bytes, password: Union[bytes, str, None]) -> Optional[X509Cert]:
184+
"""
185+
Load an X.509 certificate from PKCS12-encoded data.
186+
187+
:raises TypeError:
188+
:raises ValueError:
189+
"""
190+
if isinstance(password, str):
191+
password = password.encode()
192+
193+
try:
194+
p12 = cryptography.hazmat.primitives.serialization.pkcs12.load_pkcs12(
195+
data=pfx_value, password=password
196+
)
197+
except TypeError:
198+
# Examples:
199+
# - "TypeError: argument 'data': a bytes-like object is required, not 'NoneType'"
200+
# - "argument 'password': from_buffer() cannot return the address of a unicode object"
201+
raise
202+
except ValueError:
203+
# Examples:
204+
# - "Invalid password or PKCS12 data"
205+
# - "Could not deserialize PKCS12 data"
206+
raise
207+
208+
pkcs12_cert = p12.cert
209+
x509_cert = pkcs12_cert.certificate if pkcs12_cert is not None else None
210+
return x509_cert
211+
212+
213+
# Aliases for `load_pfx_x509_cert()`:
214+
load_pkcs12_x509_cert = load_pfx_x509_cert
215+
load_p12_x509_cert = load_pfx_x509_cert

src/tests/test_libs_crypto_utils.py

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import unittest
24
from binascii import a2b_hex
35
from datetime import datetime
@@ -11,6 +13,7 @@
1113
add_pem_cert_header_footer,
1214
load_der_x509_cert,
1315
load_pem_x509_cert,
16+
load_pfx_x509_cert,
1417
remove_pem_cert_header_footer,
1518
x509_cert_der_to_pem,
1619
x509_cert_pem_to_der,
@@ -1098,3 +1101,9 @@ def test_add_pem_cert_header_footer(self) -> None:
10981101
def test_remove_pem_cert_header_footer(self) -> None:
10991102
# TODO: implement for 'remove_pem_cert_header_footer'
11001103
pass
1104+
1105+
1106+
class LoadPfxX509CertTest(unittest.TestCase):
1107+
@unittest.skip("TODO: Implement for 'load_pfx_x509_cert'")
1108+
def test_load_pfx_x509_cert_ok(self) -> None:
1109+
assert bool(load_pfx_x509_cert)

0 commit comments

Comments
 (0)