- 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:
Michael Greene
2022-11-23 15:33:36 -08:00
parent 830bdab323
commit eead880d09
3 changed files with 87 additions and 89 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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):