Skip to content

Commit 3c34ae9

Browse files
authored
Switch to using "cryptography" Python lib (google#953)
Replaces "ecdsa" and "pycrypto" libraries with the "cryptography" library The ecdsa library is considered to be poorly implemented, whilst the pycrypto library is poorly maintained. See: https://cryptography.io Changes some errors from EncodingError to SignatureError.
1 parent e8ae3bd commit 3c34ae9

File tree

5 files changed

+64
-73
lines changed

5 files changed

+64
-73
lines changed

python/README

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ gflags (python_gflags if using easy_install)
55
gviz-api-py (easy_install 'https://google-visualization-python.googlecode.com/files/gviz_api_py-1.8.2.tar.gz')
66
requests (at least version 1.0)
77
protobuf
8-
ecdsa
98
mock
109
twisted (at least 12.1; tested with 13.2)
11-
pycrypto (at least 2.5; tested with 2.6.1)
10+
cryptography
1211

1312
If you use pip, simply run `pip install -r requirements.txt`
1413

python/ct/crypto/verify_ecdsa.py

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
from ct.crypto import error
22
from ct.crypto import pem
3-
from ct.crypto.asn1 import types
43
from ct.proto import client_pb2
54

6-
import hashlib
7-
import ecdsa
8-
9-
class _ECDSASignature(types.Sequence):
10-
components = (
11-
(types.Component("r", types.Integer)),
12-
(types.Component("s", types.Integer))
13-
)
5+
import cryptography
6+
from cryptography.hazmat.backends import default_backend as crypto_backend
7+
from cryptography.hazmat.primitives.asymmetric import ec
8+
from cryptography.hazmat.primitives import hashes
9+
from cryptography.hazmat.primitives import serialization
1410

1511
class EcdsaVerifier(object):
1612
"""Verifies ECDSA signatures."""
@@ -20,11 +16,6 @@ class EcdsaVerifier(object):
2016
# The hash algorithm used for this public key."""
2117
HASH_ALGORITHM = client_pb2.DigitallySigned.SHA256
2218

23-
# Markers to look for when reading a PEM-encoded ECDSA public key."""
24-
__READ_MARKERS = ("PUBLIC KEY", "ECDSA PUBLIC KEY")
25-
# A marker to write when writing a PEM-encoded ECDSA public key."""
26-
__WRITE_MARKER = "ECDSA PUBLIC KEY"
27-
2819
def __init__(self, key_info):
2920
"""Creates a verifier that uses a PEM-encoded ECDSA public key.
3021
@@ -33,22 +24,27 @@ def __init__(self, key_info):
3324
3425
Raises:
3526
- PemError: If the key has an invalid encoding
27+
- UnsupportedAlgorithmError: If the key uses an unsupported algorithm
3628
"""
3729
if (key_info.type != client_pb2.KeyInfo.ECDSA):
3830
raise error.UnsupportedAlgorithmError(
3931
"Expected ECDSA key, but got key type %d" % key_info.type)
4032

41-
# Will raise a PemError on invalid encoding
42-
self.__der, _ = pem.from_pem(key_info.pem_key, self.__READ_MARKERS)
33+
pem_key = str(key_info.pem_key)
34+
4335
try:
44-
self.__key = ecdsa.VerifyingKey.from_der(self.__der)
45-
except ecdsa.der.UnexpectedDER as e:
46-
raise error.EncodingError(e)
36+
self.__key = crypto_backend().load_pem_public_key(pem_key)
37+
except ValueError as e:
38+
raise pem.PemError(e)
39+
except cryptography.exceptions.UnsupportedAlgorithm as e:
40+
raise error.UnsupportedAlgorithmError(e)
4741

4842
def __repr__(self):
49-
return "%s(public key: %r)" % (self.__class__.__name__,
50-
pem.to_pem(self.__der,
51-
self.__WRITE_MARKER))
43+
key_pem = self.__key.public_bytes(
44+
serialization.Encoding.PEM,
45+
serialization.PublicFormat.SubjectPublicKeyInfo)
46+
47+
return "%s(public key: %r)" % (self.__class__.__name__, key_pem)
5248

5349
@error.returns_true_or_raises
5450
def verify(self, signature_input, signature):
@@ -62,18 +58,16 @@ def verify(self, signature_input, signature):
6258
- True if the signature verifies.
6359
6460
Raises:
65-
- error.EncodingError: If the signature encoding is invalid.
6661
- error.SignatureError: If the signature fails verification.
6762
"""
63+
verifier = self.__key.verifier(signature, ec.ECDSA(hashes.SHA256()))
64+
verifier.update(signature_input)
65+
6866
try:
69-
_ECDSASignature.decode(signature)
70-
return self.__key.verify(signature, signature_input,
71-
hashfunc=hashlib.sha256,
72-
sigdecode=ecdsa.util.sigdecode_der)
73-
except (ecdsa.der.UnexpectedDER, error.ASN1Error) as e:
74-
raise error.EncodingError("Invalid DER encoding for signature %s",
75-
signature.encode("hex"), e)
76-
except ecdsa.keys.BadSignatureError:
77-
raise error.SignatureError("Signature did not verify: %s",
67+
verifier.verify()
68+
except cryptography.exceptions.InvalidSignature:
69+
raise error.SignatureError("Signature did not verify: %s" %
7870
signature.encode("hex"))
7971

72+
return True
73+

python/ct/crypto/verify_rsa.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
from ct.crypto import pem
33
from ct.proto import client_pb2
44

5-
import Crypto.Hash.SHA256
6-
import Crypto.PublicKey.RSA
7-
import Crypto.Signature.PKCS1_v1_5
5+
import cryptography
6+
from cryptography.hazmat.backends import default_backend as crypto_backend
7+
from cryptography.hazmat.primitives import hashes
8+
from cryptography.hazmat.primitives import serialization
9+
from cryptography.hazmat.primitives.asymmetric import padding
810

911
class RsaVerifier(object):
1012
"""Verifies RSA signatures."""
@@ -14,11 +16,6 @@ class RsaVerifier(object):
1416
# The hash algorithm used for this public key.
1517
HASH_ALGORITHM = client_pb2.DigitallySigned.SHA256
1618

17-
# Markers to look for when reading a PEM-encoded RSA public key.
18-
__READ_MARKERS = ("PUBLIC KEY", "RSA PUBLIC KEY")
19-
# A marker to write when writing a PEM-encoded RSA public key.
20-
__WRITE_MARKER = "RSA PUBLIC KEY"
21-
2219
def __init__(self, key_info):
2320
"""Creates a verifier that uses a PEM-encoded RSA public key.
2421
@@ -27,22 +24,27 @@ def __init__(self, key_info):
2724
2825
Raises:
2926
- PemError: If the key has an invalid encoding
27+
- UnsupportedAlgorithmError: If the key uses an unsupported algorithm
3028
"""
3129
if (key_info.type != client_pb2.KeyInfo.RSA):
3230
raise error.UnsupportedAlgorithmError(
3331
"Expected RSA key, but got key type %d" % key_info.type)
3432

35-
# Will raise a PemError on invalid encoding
36-
self.__der, _ = pem.from_pem(key_info.pem_key, self.__READ_MARKERS)
33+
pem_key = str(key_info.pem_key)
34+
3735
try:
38-
self.__key = Crypto.PublicKey.RSA.importKey(self.__der)
39-
except (ValueError, IndexError, TypeError) as e:
40-
raise error.EncodingError(e)
36+
self.__key = crypto_backend().load_pem_public_key(pem_key)
37+
except ValueError as e:
38+
raise pem.PemError(e)
39+
except cryptography.exceptions.UnsupportedAlgorithm as e:
40+
raise error.UnsupportedAlgorithmError(e)
4141

4242
def __repr__(self):
43-
return "%s(public key: %r)" % (self.__class__.__name__,
44-
pem.to_pem(self.__der,
45-
self.__WRITE_MARKER))
43+
key_pem = self.__key.public_bytes(
44+
serialization.Encoding.PEM,
45+
serialization.PublicFormat.SubjectPublicKeyInfo)
46+
47+
return "%s(public key: %r)" % (self.__class__.__name__, key_pem)
4648

4749
@error.returns_true_or_raises
4850
def verify(self, signature_input, signature):
@@ -58,12 +60,13 @@ def verify(self, signature_input, signature):
5860
Raises:
5961
- error.SignatureError: If the signature fails verification.
6062
"""
61-
verifier = Crypto.Signature.PKCS1_v1_5.new(self.__key)
62-
sha256_hash = Crypto.Hash.SHA256.new(signature_input)
63+
verifier = self.__key.verifier(signature, padding.PKCS1v15(), hashes.SHA256())
64+
verifier.update(signature_input)
6365

64-
if verifier.verify(sha256_hash, signature):
65-
return True
66-
else:
67-
raise error.SignatureError("Signature did not verify: %s",
66+
try:
67+
verifier.verify()
68+
except cryptography.exceptions.InvalidSignature:
69+
raise error.SignatureError("Signature did not verify: %s" %
6870
signature.encode("hex"))
6971

72+
return True

python/ct/crypto/verify_test.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -350,21 +350,17 @@ def test_verify_sth_for_bad_asn1_length(self):
350350
sth_fixture.tree_head_signature[:i] +
351351
chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
352352
sth_fixture.tree_head_signature[i+1:])
353-
self.assertRaises(error.EncodingError, verifier.verify_sth, sth)
353+
self.assertRaises(error.SignatureError, verifier.verify_sth, sth)
354354

355355
# Increasing the length means there are not enough ASN.1 bytes left to
356-
# decode the sequence, however the ecdsa module silently slices it.
357-
# Our ECDSA verifier checks for it and will fail.
356+
# decode the sequence.
358357
sth = client_pb2.SthResponse()
359358
sth.CopyFrom(sth_fixture)
360359
sth.tree_head_signature = (
361360
sth_fixture.tree_head_signature[:i] +
362361
chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
363362
sth_fixture.tree_head_signature[i+1:])
364-
self.assertRaises(
365-
error.EncodingError,
366-
verifier.verify_sth,
367-
sth)
363+
self.assertRaises(error.SignatureError, verifier.verify_sth, sth)
368364

369365
# The byte that encodes the length of the first integer r in the
370366
# sequence (r, s). Modifying the length corrupts the second integer
@@ -376,35 +372,35 @@ def test_verify_sth_for_bad_asn1_length(self):
376372
sth_fixture.tree_head_signature[:i] +
377373
chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
378374
sth_fixture.tree_head_signature[i+1:])
379-
self.assertRaises(error.EncodingError, verifier.verify_sth, sth)
375+
self.assertRaises(error.SignatureError, verifier.verify_sth, sth)
380376

