-
Notifications
You must be signed in to change notification settings - Fork 81
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
473 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
# Author: Stanislav Zidek | ||
# See the LICENSE file for legal information regarding use of this file. | ||
|
||
"""Abstract class for ECDSA.""" | ||
|
||
from .cryptomath import * | ||
from . import tlshashlib as hashlib | ||
from ..errors import MaskTooLongError, MessageTooLongError, EncodingError, \ | ||
InvalidSignature | ||
|
||
|
||
class ECDSAKey(object): | ||
"""This is an abstract base class for ECDSA keys. | ||
Particular implementations of ECDSA keys, such as | ||
:py:class:`~.python_ecdsakey.Python_ECDSAKey` | ||
... more coming | ||
inherit from this. | ||
To create or parse an ECDSA key, don't use one of these classes | ||
directly. Instead, use the factory functions in | ||
:py:class:`~tlslite.utils.keyfactory`. | ||
""" | ||
|
||
def __init__(self, public_key, private_key): | ||
"""Create a new ECDSA key. | ||
If public_key or private_key are passed in, the new key | ||
will be initialized. | ||
:param public_key: ECDSA public key. | ||
:param private_key: ECDSA private key. | ||
""" | ||
raise NotImplementedError() | ||
|
||
def __len__(self): | ||
"""Return the length of this key in bits. | ||
:rtype: int | ||
""" | ||
raise NotImplementedError() | ||
|
||
def hasPrivateKey(self): | ||
"""Return whether or not this key has a private component. | ||
:rtype: bool | ||
""" | ||
raise NotImplementedError() | ||
|
||
def _sign(self, data): | ||
raise NotImplementedError() | ||
|
||
def _hashAndSign(self, data, hAlg): | ||
raise NotImplementedError() | ||
|
||
def hashAndSign(self, bytes, rsaScheme=None, hAlg='sha1', sLen=None): | ||
"""Hash and sign the passed-in bytes. | ||
This requires the key to have a private component. It performs | ||
a signature on the passed-in data with selected hash algorithm. | ||
:type bytes: str or bytearray | ||
:param bytes: The value which will be hashed and signed. | ||
:type rsaScheme: str | ||
:param rsaScheme: Ignored | ||
:type hAlg: str | ||
:param hAlg: The hash algorithm that will be used | ||
:type sLen: int | ||
:param sLen: Ignored | ||
:rtype: bytearray | ||
:returns: A PKCS1 or PSS signature on the passed-in data. | ||
""" | ||
rsaScheme = rsaScheme.lower() | ||
hAlg = hAlg.lower() | ||
hashBytes = secureHash(bytearray(bytes), hAlg) | ||
return self.sign(hashBytes, padding=rsaScheme, hashAlg=hAlg, | ||
saltLen=sLen) | ||
|
||
def hashAndVerify(self, sigBytes, bytes, rsaScheme='PKCS1', hAlg='sha1', | ||
sLen=0): | ||
"""Hash and verify the passed-in bytes with the signature. | ||
This verifies a PKCS1 or PSS signature on the passed-in data | ||
with selected hash algorithm. | ||
:type sigBytes: bytearray | ||
:param sigBytes: A PKCS1 or PSS signature. | ||
:type bytes: str or bytearray | ||
:param bytes: The value which will be hashed and verified. | ||
:type rsaScheme: str | ||
:param rsaScheme: The type of ECDSA scheme that will be applied, | ||
"PKCS1" for ECDSASSA-PKCS#1 v1.5 signature and "PSS" | ||
for ECDSASSA-PSS with MGF1 signature method | ||
:type hAlg: str | ||
:param hAlg: The hash algorithm that will be used | ||
:type sLen: int | ||
:param sLen: The length of intended salt value, applicable only | ||
for ECDSASSA-PSS signatures | ||
:rtype: bool | ||
:returns: Whether the signature matches the passed-in data. | ||
""" | ||
rsaScheme = rsaScheme.lower() | ||
hAlg = hAlg.lower() | ||
|
||
hashBytes = secureHash(bytearray(bytes), hAlg) | ||
return self.verify(sigBytes, hashBytes, rsaScheme, hAlg, sLen) | ||
|
||
def sign(self, bytes, padding='pkcs1', hashAlg=None, saltLen=None): | ||
"""Sign the passed-in bytes. | ||
This requires the key to have a private component. It performs | ||
a PKCS1 signature on the passed-in data. | ||
:type bytes: bytearray | ||
:param bytes: The value which will be signed. | ||
:type padding: str | ||
:param padding: name of the rsa padding mode to use, supported: | ||
"pkcs1" for ECDSASSA-PKCS1_1_5 and "pss" for ECDSASSA-PSS. | ||
:type hashAlg: str | ||
:param hashAlg: name of hash to be encoded using the PKCS#1 prefix | ||
for "pkcs1" padding or the hash used for MGF1 in "pss". Parameter | ||
is mandatory for "pss" padding. | ||
:type saltLen: int | ||
:param saltLen: length of salt used for the PSS padding. Default | ||
is the length of the hash output used. | ||
:rtype: bytearray | ||
:returns: A PKCS1 signature on the passed-in data. | ||
""" | ||
padding = padding.lower() | ||
if padding == 'pkcs1': | ||
if hashAlg is not None: | ||
bytes = self.addPKCS1Prefix(bytes, hashAlg) | ||
sigBytes = self._raw_pkcs1_sign(bytes) | ||
elif padding == "pss": | ||
sigBytes = self.ECDSASSA_PSS_sign(bytes, hashAlg, saltLen) | ||
else: | ||
raise UnknownECDSAType("Unknown ECDSA algorithm type") | ||
return sigBytes | ||
|
||
def verify(self, sigBytes, bytes, padding='pkcs1', hashAlg=None, | ||
saltLen=None): | ||
"""Verify the passed-in bytes with the signature. | ||
This verifies a PKCS1 signature on the passed-in data. | ||
:type sigBytes: bytearray | ||
:param sigBytes: A PKCS1 signature. | ||
:type bytes: bytearray | ||
:param bytes: The value which will be verified. | ||
:rtype: bool | ||
:returns: Whether the signature matches the passed-in data. | ||
""" | ||
if padding == "pkcs1" and hashAlg == 'sha1': | ||
# Try it with/without the embedded NULL | ||
prefixedHashBytes1 = self.addPKCS1SHA1Prefix(bytes, False) | ||
prefixedHashBytes2 = self.addPKCS1SHA1Prefix(bytes, True) | ||
result1 = self._raw_pkcs1_verify(sigBytes, prefixedHashBytes1) | ||
result2 = self._raw_pkcs1_verify(sigBytes, prefixedHashBytes2) | ||
return (result1 or result2) | ||
elif padding == 'pkcs1': | ||
if hashAlg is not None: | ||
bytes = self.addPKCS1Prefix(bytes, hashAlg) | ||
res = self._raw_pkcs1_verify(sigBytes, bytes) | ||
return res | ||
elif padding == "pss": | ||
try: | ||
res = self.ECDSASSA_PSS_verify(bytes, sigBytes, hashAlg, saltLen) | ||
except InvalidSignature: | ||
res = False | ||
return res | ||
else: | ||
raise UnknownECDSAType("Unknown ECDSA algorithm type") | ||
|
||
def acceptsPassword(self): | ||
"""Return True if the write() method accepts a password for use | ||
in encrypting the private key. | ||
:rtype: bool | ||
""" | ||
raise NotImplementedError() | ||
|
||
def write(self, password=None): | ||
"""Return a string containing the key. | ||
:rtype: str | ||
:returns: A string describing the key, in whichever format (PEM) | ||
is native to the implementation. | ||
""" | ||
raise NotImplementedError() | ||
|
||
@staticmethod | ||
def generate(bits): | ||
"""Generate a new key with the specified bit length. | ||
:rtype: ~tlslite.utils.ECDSAKey.ECDSAKey | ||
""" | ||
raise NotImplementedError() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
|
||
from .ecdsakey import ECDSAKey | ||
from ecdsa.util import sigencode_der | ||
from .tlshashlib import new | ||
|
||
class Python_ECDSAKey(ECDSAKey): | ||
def __init__(self, public_key, private_key=None): | ||
if not public_key and private_key: | ||
self.public_key = private_key.get_verifying_key() | ||
else: | ||
self.public_key = public_key | ||
|
||
self.private_key = private_key | ||
|
||
def hasPrivateKey(self): | ||
return bool(self.private_key) | ||
|
||
def acceptsPassword(self): | ||
return False | ||
|
||
def generate(bits): | ||
raise NotImplementedError() | ||
|
||
def _sign(self, data): | ||
return private_key.sign_digest_deterministic(data, | ||
sigencode=sigencode_der) | ||
|
||
def _hashAndSign(self, data, hAlg): | ||
return private_key.sign_deterministic(data, | ||
hash=new(hAlg), | ||
sigencode=sigencode_der) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
|
||
from .python_rsakey import Python_RSAKey | ||
from .python_ecdsakey import Python_ECDSAKey | ||
from .pem import dePem, pemSniff | ||
from .asn1parser import ASN1Parser | ||
from .cryptomath import bytesToNumber | ||
from ecdsa.curves import NIST256p | ||
from ecdsa.keys import SigningKey, VerifyingKey | ||
|
||
class Python_Key(object): | ||
|
||
@staticmethod | ||
def parsePEM(s, passwordCallback=None): | ||
"""Parse a string containing a PEM-encoded <privateKey>.""" | ||
|
||
if pemSniff(s, "PRIVATE KEY"): | ||
bytes = dePem(s, "PRIVATE KEY") | ||
return Python_Key._parsePKCS8(bytes) | ||
elif pemSniff(s, "RSA PRIVATE KEY"): | ||
bytes = dePem(s, "RSA PRIVATE KEY") | ||
return Python_Key._parseSSLeay(bytes) | ||
elif pemSniff(s, "EC PRIVATE KEY"): | ||
bytes = dePem(s, "EC PRIVATE KEY") | ||
return Python_Key._parseECSSLeay(bytes) | ||
else: | ||
raise SyntaxError("Not a PEM private key file") | ||
|
||
@staticmethod | ||
def _parsePKCS8(bytes): | ||
p = ASN1Parser(bytes) | ||
|
||
# first element in PrivateKeyInfo is an INTEGER | ||
version = p.getChild(0).value | ||
if bytesToNumber(version) != 0: | ||
raise SyntaxError("Unrecognized PKCS8 version") | ||
|
||
# second element in PrivateKeyInfo is a SEQUENCE of type | ||
# AlgorithmIdentifier | ||
algIdent = p.getChild(1) | ||
seqLen = algIdent.getChildCount() | ||
# first item of AlgorithmIdentifier is an OBJECT (OID) | ||
oid = algIdent.getChild(0) | ||
if list(oid.value) == [42, 134, 72, 134, 247, 13, 1, 1, 1]: | ||
keyType = "rsa" | ||
elif list(oid.value) == [42, 134, 72, 134, 247, 13, 1, 1, 10]: | ||
keyType = "rsa-pss" | ||
elif list(oid.value) == [42, 134, 72, 206, 61, 2, 1]: | ||
keyType = "ecdsa" | ||
else: | ||
raise SyntaxError("Unrecognized AlgorithmIdentifier: {0}" | ||
.format(list(oid.value))) | ||
# second item of AlgorithmIdentifier are parameters (defined by | ||
# above algorithm) | ||
if keyType == "rsa": | ||
if seqLen != 2: | ||
raise SyntaxError("Missing parameters for RSA algorithm ID") | ||
parameters = algIdent.getChild(1) | ||
if parameters.value != bytearray(0): | ||
raise SyntaxError("RSA parameters are not NULL") | ||
elif keyType == "ecdsa": | ||
if seqLen != 2: | ||
raise SyntaxError("Invalid encoding of algorithm identifier") | ||
curveID = algIdent.getChild(1) | ||
if list(curveID.value) == [42, 134, 72, 206, 61, 3, 1, 7]: | ||
curve = NIST256p | ||
else: | ||
raise SyntaxError("Unknown curve") | ||
else: # rsa-pss | ||
pass # ignore parameters - don't apply restrictions | ||
|
||
if seqLen > 2: | ||
raise SyntaxError("Invalid encoding of AlgorithmIdentifier") | ||
|
||
#Get the privateKey | ||
privateKeyP = p.getChild(2) | ||
|
||
#Adjust for OCTET STRING encapsulation | ||
privateKeyP = ASN1Parser(privateKeyP.value) | ||
|
||
if keyType == "ecdsa": | ||
return Python_Key._parseECDSAPrivateKey(privateKeyP, | ||
curve) | ||
else: | ||
return Python_Key._parseASN1PrivateKey(privateKeyP) | ||
|
||
@staticmethod | ||
def _parseSSLeay(bytes): | ||
privateKeyP = ASN1Parser(bytes) | ||
return Python_Key._parseASN1PrivateKey(privateKeyP) | ||
|
||
@staticmethod | ||
def _parseECSSLeay(bytes): | ||
return Python_ECDSAKey(None, SigningKey.from_der(bytes)) | ||
|
||
@staticmethod | ||
def _parseECDSAPrivateKey(private, curve): | ||
ver = private.getChild(0) | ||
if ver.value != b'\x01': | ||
raise SyntaxError("Unexpect EC key version") | ||
private_key = private.getChild(1) | ||
public_key = private.getChild(2) | ||
# first two bytes are the ASN.1 custom type and the length of payload | ||
# while the latter two bytes are just specification of the public | ||
# key encoding (uncompressed) | ||
if list(public_key.value[:1]) != [3] or \ | ||
list(public_key.value[2:4]) != [0, 4]: | ||
raise SyntaxError("Invalid or unsupported encoding of public key") | ||
return Python_ECDSAKey(VerifyingKey.from_string(public_key.value[4:], | ||
curve), | ||
SigningKey.from_string(private_key.value, | ||
curve)) | ||
|
||
@staticmethod | ||
def _parseASN1PrivateKey(privateKeyP): | ||
version = privateKeyP.getChild(0).value[0] | ||
if version != 0: | ||
raise SyntaxError("Unrecognized RSAPrivateKey version") | ||
n = bytesToNumber(privateKeyP.getChild(1).value) | ||
e = bytesToNumber(privateKeyP.getChild(2).value) | ||
d = bytesToNumber(privateKeyP.getChild(3).value) | ||
p = bytesToNumber(privateKeyP.getChild(4).value) | ||
q = bytesToNumber(privateKeyP.getChild(5).value) | ||
dP = bytesToNumber(privateKeyP.getChild(6).value) | ||
dQ = bytesToNumber(privateKeyP.getChild(7).value) | ||
qInv = bytesToNumber(privateKeyP.getChild(8).value) | ||
return Python_RSAKey(n, e, d, p, q, dP, dQ, qInv) | ||
|
Oops, something went wrong.