- move FlagEnum and FlagEnumMeta back into types.py where they belong
- fix capitalization and spelling of SecurityIssues flag members - pythonize the names of SAFE_CURVES and MINIMUM_ASYMMETRIC_KE?Y_LENGTHS - move the functionality of is_hash_considered_secure into a HashAlgorithm property called is_considered_secure where it always should have been - move the functionality of check_assymetric_algo_and_its_parameters into a PubKeyAlgorithm function called validate_params like it always should have been
This commit is contained in:
@@ -12,7 +12,6 @@ from collections import namedtuple
|
||||
from enum import Enum
|
||||
from enum import IntEnum
|
||||
from enum import IntFlag
|
||||
from enum import EnumMeta
|
||||
|
||||
from pyasn1.type.univ import ObjectIdentifier
|
||||
|
||||
@@ -20,6 +19,7 @@ from cryptography.hazmat.backends import openssl
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
|
||||
from .types import FlagEnum
|
||||
from .decorators import classproperty
|
||||
from ._curves import BrainpoolP256R1, BrainpoolP384R1, BrainpoolP512R1, X25519, Ed25519
|
||||
|
||||
@@ -41,31 +41,15 @@ __all__ = ['Backend',
|
||||
'TrustLevel',
|
||||
'KeyFlags',
|
||||
'Features',
|
||||
'FlagEnumMeta',
|
||||
'RevocationKeyClass',
|
||||
'NotationDataFlags',
|
||||
'TrustFlags',
|
||||
'check_assymetric_algo_and_its_parameters',
|
||||
'is_hash_considered_secure']
|
||||
'TrustFlags',]
|
||||
|
||||
|
||||
# this is 50 KiB
|
||||
_hashtunedata = bytearray([10, 11, 12, 13, 14, 15, 16, 17] * 128 * 50)
|
||||
|
||||
|
||||
class FlagEnumMeta(EnumMeta):
|
||||
def __and__(self, other):
|
||||
return { f for f in iter(self) if f.value & other }
|
||||
|
||||
def __rand__(self, other): # pragma: no cover
|
||||
return self & other
|
||||
|
||||
|
||||
namespace = FlagEnumMeta.__prepare__('FlagEnum', (IntEnum,))
|
||||
FlagEnum = FlagEnumMeta('FlagEnum', (IntEnum,), namespace)
|
||||
|
||||
|
||||
|
||||
class Backend(Enum):
|
||||
OpenSSL = openssl.backend
|
||||
|
||||
@@ -301,6 +285,26 @@ class PubKeyAlgorithm(IntEnum):
|
||||
PubKeyAlgorithm.RSASign,
|
||||
PubKeyAlgorithm.FormerlyElGamalEncryptOrSign}
|
||||
|
||||
def validate_params(self, size):
|
||||
min_size = MINIMUM_ASYMMETRIC_KEY_LENGTHS.get(self)
|
||||
if min_size is not None:
|
||||
if isinstance(min_size, set):
|
||||
# ECC
|
||||
curve = size
|
||||
safe_curves = min_size
|
||||
if curve in safe_curves:
|
||||
return SecurityIssues.OK
|
||||
else:
|
||||
return SecurityIssues.InsecureCurve
|
||||
else:
|
||||
# not ECC
|
||||
if size >= min_size:
|
||||
return SecurityIssues.OK
|
||||
else:
|
||||
return SecurityIssues.AsymmetricKeyLengthIsTooShort
|
||||
# min_size is None
|
||||
return SecurityIssues.BrokenAsymmetricFunc
|
||||
|
||||
|
||||
class CompressionAlgorithm(IntEnum):
|
||||
"""Supported compression algorithms."""
|
||||
@@ -361,7 +365,6 @@ class HashAlgorithm(IntEnum):
|
||||
#SHA3_256 = 13
|
||||
#SHA3_384 = 14
|
||||
#SHA3_512 = 15
|
||||
|
||||
|
||||
def __init__(self, *args):
|
||||
super(self.__class__, self).__init__()
|
||||
@@ -383,9 +386,27 @@ class HashAlgorithm(IntEnum):
|
||||
def is_supported(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def is_second_preimage_resistant(self):
|
||||
return self in {HashAlgorithm.SHA1}
|
||||
|
||||
@property
|
||||
def is_collision_resistant(self):
|
||||
return self in {HashAlgorithm.SHA256, HashAlgorithm.SHA384, HashAlgorithm.SHA512}
|
||||
|
||||
@property
|
||||
def is_considered_secure(self):
|
||||
if self.is_collision_resistant:
|
||||
return SecurityIssues.OK
|
||||
|
||||
warnings.warn('Hash function {hash} is not considered collision resistant'.format(hash=repr(self)))
|
||||
issues = SecurityIssues.HashFunctionNotCollisionResistant
|
||||
|
||||
if not self.is_second_preimage_resistant:
|
||||
issues |= SecurityIssues.HashFunctionNotSecondPreimageResistant
|
||||
|
||||
return issues
|
||||
|
||||
secondPreimageResistantHashes = {HashAlgorithm.SHA1}
|
||||
collisionResistantHashses = {HashAlgorithm.SHA256, HashAlgorithm.SHA384, HashAlgorithm.SHA512}
|
||||
|
||||
class RevocationReason(IntEnum):
|
||||
"""Reasons explaining why a key or certificate was revoked."""
|
||||
@@ -566,67 +587,32 @@ class TrustFlags(FlagEnum):
|
||||
|
||||
class SecurityIssues(IntFlag):
|
||||
OK = 0
|
||||
wrongSig = (1 << 0)
|
||||
expired = (1 << 1)
|
||||
disabled = (1 << 2)
|
||||
revoked = (1 << 3)
|
||||
invalid = (1 << 4)
|
||||
brokenAssymetricFunc = (1 << 5)
|
||||
hashFunctionNotCollisionResistant = (1 << 6)
|
||||
hashFunctionNotSecondPreimageResistant = (1 << 7)
|
||||
assymetricKeyLengthIsTooShort = (1 << 8)
|
||||
insecureCurve = (1 << 9)
|
||||
noSelfSignature = (1 << 10)
|
||||
WrongSig = (1 << 0)
|
||||
Expired = (1 << 1)
|
||||
Disabled = (1 << 2)
|
||||
Revoked = (1 << 3)
|
||||
Invalid = (1 << 4)
|
||||
BrokenAsymmetricFunc = (1 << 5)
|
||||
HashFunctionNotCollisionResistant = (1 << 6)
|
||||
HashFunctionNotSecondPreimageResistant = (1 << 7)
|
||||
AsymmetricKeyLengthIsTooShort = (1 << 8)
|
||||
InsecureCurve = (1 << 9)
|
||||
NoSelfSignature = (1 << 10)
|
||||
|
||||
|
||||
# https://safecurves.cr.yp.to/
|
||||
safeCurves = {
|
||||
SAFE_CURVES = {
|
||||
EllipticCurveOID.Curve25519,
|
||||
EllipticCurveOID.Ed25519,
|
||||
}
|
||||
|
||||
minimumAssymetricKeyLegths = {
|
||||
MINIMUM_ASYMMETRIC_KEY_LENGTHS = {
|
||||
PubKeyAlgorithm.RSAEncryptOrSign: 2048,
|
||||
PubKeyAlgorithm.RSASign: 2048,
|
||||
PubKeyAlgorithm.ElGamal: 2048,
|
||||
PubKeyAlgorithm.DSA: 2048,
|
||||
|
||||
PubKeyAlgorithm.ECDSA: safeCurves,
|
||||
PubKeyAlgorithm.EdDSA: safeCurves,
|
||||
PubKeyAlgorithm.ECDH: safeCurves,
|
||||
PubKeyAlgorithm.ECDSA: SAFE_CURVES,
|
||||
PubKeyAlgorithm.EdDSA: SAFE_CURVES,
|
||||
PubKeyAlgorithm.ECDH: SAFE_CURVES,
|
||||
}
|
||||
|
||||
|
||||
|
||||
def is_hash_considered_secure(hash):
|
||||
if hash in collisionResistantHashses:
|
||||
return SecurityIssues.OK
|
||||
|
||||
warnings.warn("Hash function " + repr(hash) + " is not considered collision resistant")
|
||||
issues = SecurityIssues.hashFunctionNotCollisionResistant
|
||||
|
||||
if hash not in secondPreimageResistantHashes:
|
||||
issues |= hashFunctionNotSecondPreimageResistant
|
||||
|
||||
return issues
|
||||
|
||||
def check_assymetric_algo_and_its_parameters(algo, size):
|
||||
if algo in minimumAssymetricKeyLegths:
|
||||
minLOrSetOfSecureCurves = minimumAssymetricKeyLegths[algo]
|
||||
if isinstance(minLOrSetOfSecureCurves, set): # ECC
|
||||
curve = size
|
||||
safeCurvesForThisAlg = minLOrSetOfSecureCurves
|
||||
if curve in safeCurvesForThisAlg:
|
||||
return SecurityIssues.OK
|
||||
else:
|
||||
warnings.warn("Curve " + repr(curve) + " is not considered secure for " + repr(algo))
|
||||
return SecurityIssues.insecureCurve
|
||||
else:
|
||||
minL = minLOrSetOfSecureCurves
|
||||
if size < minL:
|
||||
warnings.warn("Assymetric algo " + repr(algo) + " needs key at least of " + repr(minL) + " bits effective length to be considered secure")
|
||||
return SecurityIssues.assymetricKeyLengthIsTooShort
|
||||
else:
|
||||
return SecurityIssues.OK
|
||||
else:
|
||||
warnings.warn("Assymetric algo " + repr(algo) + " is not considered secure")
|
||||
return SecurityIssues.brokenAssymetricFunc
|
||||
|
||||
22
pgpy/pgp.py
22
pgpy/pgp.py
@@ -35,8 +35,6 @@ from .constants import RevocationReason
|
||||
from .constants import SignatureType
|
||||
from .constants import SymmetricKeyAlgorithm
|
||||
from .constants import SecurityIssues
|
||||
from .constants import check_assymetric_algo_and_its_parameters
|
||||
from .constants import is_hash_considered_secure
|
||||
|
||||
from .decorators import KeyAction
|
||||
|
||||
@@ -172,10 +170,9 @@ class PGPSignature(Armorable, ParentRef, PGPObject):
|
||||
The :py:obj:`~constants.HashAlgorithm` used when computing this signature.
|
||||
"""
|
||||
return self._signature.halg
|
||||
|
||||
|
||||
|
||||
def check_primitives(self):
|
||||
return is_hash_considered_secure(self.hash_algorithm)
|
||||
return self.hash_algorithm.is_considered_secure
|
||||
|
||||
def check_soundness(self):
|
||||
return self.check_primitives()
|
||||
@@ -2375,10 +2372,10 @@ class PGPKey(Armorable, ParentRef, PGPObject):
|
||||
if selfSigs:
|
||||
for s in selfSigs:
|
||||
if not self.verify(self, s):
|
||||
res |= SecurityIssues.invalid
|
||||
res |= SecurityIssues.Invalid
|
||||
break
|
||||
else:
|
||||
return SecurityIssues.noSelfSignature
|
||||
return SecurityIssues.NoSelfSignature
|
||||
return res
|
||||
|
||||
def _do_self_signatures_verification(self):
|
||||
@@ -2400,17 +2397,17 @@ class PGPKey(Armorable, ParentRef, PGPObject):
|
||||
return self._self_verified
|
||||
|
||||
def check_primitives(self):
|
||||
return check_assymetric_algo_and_its_parameters(self.key_algorithm, self.key_size)
|
||||
return self.key_algorithm.validate_params(self.key_size)
|
||||
|
||||
def check_management(self, self_verifying=False):
|
||||
res = self.self_verified
|
||||
if self.is_expired:
|
||||
warnings.warn("Key " + repr(self) + " has expired at " + str(self.expires_at))
|
||||
res |= SecurityIssues.expired
|
||||
res |= SecurityIssues.Expired
|
||||
|
||||
warnings.warn("TODO: Revocation checks are not yet implemented!!!")
|
||||
warnings.warn("TODO: Flags (s.a. `disabled`) checks are not yet implemented!!!")
|
||||
res |= int(bool(list(self.revocation_signatures))) * SecurityIssues.revoked
|
||||
res |= int(bool(list(self.revocation_signatures))) * SecurityIssues.Revoked
|
||||
return res
|
||||
|
||||
def check_soundness(self, self_verifying=False):
|
||||
@@ -2463,7 +2460,6 @@ class PGPKey(Armorable, ParentRef, PGPObject):
|
||||
for subkey in subject.subkeys.values():
|
||||
for sig in _filter_sigs(subkey.__sig__):
|
||||
sspairs.append((sig, subkey))
|
||||
|
||||
|
||||
elif signature.signer in {self.fingerprint.keyid} | set(self.subkeys):
|
||||
sspairs += [(signature, subject)]
|
||||
@@ -2487,7 +2483,7 @@ class PGPKey(Armorable, ParentRef, PGPObject):
|
||||
signature_issues = self.check_primitives()
|
||||
|
||||
if self_verifying:
|
||||
signature_issues &= ~SecurityIssues.hashFunctionNotCollisionResistant
|
||||
signature_issues &= ~SecurityIssues.HashFunctionNotCollisionResistant
|
||||
|
||||
issues = signature_issues | subkey_issues
|
||||
if issues:
|
||||
@@ -2497,7 +2493,7 @@ class PGPKey(Armorable, ParentRef, PGPObject):
|
||||
if verified is NotImplemented:
|
||||
raise NotImplementedError(sig.key_algorithm)
|
||||
|
||||
sigv.add_sigsubj(sig, self, subj, SecurityIssues.wrongSig if not verified else SecurityIssues.OK)
|
||||
sigv.add_sigsubj(sig, self, subj, SecurityIssues.WrongSig if not verified else SecurityIssues.OK)
|
||||
|
||||
return sigv
|
||||
|
||||
|
||||
@@ -20,12 +20,14 @@ from enum import IntEnum
|
||||
from .decorators import sdproperty
|
||||
|
||||
from .errors import PGPError
|
||||
from .constants import SecurityIssues
|
||||
|
||||
__all__ = ['Armorable',
|
||||
'ParentRef',
|
||||
'PGPObject',
|
||||
'Field',
|
||||
'Fingerprint',
|
||||
'FlagEnum',
|
||||
'FlagEnumMeta',
|
||||
'Header',
|
||||
'MetaDispatchable',
|
||||
'Dispatchable',
|
||||
@@ -624,10 +626,25 @@ class SignatureVerification(object):
|
||||
def __repr__(self):
|
||||
return "<"+ self.__class__.__name__ + "({" + str(bool(self)) + "})>"
|
||||
|
||||
def add_sigsubj(self, signature, by, subject=None, issues=SecurityIssues(0xFF)):
|
||||
def add_sigsubj(self, signature, by, subject=None, issues=None):
|
||||
if issues is None:
|
||||
from .constants import SecurityIssues
|
||||
issues = SecurityIssues(0xFF)
|
||||
self._subjects.append(self._sigsubj(issues, by, signature, subject))
|
||||
|
||||
|
||||
class FlagEnumMeta(EnumMeta):
|
||||
def __and__(self, other):
|
||||
return { f for f in iter(self) if f.value & other }
|
||||
|
||||
def __rand__(self, other): # pragma: no cover
|
||||
return self & other
|
||||
|
||||
|
||||
namespace = FlagEnumMeta.__prepare__('FlagEnum', (IntEnum,))
|
||||
FlagEnum = FlagEnumMeta('FlagEnum', (IntEnum,), namespace)
|
||||
|
||||
|
||||
class Fingerprint(str):
|
||||
"""
|
||||
A subclass of ``str``. Can be compared using == and != to ``str``, ``unicode``, and other :py:obj:`Fingerprint` instances.
|
||||
@@ -691,7 +708,6 @@ class Fingerprint(str):
|
||||
return self.__class__.__name__+"("+repr(self.__pretty__())+")"
|
||||
|
||||
|
||||
|
||||
class SorteDeque(collections.deque):
|
||||
"""A deque subclass that tries to maintain sorted ordering using bisect"""
|
||||
def insort(self, item):
|
||||
|
||||
Reference in New Issue
Block a user