381377
sth = client_pb2.SthResponse()
382378
sth.CopyFrom(sth_fixture)
383379
sth.tree_head_signature = (
384380
sth_fixture.tree_head_signature[:i] +
385381
chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
386382
sth_fixture.tree_head_signature[i+1:])
387-
self.assertRaises(error.EncodingError, verifier.verify_sth, sth)
383+
self.assertRaises(error.SignatureError, verifier.verify_sth, sth)
388384

389385
# The byte that encodes the length of the second integer s in the
390-
# sequence (r, s). Increasing this length leaves bytes unread which
391-
# is now also detected in the verify_ecdsa module.
386+
# sequence (r, s). Modifying the length corrupts the integer and causes
387+
# a decoding error.
392388
i = 42
393389
sth = client_pb2.SthResponse()
394390
sth.CopyFrom(sth_fixture)
395391
sth.tree_head_signature = (
396392
sth_fixture.tree_head_signature[:i] +
397393
chr(ord(sth_fixture.tree_head_signature[i]) - 1) +
398394
sth_fixture.tree_head_signature[i+1:])
399-
self.assertRaises(error.EncodingError, verifier.verify_sth, sth)
395+
self.assertRaises(error.SignatureError, verifier.verify_sth, sth)
400396

401397
sth = client_pb2.SthResponse()
402398
sth.CopyFrom(sth_fixture)
403399
sth.tree_head_signature = (
404400
sth_fixture.tree_head_signature[:i] +
405401
chr(ord(sth_fixture.tree_head_signature[i]) + 1) +
406402
sth_fixture.tree_head_signature[i+1:])
407-
self.assertRaises(error.EncodingError, verifier.verify_sth, sth)
403+
self.assertRaises(error.SignatureError, verifier.verify_sth, sth)
408404

