68
68
# Import pyca/cryptography routines needed to generate and load cryptographic
69
69
# keys in PEM format.
70
70
from cryptography .hazmat .primitives import serialization
71
- from cryptography .hazmat .backends .interfaces import PEMSerializationBackend
72
71
from cryptography .hazmat .primitives .serialization import load_pem_private_key
73
72
from cryptography .hazmat .backends import default_backend
74
73
85
84
# pyca/cryptography requires hash objects to generate PKCS#1 PSS
86
85
# signatures (i.e., padding.PSS). The 'hmac' module is needed to verify
87
86
# ciphertexts in encrypted key files.
88
- from cryptography .hazmat .primitives import hashes
89
- from cryptography .hazmat .primitives import hmac
87
+ from cryptography .hazmat .primitives import hashes , hmac
90
88
91
89
# RSA's probabilistic signature scheme with appendix (RSASSA-PSS).
92
90
# PKCS#1 v1.5 is available for compatibility with existing applications, but
93
91
# RSASSA-PSS is encouraged for newer applications. RSASSA-PSS generates
94
- # a random salt to ensure the signature generated is probabilistic rather than
95
- # deterministic (e.g., PKCS#1 v1.5).
92
+ # a random salt to ensure the signature generated is probabilistic rather
93
+ # than deterministic (e.g., PKCS#1 v1.5).
96
94
# http://en.wikipedia.org/wiki/RSA-PSS#Schemes
97
95
# https://tools.ietf.org/html/rfc3447#section-8.1
98
96
# The 'padding' module is needed for PSS signatures.
99
97
from cryptography .hazmat .primitives .asymmetric import padding
100
98
101
99
# Import pyca/cryptography's Key Derivation Function (KDF) module.
102
- # 'securesystemslib.keys.py' needs this module to derive a secret key according
103
- # to the Password-Based Key Derivation Function 2 specification. The derived
104
- # key is used as the symmetric key to encrypt securesystemslib key information.
105
- # PKCS#5 v2.0 PBKDF2 specification: http://tools.ietf.org/html/rfc2898#section-5.2
100
+ # 'securesystemslib.keys.py' needs this module to derive a secret key
101
+ # according to the Password-Based Key Derivation Function 2 specification.
102
+ # The derived key is used as the symmetric key to encrypt securesystemslib
103
+ # key information.
104
+ # PKCS#5 v2.0 PBKDF2 specification:
105
+ # http://tools.ietf.org/html/rfc2898#section-5.2
106
106
from cryptography .hazmat .primitives .kdf .pbkdf2 import PBKDF2HMAC
107
107
108
108
# pyca/cryptography's AES implementation available in 'ciphers.Cipher. and
112
112
from cryptography .hazmat .primitives .ciphers import Cipher , algorithms
113
113
114
114
# The mode of operation is presently set to CTR (CounTeR Mode) for symmetric
115
- # block encryption (AES-256, where the symmetric key is 256 bits). 'modes' can
116
- # be used as an argument to 'ciphers.Cipher' to specify the mode of operation
117
- # for the block cipher. The initial random block, or initialization vector
118
- # (IV), can be set to begin the process of incrementing the 128-bit blocks and
119
- # allowing the AES algorithm to perform cipher block operations on them.
115
+ # block encryption (AES-256, where the symmetric key is 256 bits). 'modes'
116
+ # can be used as an argument to 'ciphers.Cipher' to specify the mode of
117
+ # operation for the block cipher. The initial random block, or
118
+ # initialization vector (IV), can be set to begin the process of
119
+ # incrementing the 128-bit blocks and allowing the AES algorithm to perform
120
+ # cipher block operations on them.
120
121
from cryptography .hazmat .primitives .ciphers import modes
121
122
except ImportError :
122
123
CRYPTO = False
@@ -246,7 +247,54 @@ def generate_rsa_public_and_private(bits=_DEFAULT_RSA_KEY_BITS):
246
247
247
248
248
249
249
- def create_rsa_signature (private_key , data , scheme = 'rsassa-pss-sha256' ):
250
+ # The RSA-PSS scheme allows for choosing the salt length:
251
+ # https://crypto.stackexchange.com/questions/1217/rsa-pss-salt-size
252
+ # For compatibility with Golang, we borrow the salt lengths typically used in
253
+ # its crypto package.
254
+ # https://github.com/golang/go/blob/11f92e9dae96939c2d784ae963fa7763c300660b/src/crypto/rsa/pss.go#L225-L232
255
+ # FIXME: really, we should encode the salt length as part of the metadata on
256
+ # how to use the RSA-PSS public key.
257
+ class SaltLengthType :
258
+ """A class to represent common salt lengths for RSA-PSS."""
259
+
260
+ @classmethod
261
+ def get_salt_length (self , algorithm ):
262
+ """Get the salt length as integer."""
263
+ raise NotImplementedError
264
+
265
+
266
+
267
+
268
+
269
+ # NOTE: This is what used to be the standard behaviour.
270
+ class HashSaltLengthType (SaltLengthType ):
271
+ """Salt length to equal the length of the hash used in the signature."""
272
+
273
+ @classmethod
274
+ def get_salt_length (cls , algorithm ):
275
+ """Get the salt length as integer."""
276
+ return algorithm .digest_size
277
+
278
+
279
+
280
+
281
+
282
+ class MaxSaltLengthType (SaltLengthType ):
283
+ """Salt length in a PSS signature to be as large as possible when
284
+ signing, and to be auto-detected when verifying."""
285
+
286
+ @classmethod
287
+ def get_salt_length (cls , algorithm ):
288
+ """Get the salt length as integer."""
289
+ # NOTE: We disregard algorithm here.
290
+ return padding .PSS .MAX_LENGTH
291
+
292
+
293
+
294
+
295
+
296
+ def create_rsa_signature (private_key , data , scheme = 'rsassa-pss-sha256' ,
297
+ salt_length_type = HashSaltLengthType ):
250
298
"""
251
299
<Purpose>
252
300
Generate a 'scheme' signature. The signature, and the signature scheme
@@ -279,6 +327,10 @@ def create_rsa_signature(private_key, data, scheme='rsassa-pss-sha256'):
279
327
scheme:
280
328
The signature scheme used to generate the signature.
281
329
330
+ salt_length_type:
331
+ The strategy for determining the length of the salt used in RSA-PSS, one of
332
+ HashSaltLengthType or MaxSaltLengthType.
333
+
282
334
<Exceptions>
283
335
securesystemslib.exceptions.FormatError, if 'private_key' is improperly
284
336
formatted.
@@ -337,9 +389,12 @@ def create_rsa_signature(private_key, data, scheme='rsassa-pss-sha256'):
337
389
# Generate an RSSA-PSS signature. Raise
338
390
# 'securesystemslib.exceptions.CryptoError' for any of the expected
339
391
# exceptions raised by pyca/cryptography.
340
- signature = private_key_object .sign (
341
- data , padding .PSS (mgf = padding .MGF1 (digest_obj .algorithm ),
342
- salt_length = digest_obj .algorithm .digest_size ), digest_obj .algorithm )
392
+ signature = private_key_object .sign (data ,
393
+ padding .PSS (
394
+ mgf = padding .MGF1 (digest_obj .algorithm ),
395
+ salt_length = salt_length_type .get_salt_length (
396
+ digest_obj .algorithm )),
397
+ digest_obj .algorithm )
343
398
344
399
elif scheme .startswith ('rsa-pkcs1v15' ):
345
400
# Generate an RSA-PKCS1v15 signature. Raise
@@ -382,7 +437,8 @@ def create_rsa_signature(private_key, data, scheme='rsassa-pss-sha256'):
382
437
383
438
384
439
385
- def verify_rsa_signature (signature , signature_scheme , public_key , data ):
440
+ def verify_rsa_signature (signature , signature_scheme , public_key , data ,
441
+ salt_length_type = HashSaltLengthType ):
386
442
"""
387
443
<Purpose>
388
444
Determine whether the corresponding private key of 'public_key' produced
@@ -415,6 +471,9 @@ def verify_rsa_signature(signature, signature_scheme, public_key, data):
415
471
Data used by securesystemslib.keys.create_signature() to generate
416
472
'signature'. 'data' (a string) is needed here to verify 'signature'.
417
473
474
+ salt_length_type:
475
+ The length of salt used in RSA-PSS.
476
+
418
477
<Exceptions>
419
478
securesystemslib.exceptions.FormatError, if 'signature',
420
479
'signature_scheme', 'public_key', or 'data' are improperly formatted.
@@ -455,11 +514,6 @@ def verify_rsa_signature(signature, signature_scheme, public_key, data):
455
514
# What about 'data'?
456
515
securesystemslib .formats .DATA_SCHEMA .check_match (data )
457
516
458
- # Verify whether the private key of 'public_key' produced 'signature'.
459
- # Before returning the 'valid_signature' Boolean result, ensure 'RSASSA-PSS'
460
- # was used as the signature scheme.
461
- valid_signature = False
462
-
463
517
# Verify the RSASSA-PSS signature with pyca/cryptography.
464
518
try :
465
519
public_key_object = serialization .load_pem_public_key (
@@ -474,19 +528,22 @@ def verify_rsa_signature(signature, signature_scheme, public_key, data):
474
528
try :
475
529
if signature_scheme .startswith ('rsassa-pss' ):
476
530
public_key_object .verify (signature , data ,
477
- padding .PSS (mgf = padding .MGF1 (digest_obj .algorithm ),
478
- salt_length = digest_obj .algorithm .digest_size ),
531
+ padding .PSS (
532
+ mgf = padding .MGF1 (digest_obj .algorithm ),
533
+ salt_length = salt_length_type .get_salt_length (
534
+ digest_obj .algorithm )),
479
535
digest_obj .algorithm )
480
536
481
537
elif signature_scheme .startswith ('rsa-pkcs1v15' ):
482
538
public_key_object .verify (signature , data , padding .PKCS1v15 (),
483
539
digest_obj .algorithm )
484
540
485
- # The RSA_SCHEME_SCHEMA.check_match() above should have validated 'scheme'.
486
- # This is a defensive check check. .
541
+ # The RSA_SCHEME_SCHEMA.check_match() above should have validated
542
+ # 'scheme'. This is a defensive check.
487
543
else : # pragma: no cover
488
- raise securesystemslib .exceptions .UnsupportedAlgorithmError ('Unsupported'
489
- ' signature scheme is specified: ' + repr (scheme ))
544
+ raise securesystemslib .exceptions .UnsupportedAlgorithmError (
545
+ 'Unsupported signature scheme is specified: ' + \
546
+ repr (signature_scheme ))
490
547
491
548
return True
492
549
@@ -496,7 +553,8 @@ def verify_rsa_signature(signature, signature_scheme, public_key, data):
496
553
# Raised by load_pem_public_key().
497
554
except (ValueError , cryptography .exceptions .UnsupportedAlgorithm ) as e :
498
555
raise securesystemslib .exceptions .CryptoError ('The PEM could not be'
499
- ' decoded successfully, or contained an unsupported key type: ' + str (e ))
556
+ ' decoded successfully, or contained an unsupported key type: ' + \
557
+ str (e ))
500
558
501
559
502
560
@@ -679,7 +737,8 @@ def create_rsa_public_and_private_from_pem(pem, passphrase=None):
679
737
# Or if the key was encrypted but no password was supplied.
680
738
# UnsupportedAlgorithm: If the private key (or if the key is encrypted with
681
739
# an unsupported symmetric cipher) is not supported by the backend.
682
- except (ValueError , TypeError , cryptography .exceptions .UnsupportedAlgorithm ) as e :
740
+ except (ValueError , TypeError ,
741
+ cryptography .exceptions .UnsupportedAlgorithm ) as e :
683
742
# Raise 'securesystemslib.exceptions.CryptoError' and pyca/cryptography's
684
743
# exception message. Avoid propogating pyca/cryptography's exception trace
685
744
# to avoid revealing sensitive error.
@@ -739,7 +798,8 @@ def encrypt_key(key_object, password):
739
798
'1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}}
740
799
>>> passphrase = 'secret'
741
800
>>> encrypted_key = encrypt_key(ed25519_key, passphrase)
742
- >>> securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key.encode('utf-8'))
801
+ >>> securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.matches(
802
+ encrypted_key.encode('utf-8'))
743
803
True
744
804
745
805
<Arguments>
@@ -770,7 +830,8 @@ def encrypt_key(key_object, password):
770
830
encryption key.
771
831
772
832
<Returns>
773
- An encrypted string in 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA' format.
833
+ An encrypted string in 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA'
834
+ format.
774
835
"""
775
836
776
837
if not CRYPTO : # pragma: no cover
@@ -786,7 +847,8 @@ def encrypt_key(key_object, password):
786
847
securesystemslib .formats .PASSWORD_SCHEMA .check_match (password )
787
848
788
849
# Ensure the private portion of the key is included in 'key_object'.
789
- if 'private' not in key_object ['keyval' ] or not key_object ['keyval' ]['private' ]:
850
+ if 'private' not in key_object ['keyval' ] or \
851
+ not key_object ['keyval' ]['private' ]:
790
852
raise securesystemslib .exceptions .FormatError ('Key object does not contain'
791
853
' a private part.' )
792
854
@@ -815,10 +877,10 @@ def decrypt_key(encrypted_key, password):
815
877
"""
816
878
<Purpose>
817
879
Return a string containing 'encrypted_key' in non-encrypted form.
818
- The decrypt_key() function can be applied to the encrypted string to restore
819
- the original key object, a securesystemslib key (e.g., RSAKEY_SCHEMA ,
820
- ED25519KEY_SCHEMA). This function calls the appropriate cryptography module
821
- (i.e., rsa_keys.py) to perform the decryption.
880
+ The decrypt_key() function can be applied to the encrypted string to
881
+ restore the original key object, a securesystemslib key (e.g.,
882
+ RSAKEY_SCHEMA, ED25519KEY_SCHEMA). This function calls the appropriate
883
+ cryptography module (i.e., rsa_keys.py) to perform the decryption.
822
884
823
885
Encrypted securesystemslib keys use AES-256-CTR-Mode and passwords
824
886
strengthened with PBKDF2-HMAC-SHA256 (100K iterations be default, but may
@@ -875,7 +937,8 @@ def decrypt_key(encrypted_key, password):
875
937
used to re-derive the encryption/decryption key.
876
938
877
939
<Returns>
878
- The decrypted key object in 'securesystemslib.formats.ANYKEY_SCHEMA' format.
940
+ The decrypted key object in 'securesystemslib.formats.ANYKEY_SCHEMA'
941
+ format.
879
942
"""
880
943
881
944
if not CRYPTO : # pragma: no cover
@@ -944,11 +1007,11 @@ def _generate_derived_key(password, salt=None, iterations=None):
944
1007
945
1008
def _encrypt (key_data , derived_key_information ):
946
1009
"""
947
- Encrypt 'key_data' using the Advanced Encryption Standard (AES-256) algorithm.
948
- 'derived_key_information' should contain a key strengthened by PBKDF2. The
949
- key size is 256 bits and AES's mode of operation is set to CTR (CounTeR Mode).
950
- The HMAC of the ciphertext is generated to ensure the ciphertext has not been
951
- modified.
1010
+ Encrypt 'key_data' using the Advanced Encryption Standard (AES-256)
1011
+ algorithm. 'derived_key_information' should contain a key strengthened by
1012
+ PBKDF2. The key size is 256 bits and AES's mode of operation is set to CTR
1013
+ (CounTeR Mode). The HMAC of the ciphertext is generated to ensure the
1014
+ ciphertext has not been modified.
952
1015
953
1016
'key_data' is the JSON string representation of the key. In the case
954
1017
of RSA keys, this format would be 'securesystemslib.formats.RSAKEY_SCHEMA':
@@ -984,15 +1047,15 @@ def _encrypt(key_data, derived_key_information):
984
1047
985
1048
# Encrypt the plaintext and get the associated ciphertext.
986
1049
# Do we need to check for any exceptions?
987
- ciphertext = encryptor .update (key_data .encode ('utf-8' )) + encryptor .finalize ()
1050
+ ciphertext = encryptor .update (key_data .encode ('utf-8' )) + \
1051
+ encryptor .finalize ()
988
1052
989
1053
# Generate the hmac of the ciphertext to ensure it has not been modified.
990
1054
# The decryption routine may verify a ciphertext without having to perform
991
1055
# a decryption operation.
992
1056
symmetric_key = derived_key_information ['derived_key' ]
993
1057
salt = derived_key_information ['salt' ]
994
- hmac_object = \
995
- cryptography .hazmat .primitives .hmac .HMAC (symmetric_key , hashes .SHA256 (),
1058
+ hmac_object = hmac .HMAC (symmetric_key , hashes .SHA256 (),
996
1059
backend = default_backend ())
997
1060
hmac_object .update (ciphertext )
998
1061
hmac_value = binascii .hexlify (hmac_object .finalize ())
@@ -1031,7 +1094,7 @@ def _decrypt(file_contents, password):
1031
1094
# separating. Raise 'securesystemslib.exceptions.CryptoError', if
1032
1095
# 'file_contents' does not contains the expected data layout.
1033
1096
try :
1034
- salt , iterations , hmac , iv , ciphertext = \
1097
+ salt , iterations , observed_hmac , iv , ciphertext = \
1035
1098
file_contents .split (_ENCRYPTION_DELIMITER )
1036
1099
1037
1100
except ValueError :
@@ -1054,14 +1117,14 @@ def _decrypt(file_contents, password):
1054
1117
# See the encryption routine for why we use the encrypt-then-MAC approach.
1055
1118
# The decryption routine may verify a ciphertext without having to perform
1056
1119
# a decryption operation.
1057
- generated_hmac_object = \
1058
- cryptography .hazmat .primitives .hmac .HMAC (symmetric_key , hashes .SHA256 (),
1120
+ generated_hmac_object = hmac .HMAC (symmetric_key , hashes .SHA256 (),
1059
1121
backend = default_backend ())
1060
1122
generated_hmac_object .update (ciphertext )
1061
1123
generated_hmac = binascii .hexlify (generated_hmac_object .finalize ())
1062
1124
1063
1125
1064
- if not securesystemslib .util .digests_are_equal (generated_hmac .decode (), hmac ):
1126
+ if not securesystemslib .util .digests_are_equal (
1127
+ generated_hmac .decode (), observed_hmac ):
1065
1128
raise securesystemslib .exceptions .CryptoError ('Decryption failed.' )
1066
1129
1067
1130
# Construct a Cipher object, with the key and iv.
0 commit comments