Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for elliptic curve private keys #158

Merged
merged 4 commits into from
Dec 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion endesive/pdf/cms.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import struct
from cryptography.hazmat import backends
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.hazmat.primitives.asymmetric import ec
from endesive import signer
from endesive.pdf.PyPDF2 import pdf, generic as po

Expand Down Expand Up @@ -660,7 +661,7 @@ def sign(
algomd = obj["/DigestMethod"][1:].lower()

# produce smaller signatures, but must be signed twice
aligned = udct.get("aligned", 0)
aligned = udct.get("aligned", 4096 if isinstance(key, ec.EllipticCurvePrivateKey) else 0)
if aligned:
zeros = b"00" * aligned
else:
Expand Down
13 changes: 9 additions & 4 deletions endesive/signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from oscrypto import asymmetric
from cryptography.hazmat import backends
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, utils
from cryptography.hazmat.primitives.asymmetric import padding, utils, ec


def cert2asn(cert, cert_bytes=True):
Expand Down Expand Up @@ -354,9 +354,14 @@ def sign(
utils.Prehashed(hashes.SHA512()),
)
else:
signed_value_signature = key.sign(
tosign, padding.PKCS1v15(), getattr(hashes, hashalgo.upper())()
)
if isinstance(key, ec.EllipticCurvePrivateKey):
signed_value_signature = key.sign(
tosign, ec.ECDSA(getattr(hashes, hashalgo.upper())())
)
else:
signed_value_signature = key.sign(
tosign, padding.PKCS1v15(), getattr(hashes, hashalgo.upper())()
)

if timestampurl is not None:
datas["content"]["signer_infos"][0]["unsigned_attrs"] = timestamp(
Expand Down
14 changes: 12 additions & 2 deletions endesive/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from certvalidator import CertificateValidator, ValidationContext

from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric import padding, ec
from cryptography import x509 as cx509
from cryptography.hazmat.backends import default_backend

Expand Down Expand Up @@ -67,7 +67,17 @@ def verify(self, datas, datau):
sigalgo = signed_data["signer_infos"][0]["signature_algorithm"]
# sigalgo.debug()
sigalgoname = sigalgo.signature_algo
if sigalgoname == "rsassa_pss":
if isinstance(public_key, ec.EllipticCurvePublicKey):
try:
public_key.verify(
signature,
signedData,
ec.ECDSA(getattr(hashes, algo.upper())()),
)
signatureok = True
except Exception as e:
signatureok = False
elif sigalgoname == "rsassa_pss":
parameters = sigalgo["parameters"]
# parameters.debug()
# print(parameters.native)
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ softhsm2.conf
pdf-signed-cms.pdf
pdf-encrypted-signed.pdf
pdf-signed-appearance.pdf
pdf-signed-appearance-ec.pdf
pdf-signed-cms-aligned.pdf
plain-signed-attr.txt
plain-signed-noattr.txt
Expand Down
37 changes: 28 additions & 9 deletions tests/test_cert.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import rsa, ec
from cryptography.hazmat.primitives.serialization import pkcs12
from cryptography.x509.oid import NameOID

Expand All @@ -35,6 +35,11 @@ def fixture(fname):
cert2_key,
cert2_pub,
cert2_p12,
cert3_csr,
cert3_cert,
cert3_key,
cert3_pub,
cert3_p12,
) = (
fixture("demo2_ca.crt.pem"),
fixture("demo2_ca.key.pem"),
Expand All @@ -43,23 +48,33 @@ def fixture(fname):
fixture("demo2_user1.key.pem"),
fixture("demo2_user1.pub.pem"),
fixture("demo2_user1.p12"),
fixture("demo2_user1.csr.pem"),
fixture("demo2_user2.csr.pem"),
fixture("demo2_user2.crt.pem"),
fixture("demo2_user2.key.pem"),
fixture("demo2_user2.pub.pem"),
fixture("demo2_user2.p12"),
fixture("demo2_user3.csr.pem"),
fixture("demo2_user3.crt.pem"),
fixture("demo2_user3.key.pem"),
fixture("demo2_user3.pub.pem"),
fixture("demo2_user3.p12"),
)


class CA(object):
def __init__(self):
self.createCA()

def key_create(self) -> rsa.RSAPrivateKey:
def key_create_rsa(self) -> rsa.RSAPrivateKey:
return rsa.generate_private_key(
public_exponent=65537, key_size=2048, backend=default_backend()
)

def key_create_ec(self) -> ec.EllipticCurvePrivateKey:
return ec.generate_private_key(
ec.SECP256R1(), default_backend()
)

def key_save(self, fname: str, key: rsa.RSAPrivateKey, password: str) -> None:
# Write our key to disk for safe keeping
with open(fname, "wb") as f:
Expand Down Expand Up @@ -319,7 +334,7 @@ def createCA(self) -> None:
cert = self.cert_load(ca_cert)
key = self.key_load(ca_key, "1234")
else:
key = self.key_create()
key = self.key_create_rsa()
cert = self.ca_create(key)

self.key_save(ca_key, key, "1234")
Expand All @@ -329,14 +344,17 @@ def createCA(self) -> None:
self.ca_pk = key

def USER(self, no, cert, cert_key, cert_pub, cert_p12) -> None:
client_pk = self.key_create()
client_csr = self.csr_create("demo%[email protected]" % no, client_pk, commonname="USER %s"%no)
if no == 1:
client_pk = self.key_create_rsa()
fname = cert1_csr
self.csr_save(fname, client_csr)
else:
elif no == 2:
client_pk = self.key_create_rsa()
fname = cert2_csr
self.csr_save(fname, client_csr)
else:
client_pk = self.key_create_ec()
fname = cert3_csr
client_csr = self.csr_create("demo%[email protected]" % no, client_pk, commonname="USER %s"%no)
self.csr_save(fname, client_csr)
client_cert = self.csr_sign(fname)
self.cert_save(cert, client_cert)
self.key_save(cert_key, client_pk, "1234")
Expand All @@ -348,6 +366,7 @@ def USER(self, no, cert, cert_key, cert_pub, cert_p12) -> None:
def createUSERS(self):
self.USER(1, cert1_cert, cert1_key, cert1_pub, cert1_p12)
self.USER(2, cert2_cert, cert2_key, cert2_pub, cert2_p12)
self.USER(3, cert3_cert, cert3_key, cert3_pub, cert3_p12)


class CATests(unittest.TestCase):
Expand Down
46 changes: 46 additions & 0 deletions tests/test_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,52 @@ def test_pdf_signature_appearance(self):
for (hashok, signatureok, certok) in results:
assert signatureok and hashok and certok

def test_pdf_signature_appearance_ec(self):
dct = {
'aligned': 4096,
'sigflags': 3,
'sigflagsft': 132,
'sigpage': 0,
'sigbutton': False,
'sigfield': 'Signature-1667820612.078739',
'auto_sigfield': False,
'sigandcertify': False,
'signaturebox': [175.79446979865773, 294.7236779911374, 447.47683221476507, 573.2810782865583],
'contact': '',
'location': '',
'reason': '',
'signingdate': "D:20221107123012+00'00'",
'signature_appearance': {
'background': [0.75, 0.8, 0.95],
'outline': [0.2, 0.3, 0.5],
'border': 1,
'labels': True,
'display': ['date']
}
}
p12 = test_cert.CA().pk12_load(test_cert.cert3_p12, '1234')
fname = fixture('pdf.pdf')
with open(fname, 'rb') as fh:
datau = fh.read()
datas = pdf.cms.sign(datau, dct,
p12[0], p12[1], p12[2],
'sha256'
)
fname = fname.replace('.pdf', '-signed-appearance-ec.pdf')
with open(fname, 'wb') as fp:
fp.write(datau)
fp.write(datas)

with open(test_cert.ca_cert, 'rb') as fh:
trusted_cert_pems = (fh.read(),)
with open(fname, 'rb') as fh:
data = fh.read()
results = pdf.verify(
data, trusted_cert_pems, "/etc/ssl/certs"
)
for (hashok, signatureok, certok) in results:
assert signatureok and hashok and certok

def test_pdf_signature_manual(self):
date = datetime.datetime.utcnow() - datetime.timedelta(hours=12)
date = date.strftime('D:%Y%m%d%H%M%S+00\'00\'')
Expand Down