409405
# Trailing garbage is correctly detected.
410406
sth = client_pb2.SthResponse()
@@ -414,7 +410,7 @@ def test_verify_sth_for_bad_asn1_length(self):
414410
# Correct outer length to include trailing garbage.
415411
chr(ord(sth_fixture.tree_head_signature[3]) + 1) +
416412
sth_fixture.tree_head_signature[4:]) + "\x01"
417-
self.assertRaises(error.EncodingError, verifier.verify_sth, sth)
413+
self.assertRaises(error.SignatureError, verifier.verify_sth, sth)
418414

419415
def test_verify_sth_for_bad_asn1_signature(self):
420416
# www.google.com certificate for which a bad SCT was issued.
@@ -463,7 +459,7 @@ def test_verify_sth_for_bad_asn1_signature(self):
463459
'PUBLIC KEY')
464460
verifier = verify.LogVerifier(key_info)
465461
self.assertRaises(
466-
error.EncodingError,
462+
error.SignatureError,
467463
verifier.verify_sct,
468464
symantec_sct,
469465
[cert.Certificate.from_pem("\n".join(google_cert)),])

python/requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
dnspython
2-
ecdsa
32
git+https://github.com/google/google-visualization-python.git@04e4c03909980bc12be2ebb3ecf476716d9855d4
43
mock>=1.0
54
protobuf
65
python-gflags
76
requests>=1.0
87
Twisted>=12.1
98
bitstring
10-
pycrypto>=2.5
119
jsonschema
10+
cryptography>=1.0

0 commit comments

Comments
 (0)