diff --git a/.travis/install.sh b/.travis/install.sh index 447603ba7826c738b4d2d77f45297d0ed13b8578..ab33fd6062ee4bd8c80887158aa4ab67060b0223 100644 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -3,7 +3,7 @@ if [ -z $TRAVIS_SUDO ] && [ "$TRAVIS_OS_NAME" = "osx" ] then PIP_INSTALL_FLAGS="--user" fi -$TRAVIS_SUDO pip install $PIP_INSTALL_FLAGS pycrypto mock +$TRAVIS_SUDO pip install $PIP_INSTALL_FLAGS pycrypto ecdsa mock #Â Install pcap & dnet if [ ! -z $SCAPY_USE_PCAPDNET ] diff --git a/scapy/all.py b/scapy/all.py index d39eb322253b6b9049dbe19c9686ac8c1045b94c..15dd3ca43adf01673140a43fc75d3bcd46e58b35 100644 --- a/scapy/all.py +++ b/scapy/all.py @@ -43,7 +43,5 @@ from asn1.asn1 import * from asn1.ber import * from asn1.mib import * -from crypto import * - from pipetool import * from scapypipes import * diff --git a/scapy/asn1/asn1.py b/scapy/asn1/asn1.py index 624a5e32434de8a6cba2cbea8778267370b0392e..6823d45ba0e406d7685e8962cb714a75c10d2a46 100644 --- a/scapy/asn1/asn1.py +++ b/scapy/asn1/asn1.py @@ -257,7 +257,8 @@ class ASN1_BOOLEAN(ASN1_INTEGER): class ASN1_BIT_STRING(ASN1_Object): """ /!\ ASN1_BIT_STRING values are bit strings like "011101". - /!\ A zero-bit padded readable string is provided nonetheless. + /!\ A zero-bit padded readable string is provided nonetheless, + /!\ which is also output when __str__ is called. """ tag = ASN1_Class_UNIVERSAL.BIT_STRING def __init__(self, val, readable=False): @@ -282,6 +283,8 @@ class ASN1_BIT_STRING(ASN1_Object): if len(s) > 20: s = s[:10] + "..." + s[-10:] return "<%s[%r] (%d unused bit%s)>" % (self.__dict__.get("name", self.__class__.__name__), s, self.unused_bits, "s" if self.unused_bits>1 else "") + def __str__(self): + return self.val_readable class ASN1_STRING(ASN1_Object): tag = ASN1_Class_UNIVERSAL.STRING diff --git a/scapy/asn1/mib.py b/scapy/asn1/mib.py index a77bbe84f57c1f20789ec22e4c9d19b6a3a409aa..5506c0f99f479292a800ae67467ba4887ce5e184 100644 --- a/scapy/asn1/mib.py +++ b/scapy/asn1/mib.py @@ -450,6 +450,16 @@ x962Signature_oids = { ####### elliptic curves ####### +ansiX962Curve_oids = { + "prime192v1" : "1.2.840.10045.3.1.1", + "prime192v2" : "1.2.840.10045.3.1.2", + "prime192v3" : "1.2.840.10045.3.1.3", + "prime239v1" : "1.2.840.10045.3.1.4", + "prime239v2" : "1.2.840.10045.3.1.5", + "prime239v3" : "1.2.840.10045.3.1.6", + "prime256v1" : "1.2.840.10045.3.1.7" + } + certicomCurve_oids = { "ansit163k1" : "1.3.132.0.1", "ansit163r1" : "1.3.132.0.2", @@ -558,6 +568,7 @@ x509_oids_sets = [ evPolicy_oids, x962KeyType_oids, x962Signature_oids, + ansiX962Curve_oids, certicomCurve_oids ] @@ -567,3 +578,28 @@ for oids_set in x509_oids_sets: x509_oids.update(oids_set) conf.mib = MIBDict(_name="MIB", **x509_oids) + + +######################### +## Hash mapping helper ## +######################### + +# This dict enables static access to string references to the hash functions +# of some algorithms from pkcs1_oids and x962Signature_oids. + +hash_by_oid = { + "1.2.840.113549.1.1.2" : "md2", + "1.2.840.113549.1.1.3" : "md4", + "1.2.840.113549.1.1.4" : "md5", + "1.2.840.113549.1.1.5" : "sha1", + "1.2.840.113549.1.1.11" : "sha256", + "1.2.840.113549.1.1.12" : "sha384", + "1.2.840.113549.1.1.13" : "sha512", + "1.2.840.113549.1.1.14" : "sha224", + "1.2.840.10045.4.1" : "sha1", + "1.2.840.10045.4.3.1" : "sha224", + "1.2.840.10045.4.3.2" : "sha256", + "1.2.840.10045.4.3.3" : "sha384", + "1.2.840.10045.4.3.4" : "sha512" + } + diff --git a/scapy/crypto/cert.py b/scapy/crypto/cert.py deleted file mode 100644 index c89656a58c5ffe9e1d1ca5ddaca1a3ed6c522d92..0000000000000000000000000000000000000000 --- a/scapy/crypto/cert.py +++ /dev/null @@ -1,2485 +0,0 @@ -## This file is part of Scapy -## See http://www.secdev.org/projects/scapy for more informations -## Copyright (C) Arnaud Ebalard <arno@natisbad.org> -## This program is published under a GPLv2 license - -""" -Cryptographic certificates. -""" - -import os, sys, math, socket, struct, hmac, string, time, random, tempfile -from subprocess import Popen, PIPE -from scapy.utils import strxor -try: - HAS_HASHLIB=True - import hashlib -except: - HAS_HASHLIB=False - -from Crypto.PublicKey import * -from Crypto.Cipher import * -from Crypto.Hash import * -from Crypto.Util import number - -# Maximum allowed size in bytes for a certificate file, to avoid -# loading huge file when importing a cert -MAX_KEY_SIZE=50*1024 -MAX_CERT_SIZE=50*1024 -MAX_CRL_SIZE=10*1024*1024 # some are that big - -##################################################################### -# Some helpers -##################################################################### - -def popen3(cmd): - p = Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE, - close_fds=True) - return p.stdout, p.stdin, p.stderr - -def warning(m): - print "WARNING: %s" % m - -def randstring(l): - """ - Returns a random string of length l (l >= 0) - """ - tmp = map(lambda x: struct.pack("B", random.randrange(0, 256, 1)), [""]*l) - return "".join(tmp) - -def zerofree_randstring(l): - """ - Returns a random string of length l (l >= 0) without zero in it. - """ - tmp = map(lambda x: struct.pack("B", random.randrange(1, 256, 1)), [""]*l) - return "".join(tmp) - -def strand(s1, s2): - """ - Returns the binary AND of the 2 provided strings s1 and s2. s1 and s2 - must be of same length. - """ - return "".join(map(lambda x,y:chr(ord(x)&ord(y)), s1, s2)) - -# OS2IP function defined in RFC 3447 for octet string to integer conversion -def pkcs_os2ip(x): - """ - Accepts a byte string as input parameter and return the associated long - value: - - Input : x octet string to be converted - - Output: x corresponding nonnegative integer - - Reverse function is pkcs_i2osp() - """ - return number.bytes_to_long(x) - -# IP2OS function defined in RFC 3447 for octet string to integer conversion -def pkcs_i2osp(x,xLen): - """ - Converts a long (the first parameter) to the associated byte string - representation of length l (second parameter). Basically, the length - parameters allow the function to perform the associated padding. - - Input : x nonnegative integer to be converted - xLen intended length of the resulting octet string - - Output: x corresponding nonnegative integer - - Reverse function is pkcs_os2ip(). - """ - z = number.long_to_bytes(x) - padlen = max(0, xLen-len(z)) - return '\x00'*padlen + z - -# for every hash function a tuple is provided, giving access to -# - hash output length in byte -# - associated hash function that take data to be hashed as parameter -# XXX I do not provide update() at the moment. -# - DER encoding of the leading bits of digestInfo (the hash value -# will be concatenated to create the complete digestInfo). -# -# Notes: -# - MD4 asn.1 value should be verified. Also, as stated in -# PKCS#1 v2.1, MD4 should not be used. -# - hashlib is available from http://code.krypto.org/python/hashlib/ -# - 'tls' one is the concatenation of both md5 and sha1 hashes used -# by SSL/TLS when signing/verifying things -_hashFuncParams = { - "md2" : (16, - lambda x: MD2.new(x).digest(), - '\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x02\x05\x00\x04\x10'), - "md4" : (16, - lambda x: MD4.new(x).digest(), - '\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x04\x05\x00\x04\x10'), # is that right ? - "md5" : (16, - lambda x: MD5.new(x).digest(), - '\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10'), - "sha1" : (20, - lambda x: SHA.new(x).digest(), - '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'), - "tls" : (36, - lambda x: MD5.new(x).digest() + SHA.new(x).digest(), - '') } - -if HAS_HASHLIB: - _hashFuncParams["sha224"] = (28, - lambda x: hashlib.sha224(x).digest(), - '\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c') - _hashFuncParams["sha256"] = (32, - lambda x: hashlib.sha256(x).digest(), - '\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20') - _hashFuncParams["sha384"] = (48, - lambda x: hashlib.sha384(x).digest(), - '\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30') - _hashFuncParams["sha512"] = (64, - lambda x: hashlib.sha512(x).digest(), - '\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40') -else: - warning("hashlib support is not available. Consider installing it") - warning("if you need sha224, sha256, sha384 and sha512 algs.") - -def pkcs_mgf1(mgfSeed, maskLen, h): - """ - Implements generic MGF1 Mask Generation function as described in - Appendix B.2.1 of RFC 3447. The hash function is passed by name. - valid values are 'md2', 'md4', 'md5', 'sha1', 'tls, 'sha256', - 'sha384' and 'sha512'. Returns None on error. - - Input: - mgfSeed: seed from which mask is generated, an octet string - maskLen: intended length in octets of the mask, at most 2^32 * hLen - hLen (see below) - h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', - 'sha256', 'sha384'). hLen denotes the length in octets of - the hash function output. - - Output: - an octet string of length maskLen - """ - - # steps are those of Appendix B.2.1 - if h not in _hashFuncParams: - warning("pkcs_mgf1: invalid hash (%s) provided") - return None - hLen = _hashFuncParams[h][0] - hFunc = _hashFuncParams[h][1] - if maskLen > 2**32 * hLen: # 1) - warning("pkcs_mgf1: maskLen > 2**32 * hLen") - return None - T = "" # 2) - maxCounter = math.ceil(float(maskLen) / float(hLen)) # 3) - counter = 0 - while counter < maxCounter: - C = pkcs_i2osp(counter, 4) - T += hFunc(mgfSeed + C) - counter += 1 - return T[:maskLen] - - -def pkcs_emsa_pss_encode(M, emBits, h, mgf, sLen): - """ - Implements EMSA-PSS-ENCODE() function described in Sect. 9.1.1 of RFC 3447 - - Input: - M : message to be encoded, an octet string - emBits: maximal bit length of the integer resulting of pkcs_os2ip(EM), - where EM is the encoded message, output of the function. - h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', - 'sha256', 'sha384'). hLen denotes the length in octets of - the hash function output. - mgf : the mask generation function f : seed, maskLen -> mask - sLen : intended length in octets of the salt - - Output: - encoded message, an octet string of length emLen = ceil(emBits/8) - - On error, None is returned. - """ - - # 1) is not done - hLen = _hashFuncParams[h][0] # 2) - hFunc = _hashFuncParams[h][1] - mHash = hFunc(M) - emLen = int(math.ceil(emBits/8.)) - if emLen < hLen + sLen + 2: # 3) - warning("encoding error (emLen < hLen + sLen + 2)") - return None - salt = randstring(sLen) # 4) - MPrime = '\x00'*8 + mHash + salt # 5) - H = hFunc(MPrime) # 6) - PS = '\x00'*(emLen - sLen - hLen - 2) # 7) - DB = PS + '\x01' + salt # 8) - dbMask = mgf(H, emLen - hLen - 1) # 9) - maskedDB = strxor(DB, dbMask) # 10) - l = (8*emLen - emBits)/8 # 11) - rem = 8*emLen - emBits - 8*l # additionnal bits - andMask = l*'\x00' - if rem: - j = chr(sum(1<<x for x in xrange(8 - rem))) - andMask += j - l += 1 - maskedDB = strand(maskedDB[:l], andMask) + maskedDB[l:] - EM = maskedDB + H + '\xbc' # 12) - return EM # 13) - - -def pkcs_emsa_pss_verify(M, EM, emBits, h, mgf, sLen): - """ - Implements EMSA-PSS-VERIFY() function described in Sect. 9.1.2 of RFC 3447 - - Input: - M : message to be encoded, an octet string - EM : encoded message, an octet string of length emLen = ceil(emBits/8) - emBits: maximal bit length of the integer resulting of pkcs_os2ip(EM) - h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', - 'sha256', 'sha384'). hLen denotes the length in octets of - the hash function output. - mgf : the mask generation function f : seed, maskLen -> mask - sLen : intended length in octets of the salt - - Output: - True if the verification is ok, False otherwise. - """ - - # 1) is not done - hLen = _hashFuncParams[h][0] # 2) - hFunc = _hashFuncParams[h][1] - mHash = hFunc(M) - emLen = int(math.ceil(emBits/8.)) # 3) - if emLen < hLen + sLen + 2: - return False - if EM[-1] != '\xbc': # 4) - return False - l = emLen - hLen - 1 # 5) - maskedDB = EM[:l] - H = EM[l:l+hLen] - l = (8*emLen - emBits)/8 # 6) - rem = 8*emLen - emBits - 8*l # additionnal bits - andMask = l*'\xff' - if rem: - j = chr(~sum(1 << x for x in xrange(8 - rem)) & 0xff) - andMask += j - l += 1 - if strand(maskedDB[:l], andMask) != '\x00'*l: - return False - dbMask = mgf(H, emLen - hLen - 1) # 7) - DB = strxor(maskedDB, dbMask) # 8) - l = (8*emLen - emBits)/8 # 9) - rem = 8*emLen - emBits - 8*l # additionnal bits - andMask = l*'\x00' - if rem: - j = chr(sum(1 << x for x in xrange(8 - rem))) - andMask += j - l += 1 - DB = strand(DB[:l], andMask) + DB[l:] - l = emLen - hLen - sLen - 1 # 10) - if DB[:l] != '\x00'*(l-1) + '\x01': - return False - salt = DB[-sLen:] # 11) - MPrime = '\x00'*8 + mHash + salt # 12) - HPrime = hFunc(MPrime) # 13) - return H == HPrime # 14) - - -def pkcs_emsa_pkcs1_v1_5_encode(M, emLen, h): # section 9.2 of RFC 3447 - """ - Implements EMSA-PKCS1-V1_5-ENCODE() function described in Sect. - 9.2 of RFC 3447. - - Input: - M : message to be encode, an octet string - emLen: intended length in octets of the encoded message, at least - tLen + 11, where tLen is the octet length of the DER encoding - T of a certain value computed during the encoding operation. - h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', - 'sha256', 'sha384'). hLen denotes the length in octets of - the hash function output. - - Output: - encoded message, an octet string of length emLen - - On error, None is returned. - """ - hLen = _hashFuncParams[h][0] # 1) - hFunc = _hashFuncParams[h][1] - H = hFunc(M) - hLeadingDigestInfo = _hashFuncParams[h][2] # 2) - T = hLeadingDigestInfo + H - tLen = len(T) - if emLen < tLen + 11: # 3) - warning("pkcs_emsa_pkcs1_v1_5_encode: intended encoded message length too short") - return None - PS = '\xff'*(emLen - tLen - 3) # 4) - EM = '\x00' + '\x01' + PS + '\x00' + T # 5) - return EM # 6) - - -# XXX should add other pgf1 instance in a better fashion. - -def create_ca_file(anchor_list, filename): - """ - Concatenate all the certificates (PEM format for the export) in - 'anchor_list' and write the result to file 'filename'. On success - 'filename' is returned, None otherwise. - - If you are used to OpenSSL tools, this function builds a CAfile - that can be used for certificate and CRL check. - - Also see create_temporary_ca_file(). - """ - try: - f = open(filename, "w") - for a in anchor_list: - s = a.output(fmt="PEM") - f.write(s) - f.close() - except: - return None - return filename - -def create_temporary_ca_file(anchor_list): - """ - Concatenate all the certificates (PEM format for the export) in - 'anchor_list' and write the result to file to a temporary file - using mkstemp() from tempfile module. On success 'filename' is - returned, None otherwise. - - If you are used to OpenSSL tools, this function builds a CAfile - that can be used for certificate and CRL check. - - Also see create_temporary_ca_file(). - """ - try: - f, fname = tempfile.mkstemp() - for a in anchor_list: - s = a.output(fmt="PEM") - l = os.write(f, s) - os.close(f) - except: - return None - return fname - -def create_temporary_ca_path(anchor_list, folder): - """ - Create a CA path folder as defined in OpenSSL terminology, by - storing all certificates in 'anchor_list' list in PEM format - under provided 'folder' and then creating the associated links - using the hash as usually done by c_rehash. - - Note that you can also include CRL in 'anchor_list'. In that - case, they will also be stored under 'folder' and associated - links will be created. - - In folder, the files are created with names of the form - 0...ZZ.pem. If you provide an empty list, folder will be created - if it does not already exist, but that's all. - - The number of certificates written to folder is returned on - success, None on error. - """ - # We should probably avoid writing duplicate anchors and also - # check if they are all certs. - try: - if not os.path.isdir(folder): - os.makedirs(folder) - except: - return None - - l = len(anchor_list) - if l == 0: - return None - fmtstr = "%%0%sd.pem" % math.ceil(math.log(l, 10)) - i = 0 - try: - for a in anchor_list: - fname = os.path.join(folder, fmtstr % i) - f = open(fname, "w") - s = a.output(fmt="PEM") - f.write(s) - f.close() - i += 1 - except: - return None - - r,w,e=popen3(["c_rehash", folder]) - r.close(); w.close(); e.close() - - return l - - -##################################################################### -# Public Key Cryptography related stuff -##################################################################### - -class OSSLHelper: - def _apply_ossl_cmd(self, osslcmd, rawdata): - r,w,e=popen3(osslcmd) - w.write(rawdata) - w.close() - res = r.read() - r.close() - e.close() - return res - -class _EncryptAndVerify: - ### Below are encryption methods - - def _rsaep(self, m): - """ - Internal method providing raw RSA encryption, i.e. simple modular - exponentiation of the given message representative 'm', a long - between 0 and n-1. - - This is the encryption primitive RSAEP described in PKCS#1 v2.1, - i.e. RFC 3447 Sect. 5.1.1. - - Input: - m: message representative, a long between 0 and n-1, where - n is the key modulus. - - Output: - ciphertext representative, a long between 0 and n-1 - - Not intended to be used directly. Please, see encrypt() method. - """ - - n = self.modulus - if type(m) is int: - m = long(m) - if type(m) is not long or m > n-1: - warning("Key._rsaep() expects a long between 0 and n-1") - return None - - return self.key.encrypt(m, "")[0] - - - def _rsaes_pkcs1_v1_5_encrypt(self, M): - """ - Implements RSAES-PKCS1-V1_5-ENCRYPT() function described in section - 7.2.1 of RFC 3447. - - Input: - M: message to be encrypted, an octet string of length mLen, where - mLen <= k - 11 (k denotes the length in octets of the key modulus) - - Output: - ciphertext, an octet string of length k - - On error, None is returned. - """ - - # 1) Length checking - mLen = len(M) - k = self.modulusLen / 8 - if mLen > k - 11: - warning("Key._rsaes_pkcs1_v1_5_encrypt(): message too " - "long (%d > %d - 11)" % (mLen, k)) - return None - - # 2) EME-PKCS1-v1_5 encoding - PS = zerofree_randstring(k - mLen - 3) # 2.a) - EM = '\x00' + '\x02' + PS + '\x00' + M # 2.b) - - # 3) RSA encryption - m = pkcs_os2ip(EM) # 3.a) - c = self._rsaep(m) # 3.b) - C = pkcs_i2osp(c, k) # 3.c) - - return C # 4) - - - def _rsaes_oaep_encrypt(self, M, h=None, mgf=None, L=None): - """ - Internal method providing RSAES-OAEP-ENCRYPT as defined in Sect. - 7.1.1 of RFC 3447. Not intended to be used directly. Please, see - encrypt() method for type "OAEP". - - - Input: - M : message to be encrypted, an octet string of length mLen - where mLen <= k - 2*hLen - 2 (k denotes the length in octets - of the RSA modulus and hLen the length in octets of the hash - function output) - h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', - 'sha256', 'sha384'). hLen denotes the length in octets of - the hash function output. 'sha1' is used by default if not - provided. - mgf: the mask generation function f : seed, maskLen -> mask - L : optional label to be associated with the message; the default - value for L, if not provided is the empty string - - Output: - ciphertext, an octet string of length k - - On error, None is returned. - """ - # The steps below are the one described in Sect. 7.1.1 of RFC 3447. - # 1) Length Checking - # 1.a) is not done - mLen = len(M) - if h is None: - h = "sha1" - if h not in _hashFuncParams: - warning("Key._rsaes_oaep_encrypt(): unknown hash function %s.", h) - return None - hLen = _hashFuncParams[h][0] - hFun = _hashFuncParams[h][1] - k = self.modulusLen / 8 - if mLen > k - 2*hLen - 2: # 1.b) - warning("Key._rsaes_oaep_encrypt(): message too long.") - return None - - # 2) EME-OAEP encoding - if L is None: # 2.a) - L = "" - lHash = hFun(L) - PS = '\x00'*(k - mLen - 2*hLen - 2) # 2.b) - DB = lHash + PS + '\x01' + M # 2.c) - seed = randstring(hLen) # 2.d) - if mgf is None: # 2.e) - mgf = lambda x,y: pkcs_mgf1(x,y,h) - dbMask = mgf(seed, k - hLen - 1) - maskedDB = strxor(DB, dbMask) # 2.f) - seedMask = mgf(maskedDB, hLen) # 2.g) - maskedSeed = strxor(seed, seedMask) # 2.h) - EM = '\x00' + maskedSeed + maskedDB # 2.i) - - # 3) RSA Encryption - m = pkcs_os2ip(EM) # 3.a) - c = self._rsaep(m) # 3.b) - C = pkcs_i2osp(c, k) # 3.c) - - return C # 4) - - - def encrypt(self, m, t=None, h=None, mgf=None, L=None): - """ - Encrypt message 'm' using 't' encryption scheme where 't' can be: - - - None: the message 'm' is directly applied the RSAEP encryption - primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 - Sect 5.1.1. Simply put, the message undergo a modular - exponentiation using the public key. Additionnal method - parameters are just ignored. - - - 'pkcs': the message 'm' is applied RSAES-PKCS1-V1_5-ENCRYPT encryption - scheme as described in section 7.2.1 of RFC 3447. In that - context, other parameters ('h', 'mgf', 'l') are not used. - - - 'oaep': the message 'm' is applied the RSAES-OAEP-ENCRYPT encryption - scheme, as described in PKCS#1 v2.1, i.e. RFC 3447 Sect - 7.1.1. In that context, - - o 'h' parameter provides the name of the hash method to use. - Possible values are "md2", "md4", "md5", "sha1", "tls", - "sha224", "sha256", "sha384" and "sha512". if none is provided, - sha1 is used. - - o 'mgf' is the mask generation function. By default, mgf - is derived from the provided hash function using the - generic MGF1 (see pkcs_mgf1() for details). - - o 'L' is the optional label to be associated with the - message. If not provided, the default value is used, i.e - the empty string. No check is done on the input limitation - of the hash function regarding the size of 'L' (for - instance, 2^61 - 1 for SHA-1). You have been warned. - """ - - if t is None: # Raw encryption - m = pkcs_os2ip(m) - c = self._rsaep(m) - return pkcs_i2osp(c, self.modulusLen/8) - - elif t == "pkcs": - return self._rsaes_pkcs1_v1_5_encrypt(m) - - elif t == "oaep": - return self._rsaes_oaep_encrypt(m, h, mgf, L) - - else: - warning("Key.encrypt(): Unknown encryption type (%s) provided" % t) - return None - - ### Below are verification related methods - - def _rsavp1(self, s): - """ - Internal method providing raw RSA verification, i.e. simple modular - exponentiation of the given signature representative 'c', an integer - between 0 and n-1. - - This is the signature verification primitive RSAVP1 described in - PKCS#1 v2.1, i.e. RFC 3447 Sect. 5.2.2. - - Input: - s: signature representative, an integer between 0 and n-1, - where n is the key modulus. - - Output: - message representative, an integer between 0 and n-1 - - Not intended to be used directly. Please, see verify() method. - """ - return self._rsaep(s) - - def _rsassa_pss_verify(self, M, S, h=None, mgf=None, sLen=None): - """ - Implements RSASSA-PSS-VERIFY() function described in Sect 8.1.2 - of RFC 3447 - - Input: - M: message whose signature is to be verified - S: signature to be verified, an octet string of length k, where k - is the length in octets of the RSA modulus n. - - Output: - True is the signature is valid. False otherwise. - """ - - # Set default parameters if not provided - if h is None: # By default, sha1 - h = "sha1" - if h not in _hashFuncParams: - warning("Key._rsassa_pss_verify(): unknown hash function " - "provided (%s)" % h) - return False - if mgf is None: # use mgf1 with underlying hash function - mgf = lambda x,y: pkcs_mgf1(x, y, h) - if sLen is None: # use Hash output length (A.2.3 of RFC 3447) - hLen = _hashFuncParams[h][0] - sLen = hLen - - # 1) Length checking - modBits = self.modulusLen - k = modBits / 8 - if len(S) != k: - return False - - # 2) RSA verification - s = pkcs_os2ip(S) # 2.a) - m = self._rsavp1(s) # 2.b) - emLen = math.ceil((modBits - 1) / 8.) # 2.c) - EM = pkcs_i2osp(m, emLen) - - # 3) EMSA-PSS verification - Result = pkcs_emsa_pss_verify(M, EM, modBits - 1, h, mgf, sLen) - - return Result # 4) - - - def _rsassa_pkcs1_v1_5_verify(self, M, S, h): - """ - Implements RSASSA-PKCS1-v1_5-VERIFY() function as described in - Sect. 8.2.2 of RFC 3447. - - Input: - M: message whose signature is to be verified, an octet string - S: signature to be verified, an octet string of length k, where - k is the length in octets of the RSA modulus n - h: hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', - 'sha256', 'sha384'). - - Output: - True if the signature is valid. False otherwise. - """ - - # 1) Length checking - k = self.modulusLen / 8 - if len(S) != k: - warning("invalid signature (len(S) != k)") - return False - - # 2) RSA verification - s = pkcs_os2ip(S) # 2.a) - m = self._rsavp1(s) # 2.b) - EM = pkcs_i2osp(m, k) # 2.c) - - # 3) EMSA-PKCS1-v1_5 encoding - EMPrime = pkcs_emsa_pkcs1_v1_5_encode(M, k, h) - if EMPrime is None: - warning("Key._rsassa_pkcs1_v1_5_verify(): unable to encode.") - return False - - # 4) Comparison - return EM == EMPrime - - - def verify(self, M, S, t=None, h=None, mgf=None, sLen=None): - """ - Verify alleged signature 'S' is indeed the signature of message 'M' using - 't' signature scheme where 't' can be: - - - None: the alleged signature 'S' is directly applied the RSAVP1 signature - primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 Sect - 5.2.1. Simply put, the provided signature is applied a moular - exponentiation using the public key. Then, a comparison of the - result is done against 'M'. On match, True is returned. - Additionnal method parameters are just ignored. - - - 'pkcs': the alleged signature 'S' and message 'M' are applied - RSASSA-PKCS1-v1_5-VERIFY signature verification scheme as - described in Sect. 8.2.2 of RFC 3447. In that context, - the hash function name is passed using 'h'. Possible values are - "md2", "md4", "md5", "sha1", "tls", "sha224", "sha256", "sha384" - and "sha512". If none is provided, sha1 is used. Other additionnal - parameters are ignored. - - - 'pss': the alleged signature 'S' and message 'M' are applied - RSASSA-PSS-VERIFY signature scheme as described in Sect. 8.1.2. - of RFC 3447. In that context, - - o 'h' parameter provides the name of the hash method to use. - Possible values are "md2", "md4", "md5", "sha1", "tls", "sha224", - "sha256", "sha384" and "sha512". if none is provided, sha1 - is used. - - o 'mgf' is the mask generation function. By default, mgf - is derived from the provided hash function using the - generic MGF1 (see pkcs_mgf1() for details). - - o 'sLen' is the length in octet of the salt. You can overload the - default value (the octet length of the hash value for provided - algorithm) by providing another one with that parameter. - """ - if t is None: # RSAVP1 - S = pkcs_os2ip(S) - n = self.modulus - if S > n-1: - warning("Signature to be verified is too long for key modulus") - return False - m = self._rsavp1(S) - if m is None: - return False - l = int(math.ceil(math.log(m, 2) / 8.)) # Hack - m = pkcs_i2osp(m, l) - return M == m - - elif t == "pkcs": # RSASSA-PKCS1-v1_5-VERIFY - if h is None: - h = "sha1" - return self._rsassa_pkcs1_v1_5_verify(M, S, h) - - elif t == "pss": # RSASSA-PSS-VERIFY - return self._rsassa_pss_verify(M, S, h, mgf, sLen) - - else: - warning("Key.verify(): Unknown signature type (%s) provided" % t) - return None - -class _DecryptAndSignMethods(OSSLHelper): - ### Below are decryption related methods. Encryption ones are inherited - ### from PubKey - - def _rsadp(self, c): - """ - Internal method providing raw RSA decryption, i.e. simple modular - exponentiation of the given ciphertext representative 'c', a long - between 0 and n-1. - - This is the decryption primitive RSADP described in PKCS#1 v2.1, - i.e. RFC 3447 Sect. 5.1.2. - - Input: - c: ciphertest representative, a long between 0 and n-1, where - n is the key modulus. - - Output: - ciphertext representative, a long between 0 and n-1 - - Not intended to be used directly. Please, see encrypt() method. - """ - - n = self.modulus - if type(c) is int: - c = long(c) - if type(c) is not long or c > n-1: - warning("Key._rsaep() expects a long between 0 and n-1") - return None - - return self.key.decrypt(c) - - - def _rsaes_pkcs1_v1_5_decrypt(self, C): - """ - Implements RSAES-PKCS1-V1_5-DECRYPT() function described in section - 7.2.2 of RFC 3447. - - Input: - C: ciphertext to be decrypted, an octet string of length k, where - k is the length in octets of the RSA modulus n. - - Output: - an octet string of length k at most k - 11 - - on error, None is returned. - """ - - # 1) Length checking - cLen = len(C) - k = self.modulusLen / 8 - if cLen != k or k < 11: - warning("Key._rsaes_pkcs1_v1_5_decrypt() decryption error " - "(cLen != k or k < 11)") - return None - - # 2) RSA decryption - c = pkcs_os2ip(C) # 2.a) - m = self._rsadp(c) # 2.b) - EM = pkcs_i2osp(m, k) # 2.c) - - # 3) EME-PKCS1-v1_5 decoding - - # I am aware of the note at the end of 7.2.2 regarding error - # conditions reporting but the one provided below are for _local_ - # debugging purposes. --arno - - if EM[0] != '\x00': - warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error " - "(first byte is not 0x00)") - return None - - if EM[1] != '\x02': - warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error " - "(second byte is not 0x02)") - return None - - tmp = EM[2:].split('\x00', 1) - if len(tmp) != 2: - warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error " - "(no 0x00 to separate PS from M)") - return None - - PS, M = tmp - if len(PS) < 8: - warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error " - "(PS is less than 8 byte long)") - return None - - return M # 4) - - - def _rsaes_oaep_decrypt(self, C, h=None, mgf=None, L=None): - """ - Internal method providing RSAES-OAEP-DECRYPT as defined in Sect. - 7.1.2 of RFC 3447. Not intended to be used directly. Please, see - encrypt() method for type "OAEP". - - - Input: - C : ciphertext to be decrypted, an octet string of length k, where - k = 2*hLen + 2 (k denotes the length in octets of the RSA modulus - and hLen the length in octets of the hash function output) - h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', - 'sha256', 'sha384'). 'sha1' is used if none is provided. - mgf: the mask generation function f : seed, maskLen -> mask - L : optional label whose association with the message is to be - verified; the default value for L, if not provided is the empty - string. - - Output: - message, an octet string of length k mLen, where mLen <= k - 2*hLen - 2 - - On error, None is returned. - """ - # The steps below are the one described in Sect. 7.1.2 of RFC 3447. - - # 1) Length Checking - # 1.a) is not done - if h is None: - h = "sha1" - if h not in _hashFuncParams: - warning("Key._rsaes_oaep_decrypt(): unknown hash function %s.", h) - return None - hLen = _hashFuncParams[h][0] - hFun = _hashFuncParams[h][1] - k = self.modulusLen / 8 - cLen = len(C) - if cLen != k: # 1.b) - warning("Key._rsaes_oaep_decrypt(): decryption error. " - "(cLen != k)") - return None - if k < 2*hLen + 2: - warning("Key._rsaes_oaep_decrypt(): decryption error. " - "(k < 2*hLen + 2)") - return None - - # 2) RSA decryption - c = pkcs_os2ip(C) # 2.a) - m = self._rsadp(c) # 2.b) - EM = pkcs_i2osp(m, k) # 2.c) - - # 3) EME-OAEP decoding - if L is None: # 3.a) - L = "" - lHash = hFun(L) - Y = EM[:1] # 3.b) - if Y != '\x00': - warning("Key._rsaes_oaep_decrypt(): decryption error. " - "(Y is not zero)") - return None - maskedSeed = EM[1:1+hLen] - maskedDB = EM[1+hLen:] - if mgf is None: - mgf = lambda x,y: pkcs_mgf1(x, y, h) - seedMask = mgf(maskedDB, hLen) # 3.c) - seed = strxor(maskedSeed, seedMask) # 3.d) - dbMask = mgf(seed, k - hLen - 1) # 3.e) - DB = strxor(maskedDB, dbMask) # 3.f) - - # I am aware of the note at the end of 7.1.2 regarding error - # conditions reporting but the one provided below are for _local_ - # debugging purposes. --arno - - lHashPrime = DB[:hLen] # 3.g) - tmp = DB[hLen:].split('\x01', 1) - if len(tmp) != 2: - warning("Key._rsaes_oaep_decrypt(): decryption error. " - "(0x01 separator not found)") - return None - PS, M = tmp - if PS != '\x00'*len(PS): - warning("Key._rsaes_oaep_decrypt(): decryption error. " - "(invalid padding string)") - return None - if lHash != lHashPrime: - warning("Key._rsaes_oaep_decrypt(): decryption error. " - "(invalid hash)") - return None - return M # 4) - - - def decrypt(self, C, t=None, h=None, mgf=None, L=None): - """ - Decrypt ciphertext 'C' using 't' decryption scheme where 't' can be: - - - None: the ciphertext 'C' is directly applied the RSADP decryption - primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 - Sect 5.1.2. Simply, put the message undergo a modular - exponentiation using the private key. Additionnal method - parameters are just ignored. - - - 'pkcs': the ciphertext 'C' is applied RSAES-PKCS1-V1_5-DECRYPT - decryption scheme as described in section 7.2.2 of RFC 3447. - In that context, other parameters ('h', 'mgf', 'l') are not - used. - - - 'oaep': the ciphertext 'C' is applied the RSAES-OAEP-DECRYPT decryption - scheme, as described in PKCS#1 v2.1, i.e. RFC 3447 Sect - 7.1.2. In that context, - - o 'h' parameter provides the name of the hash method to use. - Possible values are "md2", "md4", "md5", "sha1", "tls", - "sha224", "sha256", "sha384" and "sha512". if none is provided, - sha1 is used by default. - - o 'mgf' is the mask generation function. By default, mgf - is derived from the provided hash function using the - generic MGF1 (see pkcs_mgf1() for details). - - o 'L' is the optional label to be associated with the - message. If not provided, the default value is used, i.e - the empty string. No check is done on the input limitation - of the hash function regarding the size of 'L' (for - instance, 2^61 - 1 for SHA-1). You have been warned. - """ - if t is None: - C = pkcs_os2ip(C) - c = self._rsadp(C) - l = int(math.ceil(math.log(c, 2) / 8.)) # Hack - return pkcs_i2osp(c, l) - - elif t == "pkcs": - return self._rsaes_pkcs1_v1_5_decrypt(C) - - elif t == "oaep": - return self._rsaes_oaep_decrypt(C, h, mgf, L) - - else: - warning("Key.decrypt(): Unknown decryption type (%s) provided" % t) - return None - - ### Below are signature related methods. Verification ones are inherited from - ### PubKey - - def _rsasp1(self, m): - """ - Internal method providing raw RSA signature, i.e. simple modular - exponentiation of the given message representative 'm', an integer - between 0 and n-1. - - This is the signature primitive RSASP1 described in PKCS#1 v2.1, - i.e. RFC 3447 Sect. 5.2.1. - - Input: - m: message representative, an integer between 0 and n-1, where - n is the key modulus. - - Output: - signature representative, an integer between 0 and n-1 - - Not intended to be used directly. Please, see sign() method. - """ - return self._rsadp(m) - - - def _rsassa_pss_sign(self, M, h=None, mgf=None, sLen=None): - """ - Implements RSASSA-PSS-SIGN() function described in Sect. 8.1.1 of - RFC 3447. - - Input: - M: message to be signed, an octet string - - Output: - signature, an octet string of length k, where k is the length in - octets of the RSA modulus n. - - On error, None is returned. - """ - - # Set default parameters if not provided - if h is None: # By default, sha1 - h = "sha1" - if h not in _hashFuncParams: - warning("Key._rsassa_pss_sign(): unknown hash function " - "provided (%s)" % h) - return None - if mgf is None: # use mgf1 with underlying hash function - mgf = lambda x,y: pkcs_mgf1(x, y, h) - if sLen is None: # use Hash output length (A.2.3 of RFC 3447) - hLen = _hashFuncParams[h][0] - sLen = hLen - - # 1) EMSA-PSS encoding - modBits = self.modulusLen - k = modBits / 8 - EM = pkcs_emsa_pss_encode(M, modBits - 1, h, mgf, sLen) - if EM is None: - warning("Key._rsassa_pss_sign(): unable to encode") - return None - - # 2) RSA signature - m = pkcs_os2ip(EM) # 2.a) - s = self._rsasp1(m) # 2.b) - S = pkcs_i2osp(s, k) # 2.c) - - return S # 3) - - - def _rsassa_pkcs1_v1_5_sign(self, M, h): - """ - Implements RSASSA-PKCS1-v1_5-SIGN() function as described in - Sect. 8.2.1 of RFC 3447. - - Input: - M: message to be signed, an octet string - h: hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls' - 'sha256', 'sha384'). - - Output: - the signature, an octet string. - """ - - # 1) EMSA-PKCS1-v1_5 encoding - k = self.modulusLen / 8 - EM = pkcs_emsa_pkcs1_v1_5_encode(M, k, h) - if EM is None: - warning("Key._rsassa_pkcs1_v1_5_sign(): unable to encode") - return None - - # 2) RSA signature - m = pkcs_os2ip(EM) # 2.a) - s = self._rsasp1(m) # 2.b) - S = pkcs_i2osp(s, k) # 2.c) - - return S # 3) - - - def sign(self, M, t=None, h=None, mgf=None, sLen=None): - """ - Sign message 'M' using 't' signature scheme where 't' can be: - - - None: the message 'M' is directly applied the RSASP1 signature - primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 Sect - 5.2.1. Simply put, the message undergo a modular exponentiation - using the private key. Additionnal method parameters are just - ignored. - - - 'pkcs': the message 'M' is applied RSASSA-PKCS1-v1_5-SIGN signature - scheme as described in Sect. 8.2.1 of RFC 3447. In that context, - the hash function name is passed using 'h'. Possible values are - "md2", "md4", "md5", "sha1", "tls", "sha224", "sha256", "sha384" - and "sha512". If none is provided, sha1 is used. Other additionnal - parameters are ignored. - - - 'pss' : the message 'M' is applied RSASSA-PSS-SIGN signature scheme as - described in Sect. 8.1.1. of RFC 3447. In that context, - - o 'h' parameter provides the name of the hash method to use. - Possible values are "md2", "md4", "md5", "sha1", "tls", "sha224", - "sha256", "sha384" and "sha512". if none is provided, sha1 - is used. - - o 'mgf' is the mask generation function. By default, mgf - is derived from the provided hash function using the - generic MGF1 (see pkcs_mgf1() for details). - - o 'sLen' is the length in octet of the salt. You can overload the - default value (the octet length of the hash value for provided - algorithm) by providing another one with that parameter. - """ - - if t is None: # RSASP1 - M = pkcs_os2ip(M) - n = self.modulus - if M > n-1: - warning("Message to be signed is too long for key modulus") - return None - s = self._rsasp1(M) - if s is None: - return None - return pkcs_i2osp(s, self.modulusLen/8) - - elif t == "pkcs": # RSASSA-PKCS1-v1_5-SIGN - if h is None: - h = "sha1" - return self._rsassa_pkcs1_v1_5_sign(M, h) - - elif t == "pss": # RSASSA-PSS-SIGN - return self._rsassa_pss_sign(M, h, mgf, sLen) - - else: - warning("Key.sign(): Unknown signature type (%s) provided" % t) - return None - - -def openssl_parse_RSA(fmt="PEM"): - return popen3(['openssl', 'rsa', '-text', '-pubin', '-inform', fmt, '-noout']) -def openssl_convert_RSA(infmt="PEM", outfmt="DER"): - return ['openssl', 'rsa', '-pubin', '-inform', infmt, '-outform', outfmt] - -class PubKey(OSSLHelper, _EncryptAndVerify): - # Below are the fields we recognize in the -text output of openssl - # and from which we extract information. We expect them in that - # order. Number of spaces does matter. - possible_fields = [ "Modulus (", - "Exponent:" ] - possible_fields_count = len(possible_fields) - - def __init__(self, keypath): - error_msg = "Unable to import key." - - # XXX Temporary hack to use PubKey inside Cert - if type(keypath) is tuple: - e, m, mLen = keypath - self.modulus = m - self.modulusLen = mLen - self.pubExp = e - return - - fields_dict = {} - for k in self.possible_fields: - fields_dict[k] = None - - self.keypath = None - rawkey = None - - if (not '\x00' in keypath) and os.path.isfile(keypath): # file - self.keypath = keypath - key_size = os.path.getsize(keypath) - if key_size > MAX_KEY_SIZE: - raise Exception(error_msg) - try: - f = open(keypath) - rawkey = f.read() - f.close() - except: - raise Exception(error_msg) - else: - rawkey = keypath - - if rawkey is None: - raise Exception(error_msg) - - self.rawkey = rawkey - - key_header = "-----BEGIN PUBLIC KEY-----" - key_footer = "-----END PUBLIC KEY-----" - l = rawkey.split(key_header, 1) - if len(l) == 2: # looks like PEM - tmp = l[1] - l = tmp.split(key_footer, 1) - if len(l) == 2: - tmp = l[0] - rawkey = "%s%s%s\n" % (key_header, tmp, key_footer) - else: - raise Exception(error_msg) - r,w,e = openssl_parse_RSA("PEM") - w.write(rawkey) - w.close() - textkey = r.read() - r.close() - res = e.read() - e.close() - if res == '': - self.format = "PEM" - self.pemkey = rawkey - self.textkey = textkey - cmd = openssl_convert_RSA_cmd("PEM", "DER") - self.derkey = self._apply_ossl_cmd(cmd, rawkey) - else: - raise Exception(error_msg) - else: # not PEM, try DER - r,w,e = openssl_parse_RSA("DER") - w.write(rawkey) - w.close() - textkey = r.read() - r.close() - res = e.read() - if res == '': - self.format = "DER" - self.derkey = rawkey - self.textkey = textkey - cmd = openssl_convert_RSA_cmd("DER", "PEM") - self.pemkey = self._apply_ossl_cmd(cmd, rawkey) - cmd = openssl_convert_RSA_cmd("DER", "DER") - self.derkey = self._apply_ossl_cmd(cmd, rawkey) - else: - try: # Perhaps it is a cert - c = Cert(keypath) - except: - raise Exception(error_msg) - # TODO: - # Reconstruct a key (der and pem) and provide: - # self.format - # self.derkey - # self.pemkey - # self.textkey - # self.keypath - - self.osslcmdbase = ['openssl', 'rsa', '-pubin', '-inform', self.format] - - self.keypath = keypath - - # Parse the -text output of openssl to make things available - l = self.textkey.split('\n', 1) - if len(l) != 2: - raise Exception(error_msg) - cur, tmp = l - i = 0 - k = self.possible_fields[i] # Modulus ( - cur = cur[len(k):] + '\n' - while k: - l = tmp.split('\n', 1) - if len(l) != 2: # Over - fields_dict[k] = cur - break - l, tmp = l - - newkey = 0 - # skip fields we have already seen, this is the purpose of 'i' - for j in xrange(i, self.possible_fields_count): - f = self.possible_fields[j] - if l.startswith(f): - fields_dict[k] = cur - cur = l[len(f):] + '\n' - k = f - newkey = 1 - i = j + 1 - break - if newkey == 1: - continue - cur += l + '\n' - - # modulus and modulus length - v = fields_dict["Modulus ("] - self.modulusLen = None - if v: - v, rem = v.split(' bit):', 1) - self.modulusLen = int(v) - rem = rem.replace('\n','').replace(' ','').replace(':','') - self.modulus = long(rem, 16) - if self.modulus is None: - raise Exception(error_msg) - - # public exponent - v = fields_dict["Exponent:"] - self.pubExp = None - if v: - self.pubExp = long(v.split('(', 1)[0]) - if self.pubExp is None: - raise Exception(error_msg) - - self.key = RSA.construct((self.modulus, self.pubExp, )) - - def __str__(self): - return self.derkey - - -class Key(OSSLHelper, _DecryptAndSignMethods, _EncryptAndVerify): - # Below are the fields we recognize in the -text output of openssl - # and from which we extract information. We expect them in that - # order. Number of spaces does matter. - possible_fields = [ "Private-Key: (", - "modulus:", - "publicExponent:", - "privateExponent:", - "prime1:", - "prime2:", - "exponent1:", - "exponent2:", - "coefficient:" ] - possible_fields_count = len(possible_fields) - - def __init__(self, keypath): - error_msg = "Unable to import key." - - fields_dict = {} - for k in self.possible_fields: - fields_dict[k] = None - - self.keypath = None - rawkey = None - - if (not '\x00' in keypath) and os.path.isfile(keypath): - self.keypath = keypath - key_size = os.path.getsize(keypath) - if key_size > MAX_KEY_SIZE: - raise Exception(error_msg) - try: - f = open(keypath) - rawkey = f.read() - f.close() - except: - raise Exception(error_msg) - else: - rawkey = keypath - - if rawkey is None: - raise Exception(error_msg) - - self.rawkey = rawkey - - # Let's try to get file format : PEM or DER. - fmtstr = 'openssl rsa -text -inform %s -noout' - convertstr = 'openssl rsa -inform %s -outform %s' - key_header = "-----BEGIN RSA PRIVATE KEY-----" - key_footer = "-----END RSA PRIVATE KEY-----" - l = rawkey.split(key_header, 1) - if len(l) == 2: # looks like PEM - tmp = l[1] - l = tmp.split(key_footer, 1) - if len(l) == 2: - tmp = l[0] - rawkey = "%s%s%s\n" % (key_header, tmp, key_footer) - else: - raise Exception(error_msg) - r,w,e = popen3((fmtstr % "PEM").split(" ")) - w.write(rawkey) - w.close() - textkey = r.read() - r.close() - res = e.read() - e.close() - if res == '': - self.format = "PEM" - self.pemkey = rawkey - self.textkey = textkey - cmd = (convertstr % ("PEM", "DER")).split(" ") - self.derkey = self._apply_ossl_cmd(cmd, rawkey) - else: - raise Exception(error_msg) - else: # not PEM, try DER - r,w,e = popen3((fmtstr % "DER").split(" ")) - w.write(rawkey) - w.close() - textkey = r.read() - r.close() - res = e.read() - if res == '': - self.format = "DER" - self.derkey = rawkey - self.textkey = textkey - cmd = (convertstr % ("DER", "PEM")).split(" ") - self.pemkey = self._apply_ossl_cmd(cmd, rawkey) - cmd = (convertstr % ("DER", "DER")).split(" ") - self.derkey = self._apply_ossl_cmd(cmd, rawkey) - else: - raise Exception(error_msg) - - self.osslcmdbase = ['openssl', 'rsa', '-inform', self.format] - - r,w,e = popen3(["openssl", "asn1parse", "-inform", "DER"]) - w.write(self.derkey) - w.close() - self.asn1parsekey = r.read() - r.close() - res = e.read() - e.close() - if res != '': - raise Exception(error_msg) - - self.keypath = keypath - - # Parse the -text output of openssl to make things available - l = self.textkey.split('\n', 1) - if len(l) != 2: - raise Exception(error_msg) - cur, tmp = l - i = 0 - k = self.possible_fields[i] # Private-Key: ( - cur = cur[len(k):] + '\n' - while k: - l = tmp.split('\n', 1) - if len(l) != 2: # Over - fields_dict[k] = cur - break - l, tmp = l - - newkey = 0 - # skip fields we have already seen, this is the purpose of 'i' - for j in xrange(i, self.possible_fields_count): - f = self.possible_fields[j] - if l.startswith(f): - fields_dict[k] = cur - cur = l[len(f):] + '\n' - k = f - newkey = 1 - i = j + 1 - break - if newkey == 1: - continue - cur += l + '\n' - - # modulus length - v = fields_dict["Private-Key: ("] - self.modulusLen = None - if v: - self.modulusLen = int(v.split(' bit', 1)[0]) - if self.modulusLen is None: - raise Exception(error_msg) - - # public exponent - v = fields_dict["publicExponent:"] - self.pubExp = None - if v: - self.pubExp = long(v.split('(', 1)[0]) - if self.pubExp is None: - raise Exception(error_msg) - - tmp = {} - for k in ["modulus:", "privateExponent:", "prime1:", "prime2:", - "exponent1:", "exponent2:", "coefficient:"]: - v = fields_dict[k] - if v: - s = v.replace('\n', '').replace(' ', '').replace(':', '') - tmp[k] = long(s, 16) - else: - raise Exception(error_msg) - - self.modulus = tmp["modulus:"] - self.privExp = tmp["privateExponent:"] - self.prime1 = tmp["prime1:"] - self.prime2 = tmp["prime2:"] - self.exponent1 = tmp["exponent1:"] - self.exponent2 = tmp["exponent2:"] - self.coefficient = tmp["coefficient:"] - - self.key = RSA.construct((self.modulus, self.pubExp, self.privExp)) - - def __str__(self): - return self.derkey - - -# We inherit from PubKey to get access to all encryption and verification -# methods. To have that working, we simply need Cert to provide -# modulusLen and key attribute. -# XXX Yes, it is a hack. -class Cert(OSSLHelper, _EncryptAndVerify): - # Below are the fields we recognize in the -text output of openssl - # and from which we extract information. We expect them in that - # order. Number of spaces does matter. - possible_fields = [ " Version:", - " Serial Number:", - " Signature Algorithm:", - " Issuer:", - " Not Before:", - " Not After :", - " Subject:", - " Public Key Algorithm:", - " Modulus (", - " Exponent:", - " X509v3 Subject Key Identifier:", - " X509v3 Authority Key Identifier:", - " keyid:", - " DirName:", - " serial:", - " X509v3 Basic Constraints:", - " X509v3 Key Usage:", - " X509v3 Extended Key Usage:", - " X509v3 CRL Distribution Points:", - " Authority Information Access:", - " Signature Algorithm:" ] - possible_fields_count = len(possible_fields) - - def __init__(self, certpath): - error_msg = "Unable to import certificate." - - fields_dict = {} - for k in self.possible_fields: - fields_dict[k] = None - - self.certpath = None - rawcert = None - - if (not '\x00' in certpath) and os.path.isfile(certpath): # file - self.certpath = certpath - cert_size = os.path.getsize(certpath) - if cert_size > MAX_CERT_SIZE: - raise Exception(error_msg) - try: - f = open(certpath) - rawcert = f.read() - f.close() - except: - raise Exception(error_msg) - else: - rawcert = certpath - - if rawcert is None: - raise Exception(error_msg) - - self.rawcert = rawcert - - # Let's try to get file format : PEM or DER. - fmtstr = 'openssl x509 -text -inform %s -noout' - convertstr = 'openssl x509 -inform %s -outform %s' - cert_header = "-----BEGIN CERTIFICATE-----" - cert_footer = "-----END CERTIFICATE-----" - l = rawcert.split(cert_header, 1) - if len(l) == 2: # looks like PEM - tmp = l[1] - l = tmp.split(cert_footer, 1) - if len(l) == 2: - tmp = l[0] - rawcert = "%s%s%s\n" % (cert_header, tmp, cert_footer) - else: - raise Exception(error_msg) - r,w,e = popen3((fmtstr % "PEM").split(" ")) - w.write(rawcert) - w.close() - textcert = r.read() - r.close() - res = e.read() - e.close() - if res == '': - self.format = "PEM" - self.pemcert = rawcert - self.textcert = textcert - cmd = (convertstr % ("PEM", "DER")).split(" ") - self.dercert = self._apply_ossl_cmd(cmd, rawcert) - else: - raise Exception(error_msg) - else: # not PEM, try DER - r,w,e = popen3((fmtstr % "DER").split(" ")) - w.write(rawcert) - w.close() - textcert = r.read() - r.close() - res = e.read() - if res == '': - self.format = "DER" - self.dercert = rawcert - self.textcert = textcert - cmd = (convertstr % ("DER", "PEM")).split(" ") - self.pemcert = self._apply_ossl_cmd(cmd, rawcert) - cmd = (convertstr % ("DER", "DER")).split(" ") - self.dercert = self._apply_ossl_cmd(cmd, rawcert) - else: - raise Exception(error_msg) - - self.osslcmdbase = ['openssl', 'x509', '-inform', self.format] - - r,w,e = popen3('openssl asn1parse -inform DER'.split(' ')) - w.write(self.dercert) - w.close() - self.asn1parsecert = r.read() - r.close() - res = e.read() - e.close() - if res != '': - raise Exception(error_msg) - - # Grab _raw_ X509v3 Authority Key Identifier, if any. - tmp = self.asn1parsecert.split(":X509v3 Authority Key Identifier", 1) - self.authorityKeyID = None - if len(tmp) == 2: - tmp = tmp[1] - tmp = tmp.split("[HEX DUMP]:", 1)[1] - self.authorityKeyID=tmp.split('\n',1)[0] - - # Grab _raw_ X509v3 Subject Key Identifier, if any. - tmp = self.asn1parsecert.split(":X509v3 Subject Key Identifier", 1) - self.subjectKeyID = None - if len(tmp) == 2: - tmp = tmp[1] - tmp = tmp.split("[HEX DUMP]:", 1)[1] - self.subjectKeyID=tmp.split('\n',1)[0] - - # Get tbsCertificate using the worst hack. output of asn1parse - # looks like that: - # - # 0:d=0 hl=4 l=1298 cons: SEQUENCE - # 4:d=1 hl=4 l=1018 cons: SEQUENCE - # ... - # - l1,l2 = self.asn1parsecert.split('\n', 2)[:2] - hl1 = int(l1.split("hl=",1)[1].split("l=",1)[0]) - rem = l2.split("hl=",1)[1] - hl2, rem = rem.split("l=",1) - hl2 = int(hl2) - l = int(rem.split("cons",1)[0]) - self.tbsCertificate = self.dercert[hl1:hl1+hl2+l] - - # Parse the -text output of openssl to make things available - tmp = self.textcert.split('\n', 2)[2] - l = tmp.split('\n', 1) - if len(l) != 2: - raise Exception(error_msg) - cur, tmp = l - i = 0 - k = self.possible_fields[i] # Version: - cur = cur[len(k):] + '\n' - while k: - l = tmp.split('\n', 1) - if len(l) != 2: # Over - fields_dict[k] = cur - break - l, tmp = l - - newkey = 0 - # skip fields we have already seen, this is the purpose of 'i' - for j in xrange(i, self.possible_fields_count): - f = self.possible_fields[j] - if l.startswith(f): - fields_dict[k] = cur - cur = l[len(f):] + '\n' - k = f - newkey = 1 - i = j + 1 - break - if newkey == 1: - continue - cur += l + '\n' - - # version - v = fields_dict[" Version:"] - self.version = None - if v: - self.version = int(v[1:2]) - if self.version is None: - raise Exception(error_msg) - - # serial number - v = fields_dict[" Serial Number:"] - self.serial = None - if v: - v = v.replace('\n', '').strip() - if "0x" in v: - v = v.split("0x", 1)[1].split(')', 1)[0] - v = v.replace(':', '').upper() - if len(v) % 2: - v = '0' + v - self.serial = v - if self.serial is None: - raise Exception(error_msg) - - # Signature Algorithm - v = fields_dict[" Signature Algorithm:"] - self.sigAlg = None - if v: - v = v.split('\n',1)[0] - v = v.strip() - self.sigAlg = v - if self.sigAlg is None: - raise Exception(error_msg) - - # issuer - v = fields_dict[" Issuer:"] - self.issuer = None - if v: - v = v.split('\n',1)[0] - v = v.strip() - self.issuer = v - if self.issuer is None: - raise Exception(error_msg) - - # not before - v = fields_dict[" Not Before:"] - self.notBefore_str = None - if v: - v = v.split('\n',1)[0] - v = v.strip() - self.notBefore_str = v - if self.notBefore_str is None: - raise Exception(error_msg) - try: - self.notBefore = time.strptime(self.notBefore_str, - "%b %d %H:%M:%S %Y %Z") - except: - self.notBefore = time.strptime(self.notBefore_str, - "%b %d %H:%M:%S %Y") - self.notBefore_str_simple = time.strftime("%x", self.notBefore) - - # not after - v = fields_dict[" Not After :"] - self.notAfter_str = None - if v: - v = v.split('\n',1)[0] - v = v.strip() - self.notAfter_str = v - if self.notAfter_str is None: - raise Exception(error_msg) - try: - self.notAfter = time.strptime(self.notAfter_str, - "%b %d %H:%M:%S %Y %Z") - except: - self.notAfter = time.strptime(self.notAfter_str, - "%b %d %H:%M:%S %Y") - self.notAfter_str_simple = time.strftime("%x", self.notAfter) - - # subject - v = fields_dict[" Subject:"] - self.subject = None - if v: - v = v.split('\n',1)[0] - v = v.strip() - self.subject = v - if self.subject is None: - raise Exception(error_msg) - - # Public Key Algorithm - v = fields_dict[" Public Key Algorithm:"] - self.pubKeyAlg = None - if v: - v = v.split('\n',1)[0] - v = v.strip() - self.pubKeyAlg = v - if self.pubKeyAlg is None: - raise Exception(error_msg) - - # Modulus - v = fields_dict[" Modulus ("] - self.modulus = None - if v: - v,t = v.split(' bit):',1) - self.modulusLen = int(v) - t = t.replace(' ', '').replace('\n', ''). replace(':', '') - self.modulus_hexdump = t - self.modulus = long(t, 16) - if self.modulus is None: - raise Exception(error_msg) - - # Exponent - v = fields_dict[" Exponent:"] - self.exponent = None - if v: - v = v.split('(',1)[0] - self.exponent = long(v) - if self.exponent is None: - raise Exception(error_msg) - - # Public Key instance - self.key = RSA.construct((self.modulus, self.exponent, )) - - # Subject Key Identifier - - # Authority Key Identifier: keyid, dirname and serial - self.authorityKeyID_keyid = None - self.authorityKeyID_dirname = None - self.authorityKeyID_serial = None - if self.authorityKeyID: # (hex version already done using asn1parse) - v = fields_dict[" keyid:"] - if v: - v = v.split('\n',1)[0] - v = v.strip().replace(':', '') - self.authorityKeyID_keyid = v - v = fields_dict[" DirName:"] - if v: - v = v.split('\n',1)[0] - self.authorityKeyID_dirname = v - v = fields_dict[" serial:"] - if v: - v = v.split('\n',1)[0] - v = v.strip().replace(':', '') - self.authorityKeyID_serial = v - - # Basic constraints - self.basicConstraintsCritical = False - self.basicConstraints=None - v = fields_dict[" X509v3 Basic Constraints:"] - if v: - self.basicConstraints = {} - v,t = v.split('\n',2)[:2] - if "critical" in v: - self.basicConstraintsCritical = True - if "CA:" in t: - self.basicConstraints["CA"] = t.split('CA:')[1][:4] == "TRUE" - if "pathlen:" in t: - self.basicConstraints["pathlen"] = int(t.split('pathlen:')[1]) - - # X509v3 Key Usage - self.keyUsage = [] - v = fields_dict[" X509v3 Key Usage:"] - if v: - # man 5 x509v3_config - ku_mapping = {"Digital Signature": "digitalSignature", - "Non Repudiation": "nonRepudiation", - "Key Encipherment": "keyEncipherment", - "Data Encipherment": "dataEncipherment", - "Key Agreement": "keyAgreement", - "Certificate Sign": "keyCertSign", - "CRL Sign": "cRLSign", - "Encipher Only": "encipherOnly", - "Decipher Only": "decipherOnly"} - v = v.split('\n',2)[1] - l = map(lambda x: x.strip(), v.split(',')) - while l: - c = l.pop() - if ku_mapping.has_key(c): - self.keyUsage.append(ku_mapping[c]) - else: - self.keyUsage.append(c) # Add it anyway - print "Found unknown X509v3 Key Usage: '%s'" % c - print "Report it to arno (at) natisbad.org for addition" - - # X509v3 Extended Key Usage - self.extKeyUsage = [] - v = fields_dict[" X509v3 Extended Key Usage:"] - if v: - # man 5 x509v3_config: - eku_mapping = {"TLS Web Server Authentication": "serverAuth", - "TLS Web Client Authentication": "clientAuth", - "Code Signing": "codeSigning", - "E-mail Protection": "emailProtection", - "Time Stamping": "timeStamping", - "Microsoft Individual Code Signing": "msCodeInd", - "Microsoft Commercial Code Signing": "msCodeCom", - "Microsoft Trust List Signing": "msCTLSign", - "Microsoft Encrypted File System": "msEFS", - "Microsoft Server Gated Crypto": "msSGC", - "Netscape Server Gated Crypto": "nsSGC", - "IPSec End System": "iPsecEndSystem", - "IPSec Tunnel": "iPsecTunnel", - "IPSec User": "iPsecUser"} - v = v.split('\n',2)[1] - l = map(lambda x: x.strip(), v.split(',')) - while l: - c = l.pop() - if eku_mapping.has_key(c): - self.extKeyUsage.append(eku_mapping[c]) - else: - self.extKeyUsage.append(c) # Add it anyway - print "Found unknown X509v3 Extended Key Usage: '%s'" % c - print "Report it to arno (at) natisbad.org for addition" - - # CRL Distribution points - self.cRLDistributionPoints = [] - v = fields_dict[" X509v3 CRL Distribution Points:"] - if v: - v = v.split("\n\n", 1)[0] - v = v.split("URI:")[1:] - self.CRLDistributionPoints = map(lambda x: x.strip(), v) - - # Authority Information Access: list of tuples ("method", "location") - self.authorityInfoAccess = [] - v = fields_dict[" Authority Information Access:"] - if v: - v = v.split("\n\n", 1)[0] - v = v.split("\n")[1:] - for e in v: - method, location = map(lambda x: x.strip(), e.split(" - ", 1)) - self.authorityInfoAccess.append((method, location)) - - # signature field - v = fields_dict[" Signature Algorithm:" ] - self.sig = None - if v: - v = v.split('\n',1)[1] - v = v.replace(' ', '').replace('\n', '') - self.sig = "".join(map(lambda x: chr(int(x, 16)), v.split(':'))) - self.sigLen = len(self.sig) - if self.sig is None: - raise Exception(error_msg) - - def isIssuerCert(self, other): - """ - True if 'other' issued 'self', i.e.: - - self.issuer == other.subject - - self is signed by other - """ - # XXX should be done on raw values, instead of their textual repr - if self.issuer != other.subject: - return False - - # Sanity check regarding modulus length and the - # signature length - keyLen = (other.modulusLen + 7)/8 - if keyLen != self.sigLen: - return False - - unenc = other.encrypt(self.sig) # public key encryption, i.e. decrypt - - # XXX Check block type (00 or 01 and type of padding) - unenc = unenc[1:] - if not '\x00' in unenc: - return False - pos = unenc.index('\x00') - unenc = unenc[pos+1:] - - found = None - for k in _hashFuncParams: - if self.sigAlg.startswith(k): - found = k - break - if not found: - return False - hlen, hfunc, digestInfo = _hashFuncParams[k] - - if len(unenc) != (hlen+len(digestInfo)): - return False - - if not unenc.startswith(digestInfo): - return False - - h = unenc[-hlen:] - myh = hfunc(self.tbsCertificate) - - return h == myh - - def chain(self, certlist): - """ - Construct the chain of certificates leading from 'self' to the - self signed root using the certificates in 'certlist'. If the - list does not provide all the required certs to go to the root - the function returns a incomplete chain starting with the - certificate. This fact can be tested by tchecking if the last - certificate of the returned chain is self signed (if c is the - result, c[-1].isSelfSigned()) - """ - d = {} - for c in certlist: - # XXX we should check if we have duplicate - d[c.subject] = c - res = [self] - cur = self - while not cur.isSelfSigned(): - if d.has_key(cur.issuer): - possible_issuer = d[cur.issuer] - if cur.isIssuerCert(possible_issuer): - res.append(possible_issuer) - cur = possible_issuer - else: - break - return res - - def remainingDays(self, now=None): - """ - Based on the value of notBefore field, returns the number of - days the certificate will still be valid. The date used for the - comparison is the current and local date, as returned by - time.localtime(), except if 'now' argument is provided another - one. 'now' argument can be given as either a time tuple or a string - representing the date. Accepted format for the string version - are: - - - '%b %d %H:%M:%S %Y %Z' e.g. 'Jan 30 07:38:59 2008 GMT' - - '%m/%d/%y' e.g. '01/30/08' (less precise) - - If the certificate is no more valid at the date considered, then, - a negative value is returned representing the number of days - since it has expired. - - The number of days is returned as a float to deal with the unlikely - case of certificates that are still just valid. - """ - if now is None: - now = time.localtime() - elif type(now) is str: - try: - if '/' in now: - now = time.strptime(now, '%m/%d/%y') - else: - now = time.strptime(now, '%b %d %H:%M:%S %Y %Z') - except: - warning("Bad time string provided '%s'. Using current time" % now) - now = time.localtime() - - now = time.mktime(now) - nft = time.mktime(self.notAfter) - diff = (nft - now)/(24.*3600) - return diff - - - # return SHA-1 hash of cert embedded public key - # !! At the moment, the trailing 0 is in the hashed string if any - def keyHash(self): - m = self.modulus_hexdump - res = [] - i = 0 - l = len(m) - while i<l: # get a string version of modulus - res.append(struct.pack("B", int(m[i:i+2], 16))) - i += 2 - return sha.new("".join(res)).digest() - - def output(self, fmt="DER"): - if fmt == "DER": - return self.dercert - elif fmt == "PEM": - return self.pemcert - elif fmt == "TXT": - return self.textcert - - def export(self, filename, fmt="DER"): - """ - Export certificate in 'fmt' format (PEM, DER or TXT) to file 'filename' - """ - f = open(filename, "wb") - f.write(self.output(fmt)) - f.close() - - def isSelfSigned(self): - """ - Return True if the certificate is self signed: - - issuer and subject are the same - - the signature of the certificate is valid. - """ - if self.issuer == self.subject: - return self.isIssuerCert(self) - return False - - # Print main informations stored in certificate - def show(self): - print "Serial: %s" % self.serial - print "Issuer: " + self.issuer - print "Subject: " + self.subject - print "Validity: %s to %s" % (self.notBefore_str_simple, - self.notAfter_str_simple) - - def __repr__(self): - return "[X.509 Cert. Subject:%s, Issuer:%s]" % (self.subject, self.issuer) - - def __str__(self): - return self.dercert - - def verifychain(self, anchors, untrusted=None): - """ - Perform verification of certificate chains for that certificate. The - behavior of verifychain method is mapped (and also based) on openssl - verify userland tool (man 1 verify). - A list of anchors is required. untrusted parameter can be provided - a list of untrusted certificates that can be used to reconstruct the - chain. - - If you have a lot of certificates to verify against the same - list of anchor, consider constructing this list as a cafile - and use .verifychain_from_cafile() instead. - """ - cafile = create_temporary_ca_file(anchors) - if not cafile: - return False - untrusted_file = None - if untrusted: - untrusted_file = create_temporary_ca_file(untrusted) # hack - if not untrusted_file: - os.unlink(cafile) - return False - res = self.verifychain_from_cafile(cafile, - untrusted_file=untrusted_file) - os.unlink(cafile) - if untrusted_file: - os.unlink(untrusted_file) - return res - - def verifychain_from_cafile(self, cafile, untrusted_file=None): - """ - Does the same job as .verifychain() but using the list of anchors - from the cafile. This is useful (because more efficient) if - you have a lot of certificates to verify do it that way: it - avoids the creation of a cafile from anchors at each call. - - As for .verifychain(), a list of untrusted certificates can be - passed (as a file, this time) - """ - cmd = ["openssl", "verify", "-CAfile", cafile] - if untrusted_file: - cmd += ["-untrusted", untrusted_file] - try: - pemcert = self.output(fmt="PEM") - cmdres = self._apply_ossl_cmd(cmd, pemcert) - except: - return False - return cmdres.endswith("\nOK\n") or cmdres.endswith(": OK\n") - - def verifychain_from_capath(self, capath, untrusted_file=None): - """ - Does the same job as .verifychain_from_cafile() but using the list - of anchors in capath directory. The directory should contain - certificates files in PEM format with associated links as - created using c_rehash utility (man c_rehash). - - As for .verifychain_from_cafile(), a list of untrusted certificates - can be passed as a file (concatenation of the certificates in - PEM format) - """ - cmd = ["openssl", "verify", "-CApath", capath] - if untrusted_file: - cmd += ["-untrusted", untrusted_file] - try: - pemcert = self.output(fmt="PEM") - cmdres = self._apply_ossl_cmd(cmd, pemcert) - except: - return False - return cmdres.endswith("\nOK\n") or cmdres.endswith(": OK\n") - - def is_revoked(self, crl_list): - """ - Given a list of trusted CRL (their signature has already been - verified with trusted anchors), this function returns True if - the certificate is marked as revoked by one of those CRL. - - Note that if the Certificate was on hold in a previous CRL and - is now valid again in a new CRL and bot are in the list, it - will be considered revoked: this is because _all_ CRLs are - checked (not only the freshest) and revocation status is not - handled. - - Also note that the check on the issuer is performed on the - Authority Key Identifier if available in _both_ the CRL and the - Cert. Otherwise, the issuers are simply compared. - """ - for c in crl_list: - if (self.authorityKeyID is not None and - c.authorityKeyID is not None and - self.authorityKeyID == c.authorityKeyID): - return self.serial in map(lambda x: x[0], c.revoked_cert_serials) - elif (self.issuer == c.issuer): - return self.serial in map(lambda x: x[0], c.revoked_cert_serials) - return False - -def print_chain(l): - llen = len(l) - 1 - if llen < 0: - return "" - c = l[llen] - llen -= 1 - s = "_ " - if not c.isSelfSigned(): - s = "_ ... [Missing Root]\n" - else: - s += "%s [Self Signed]\n" % c.subject - i = 1 - while (llen != -1): - c = l[llen] - s += "%s\_ %s" % (" "*i, c.subject) - if llen != 0: - s += "\n" - i += 2 - llen -= 1 - print s - -# import popen2 -# a=popen3("openssl crl -text -inform DER -noout ", capturestderr=True) -# a.tochild.write(open("samples/klasa1.crl").read()) -# a.tochild.close() -# a.poll() - -class CRL(OSSLHelper): - # Below are the fields we recognize in the -text output of openssl - # and from which we extract information. We expect them in that - # order. Number of spaces does matter. - possible_fields = [ " Version", - " Signature Algorithm:", - " Issuer:", - " Last Update:", - " Next Update:", - " CRL extensions:", - " X509v3 Issuer Alternative Name:", - " X509v3 Authority Key Identifier:", - " keyid:", - " DirName:", - " serial:", - " X509v3 CRL Number:", - "Revoked Certificates:", - "No Revoked Certificates.", - " Signature Algorithm:" ] - possible_fields_count = len(possible_fields) - - def __init__(self, crlpath): - error_msg = "Unable to import CRL." - - fields_dict = {} - for k in self.possible_fields: - fields_dict[k] = None - - self.crlpath = None - rawcrl = None - - if (not '\x00' in crlpath) and os.path.isfile(crlpath): - self.crlpath = crlpath - cert_size = os.path.getsize(crlpath) - if cert_size > MAX_CRL_SIZE: - raise Exception(error_msg) - try: - f = open(crlpath) - rawcrl = f.read() - f.close() - except: - raise Exception(error_msg) - else: - rawcrl = crlpath - - if rawcrl is None: - raise Exception(error_msg) - - self.rawcrl = rawcrl - - # Let's try to get file format : PEM or DER. - fmtstr = 'openssl crl -text -inform %s -noout' - convertstr = 'openssl crl -inform %s -outform %s' - crl_header = "-----BEGIN X509 CRL-----" - crl_footer = "-----END X509 CRL-----" - l = rawcrl.split(crl_header, 1) - if len(l) == 2: # looks like PEM - tmp = l[1] - l = tmp.split(crl_footer, 1) - if len(l) == 2: - tmp = l[0] - rawcrl = "%s%s%s\n" % (crl_header, tmp, crl_footer) - else: - raise Exception(error_msg) - r,w,e = popen3((fmtstr % "PEM").split(" ")) - w.write(rawcrl) - w.close() - textcrl = r.read() - r.close() - res = e.read() - e.close() - if res == '': - self.format = "PEM" - self.pemcrl = rawcrl - self.textcrl = textcrl - cmd = (convertstr % ("PEM", "DER")).split(" ") - self.dercrl = self._apply_ossl_cmd(cmd, rawcrl) - else: - raise Exception(error_msg) - else: # not PEM, try DER - r,w,e = popen3((fmtstr % "DER").split(' ')) - w.write(rawcrl) - w.close() - textcrl = r.read() - r.close() - res = e.read() - if res == '': - self.format = "DER" - self.dercrl = rawcrl - self.textcrl = textcrl - cmd = (convertstr % ("DER", "PEM")).split(" ") - self.pemcrl = self._apply_ossl_cmd(cmd, rawcrl) - cmd = (convertstr % ("DER", "DER")).split(" ") - self.dercrl = self._apply_ossl_cmd(cmd, rawcrl) - else: - raise Exception(error_msg) - - self.osslcmdbase = ['openssl', 'crl', '-inform', self.format] - - r,w,e = popen3(('openssl asn1parse -inform DER').split(" ")) - w.write(self.dercrl) - w.close() - self.asn1parsecrl = r.read() - r.close() - res = e.read() - e.close() - if res != '': - raise Exception(error_msg) - - # Grab _raw_ X509v3 Authority Key Identifier, if any. - tmp = self.asn1parsecrl.split(":X509v3 Authority Key Identifier", 1) - self.authorityKeyID = None - if len(tmp) == 2: - tmp = tmp[1] - tmp = tmp.split("[HEX DUMP]:", 1)[1] - self.authorityKeyID=tmp.split('\n',1)[0] - - # Parse the -text output of openssl to make things available - tmp = self.textcrl.split('\n', 1)[1] - l = tmp.split('\n', 1) - if len(l) != 2: - raise Exception(error_msg) - cur, tmp = l - i = 0 - k = self.possible_fields[i] # Version - cur = cur[len(k):] + '\n' - while k: - l = tmp.split('\n', 1) - if len(l) != 2: # Over - fields_dict[k] = cur - break - l, tmp = l - - newkey = 0 - # skip fields we have already seen, this is the purpose of 'i' - for j in xrange(i, self.possible_fields_count): - f = self.possible_fields[j] - if l.startswith(f): - fields_dict[k] = cur - cur = l[len(f):] + '\n' - k = f - newkey = 1 - i = j + 1 - break - if newkey == 1: - continue - cur += l + '\n' - - # version - v = fields_dict[" Version"] - self.version = None - if v: - self.version = int(v[1:2]) - if self.version is None: - raise Exception(error_msg) - - # signature algorithm - v = fields_dict[" Signature Algorithm:"] - self.sigAlg = None - if v: - v = v.split('\n',1)[0] - v = v.strip() - self.sigAlg = v - if self.sigAlg is None: - raise Exception(error_msg) - - # issuer - v = fields_dict[" Issuer:"] - self.issuer = None - if v: - v = v.split('\n',1)[0] - v = v.strip() - self.issuer = v - if self.issuer is None: - raise Exception(error_msg) - - # last update - v = fields_dict[" Last Update:"] - self.lastUpdate_str = None - if v: - v = v.split('\n',1)[0] - v = v.strip() - self.lastUpdate_str = v - if self.lastUpdate_str is None: - raise Exception(error_msg) - self.lastUpdate = time.strptime(self.lastUpdate_str, - "%b %d %H:%M:%S %Y %Z") - self.lastUpdate_str_simple = time.strftime("%x", self.lastUpdate) - - # next update - v = fields_dict[" Next Update:"] - self.nextUpdate_str = None - if v: - v = v.split('\n',1)[0] - v = v.strip() - self.nextUpdate_str = v - if self.nextUpdate_str is None: - raise Exception(error_msg) - self.nextUpdate = time.strptime(self.nextUpdate_str, - "%b %d %H:%M:%S %Y %Z") - self.nextUpdate_str_simple = time.strftime("%x", self.nextUpdate) - - # XXX Do something for Issuer Alternative Name - - # Authority Key Identifier: keyid, dirname and serial - self.authorityKeyID_keyid = None - self.authorityKeyID_dirname = None - self.authorityKeyID_serial = None - if self.authorityKeyID: # (hex version already done using asn1parse) - v = fields_dict[" keyid:"] - if v: - v = v.split('\n',1)[0] - v = v.strip().replace(':', '') - self.authorityKeyID_keyid = v - v = fields_dict[" DirName:"] - if v: - v = v.split('\n',1)[0] - self.authorityKeyID_dirname = v - v = fields_dict[" serial:"] - if v: - v = v.split('\n',1)[0] - v = v.strip().replace(':', '') - self.authorityKeyID_serial = v - - # number - v = fields_dict[" X509v3 CRL Number:"] - self.number = None - if v: - v = v.split('\n',2)[1] - v = v.strip() - self.number = int(v) - - # Get the list of serial numbers of revoked certificates - self.revoked_cert_serials = [] - v = fields_dict["Revoked Certificates:"] - t = fields_dict["No Revoked Certificates."] - if (t is None and v is not None): - v = v.split("Serial Number: ")[1:] - for r in v: - s,d = r.split('\n', 1) - s = s.split('\n', 1)[0] - d = d.split("Revocation Date:", 1)[1] - d = time.strptime(d.strip(), "%b %d %H:%M:%S %Y %Z") - self.revoked_cert_serials.append((s,d)) - - # signature field - v = fields_dict[" Signature Algorithm:" ] - self.sig = None - if v: - v = v.split('\n',1)[1] - v = v.replace(' ', '').replace('\n', '') - self.sig = "".join(map(lambda x: chr(int(x, 16)), v.split(':'))) - self.sigLen = len(self.sig) - if self.sig is None: - raise Exception(error_msg) - - def __str__(self): - return self.dercrl - - # Print main informations stored in CRL - def show(self): - print "Version: %d" % self.version - print "sigAlg: " + self.sigAlg - print "Issuer: " + self.issuer - print "lastUpdate: %s" % self.lastUpdate_str_simple - print "nextUpdate: %s" % self.nextUpdate_str_simple - - def verify(self, anchors): - """ - Return True if the CRL is signed by one of the provided - anchors. False on error (invalid signature, missing anchorand, ...) - """ - cafile = create_temporary_ca_file(anchors) - if cafile is None: - return False - try: - cmd = self.osslcmdbase + ["-noout", "-CAfile", cafile] - cmdres = self._apply_ossl_cmd(cmd, self.rawcrl) - except: - os.unlink(cafile) - return False - os.unlink(cafile) - return "verify OK" in cmdres - - - diff --git a/scapy/layers/all.py b/scapy/layers/all.py index 8cc1d03b372c90ac51e06f78fe5f6669efde19b6..b30bb3ea824c88c920fdfc1c495f61fd896b100b 100644 --- a/scapy/layers/all.py +++ b/scapy/layers/all.py @@ -24,6 +24,7 @@ for _l in conf.load_layers: except Exception,e: log.warning("can't import layer %s: %s" % (_l,e)) +from scapy.layers.tls.cert import * diff --git a/scapy/crypto/__init__.py b/scapy/layers/tls/__init__.py similarity index 51% rename from scapy/crypto/__init__.py rename to scapy/layers/tls/__init__.py index b441863e820e6711a0aeaa73e46cc9222a261937..06203f002d043dd8fb394687fc3ac950aac8ddba 100644 --- a/scapy/crypto/__init__.py +++ b/scapy/layers/tls/__init__.py @@ -1,10 +1,10 @@ ## This file is part of Scapy ## See http://www.secdev.org/projects/scapy for more informations -## Copyright (C) Arnaud Ebalard <arno@natisbad.org> +## Copyright (C) Arnaud Ebalard, Maxence Tury ## This program is published under a GPLv2 license """ -Tools for handling with digital certificates. +Tools for handling TLS sessions and digital certificates. """ try: @@ -13,5 +13,12 @@ except ImportError: import logging log_loading = logging.getLogger("scapy.loading") log_loading.info("Can't import python Crypto lib. Disabled certificate manipulation tools") + +try: + import ecdsa +except ImportError: + import logging + log_loading = logging.getLogger("scapy.loading") + log_loading.info("Can't import python ecdsa lib. Disabled certificate manipulation tools") else: - from scapy.crypto.cert import * + from scapy.layers.tls.cert import * diff --git a/scapy/layers/tls/cert.py b/scapy/layers/tls/cert.py new file mode 100644 index 0000000000000000000000000000000000000000..7ed092a8e6301e018923319c35ec2910f5dca424 --- /dev/null +++ b/scapy/layers/tls/cert.py @@ -0,0 +1,889 @@ +## This file is part of Scapy +## Copyright (C) 2008 Arnaud Ebalard <arnaud.ebalard@eads.net> +## <arno@natisbad.org> +## 2015, 2016 Maxence Tury <maxence.tury@ssi.gouv.fr> +## This program is published under a GPLv2 license + +""" +High-level methods for PKI objects (X.509 certificates, CRLs, asymmetric keys). +Supports both RSA and ECDSA objects. +""" + +## This module relies on python-crypto and python-ecdsa. +## +## The classes below are wrappers for the ASN.1 objects defined in x509.py. +## By collecting their attributes, we bypass the ASN.1 structure, hence +## there is no direct method for exporting a new full DER-encoded version +## of a Cert instance after its serial has been modified (for example). +## If you need to modify an import, just use the corresponding ASN1_Packet. +## +## For instance, here is what you could do in order to modify the serial of +## 'cert' and then resign it with whatever 'key': +## f = open('cert.der') +## c = X509_Cert(f.read()) +## c.tbsCertificate.serialNumber = 0x4B1D +## k = PrivKey('key.pem') +## new_x509_cert = k.resignCert(c) +## No need for obnoxious openssl tweaking anymore. :) + +import base64, os, time + +import ecdsa +from Crypto.PublicKey import RSA + +from scapy.layers.tls.crypto.curves import import_curve +from scapy.layers.tls.crypto.pkcs1 import pkcs_os2ip, pkcs_i2osp, mapHashFunc +from scapy.layers.tls.crypto.pkcs1 import _EncryptAndVerifyRSA +from scapy.layers.tls.crypto.pkcs1 import _DecryptAndSignRSA + +from scapy.asn1.asn1 import ASN1_BIT_STRING +from scapy.asn1.mib import hash_by_oid +from scapy.layers.x509 import X509_SubjectPublicKeyInfo +from scapy.layers.x509 import RSAPublicKey, RSAPrivateKey +from scapy.layers.x509 import ECDSAPublicKey, ECDSAPrivateKey +from scapy.layers.x509 import RSAPrivateKey_OpenSSL, ECDSAPrivateKey_OpenSSL +from scapy.layers.x509 import X509_Cert, X509_CRL +from scapy.utils import binrepr + +# Maximum allowed size in bytes for a certificate file, to avoid +# loading huge file when importing a cert +MAX_KEY_SIZE = 50*1024 +MAX_CERT_SIZE = 50*1024 +MAX_CRL_SIZE = 10*1024*1024 # some are that big + + +##################################################################### +# Some helpers +##################################################################### + +def der2pem(der_string, obj="UNKNOWN"): + """ + Encode a byte string in PEM format. Header advertizes <obj> type. + """ + pem_string = "-----BEGIN %s-----\n" % obj + base64_string = base64.b64encode(der_string) + chunks = [base64_string[i:i+64] for i in range(0, len(base64_string), 64)] + pem_string += '\n'.join(chunks) + pem_string += "\n-----END %s-----\n" % obj + return pem_string + +def pem2der(pem_string): + """ + Encode every line between the first '-----\n' and the 2nd-to-last '-----'. + """ + pem_string = pem_string.replace("\r", "") + first_idx = pem_string.find("-----\n") + 6 + if pem_string.find("-----BEGIN", first_idx) != -1: + raise Exception("pem2der() expects only one PEM-encoded object") + last_idx = pem_string.rfind("-----", 0, pem_string.rfind("-----")) + base64_string = pem_string[first_idx:last_idx] + base64_string.replace("\n", "") + der_string = base64.b64decode(base64_string) + return der_string + +def split_pem(s): + """ + Split PEM objects. Useful to process concatenated certificates. + """ + pem_strings = [] + while s != "": + start_idx = s.find("-----BEGIN") + if start_idx == -1: + break + end_idx = s.find("-----END") + end_idx = s.find("\n", end_idx) + 1 + pem_strings.append(s[start_idx:end_idx]) + s = s[end_idx:] + return pem_strings + + +class _PKIObj(object): + def __init__(self, frmt, der, pem): + # Note that changing attributes of the _PKIObj does not update these + # values (e.g. modifying k.modulus does not change k.der). + self.frmt = frmt + self.der = der + self.pem = pem + + def __str__(self): + return self.der + + +class _PKIObjMaker(type): + def __call__(cls, obj_path, obj_max_size, pem_marker=None): + # This enables transparent DER and PEM-encoded data imports. + # Note that when importing a PEM file with multiple objects (like ECDSA + # private keys output by openssl), it will concatenate every object in + # order to create a 'der' attribute. When converting a 'multi' DER file + # into a PEM file, though, the PEM attribute will not be valid, + # because we do not try to identify the class of each object. + error_msg = "Unable to import data" + + if obj_path is None: + raise Exception(error_msg) + + if (not '\x00' in obj_path) and os.path.isfile(obj_path): + _size = os.path.getsize(obj_path) + if _size > obj_max_size: + raise Exception(error_msg) + try: + f = open(obj_path) + raw = f.read() + f.close() + except: + raise Exception(error_msg) + else: + raw = obj_path + + try: + if "-----BEGIN" in raw: + frmt = "PEM" + pem = raw + der_list = split_pem(raw) + der = ''.join(map(pem2der, der_list)) + else: + frmt = "DER" + der = raw + pem = "" + if pem_marker is not None: + pem = der2pem(raw, pem_marker) + # type identification may be needed for pem_marker + # in such case, the pem attribute has to be updated + except: + raise Exception(error_msg) + + p = _PKIObj(frmt, der, pem) + return p + + +##################################################################### +# PKI objects wrappers +##################################################################### + +############### +# Public Keys # +############### + +class _PubKeyFactory(_PKIObjMaker): + """ + Metaclass for PubKey creation. + It casts the appropriate class on the fly, then fills in + the appropriate attributes with updateWith() submethod. + """ + def __call__(cls, key_path): + + # First, we deal with the exceptional RSA KEA call. + if type(key_path) is tuple: + e, m, mLen = key_path + obj = type.__call__(cls) + obj.frmt = "tuple" + obj.modulus = m + obj.modulusLen = mLen + obj.pubExp = e + return obj + + # Now for the usual calls, key_path may be the path to either: + # _an X509_SubjectPublicKeyInfo, as processed by openssl; + # _an RSAPublicKey; + # _an ECDSAPublicKey. + obj = _PKIObjMaker.__call__(cls, key_path, MAX_KEY_SIZE) + try: + spki = X509_SubjectPublicKeyInfo(obj.der) + pubkey = spki.subjectPublicKey + if isinstance(pubkey, RSAPublicKey): + obj.__class__ = PubKeyRSA + obj.updateWith(pubkey) + elif isinstance(pubkey, ECDSAPublicKey): + obj.__class__ = PubKeyECDSA + obj.updateWith(spki) + else: + raise Exception("Unsupported publicKey type") + marker = "PUBLIC KEY" + except: + try: + pubkey = RSAPublicKey(obj.der) + obj.__class__ = PubKeyRSA + obj.updateWith(pubkey) + marker = "RSA PUBLIC KEY" + except: + # We cannot import an ECDSA public key without curve knowledge + raise Exception("Unable to import public key") + + if obj.frmt == "DER": + obj.pem = der2pem(obj.der, marker) + return obj + + +class PubKey(object): + """ + Parent class for both PubKeyRSA and PubKeyECDSA. + Provides a common verifyCert() method. + """ + __metaclass__ = _PubKeyFactory + + def verifyCert(self, cert): + """ + Verifies either a Cert or an X509_Cert. + """ + tbsCert = cert.tbsCertificate + sigAlg = tbsCert.signature + h = hash_by_oid[sigAlg.algorithm.val] + sigVal = str(cert.signatureValue) + return self.verify(str(tbsCert), sigVal, h=h, + t='pkcs', + sigdecode=ecdsa.util.sigdecode_der) + + +class PubKeyRSA(_PKIObj, PubKey, _EncryptAndVerifyRSA): + """ + Wrapper for RSA keys based on _EncryptAndVerifyRSA from crypto/pkcs1.py + Use the 'key' attribute to access original object. + """ + def updateWith(self, pubkey): + self.modulus = pubkey.modulus.val + self.modulusLen = len(binrepr(pubkey.modulus.val)) + self.pubExp = pubkey.publicExponent.val + self.key = RSA.construct((self.modulus, self.pubExp, )) + def encrypt(self, msg, t=None, h=None, mgf=None, L=None): + # no ECDSA encryption support, hence no ECDSA specific keywords here + return _EncryptAndVerifyRSA.encrypt(self, msg, t=t, h=h, mgf=mgf, L=L) + def verify(self, msg, sig, h=None, + t=None, mgf=None, sLen=None, + sigdecode=None): + return _EncryptAndVerifyRSA.verify(self, msg, sig, h=h, + t=t, mgf=mgf, sLen=sLen) + + +class PubKeyECDSA(_PKIObj, PubKey): + """ + Wrapper for ECDSA keys based on VerifyingKey from ecdsa library. + Use the 'key' attribute to access original object. + """ + def updateWith(self, spki): + # For now we use from_der() or from_string() methods, + # which do not offer support for compressed points. + #XXX Try using the from_public_point() method. + try: + self.key = ecdsa.VerifyingKey.from_der(str(spki)) + # from_der() raises an exception on explicit curves + except: + s = spki.subjectPublicKey.val_readable[1:] + p = spki.signatureAlgorithm.parameters + c = import_curve(p.fieldID.prime.val, + p.curve.a.val, + p.curve.b.val, + p.base.val, + p.order.val) + self.key = ecdsa.VerifyingKey.from_string(s, c) + def encrypt(self, msg, t=None, h=None, mgf=None, L=None): + # python-ecdsa does not support encryption + raise Exception("No ECDSA encryption support") + def verify(self, msg, sig, h=None, + t=None, mgf=None, sLen=None, + sigdecode=ecdsa.util.sigdecode_string): + try: + return self.key.verify(sig, msg, hashfunc=mapHashFunc(h), + sigdecode=sigdecode) + except ecdsa.keys.BadSignatureError: + return False + + +################ +# Private Keys # +################ + +class _PrivKeyFactory(_PKIObjMaker): + """ + Metaclass for PrivKey creation. + It casts the appropriate class on the fly, then fills in + the appropriate attributes with updateWith() submethod. + """ + def __call__(cls, key_path): + """ + key_path may be the path to either: + _an RSAPrivateKey_OpenSSL (as generated by openssl); + _an ECDSAPrivateKey_OpenSSL (as generated by openssl); + _an RSAPrivateKey; + _an ECDSAPrivateKey. + """ + obj = _PKIObjMaker.__call__(cls, key_path, MAX_KEY_SIZE) + multiPEM = False + try: + privkey = RSAPrivateKey_OpenSSL(obj.der) + privkey = privkey.privateKey + obj.__class__ = PrivKeyRSA + marker = "PRIVATE KEY" + except: + try: + privkey = ECDSAPrivateKey_OpenSSL(obj.der) + privkey = privkey.privateKey + obj.__class__ = PrivKeyECDSA + marker = "EC PRIVATE KEY" + multiPEM = True + except: + try: + privkey = RSAPrivateKey(obj.der) + obj.__class__ = PrivKeyRSA + marker = "RSA PRIVATE KEY" + except: + try: + privkey = ECDSAPrivateKey(obj.der) + obj.__class__ = PrivKeyECDSA + marker = "EC PRIVATE KEY" + except: + raise Exception("Unable to import private key") + obj.updateWith(privkey) + + if obj.frmt == "DER": + if multiPEM: + # this does not restore the EC PARAMETERS header + obj.pem = der2pem(str(privkey), marker) + else: + obj.pem = der2pem(obj.der, marker) + return obj + + +class PrivKey(object): + """ + Parent class for both PrivKeyRSA and PrivKeyECDSA. + Provides common signTBSCert() and resignCert() methods. + """ + __metaclass__ = _PrivKeyFactory + + def signTBSCert(self, tbsCert, h=None): + """ + Note that this will always copy the signature field from the + tbsCertificate into the signatureAlgorithm field of the result, + regardless of the coherence between its contents (which might + indicate ecdsa-with-SHA512) and the result (e.g. RSA signing MD2). + + There is a small inheritance trick for the computation of sigVal + below: in order to use a sign() method which would apply + to both PrivKeyRSA and PrivKeyECDSA, the sign() methods of the + subclasses accept any argument, be it from the RSA or ECDSA world, + and then they keep the ones they're interested in. + Here, t will be passed eventually to pkcs1._DecryptAndSignRSA.sign(), + while sigencode will be passed to ecdsa.keys.SigningKey.sign(). + """ + sigAlg = tbsCert.signature + h = h or hash_by_oid[sigAlg.algorithm.val] + sigVal = self.sign(str(tbsCert), h=h, + t='pkcs', + sigencode=ecdsa.util.sigencode_der) + c = X509_Cert() + c.tbsCertificate = tbsCert + c.signatureAlgorithm = sigAlg + c.signatureValue = ASN1_BIT_STRING(sigVal, readable=True) + return c + + def resignCert(self, cert): + # works with both Cert and X509_Cert types + return self.signTBSCert(cert.tbsCertificate) + + +class PrivKeyRSA(_PKIObj, PrivKey, _EncryptAndVerifyRSA, _DecryptAndSignRSA): + """ + Wrapper for RSA keys based on _DecryptAndSignRSA from crypto/pkcs1.py + Use the 'key' attribute to access original object. + """ + def updateWith(self, privkey): + self.modulus = privkey.modulus.val + self.modulusLen = len(binrepr(privkey.modulus.val)) + self.pubExp = privkey.publicExponent.val + self.privExp = privkey.privateExponent.val + self.prime1 = privkey.prime1.val + self.prime2 = privkey.prime2.val + self.exponent1 = privkey.exponent1.val + self.exponent2 = privkey.exponent2.val + self.coefficient = privkey.coefficient.val + self.key = RSA.construct((self.modulus, self.pubExp, self.privExp)) + def verify(self, msg, sig, h=None, + t=None, mgf=None, sLen=None, + sigdecode=None): + # Let's copy this from PubKeyRSA instead of adding another baseclass :) + return _EncryptAndVerifyRSA.verify(self, msg, sig, h=h, + t=t, mgf=mgf, sLen=sLen) + def sign(self, data, h=None, + t=None, mgf=None, sLen=None, + k=None, entropy=None, sigencode=None): + return _DecryptAndSignRSA.sign(self, data, h=h, + t=t, mgf=mgf, sLen=sLen) + + +class PrivKeyECDSA(_PKIObj, PrivKey): + """ + Wrapper for ECDSA keys based on SigningKey from ecdsa library. + Use the 'key' attribute to access original object. + """ + def updateWith(self, privkey): + self.privKey = pkcs_os2ip(privkey.privateKey.val) + self.key = ecdsa.SigningKey.from_der(str(privkey)) + self.vkey = self.key.get_verifying_key() + def verify(self, msg, sig, h=None, + t=None, mgf=None, sLen=None, + sigdecode=None): + return self.vkey.verify(sig, msg, hashfunc=mapHashFunc(h), + sigdecode=sigdecode) + def sign(self, data, h=None, + t=None, mgf=None, sLen=None, + k=None, entropy=None, sigencode=ecdsa.util.sigencode_string): + return self.key.sign(data, hashfunc=mapHashFunc(h), + k=k, entropy=entropy, sigencode=sigencode) + + +################ +# Certificates # +################ + +class _CertMaker(_PKIObjMaker): + """ + Metaclass for Cert creation. It is not necessary as it was for the keys, + but we reuse the model instead of creating redundant constructors. + """ + def __call__(cls, cert_path): + obj = _PKIObjMaker.__call__(cls, cert_path, + MAX_CERT_SIZE, "CERTIFICATE") + obj.__class__ = Cert + try: + cert = X509_Cert(obj.der) + except: + raise Exception("Unable to import certificate") + obj.updateWith(cert) + return obj + + +class Cert(_PKIObj): + """ + Wrapper for the X509_Cert from layers/x509.py. + Use the 'x509Cert' attribute to access original object. + """ + __metaclass__ = _CertMaker + + def updateWith(self, cert): + error_msg = "Unable to import certificate" + + self.x509Cert = cert + + tbsCert = cert.tbsCertificate + self.tbsCertificate = tbsCert + + if tbsCert.version: + self.version = tbsCert.version.val + 1 + else: + self.version = 1 + self.serial = tbsCert.serialNumber.val + self.sigAlg = tbsCert.signature.algorithm.oidname + self.issuer = tbsCert.get_issuer() + self.issuer_str = tbsCert.get_issuer_str() + self.issuer_hash = hash(self.issuer_str) + self.subject = tbsCert.get_subject() + self.subject_str = tbsCert.get_subject_str() + self.subject_hash = hash(self.subject_str) + + self.notBefore_str = tbsCert.validity.not_before.pretty_time + notBefore = tbsCert.validity.not_before.val + if notBefore[-1] == "Z": + notBefore = notBefore[:-1] + try: + self.notBefore = time.strptime(notBefore, "%y%m%d%H%M%S") + except: + raise Exception(error_msg) + self.notBefore_str_simple = time.strftime("%x", self.notBefore) + + self.notAfter_str = tbsCert.validity.not_after.pretty_time + notAfter = tbsCert.validity.not_after.val + if notAfter[-1] == "Z": + notAfter = notAfter[:-1] + try: + self.notAfter = time.strptime(notAfter, "%y%m%d%H%M%S") + except: + raise Exception(error_msg) + self.notAfter_str_simple = time.strftime("%x", self.notAfter) + + self.pubKey = PubKey(str(tbsCert.subjectPublicKeyInfo)) + + if tbsCert.extensions: + for extn in tbsCert.extensions: + if extn.extnID.oidname == "basicConstraints": + self.cA = False + if extn.extnValue.cA: + self.cA = not (extn.extnValue.cA.val == 0) + elif extn.extnID.oidname == "keyUsage": + self.keyUsage = extn.extnValue.get_keyUsage() + elif extn.extnID.oidname == "extKeyUsage": + self.extKeyUsage = extn.extnValue.get_extendedKeyUsage() + elif extn.extnID.oidname == "authorityKeyIdentifier": + self.authorityKeyID = extn.extnValue.keyIdentifier.val + + self.signatureValue = str(cert.signatureValue) + self.signatureLen = len(self.signatureValue) + + def isIssuerCert(self, other): + """ + True if 'other' issued 'self', i.e.: + - self.issuer == other.subject + - self is signed by other + """ + if self.issuer_hash != other.subject_hash: + return False + return other.pubKey.verifyCert(self) + + def isSelfSigned(self): + """ + Return True if the certificate is self-signed: + - issuer and subject are the same + - the signature of the certificate is valid. + """ + if self.issuer_hash == self.subject_hash: + return self.isIssuerCert(self) + return False + + def encrypt(self, msg, t=None, h=None, mgf=None, L=None): + # no ECDSA *encryption* support, hence only RSA specific keywords here + return self.pubKey.encrypt(msg, t=t, h=h, mgf=mgf, L=L) + + def verify(self, msg, sig, h=None, + t=None, mgf=None, sLen=None, + sigdecode=None): + return self.pubKey.verify(msg, sig, h=h, + t=t, mgf=mgf, sLen=sLen, + sigdecode=sigdecode) + + def remainingDays(self, now=None): + """ + Based on the value of notAfter field, returns the number of + days the certificate will still be valid. The date used for the + comparison is the current and local date, as returned by + time.localtime(), except if 'now' argument is provided another + one. 'now' argument can be given as either a time tuple or a string + representing the date. Accepted format for the string version + are: + + - '%b %d %H:%M:%S %Y %Z' e.g. 'Jan 30 07:38:59 2008 GMT' + - '%m/%d/%y' e.g. '01/30/08' (less precise) + + If the certificate is no more valid at the date considered, then + a negative value is returned representing the number of days + since it has expired. + + The number of days is returned as a float to deal with the unlikely + case of certificates that are still just valid. + """ + if now is None: + now = time.localtime() + elif type(now) is str: + try: + if '/' in now: + now = time.strptime(now, '%m/%d/%y') + else: + now = time.strptime(now, '%b %d %H:%M:%S %Y %Z') + except: + print "Bad time string provided, will use localtime() instead." + now = time.localtime() + + now = time.mktime(now) + nft = time.mktime(self.notAfter) + diff = (nft - now)/(24.*3600) + return diff + + def isRevoked(self, crl_list): + """ + Given a list of trusted CRL (their signature has already been + verified with trusted anchors), this function returns True if + the certificate is marked as revoked by one of those CRL. + + Note that if the Certificate was on hold in a previous CRL and + is now valid again in a new CRL and bot are in the list, it + will be considered revoked: this is because _all_ CRLs are + checked (not only the freshest) and revocation status is not + handled. + + Also note that the check on the issuer is performed on the + Authority Key Identifier if available in _both_ the CRL and the + Cert. Otherwise, the issuers are simply compared. + """ + for c in crl_list: + if (self.authorityKeyID is not None and + c.authorityKeyID is not None and + self.authorityKeyID == c.authorityKeyID): + return self.serial in map(lambda x: x[0], + c.revoked_cert_serials) + elif self.issuer == c.issuer: + return self.serial in map(lambda x: x[0], + c.revoked_cert_serials) + return False + + def export(self, filename, fmt="DER"): + """ + Export certificate in 'fmt' format (DER or PEM) to file 'filename' + """ + f = open(filename, "wb") + if fmt == "DER": + f.write(self.der) + elif fmt == "PEM": + f.write(self.pem) + f.close() + + def show(self): + print "Serial: %s" % self.serial + print "Issuer: " + self.issuer_str + print "Subject: " + self.subject_str + print "Validity: %s to %s" % (self.notBefore_str, self.notAfter_str) + + def __repr__(self): + return "[X.509 Cert. Subject:%s, Issuer:%s]" % (self.subject_str, self.issuer_str) + + +################################ +# Certificate Revocation Lists # +################################ + +class _CRLMaker(_PKIObjMaker): + """ + Metaclass for CRL creation. It is not necessary as it was for the keys, + but we reuse the model instead of creating redundant constructors. + """ + def __call__(cls, cert_path): + obj = _PKIObjMaker.__call__(cls, cert_path, MAX_CRL_SIZE, "X509 CRL") + obj.__class__ = CRL + try: + crl = X509_CRL(obj.der) + except: + raise Exception("Unable to import CRL") + obj.updateWith(crl) + return obj + + +class CRL(_PKIObj): + """ + Wrapper for the X509_CRL from layers/x509.py. + Use the 'x509CRL' attribute to access original object. + """ + __metaclass__ = _CRLMaker + + def updateWith(self, crl): + error_msg = "Unable to import CRL" + + self.x509CRL = crl + + tbsCertList = crl.tbsCertList + self.tbsCertList = str(tbsCertList) + + if tbsCertList.version: + self.version = tbsCertList.version.val + 1 + else: + self.version = 1 + self.sigAlg = tbsCertList.signature.algorithm.oidname + self.issuer = tbsCertList.get_issuer() + self.issuer_str = tbsCertList.get_issuer_str() + self.issuer_hash = hash(self.issuer_str) + + self.lastUpdate_str = tbsCertList.this_update.pretty_time + lastUpdate = tbsCertList.this_update.val + if lastUpdate[-1] == "Z": + lastUpdate = lastUpdate[:-1] + try: + self.lastUpdate = time.strptime(lastUpdate, "%y%m%d%H%M%S") + except: + raise Exception(error_msg) + self.lastUpdate_str_simple = time.strftime("%x", self.lastUpdate) + + self.nextUpdate = None + self.nextUpdate_str_simple = None + if tbsCertList.next_update: + self.nextUpdate_str = tbsCertList.next_update.pretty_time + nextUpdate = tbsCertList.next_update.val + if nextUpdate[-1] == "Z": + nextUpdate = nextUpdate[:-1] + try: + self.nextUpdate = time.strptime(nextUpdate, "%y%m%d%H%M%S") + except: + raise Exception(error_msg) + self.nextUpdate_str_simple = time.strftime("%x", self.nextUpdate) + + if tbsCertList.crlExtensions: + for extension in tbsCertList.crlExtensions: + if extension.extnID.oidname == "cRLNumber": + self.number = extension.extnValue.cRLNumber.val + + revoked = [] + if tbsCertList.revokedCertificates: + for cert in tbsCertList.revokedCertificates: + serial = cert.serialNumber.val + date = cert.revocationDate.val + if date[-1] == "Z": + date = date[:-1] + try: + revocationDate = time.strptime(date, "%y%m%d%H%M%S") + except: + raise Exception(error_msg) + revoked.append((serial, date)) + self.revoked_cert_serials = revoked + + self.signatureValue = str(crl.signatureValue) + self.signatureLen = len(self.signatureValue) + + def isIssuerCert(self, other): + # This is exactly the same thing as in Cert method. + if self.issuer_hash != other.subject_hash: + return False + return other.pubKey.verifyCert(self) + + def verify(self, anchors): + # Return True iff the CRL is signed by one of the provided anchors. + for a in anchors: + if self.isIssuerCert(a): + return True + return False + + def show(self): + print "Version: %d" % self.version + print "sigAlg: " + self.sigAlg + print "Issuer: " + self.issuer_str + print "lastUpdate: %s" % self.lastUpdate_str + print "nextUpdate: %s" % self.nextUpdate_str + + +###################### +# Certificate chains # +###################### + +class Chain(list): + """ + Basically, an enhanced array of Cert. + """ + def __init__(self, certList, cert0=None): + """ + Construct a chain of certificates starting with a self-signed + certificate (or any certificate submitted by the user) + and following issuer/subject matching and signature validity. + If there is exactly one chain to be constructed, it will be, + but if there are multiple potential chains, there is no guarantee + that the retained one will be the longest one. + As Cert and CRL classes both share an isIssuerCert() method, + the trailing element of a Chain may alternatively be a CRL. + + Note that we do not check AKID/{SKID/issuer/serial} matching, + nor the presence of keyCertSign in keyUsage extension (if present). + """ + list.__init__(self, ()) + if cert0: + self.append(cert0) + else: + for root_candidate in certList: + if root_candidate.isSelfSigned(): + self.append(root_candidate) + certList.remove(root_candidate) + break + + if len(self) > 0: + while certList: + l = len(self) + for c in certList: + if c.isIssuerCert(self[-1]): + self.append(c) + certList.remove(c) + break + if len(self) == l: + # no new certificate appended to self + break + + def verifyChain(self, anchors, untrusted=None): + """ + Perform verification of certificate chains for that certificate. + A list of anchors is required. The certificates in the optional + untrusted list may be used as additional elements to the final chain. + On par with chain instantiation, only one chain constructed with the + untrusted candidates will be retained. Eventually, dates are checked. + """ + untrusted = untrusted or [] + for a in anchors: + chain = Chain(self + untrusted, a) + if len(chain) == 1: # anchor only + continue + # check that the chain does not exclusively rely on untrusted + found = False + for c in self: + if c in chain[1:]: + found = True + if found: + for c in chain: + if c.remainingDays() < 0: + break + if c is chain[-1]: # we got to the end of the chain + return chain + return None + + def verifyChainFromCAFile(self, cafile, untrusted_file=None): + """ + Does the same job as .verifyChain() but using the list of anchors + from the cafile. As for .verifyChain(), a list of untrusted + certificates can be passed (as a file, this time). + """ + try: + f = open(cafile) + ca_certs = f.read() + f.close() + except: + raise Exception("Could not read from cafile") + + anchors = [Cert(c) for c in split_pem(ca_certs)] + + untrusted = None + if untrusted_file: + try: + f = open(untrusted_file) + untrusted_certs = f.read() + f.close() + except: + raise Exception("Could not read from untrusted_file") + untrusted = [Cert(c) for c in split_pem(untrusted_certs)] + + return self.verifyChain(anchors, untrusted) + + def verifyChainFromCAPath(self, capath, untrusted_file=None): + """ + Does the same job as .verifyChainFromCAFile() but using the list + of anchors in capath directory. The directory should (only) contain + certificates files in PEM format. As for .verifyChainFromCAFile(), + a list of untrusted certificates can be passed as a file + (concatenation of the certificates in PEM format). + """ + try: + anchors = [] + for cafile in os.listdir(capath): + anchors.append(Cert(open(cafile).read())) + except: + raise Exception("capath provided is not a valid cert path") + + untrusted = None + if untrusted_file: + try: + f = open(untrusted_file) + untrusted_certs = f.read() + f.close() + except: + raise Exception("Could not read from untrusted_file") + untrusted = [Cert(c) for c in split_pem(untrusted_certs)] + + return self.verifyChain(anchors, untrusted) + + def __repr__(self): + llen = len(self) - 1 + if llen < 0: + return "" + c = self[0] + s = "__ " + if not c.isSelfSigned(): + s += "%s [Not Self Signed]\n" % c.subject_str + else: + s += "%s [Self Signed]\n" % c.subject_str + idx = 1 + while idx <= llen: + c = self[idx] + s += "%s\_ %s" % (" "*idx*2, c.subject_str) + if idx != llen: + s += "\n" + idx += 1 + return s + diff --git a/scapy/layers/tls/crypto/__init__.py b/scapy/layers/tls/crypto/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b30813d2c512d146ebe4ca60798765fa72ea3625 --- /dev/null +++ b/scapy/layers/tls/crypto/__init__.py @@ -0,0 +1 @@ +__all__ = ["curves"] diff --git a/scapy/layers/tls/crypto/curves.py b/scapy/layers/tls/crypto/curves.py new file mode 100644 index 0000000000000000000000000000000000000000..0512766136a7b547056ed67d231c0d9585529501 --- /dev/null +++ b/scapy/layers/tls/crypto/curves.py @@ -0,0 +1,406 @@ +## This file is part of Scapy +## See http://www.secdev.org/projects/scapy for more informations +## Copyright (C) 2016 Pascal Delaunay, Maxence Tury +## This program is published under a GPLv2 license + +""" +Implicit elliptic curves. +""" + +# Recommended curve parameters from www.secg.org/SEC2-Ver-1.0.pdf +# and www.ecc-brainpool.org/download/Domain-parameters.pdf +# Note that this module will overwrite curves from python-ecdsa. + +import math +from ecdsa.ellipticcurve import CurveFp, Point +from ecdsa.curves import Curve +from ecdsa.numbertheory import square_root_mod_prime + +from scapy.utils import long_converter, binrepr +from scapy.layers.tls.crypto.pkcs1 import pkcs_i2osp, pkcs_os2ip + + +############################################################## +# Some helpers +############################################################## + +def encode_point(point, point_format=0): + """ + Return a string representation of the Point p, according to point_format. + """ + pLen = len(binrepr(point.curve().p())) + x = pkcs_i2osp(point.x(), math.ceil(pLen/8)) + y = pkcs_i2osp(point.y(), math.ceil(pLen/8)) + if point_format == 0: + frmt = '\x04' + elif point_format == 1: + frmt = chr(2 + y%2) + y = '' + else: + raise Exception("No support for point_format %d" % point_format) + return frmt + x + y + +def extract_coordinates(g, curve): + """ + Return the coordinates x and y as integers, + regardless of the point format of string g. + Second expected parameter is a CurveFp. + """ + p = curve.p() + point_format = g[0] + point = g[1:] + if point_format == '\x04': + point_len = len(point) + if point_len % 2 != 0: + raise Exception("Point length is not even.") + x_bytes = point[:point_len>>1] + x = pkcs_os2ip(x_bytes) % p + y_bytes = point[point_len>>1:] + y = pkcs_os2ip(y_bytes) % p + elif point_format in ['\x02', '\x03']: + x_bytes = point + x = pkcs_os2ip(x_bytes) % p + # perform the y coordinate computation with self.tls_ec + y_square = (x*x*x + curve.a()*x + curve.b()) % p + y = square_root_mod_prime(y_square, p) + y_parity = ord(point_format) % 2 # \x02 means even, \x03 means odd + if y % 2 != y_parity: + y = -y % p + else: + raise Exception("Point starts with %s. This encoding " + "is not recognized." % repr(point_format)) + if not curve.contains_point(x, y): + raise Exception("The point we extracted does not belong on the curve!") + return x, y + +def import_curve(p, a, b, g, r, name="dummyName", oid=(1, 3, 132, 0, 0xff)): + """ + Create an ecdsa.curves.Curve from the usual parameters. + Arguments may be either octet strings or integers, + except g which we expect to be an octet string. + """ + if isinstance(p, str): + p = pkcs_os2ip(p) + if isinstance(a, str): + a = pkcs_os2ip(a) + if isinstance(b, str): + b = pkcs_os2ip(b) + if isinstance(r, str): + r = pkcs_os2ip(r) + curve = CurveFp(p, a, b) + x, y = extract_coordinates(g, curve) + generator = Point(curve, x, y, r) + return Curve(name, curve, generator, oid) + + +############################################################## +# Named curves +############################################################## + +# We always provide _a as a positive integer. + +_p = long_converter(""" + ffffffff ffffffff ffffffff fffffffe ffffac73""") +_a = 0 +_b = 7 +_Gx = long_converter(""" + 3b4c382c e37aa192 a4019e76 3036f4f5 dd4d7ebb""") +_Gy = long_converter(""" + 938cf935 318fdced 6bc28286 531733c3 f03c4fee""") +_r = long_converter("""01 + 00000000 00000000 0001b8fa 16dfab9a ca16b6b3""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +SECP160k1 = Curve("SECP160k1", curve, generator, + (1, 3, 132, 0, 9), "secp160k1") + +_p = long_converter(""" + ffffffff ffffffff ffffffff ffffffff 7fffffff""") +_a = -3 % _p +_b = long_converter(""" + 1c97befc 54bd7a8b 65acf89f 81d4d4ad c565fa45""") +_Gx = long_converter(""" + 4a96b568 8ef57328 46646989 68c38bb9 13cbfc82""") +_Gy = long_converter(""" + 23a62855 3168947d 59dcc912 04235137 7ac5fb32""") +_r = long_converter("""01 + 00000000 00000000 0001f4c8 f927aed3 ca752257""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +SECP160r1 = Curve("SECP160r1", curve, generator, + (1, 3, 132, 0, 8), "secp160r1") + +_p = long_converter(""" + ffffffff ffffffff ffffffff fffffffe ffffac73""") +_a = -3 % _p +_b = long_converter(""" + b4e134d3 fb59eb8b ab572749 04664d5a f50388ba""") +_Gx = long_converter(""" + 52dcb034 293a117e 1f4ff11b 30f7199d 3144ce6d""") +_Gy = long_converter(""" + feaffef2 e331f296 e071fa0d f9982cfe a7d43f2e""") +_r = long_converter("""01 + 00000000 00000000 0000351e e786a818 f3a1a16b""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +SECP160r2 = Curve("SECP160r2", curve, generator, + (1, 3, 132, 0, 30), "secp160r2") + +_p = long_converter(""" + ffffffff ffffffff ffffffff ffffffff fffffffe ffffee37""") +_a = 0 +_b = 3 +_Gx = long_converter(""" + db4ff10e c057e9ae 26b07d02 80b7f434 1da5d1b1 eae06c7d""") +_Gy = long_converter(""" + 9b2f2f6d 9c5628a7 844163d0 15be8634 4082aa88 d95e2f9d""") +_r = long_converter(""" + ffffffff ffffffff fffffffe 26f2fc17 0f69466a 74defd8d""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +SECP192k1 = Curve("SECP192k1", curve, generator, + (1, 3, 132, 0, 31), "secp192k1") + +_p = long_converter(""" + ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff""") +_a = -3 % _p +_b = long_converter(""" + 64210519 e59c80e7 0fa7e9ab 72243049 feb8deec c146b9b1""") +_Gx = long_converter(""" + 188da80e b03090f6 7cbf20eb 43a18800 f4ff0afd 82ff1012""") +_Gy = long_converter(""" + 07192b95 ffc8da78 631011ed 6b24cdd5 73f977a1 1e794811""") +_r = long_converter(""" + ffffffff ffffffff ffffffff 99def836 146bc9b1 b4d22831""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +SECP192r1 = Curve("SECP192r1", curve, generator, + (1, 2, 840, 10045, 3, 1, 1), "prime192v1") + +_p = long_converter(""" + ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe + ffffe56d""") +_a = 0 +_b = 5 +_Gx = long_converter(""" + a1455b33 4df099df 30fc28a1 69a467e9 e47075a9 0f7e650e + b6b7a45c""") +_Gy = long_converter(""" + 7e089fed 7fba3442 82cafbd6 f7e319f7 c0b0bd59 e2ca4bdb + 556d61a5""") +_r = long_converter("""01 + 00000000 00000000 00000000 0001dce8 d2ec6184 caf0a971 + 769fb1f7""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +SECP224k1 = Curve("SECP224k1", curve, generator, + (1, 3, 132, 0, 32), "secp224k1") + +_p = long_converter(""" + ffffffff ffffffff ffffffff ffffffff 00000000 00000000 + 00000001""") +_a = -3 % _p +_b = long_converter(""" + b4050a85 0c04b3ab f5413256 5044b0b7 d7bfd8ba 270b3943 + 2355ffb4""") +_Gx = long_converter(""" + b70e0cbd 6bb4bf7f 321390b9 4a03c1d3 56c21122 343280d6 + 115c1d21""") +_Gy = long_converter(""" + bd376388 b5f723fb 4c22dfe6 cd4375a0 5a074764 44d58199 + 85007e34""") +_r = long_converter(""" + ffffffff ffffffff ffffffff ffff16a2 e0b8f03e 13dd2945 + 5c5c2a3d""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +SECP224r1 = Curve("SECP224r1", curve, generator, + (1, 3, 132, 0, 33), "secp224r1") + +# (already defined as SECP256k1 by python-ecdsa) +_p = long_converter(""" + ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff + fffffffe fffffc2f""") +_a = 0 +_b = 7 +_Gx = long_converter(""" + 79be667e f9dcbbac 55a06295 ce870b07 029bfcdb 2dce28d9 + 59f2815b 16f81798""") +_Gy = long_converter(""" + 483ada77 26a3c465 5da4fbfc 0e1108a8 fd17b448 a6855419 + 9c47d08f fb10d4b8""") +_r = long_converter(""" + ffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b + bfd25e8c d0364141""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +SECP256k1 = Curve("SECP256k1", curve, generator, + (1, 3, 132, 0, 10), "secp256k1") + +_p = long_converter(""" + ffffffff 00000001 00000000 00000000 00000000 ffffffff + ffffffff ffffffff""") +_a = -3 % _p +_b = long_converter(""" + 5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6 + 3bce3c3e 27d2604b""") +_Gx = long_converter(""" + 6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 + f4a13945 d898c296""") +_Gy = long_converter(""" + 4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece + cbb64068 37bf51f5""") +_r = long_converter(""" + ffffffff 00000000 ffffffff ffffffff bce6faad a7179e84 + f3b9cac2 fc632551""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +SECP256r1 = Curve("SECP256r1", curve, generator, + (1, 2, 840, 10045, 3, 1, 7), "prime256v1") + +_p = long_converter(""" + ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff + ffffffff fffffffe ffffffff 00000000 00000000 ffffffff""") +_a = -3 % _p +_b = long_converter(""" + b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 + 0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef""") +_Gx = long_converter(""" + aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 + 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7""") +_Gy = long_converter(""" + 3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c + e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f""") +_r = long_converter(""" + ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff + c7634d81 f4372ddf 581a0db2 48b0a77a ecec196a ccc52973""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +SECP384r1 = Curve("SECP384r1", curve, generator, + (1, 3, 132, 0, 34), "secp384r1") + +_p = long_converter(""" + 01ff ffffffff ffffffff ffffffff ffffffff ffffffff + ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff + ffffffff ffffffff ffffffff ffffffff ffffffff""") +_a = -3 % _p +_b = long_converter(""" + 0051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b + 99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd + 3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00""") +_Gx = long_converter(""" + 00c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139 + 053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127 + a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66""") +_Gy = long_converter(""" + 0118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449 + 579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901 + 3fad0761 353c7086 a272c240 88be9476 9fd16650""") +_r = long_converter(""" + 01ff ffffffff ffffffff ffffffff ffffffff ffffffff + ffffffff ffffffff fffffffa 51868783 bf2f966b 7fcc0148 + f709a5d0 3bb5c9b8 899c47ae bb6fb71e 91386409""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +SECP521r1 = Curve("SECP521r1", curve, generator, + (1, 3, 132, 0, 35), "secp521r1") + +_p = long_converter(""" + A9FB57DB A1EEA9BC 3E660A90 9D838D72 6E3BF623 D5262028 + 2013481D 1F6E5377""") +_a = long_converter(""" + 7D5A0975 FC2C3057 EEF67530 417AFFE7 FB8055C1 26DC5C6C + E94A4B44 F330B5D9""") +_b = long_converter(""" + 26DC5C6C E94A4B44 F330B5D9 BBD77CBF 95841629 5CF7E1CE + 6BCCDC18 FF8C07B6""") +_Gx = long_converter(""" + 8BD2AEB9 CB7E57CB 2C4B482F FC81B7AF B9DE27E1 E3BD23C2 + 3A4453BD 9ACE3262""") +_Gy = long_converter(""" + 547EF835 C3DAC4FD 97F8461A 14611DC9 C2774513 2DED8E54 + 5C1D54C7 2F046997""") +_r = long_converter(""" + A9FB57DB A1EEA9BC 3E660A90 9D838D71 8C397AA3 B561A6F7 + 901E0E82 974856A7""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +BRNP256r1 = Curve("BRNP256r1", curve, generator, + (1, 3, 36, 3, 3, 2, 8, 1, 1, 7), "brainpoolP256r1") + +_p = long_converter(""" + 8CB91E82 A3386D28 0F5D6F7E 50E641DF 152F7109 ED5456B4 + 12B1DA19 7FB71123 ACD3A729 901D1A71 87470013 3107EC53""") +_a = long_converter(""" + 7BC382C6 3D8C150C 3C72080A CE05AFA0 C2BEA28E 4FB22787 + 139165EF BA91F90F 8AA5814A 503AD4EB 04A8C7DD 22CE2826""") +_b = long_converter(""" + 04A8C7DD 22CE2826 8B39B554 16F0447C 2FB77DE1 07DCD2A6 + 2E880EA5 3EEB62D5 7CB43902 95DBC994 3AB78696 FA504C11""") +_Gx = long_converter(""" + 1D1C64F0 68CF45FF A2A63A81 B7C13F6B 8847A3E7 7EF14FE3 + DB7FCAFE 0CBD10E8 E826E034 36D646AA EF87B2E2 47D4AF1E""") +_Gy = long_converter(""" + 8ABE1D75 20F9C2A4 5CB1EB8E 95CFD552 62B70B29 FEEC5864 + E19C054F F9912928 0E464621 77918111 42820341 263C5315""") +_r = long_converter(""" + 8CB91E82 A3386D28 0F5D6F7E 50E641DF 152F7109 ED5456B3 + 1F166E6C AC0425A7 CF3AB6AF 6B7FC310 3B883202 E9046565""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +BRNP384r1 = Curve("BRNP384r1", curve, generator, + (1, 3, 36, 3, 3, 2, 8, 1, 1, 11), "brainpoolP384r1") + +_p = long_converter(""" + AADD9DB8 DBE9C48B 3FD4E6AE 33C9FC07 CB308DB3 B3C9D20E + D6639CCA 70330871 7D4D9B00 9BC66842 AECDA12A E6A380E6 + 2881FF2F 2D82C685 28AA6056 583A48F3""") +_a = long_converter(""" + 7830A331 8B603B89 E2327145 AC234CC5 94CBDD8D 3DF91610 + A83441CA EA9863BC 2DED5D5A A8253AA1 0A2EF1C9 8B9AC8B5 + 7F1117A7 2BF2C7B9 E7C1AC4D 77FC94CA""") +_b = long_converter(""" + 3DF91610 A83441CA EA9863BC 2DED5D5A A8253AA1 0A2EF1C9 + 8B9AC8B5 7F1117A7 2BF2C7B9 E7C1AC4D 77FC94CA DC083E67 + 984050B7 5EBAE5DD 2809BD63 8016F723""") +_Gx = long_converter(""" + 81AEE4BD D82ED964 5A21322E 9C4C6A93 85ED9F70 B5D916C1 + B43B62EE F4D0098E FF3B1F78 E2D0D48D 50D1687B 93B97D5F + 7C6D5047 406A5E68 8B352209 BCB9F822""") +_Gy = long_converter(""" + 7DDE385D 566332EC C0EABFA9 CF7822FD F209F700 24A57B1A + A000C55B 881F8111 B2DCDE49 4A5F485E 5BCA4BD8 8A2763AE + D1CA2B2F A8F05406 78CD1E0F 3AD80892""") +_r = long_converter(""" + AADD9DB8 DBE9C48B 3FD4E6AE 33C9FC07 CB308DB3 B3C9D20E + D6639CCA 70330870 553E5C41 4CA92619 41866119 7FAC1047 + 1DB1D381 085DDADD B5879682 9CA90069""") +curve = CurveFp(_p, _a, _b) +generator = Point(curve, _Gx, _Gy, _r) +BRNP512r1 = Curve("BRNP512r1", curve, generator, + (1, 3, 36, 3, 3, 2, 8, 1, 1, 13), "brainpoolP512r1") + +# we use IANA identifiers below +named_curves = { 15: SECP160k1, + 16: SECP160r1, + 17: SECP160r2, + 18: SECP192k1, + 19: SECP192r1, + 20: SECP224k1, + 21: SECP224r1, + 22: SECP256k1, + 23: SECP256r1, + 24: SECP384r1, + 25: SECP521r1, + 26: BRNP256r1, + 27: BRNP384r1, + 28: BRNP512r1 + } + +for cid, c in named_curves.iteritems(): + c.curve_id = cid + +# replace/fill previous named curves +import ecdsa.curves +ecdsa.curves.curves = named_curves.values() + diff --git a/scapy/layers/tls/crypto/pkcs1.py b/scapy/layers/tls/crypto/pkcs1.py new file mode 100644 index 0000000000000000000000000000000000000000..9c43cf4a5381b6b11568286d61523f0ee7532b32 --- /dev/null +++ b/scapy/layers/tls/crypto/pkcs1.py @@ -0,0 +1,1158 @@ +## This file is part of Scapy +## Copyright (C) 2008 Arnaud Ebalard <arno@natisbad.org> +## 2015, 2016 Maxence Tury <maxence.tury@ssi.gouv.fr> +## This program is published under a GPLv2 license + +""" +PKCS #1 methods as defined in RFC 3447. +""" + +import os, popen2, tempfile +import math, random, struct +from hashlib import md5, sha1, sha224, sha256, sha384, sha512 +from Crypto.Hash import MD2, MD4 + + +##################################################################### +# Some helpers +##################################################################### + +def _warning(m): + print "WARNING: %s" % m + +def randstring(l): + """ + Returns a random string of length l (l >= 0) + """ + tmp = map(lambda x: struct.pack("B", random.randrange(0, 256, 1)), [""]*l) + return "".join(tmp) + +def zerofree_randstring(l): + """ + Returns a random string of length l (l >= 0) without zero in it. + """ + tmp = map(lambda x: struct.pack("B", random.randrange(1, 256, 1)), [""]*l) + return "".join(tmp) + +def strxor(s1, s2): + """ + Returns the binary XOR of the 2 provided strings s1 and s2. s1 and s2 + must be of same length. + """ + return "".join(map(lambda x,y:chr(ord(x)^ord(y)), s1, s2)) + +def strand(s1, s2): + """ + Returns the binary AND of the 2 provided strings s1 and s2. s1 and s2 + must be of same length. + """ + return "".join(map(lambda x,y:chr(ord(x)&ord(y)), s1, s2)) + +# OS2IP function defined in RFC 3447 for octet string to integer conversion +def pkcs_os2ip(x): + """ + Accepts a byte string as input parameter and return the associated long + value: + + Input : x octet string to be converted + + Output: x corresponding nonnegative integer + + Reverse function is pkcs_i2osp() + """ + return int(x.encode("hex"), 16) + +# I2OSP function defined in RFC 3447 for integer to octet string conversion +def pkcs_i2osp(x, xLen): + """ + Converts a long (the first parameter) to the associated byte string + representation of length l (second parameter). Basically, the length + parameters allow the function to perform the associated padding. + + Input : x nonnegative integer to be converted + xLen intended length of the resulting octet string + + Output: x corresponding octet string + + Reverse function is pkcs_os2ip(). + """ + # The user is responsible for providing an appropriate xLen. + #if x >= 256**xLen: + # raise Exception("Integer too large for provided xLen %d" % xLen) + fmt = "%%0%dx" % (2*xLen) + return (fmt % x).decode("hex") + +def pkcs_ilen(n): + """ + This is a log base 256 which determines the minimum octet string + length for unequivocal representation of integer n by pkcs_i2osp. + """ + i = 0 + while n > 0: + n >>= 8 + i += 1 + return i + +# for every hash function a tuple is provided, giving access to +# - hash output length in byte +# - associated hash function that take data to be hashed as parameter +# XXX I do not provide update() at the moment. +# - DER encoding of the leading bits of digestInfo (the hash value +# will be concatenated to create the complete digestInfo). +# +# Notes: +# - MD4 asn.1 value should be verified. Also, as stated in +# PKCS#1 v2.1, MD4 should not be used. +# - 'tls' one is the concatenation of both md5 and sha1 hashes used +# by SSL/TLS when signing/verifying things +_hashFuncParams = { + "md2" : (16, + MD2.new, + lambda x: MD2.new(x).digest(), + '\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x02\x05\x00\x04\x10'), + "md4" : (16, + MD4.new, + lambda x: MD4.new(x).digest(), + '\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x04\x05\x00\x04\x10'), + "md5" : (16, + md5, + lambda x: md5(x).digest(), + '\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10'), + "sha1" : (20, + sha1, + lambda x: sha1(x).digest(), + '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'), + "sha224" : (28, + sha224, + lambda x: sha224(x).digest(), + '\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c'), + "sha256" : (32, + sha256, + lambda x: sha256(x).digest(), + '\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20'), + "sha384" : (48, + sha384, + lambda x: sha384(x).digest(), + '\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30'), + "sha512" : (64, + sha512, + lambda x: sha512(x).digest(), + '\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40'), + "tls" : (36, + None, + lambda x: md5(x).digest() + sha1(x).digest(), + '') + } + +def mapHashFunc(hashStr): + try: + return _hashFuncParams[hashStr][1] + except: + raise Exception("Unknown hash function %s" % hashStr) + + +def pkcs_mgf1(mgfSeed, maskLen, h): + """ + Implements generic MGF1 Mask Generation function as described in + Appendix B.2.1 of RFC 3447. The hash function is passed by name. + valid values are 'md2', 'md4', 'md5', 'sha1', 'tls, 'sha256', + 'sha384' and 'sha512'. Returns None on error. + + Input: + mgfSeed: seed from which mask is generated, an octet string + maskLen: intended length in octets of the mask, at most 2^32 * hLen + hLen (see below) + h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', + 'sha256', 'sha384'). hLen denotes the length in octets of + the hash function output. + + Output: + an octet string of length maskLen + """ + + # steps are those of Appendix B.2.1 + if not _hashFuncParams.has_key(h): + _warning("pkcs_mgf1: invalid hash (%s) provided") + return None + hLen = _hashFuncParams[h][0] + hFunc = _hashFuncParams[h][2] + if maskLen > 2**32 * hLen: # 1) + _warning("pkcs_mgf1: maskLen > 2**32 * hLen") + return None + T = "" # 2) + maxCounter = math.ceil(float(maskLen) / float(hLen)) # 3) + counter = 0 + while counter < maxCounter: + C = pkcs_i2osp(counter, 4) + T += hFunc(mgfSeed + C) + counter += 1 + return T[:maskLen] + + +def pkcs_emsa_pss_encode(M, emBits, h, mgf, sLen): + """ + Implements EMSA-PSS-ENCODE() function described in Sect. 9.1.1 of RFC 3447 + + Input: + M : message to be encoded, an octet string + emBits: maximal bit length of the integer resulting of pkcs_os2ip(EM), + where EM is the encoded message, output of the function. + h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', + 'sha256', 'sha384'). hLen denotes the length in octets of + the hash function output. + mgf : the mask generation function f : seed, maskLen -> mask + sLen : intended length in octets of the salt + + Output: + encoded message, an octet string of length emLen = ceil(emBits/8) + + On error, None is returned. + """ + + # 1) is not done + hLen = _hashFuncParams[h][0] # 2) + hFunc = _hashFuncParams[h][2] + mHash = hFunc(M) + emLen = int(math.ceil(emBits/8.)) + if emLen < hLen + sLen + 2: # 3) + _warning("encoding error (emLen < hLen + sLen + 2)") + return None + salt = randstring(sLen) # 4) + MPrime = '\x00'*8 + mHash + salt # 5) + H = hFunc(MPrime) # 6) + PS = '\x00'*(emLen - sLen - hLen - 2) # 7) + DB = PS + '\x01' + salt # 8) + dbMask = mgf(H, emLen - hLen - 1) # 9) + maskedDB = strxor(DB, dbMask) # 10) + l = (8*emLen - emBits)/8 # 11) + rem = 8*emLen - emBits - 8*l # additionnal bits + andMask = l*'\x00' + if rem: + j = chr(reduce(lambda x,y: x+y, map(lambda x: 1<<x, range(8-rem)))) + andMask += j + l += 1 + maskedDB = strand(maskedDB[:l], andMask) + maskedDB[l:] + EM = maskedDB + H + '\xbc' # 12) + return EM # 13) + + +def pkcs_emsa_pss_verify(M, EM, emBits, h, mgf, sLen): + """ + Implements EMSA-PSS-VERIFY() function described in Sect. 9.1.2 of RFC 3447 + + Input: + M : message to be encoded, an octet string + EM : encoded message, an octet string of length emLen=ceil(emBits/8) + emBits: maximal bit length of the integer resulting of pkcs_os2ip(EM) + h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', + 'sha256', 'sha384'). hLen denotes the length in octets of + the hash function output. + mgf : the mask generation function f : seed, maskLen -> mask + sLen : intended length in octets of the salt + + Output: + True if the verification is ok, False otherwise. + """ + + # 1) is not done + hLen = _hashFuncParams[h][0] # 2) + hFunc = _hashFuncParams[h][2] + mHash = hFunc(M) + emLen = int(math.ceil(emBits/8.)) # 3) + if emLen < hLen + sLen + 2: + return False + if EM[-1] != '\xbc': # 4) + return False + l = emLen - hLen - 1 # 5) + maskedDB = EM[:l] + H = EM[l:l+hLen] + l = (8*emLen - emBits)/8 # 6) + rem = 8*emLen - emBits - 8*l # additionnal bits + andMask = l*'\xff' + if rem: + val = reduce(lambda x,y: x+y, map(lambda x: 1<<x, range(8-rem))) + j = chr(~val & 0xff) + andMask += j + l += 1 + if strand(maskedDB[:l], andMask) != '\x00'*l: + return False + dbMask = mgf(H, emLen - hLen - 1) # 7) + DB = strxor(maskedDB, dbMask) # 8) + l = (8*emLen - emBits)/8 # 9) + rem = 8*emLen - emBits - 8*l # additionnal bits + andMask = l*'\x00' + if rem: + j = chr(reduce(lambda x,y: x+y, map(lambda x: 1<<x, range(8-rem)))) + andMask += j + l += 1 + DB = strand(DB[:l], andMask) + DB[l:] + l = emLen - hLen - sLen - 1 # 10) + if DB[:l] != '\x00'*(l-1) + '\x01': + return False + salt = DB[-sLen:] # 11) + MPrime = '\x00'*8 + mHash + salt # 12) + HPrime = hFunc(MPrime) # 13) + return H == HPrime # 14) + + +def pkcs_emsa_pkcs1_v1_5_encode(M, emLen, h): # section 9.2 of RFC 3447 + """ + Implements EMSA-PKCS1-V1_5-ENCODE() function described in Sect. + 9.2 of RFC 3447. + + Input: + M : message to be encode, an octet string + emLen: intended length in octets of the encoded message, at least + tLen + 11, where tLen is the octet length of the DER encoding + T of a certain value computed during the encoding operation. + h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', + 'sha256', 'sha384'). hLen denotes the length in octets of + the hash function output. + + Output: + encoded message, an octet string of length emLen + + On error, None is returned. + """ + hLen = _hashFuncParams[h][0] # 1) + hFunc = _hashFuncParams[h][2] + H = hFunc(M) + hLeadingDigestInfo = _hashFuncParams[h][3] # 2) + T = hLeadingDigestInfo + H + tLen = len(T) + if emLen < tLen + 11: # 3) + _warning("pkcs_emsa_pkcs1_v1_5_encode:" + "intended encoded message length too short") + return None + PS = '\xff'*(emLen - tLen - 3) # 4) + EM = '\x00' + '\x01' + PS + '\x00' + T # 5) + return EM # 6) + + +# XXX should add other pgf1 instance in a better fashion. + +def create_ca_file(anchor_list, filename): + """ + Concatenate all the certificates (PEM format for the export) in + 'anchor_list' and write the result to file 'filename'. On success + 'filename' is returned, None otherwise. + + If you are used to OpenSSL tools, this function builds a CAfile + that can be used for certificate and CRL check. + + Also see create_temporary_ca_file(). + """ + try: + f = open(filename, "w") + for a in anchor_list: + s = a.output(fmt="PEM") + f.write(s) + f.close() + except: + return None + return filename + +def create_temporary_ca_file(anchor_list): + """ + Concatenate all the certificates (PEM format for the export) in + 'anchor_list' and write the result to file to a temporary file + using mkstemp() from tempfile module. On success 'filename' is + returned, None otherwise. + + If you are used to OpenSSL tools, this function builds a CAfile + that can be used for certificate and CRL check. + """ + try: + f, fname = tempfile.mkstemp() + for a in anchor_list: + s = a.output(fmt="PEM") + l = os.write(f, s) + os.close(f) + except: + return None + return fname + +def create_temporary_ca_path(anchor_list, folder): + """ + Create a CA path folder as defined in OpenSSL terminology, by + storing all certificates in 'anchor_list' list in PEM format + under provided 'folder' and then creating the associated links + using the hash as usually done by c_rehash. + + Note that you can also include CRL in 'anchor_list'. In that + case, they will also be stored under 'folder' and associated + links will be created. + + In folder, the files are created with names of the form + 0...ZZ.pem. If you provide an empty list, folder will be created + if it does not already exist, but that's all. + + The number of certificates written to folder is returned on + success, None on error. + """ + # We should probably avoid writing duplicate anchors and also + # check if they are all certs. + try: + if not os.path.isdir(folder): + os.makedirs(folder) + except: + return None + + l = len(anchor_list) + if l == 0: + return None + fmtstr = "%%0%sd.pem" % math.ceil(math.log(l, 10)) + i = 0 + try: + for a in anchor_list: + fname = os.path.join(folder, fmtstr % i) + f = open(fname, "w") + s = a.output(fmt="PEM") + f.write(s) + f.close() + i += 1 + except: + return None + + r,w=popen2.popen2("c_rehash %s" % folder) + r.close(); w.close() + + return l + + +##################################################################### +# Public Key Cryptography related stuff +##################################################################### + +class _EncryptAndVerifyRSA(object): + ### Below are encryption methods + + def _rsaep(self, m): + """ + Internal method providing raw RSA encryption, i.e. simple modular + exponentiation of the given message representative 'm', a long + between 0 and n-1. + + This is the encryption primitive RSAEP described in PKCS#1 v2.1, + i.e. RFC 3447 Sect. 5.1.1. + + Input: + m: message representative, a long between 0 and n-1, where + n is the key modulus. + + Output: + ciphertext representative, a long between 0 and n-1 + + Not intended to be used directly. Please, see encrypt() method. + """ + + n = self.modulus + if isinstance(m, int): + m = long(m) + if (not isinstance(m, long)) or m > n-1: + _warning("Key._rsaep() expects a long between 0 and n-1") + return None + + return self.key.encrypt(m, "")[0] + + + def _rsaes_pkcs1_v1_5_encrypt(self, M): + """ + Implements RSAES-PKCS1-V1_5-ENCRYPT() function described in section + 7.2.1 of RFC 3447. + + Input: + M: message to be encrypted, an octet string of length mLen, where + mLen <= k-11 (k denotes the length in octets of the key modulus) + + Output: + ciphertext, an octet string of length k + + On error, None is returned. + """ + + # 1) Length checking + mLen = len(M) + k = self.modulusLen / 8 + if mLen > k - 11: + _warning("Key._rsaes_pkcs1_v1_5_encrypt(): message too " + "long (%d > %d - 11)" % (mLen, k)) + return None + + # 2) EME-PKCS1-v1_5 encoding + PS = zerofree_randstring(k - mLen - 3) # 2.a) + EM = '\x00' + '\x02' + PS + '\x00' + M # 2.b) + + # 3) RSA encryption + m = pkcs_os2ip(EM) # 3.a) + c = self._rsaep(m) # 3.b) + C = pkcs_i2osp(c, k) # 3.c) + + return C # 4) + + + def _rsaes_oaep_encrypt(self, M, h=None, mgf=None, L=None): + """ + Internal method providing RSAES-OAEP-ENCRYPT as defined in Sect. + 7.1.1 of RFC 3447. Not intended to be used directly. Please, see + encrypt() method for type "OAEP". + + Input: + M : message to be encrypted, an octet string of length mLen + where mLen <= k - 2*hLen - 2 (k denotes the length in octets + of the RSA modulus and hLen the length in octets of the hash + function output) + h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', + 'sha256', 'sha384'). hLen denotes the length in octets of + the hash function output. 'sha1' is used by default if not + provided. + mgf: the mask generation function f : seed, maskLen -> mask + L : optional label to be associated with the message; the default + value for L, if not provided is the empty string + + Output: + ciphertext, an octet string of length k + + On error, None is returned. + """ + # The steps below are the one described in Sect. 7.1.1 of RFC 3447. + # 1) Length Checking + # 1.a) is not done + mLen = len(M) + if h is None: + h = "sha1" + if not _hashFuncParams.has_key(h): + _warning("Key._rsaes_oaep_encrypt(): unknown hash function %s." % h) + return None + hLen = _hashFuncParams[h][0] + hFun = _hashFuncParams[h][2] + k = self.modulusLen / 8 + if mLen > k - 2*hLen - 2: # 1.b) + _warning("Key._rsaes_oaep_encrypt(): message too long.") + return None + + # 2) EME-OAEP encoding + if L is None: # 2.a) + L = "" + lHash = hFun(L) + PS = '\x00'*(k - mLen - 2*hLen - 2) # 2.b) + DB = lHash + PS + '\x01' + M # 2.c) + seed = randstring(hLen) # 2.d) + if mgf is None: # 2.e) + mgf = lambda x,y: pkcs_mgf1(x,y,h) + dbMask = mgf(seed, k - hLen - 1) + maskedDB = strxor(DB, dbMask) # 2.f) + seedMask = mgf(maskedDB, hLen) # 2.g) + maskedSeed = strxor(seed, seedMask) # 2.h) + EM = '\x00' + maskedSeed + maskedDB # 2.i) + + # 3) RSA Encryption + m = pkcs_os2ip(EM) # 3.a) + c = self._rsaep(m) # 3.b) + C = pkcs_i2osp(c, k) # 3.c) + + return C # 4) + + + def encrypt(self, m, t=None, h=None, mgf=None, L=None): + """ + Encrypt message 'm' using 't' encryption scheme where 't' can be: + + - None: the message 'm' is directly applied the RSAEP encryption + primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 + Sect 5.1.1. Simply put, the message undergo a modular + exponentiation using the public key. Additionnal method + parameters are just ignored. + + -'pkcs': the message 'm' is applied RSAES-PKCS1-V1_5-ENCRYPT encryption + scheme as described in section 7.2.1 of RFC 3447. In that + context, other parameters ('h', 'mgf', 'l') are not used. + + -'oaep': the message 'm' is applied the RSAES-OAEP-ENCRYPT encryption + scheme, as described in PKCS#1 v2.1, i.e. RFC 3447 Sect + 7.1.1. In that context, + + o 'h' parameter provides the name of the hash method to use. + Possible values are "md2", "md4", "md5", "sha1", "tls", + "sha224", "sha256", "sha384" and "sha512". If none is + provided, sha1 is used. + + o 'mgf' is the mask generation function. By default, mgf + is derived from the provided hash function using the + generic MGF1 (see pkcs_mgf1() for details). + + o 'L' is the optional label to be associated with the message. + If not provided, the default value is used, i.e the empty + string. No check is done on the input limitation of the hash + function regarding the size of 'L' (for instance, 2^61 - 1 + for SHA-1). You have been warned. + """ + + if t is None: # Raw encryption + m = pkcs_os2ip(m) + c = self._rsaep(m) + return pkcs_i2osp(c, self.modulusLen/8) + + elif t == "pkcs": + return self._rsaes_pkcs1_v1_5_encrypt(m) + + elif t == "oaep": + return self._rsaes_oaep_encrypt(m, h, mgf, L) + + else: + _warning("Key.encrypt(): Unknown encryption type (%s) provided" % t) + return None + + ### Below are verification related methods + + def _rsavp1(self, s): + """ + Internal method providing raw RSA verification, i.e. simple modular + exponentiation of the given signature representative 'c', an integer + between 0 and n-1. + + This is the signature verification primitive RSAVP1 described in + PKCS#1 v2.1, i.e. RFC 3447 Sect. 5.2.2. + + Input: + s: signature representative, an integer between 0 and n-1, + where n is the key modulus. + + Output: + message representative, an integer between 0 and n-1 + + Not intended to be used directly. Please, see verify() method. + """ + return self._rsaep(s) + + def _rsassa_pss_verify(self, M, S, h=None, mgf=None, sLen=None): + """ + Implements RSASSA-PSS-VERIFY() function described in Sect 8.1.2 + of RFC 3447 + + Input: + M: message whose signature is to be verified + S: signature to be verified, an octet string of length k, where k + is the length in octets of the RSA modulus n. + + Output: + True is the signature is valid. False otherwise. + """ + + # Set default parameters if not provided + if h is None: # By default, sha1 + h = "sha1" + if not _hashFuncParams.has_key(h): + _warning("Key._rsassa_pss_verify(): unknown hash function " + "provided (%s)" % h) + return False + if mgf is None: # use mgf1 with underlying hash function + mgf = lambda x,y: pkcs_mgf1(x, y, h) + if sLen is None: # use Hash output length (A.2.3 of RFC 3447) + hLen = _hashFuncParams[h][0] + sLen = hLen + + # 1) Length checking + modBits = self.modulusLen + k = modBits / 8 + if len(S) != k: + return False + + # 2) RSA verification + s = pkcs_os2ip(S) # 2.a) + m = self._rsavp1(s) # 2.b) + emLen = math.ceil((modBits - 1) / 8.) # 2.c) + EM = pkcs_i2osp(m, emLen) + + # 3) EMSA-PSS verification + Result = pkcs_emsa_pss_verify(M, EM, modBits - 1, h, mgf, sLen) + + return Result # 4) + + + def _rsassa_pkcs1_v1_5_verify(self, M, S, h): + """ + Implements RSASSA-PKCS1-v1_5-VERIFY() function as described in + Sect. 8.2.2 of RFC 3447. + + Input: + M: message whose signature is to be verified, an octet string + S: signature to be verified, an octet string of length k, where + k is the length in octets of the RSA modulus n + h: hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', + 'sha256', 'sha384'). + + Output: + True if the signature is valid. False otherwise. + """ + + # 1) Length checking + k = self.modulusLen / 8 + if len(S) != k: + _warning("invalid signature (len(S) != k)") + return False + + # 2) RSA verification + s = pkcs_os2ip(S) # 2.a) + m = self._rsavp1(s) # 2.b) + EM = pkcs_i2osp(m, k) # 2.c) + + # 3) EMSA-PKCS1-v1_5 encoding + EMPrime = pkcs_emsa_pkcs1_v1_5_encode(M, k, h) + if EMPrime is None: + _warning("Key._rsassa_pkcs1_v1_5_verify(): unable to encode.") + return False + + # 4) Comparison + return EM == EMPrime + + + def verify(self, M, S, t=None, h=None, mgf=None, sLen=None): + """ + Verify alleged signature 'S' is indeed the signature of message 'M' + using 't' signature scheme where 't' can be: + + - None: the alleged signature 'S' is directly applied the RSAVP1 + signature primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 + Sect 5.2.1. Simply put, the provided signature is applied a + modular exponentiation using the public key. Then, a comparison + of the result is done against 'M'. On match, True is returned. + Additional method parameters are just ignored. + + -'pkcs': the alleged signature 'S' and message 'M' are applied + RSASSA-PKCS1-v1_5-VERIFY signature verification scheme as + described in Sect. 8.2.2 of RFC 3447. In that context, the hash + function name is passed using 'h'. Possible values are "md2", + "md4", "md5", "sha1", "tls", "sha224", "sha256", "sha384" and + "sha512". If none is provided, sha1 is used. Other additional + parameters are ignored. + + -'pss': the alleged signature 'S' and message 'M' are applied + RSASSA-PSS-VERIFY signature scheme as described in Sect. 8.1.2. + of RFC 3447. In that context, + + o 'h' parameter provides the name of the hash method to use. + Possible values are "md2", "md4", "md5", "sha1", "tls", + "sha224", "sha256", "sha384" and "sha512". If None is + provided, sha1 is used. + + o 'mgf' is the mask generation function. By default, mgf + is derived from the provided hash function using the + generic MGF1 (see pkcs_mgf1() for details). + + o 'sLen' is the byte length of the salt. You can overload the + default value (the byte length of the hash value for provided + algorithm) by providing another one with that parameter. + """ + if t is None: # RSAVP1 + S = pkcs_os2ip(S) + n = self.modulus + if S > n-1: + _warning("Signature to be verified is too long for key modulus") + return False + m = self._rsavp1(S) + if m is None: + return False + l = int(math.ceil(math.log(m, 2) / 8.)) # Hack + m = pkcs_i2osp(m, l) + return M == m + + elif t == "pkcs": # RSASSA-PKCS1-v1_5-VERIFY + if h is None: + h = "sha1" + return self._rsassa_pkcs1_v1_5_verify(M, S, h) + + elif t == "pss": # RSASSA-PSS-VERIFY + return self._rsassa_pss_verify(M, S, h, mgf, sLen) + + else: + _warning("Key.verify(): Unknown signature type (%s) provided" % t) + return None + +class _DecryptAndSignRSA(object): + ### Below are decryption related methods. Encryption ones are inherited + ### from PubKey + + def _rsadp(self, c): + """ + Internal method providing raw RSA decryption, i.e. simple modular + exponentiation of the given ciphertext representative 'c', a long + between 0 and n-1. + + This is the decryption primitive RSADP described in PKCS#1 v2.1, + i.e. RFC 3447 Sect. 5.1.2. + + Input: + c: ciphertest representative, a long between 0 and n-1, where + n is the key modulus. + + Output: + message representative, a long between 0 and n-1 + + Not intended to be used directly. Please, see decrypt() method. + """ + + n = self.modulus + if isinstance(c, int): + c = long(c) + if (not isinstance(c, long)) or c > n-1: + _warning("Key._rsaep() expects a long between 0 and n-1") + return None + + return self.key.decrypt(c) + + + def _rsaes_pkcs1_v1_5_decrypt(self, C): + """ + Implements RSAES-PKCS1-V1_5-DECRYPT() function described in section + 7.2.2 of RFC 3447. + + Input: + C: ciphertext to be decrypted, an octet string of length k, where + k is the length in octets of the RSA modulus n. + + Output: + an octet string of length k at most k - 11 + + on error, None is returned. + """ + + # 1) Length checking + cLen = len(C) + k = self.modulusLen / 8 + if cLen != k or k < 11: + _warning("Key._rsaes_pkcs1_v1_5_decrypt() decryption error " + "(cLen != k or k < 11)") + return None + + # 2) RSA decryption + c = pkcs_os2ip(C) # 2.a) + m = self._rsadp(c) # 2.b) + EM = pkcs_i2osp(m, k) # 2.c) + + # 3) EME-PKCS1-v1_5 decoding + + # I am aware of the note at the end of 7.2.2 regarding error + # conditions reporting but the one provided below are for _local_ + # debugging purposes. --arno + + if EM[0] != '\x00': + _warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error " + "(first byte is not 0x00)") + return None + + if EM[1] != '\x02': + _warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error " + "(second byte is not 0x02)") + return None + + tmp = EM[2:].split('\x00', 1) + if len(tmp) != 2: + _warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error " + "(no 0x00 to separate PS from M)") + return None + + PS, M = tmp + if len(PS) < 8: + _warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error " + "(PS is less than 8 byte long)") + return None + + return M # 4) + + + def _rsaes_oaep_decrypt(self, C, h=None, mgf=None, L=None): + """ + Internal method providing RSAES-OAEP-DECRYPT as defined in Sect. + 7.1.2 of RFC 3447. Not intended to be used directly. Please, see + encrypt() method for type "OAEP". + + + Input: + C : ciphertext to be decrypted, an octet string of length k, where + k = 2*hLen + 2 (k denotes the byte length of the RSA modulus + and hLen the byte length of the hash function output) + h : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls', + 'sha256', 'sha384'). 'sha1' is used if none is provided. + mgf: the mask generation function f : seed, maskLen -> mask + L : optional label whose association with the message is to be + verified; the default value for L, if not provided is the empty + string. + + Output: + message, an octet string of length k mLen, where mLen <= k-2*hLen-2 + + On error, None is returned. + """ + # The steps below are the one described in Sect. 7.1.2 of RFC 3447. + + # 1) Length Checking + # 1.a) is not done + if h is None: + h = "sha1" + if not _hashFuncParams.has_key(h): + _warning("Key._rsaes_oaep_decrypt(): unknown hash function %s.", h) + return None + hLen = _hashFuncParams[h][0] + hFun = _hashFuncParams[h][2] + k = self.modulusLen / 8 + cLen = len(C) + if cLen != k: # 1.b) + _warning("Key._rsaes_oaep_decrypt(): decryption error. " + "(cLen != k)") + return None + if k < 2*hLen + 2: + _warning("Key._rsaes_oaep_decrypt(): decryption error. " + "(k < 2*hLen + 2)") + return None + + # 2) RSA decryption + c = pkcs_os2ip(C) # 2.a) + m = self._rsadp(c) # 2.b) + EM = pkcs_i2osp(m, k) # 2.c) + + # 3) EME-OAEP decoding + if L is None: # 3.a) + L = "" + lHash = hFun(L) + Y = EM[:1] # 3.b) + if Y != '\x00': + _warning("Key._rsaes_oaep_decrypt(): decryption error. " + "(Y is not zero)") + return None + maskedSeed = EM[1:1+hLen] + maskedDB = EM[1+hLen:] + if mgf is None: + mgf = lambda x,y: pkcs_mgf1(x, y, h) + seedMask = mgf(maskedDB, hLen) # 3.c) + seed = strxor(maskedSeed, seedMask) # 3.d) + dbMask = mgf(seed, k - hLen - 1) # 3.e) + DB = strxor(maskedDB, dbMask) # 3.f) + + # I am aware of the note at the end of 7.1.2 regarding error + # conditions reporting but the one provided below are for _local_ + # debugging purposes. --arno + + lHashPrime = DB[:hLen] # 3.g) + tmp = DB[hLen:].split('\x01', 1) + if len(tmp) != 2: + _warning("Key._rsaes_oaep_decrypt(): decryption error. " + "(0x01 separator not found)") + return None + PS, M = tmp + if PS != '\x00'*len(PS): + _warning("Key._rsaes_oaep_decrypt(): decryption error. " + "(invalid padding string)") + return None + if lHash != lHashPrime: + _warning("Key._rsaes_oaep_decrypt(): decryption error. " + "(invalid hash)") + return None + return M # 4) + + + def decrypt(self, C, t=None, h=None, mgf=None, L=None): + """ + Decrypt ciphertext 'C' using 't' decryption scheme where 't' can be: + + - None: the ciphertext 'C' is directly applied the RSADP decryption + primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 + Sect 5.1.2. Simply, put the message undergo a modular + exponentiation using the private key. Additionnal method + parameters are just ignored. + + - 'pkcs': the ciphertext 'C' is applied RSAES-PKCS1-V1_5-DECRYPT + decryption scheme as described in section 7.2.2 of RFC 3447. + In that context, other parameters ('h', 'mgf', 'l') are not + used. + + - 'oaep': the ciphertext 'C' is applied the RSAES-OAEP-DECRYPT + decryption scheme, as described in PKCS#1 v2.1, i.e. RFC 3447 + Sect 7.1.2. In that context, + + o 'h' parameter provides the name of the hash method to use. + Possible values are "md2", "md4", "md5", "sha1", "tls", + "sha224", "sha256", "sha384" and "sha512". If None is + provided, sha1 is used by default. + + o 'mgf' is the mask generation function. By default, mgf + is derived from the provided hash function using the + generic MGF1 (see pkcs_mgf1() for details). + + o 'L' is the optional label to be associated with the + message. If not provided, the default value is used, i.e + the empty string. No check is done on the input limitation + of the hash function regarding the size of 'L' (for + instance, 2^61 - 1 for SHA-1). You have been warned. + """ + if t is None: + C = pkcs_os2ip(C) + c = self._rsadp(C) + l = int(math.ceil(math.log(c, 2) / 8.)) # Hack + return pkcs_i2osp(c, l) + + elif t == "pkcs": + return self._rsaes_pkcs1_v1_5_decrypt(C) + + elif t == "oaep": + return self._rsaes_oaep_decrypt(C, h, mgf, L) + + else: + _warning("Key.decrypt(): Unknown decryption type (%s) provided" % t) + return None + + ### Below are signature related methods. + ### Verification methods are inherited from PubKey. + + def _rsasp1(self, m): + """ + Internal method providing raw RSA signature, i.e. simple modular + exponentiation of the given message representative 'm', an integer + between 0 and n-1. + + This is the signature primitive RSASP1 described in PKCS#1 v2.1, + i.e. RFC 3447 Sect. 5.2.1. + + Input: + m: message representative, an integer between 0 and n-1, where + n is the key modulus. + + Output: + signature representative, an integer between 0 and n-1 + + Not intended to be used directly. Please, see sign() method. + """ + return self._rsadp(m) + + + def _rsassa_pss_sign(self, M, h=None, mgf=None, sLen=None): + """ + Implements RSASSA-PSS-SIGN() function described in Sect. 8.1.1 of + RFC 3447. + + Input: + M: message to be signed, an octet string + + Output: + signature, an octet string of length k, where k is the length in + octets of the RSA modulus n. + + On error, None is returned. + """ + + # Set default parameters if not provided + if h is None: # By default, sha1 + h = "sha1" + if not _hashFuncParams.has_key(h): + _warning("Key._rsassa_pss_sign(): unknown hash function " + "provided (%s)" % h) + return None + if mgf is None: # use mgf1 with underlying hash function + mgf = lambda x,y: pkcs_mgf1(x, y, h) + if sLen is None: # use Hash output length (A.2.3 of RFC 3447) + hLen = _hashFuncParams[h][0] + sLen = hLen + + # 1) EMSA-PSS encoding + modBits = self.modulusLen + k = modBits / 8 + EM = pkcs_emsa_pss_encode(M, modBits - 1, h, mgf, sLen) + if EM is None: + _warning("Key._rsassa_pss_sign(): unable to encode") + return None + + # 2) RSA signature + m = pkcs_os2ip(EM) # 2.a) + s = self._rsasp1(m) # 2.b) + S = pkcs_i2osp(s, k) # 2.c) + + return S # 3) + + + def _rsassa_pkcs1_v1_5_sign(self, M, h): + """ + Implements RSASSA-PKCS1-v1_5-SIGN() function as described in + Sect. 8.2.1 of RFC 3447. + + Input: + M: message to be signed, an octet string + h: hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls' + 'sha256', 'sha384'). + + Output: + the signature, an octet string. + """ + + # 1) EMSA-PKCS1-v1_5 encoding + k = self.modulusLen / 8 + EM = pkcs_emsa_pkcs1_v1_5_encode(M, k, h) + if EM is None: + _warning("Key._rsassa_pkcs1_v1_5_sign(): unable to encode") + return None + + # 2) RSA signature + m = pkcs_os2ip(EM) # 2.a) + s = self._rsasp1(m) # 2.b) + S = pkcs_i2osp(s, k) # 2.c) + + return S # 3) + + + def sign(self, M, t=None, h=None, mgf=None, sLen=None): + """ + Sign message 'M' using 't' signature scheme where 't' can be: + + - None: the message 'M' is directly applied the RSASP1 signature + primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 Sect + 5.2.1. Simply put, the message undergo a modular exponentiation + using the private key. Additional method parameters are just + ignored. + + - 'pkcs': the message 'M' is applied RSASSA-PKCS1-v1_5-SIGN signature + scheme as described in Sect. 8.2.1 of RFC 3447. In that + context, the hash function name is passed using 'h'. Possible + values are "md2", "md4", "md5", "sha1", "tls", "sha224", + "sha256", "sha384" and "sha512". If none is provided, sha1 is + used. Other additional parameters are ignored. + + - 'pss' : the message 'M' is applied RSASSA-PSS-SIGN signature scheme + as described in Sect. 8.1.1. of RFC 3447. In that context, + + o 'h' parameter provides the name of the hash method to use. + Possible values are "md2", "md4", "md5", "sha1", "tls", + "sha224", "sha256", "sha384" and "sha512". If None is + provided, sha1 is used. + + o 'mgf' is the mask generation function. By default, mgf + is derived from the provided hash function using the + generic MGF1 (see pkcs_mgf1() for details). + + o 'sLen' is the byte length of the salt. You can overload the + default value (the byte length of the hash value for provided + algorithm) by providing another one with that parameter. + """ + + if t is None: # RSASP1 + M = pkcs_os2ip(M) + n = self.modulus + if M > n-1: + _warning("Message to be signed is too long for key modulus") + return None + s = self._rsasp1(M) + if s is None: + return None + return pkcs_i2osp(s, self.modulusLen/8) + + elif t == "pkcs": # RSASSA-PKCS1-v1_5-SIGN + if h is None: + h = "sha1" + return self._rsassa_pkcs1_v1_5_sign(M, h) + + elif t == "pss": # RSASSA-PSS-SIGN + return self._rsassa_pss_sign(M, h, mgf, sLen) + + else: + _warning("Key.sign(): Unknown signature type (%s) provided" % t) + return None + + diff --git a/scapy/layers/x509.py b/scapy/layers/x509.py index 1b4f078d9c9f84dde511f3a459129e3aa72801d6..a5ca38ccbeb6253c9f577e869f0fbcd1fe2e0812 100644 --- a/scapy/layers/x509.py +++ b/scapy/layers/x509.py @@ -10,6 +10,7 @@ X.509 certificates. from scapy.asn1packet import * from scapy.asn1fields import * +from scapy.fields import PacketField class ASN1P_OID(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER @@ -671,6 +672,15 @@ class ASN1F_X509_SubjectPublicKeyInfoRSA(ASN1F_SEQUENCE): RSAPublicKey)] ASN1F_SEQUENCE.__init__(self, *seq, **kargs) +class ASN1F_X509_SubjectPublicKeyInfoECDSA(ASN1F_SEQUENCE): + def __init__(self, **kargs): + seq = [ASN1F_PACKET("signatureAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_PACKET("subjectPublicKey", ECDSAPublicKey(), + ECDSAPublicKey)] + ASN1F_SEQUENCE.__init__(self, *seq, **kargs) + class ASN1F_X509_SubjectPublicKeyInfo(ASN1F_SEQUENCE): def __init__(self, **kargs): seq = [ASN1F_PACKET("signatureAlgorithm", @@ -684,7 +694,7 @@ class ASN1F_X509_SubjectPublicKeyInfo(ASN1F_SEQUENCE): if "rsa" in keytype.lower(): return ASN1F_X509_SubjectPublicKeyInfoRSA().m2i(pkt, x) elif keytype == "ecPublicKey": - return c,s + return ASN1F_X509_SubjectPublicKeyInfoECDSA().m2i(pkt, x) else: raise Exception("could not parse subjectPublicKeyInfo") def dissect(self, pkt, s): @@ -699,7 +709,8 @@ class ASN1F_X509_SubjectPublicKeyInfo(ASN1F_SEQUENCE): pkt.default_fields["subjectPublicKey"] = RSAPublicKey() return ASN1F_X509_SubjectPublicKeyInfoRSA().build(pkt) elif ktype == "ecPublicKey": - return ASN1F_SEQUENCE.build(self, pkt) + pkt.default_fields["subjectPublicKey"] = ECDSAPublicKey() + return ASN1F_X509_SubjectPublicKeyInfoECDSA().build(pkt) else: raise Exception("could not build subjectPublicKeyInfo") @@ -708,6 +719,54 @@ class X509_SubjectPublicKeyInfo(ASN1_Packet): ASN1_root = ASN1F_X509_SubjectPublicKeyInfo() +###### OpenSSL compatibility wrappers ###### + +#XXX As ECDSAPrivateKey already uses the structure from RFC 5958, +# and as we would prefer encapsulated RSA private keys to be parsed, +# this lazy implementation actually supports RSA encoding only. +# We'd rather call it RSAPrivateKey_OpenSSL than X509_PrivateKeyInfo. +class RSAPrivateKey_OpenSSL(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("version", 0, ["v1", "v2"]), + ASN1F_PACKET("privateKeyAlgorithm", + X509_AlgorithmIdentifier(), + X509_AlgorithmIdentifier), + ASN1F_PACKET("privateKey", + RSAPrivateKey(), + RSAPrivateKey, + explicit_tag=0x04), + ASN1F_optional( + ASN1F_PACKET("parameters", None, ECParameters, + explicit_tag=0xa0)), + ASN1F_optional( + ASN1F_PACKET("publicKey", None, + ECDSAPublicKey, + explicit_tag=0xa1))) + +class _PacketFieldRaw(PacketField): +# We need this hack because ECParameters parsing below must return +# a Padding payload, and making the ASN1_Packet class have Padding +# instead of Raw payload would break things... + def getfield(self, pkt, s): + i = self.m2i(pkt, s) + remain = "" + if conf.raw_layer in i: + r = i[conf.raw_layer] + del(r.underlayer.payload) + remain = r.load + return remain,i + +class ECDSAPrivateKey_OpenSSL(Packet): + name = "ECDSA Params + Private Key" + fields_desc = [ _PacketFieldRaw("ecparam", + ECParameters(), + ECParameters), + PacketField("privateKey", + ECDSAPrivateKey(), + ECDSAPrivateKey) ] + + ####### TBSCertificate & Certificate ####### default_issuer = [ diff --git a/scapy/utils.py b/scapy/utils.py index 2b13fd413810e37eefe58ce432cd30dada35c7e9..211185610594104ca118add41be1187a46a0013f 100644 --- a/scapy/utils.py +++ b/scapy/utils.py @@ -440,6 +440,8 @@ except NameError: else: binrepr = lambda val: bin(val)[2:] +def long_converter(s): + return long(s.replace('\n', '').replace(' ', ''), 16) ######################### #### Enum management #### diff --git a/test/cert.uts b/test/cert.uts new file mode 100644 index 0000000000000000000000000000000000000000..2b01c8b1ada29d91678c6e9b689096104b9ee962 --- /dev/null +++ b/test/cert.uts @@ -0,0 +1,380 @@ +# Cert extension - Regression Test Campaign + +# Try me with: +# bash test/run_tests -t test/cert.uts -F + + +########### PKCS helpers ############################################### + ++ PKCS helpers tests + += PKCS os2ip basic tests +pkcs_os2ip('\x00\x00\xff\xff') == 0xffff and pkcs_os2ip('\xff\xff\xff\xff\xff') == 0xffffffffff + += PKCS i2osp basic tests +pkcs_i2osp(0xffff, 4) == '\x00\x00\xff\xff' and pkcs_i2osp(0xffff, 2) == '\xff\xff' and pkcs_i2osp(0xffffeeee, 3) == '\xff\xff\xee\xee' + + +########### PubKey class ############################################### + ++ PubKey class tests + += PubKey class : Importing PEM-encoded RSA public key +x = PubKey(""" +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmFdqP+nTEZukS0lLP+yj +1gNImsEIf7P2ySTunceYxwkm4VE5QReDbb2L5/HLA9pPmIeQLSq/BgO1meOcbOSJ +2YVHQ28MQ56+8Crb6n28iycX4hp0H3AxRAjh0edX+q3yilvYJ4W9/NnIb/wAZwS0 +oJif/tTkVF77HybAfJde5Eqbp+bCKIvMWnambh9DRUyjrBBZo5dA1o32zpuFBrJd +I8dmUpw9gtf0F0Ba8lGZm8Uqc0GyXeXOJUE2u7CiMu3M77BM6ZLLTcow5+bQImkm +TL1SGhzwfinME1e6p3Hm//pDjuJvFaY22k05LgLuyqc59vFiB3Toldz8+AbMNjvz +AwIDAQAB +-----END PUBLIC KEY----- +""") +type(x) is PubKeyRSA + += PubKey class : key format is PEM +x.frmt == "PEM" + += PubKey class : Importing DER-encoded RSA Key +y = PubKey('0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\x98Wj?\xe9\xd3\x11\x9b\xa4KIK?\xec\xa3\xd6\x03H\x9a\xc1\x08\x7f\xb3\xf6\xc9$\xee\x9d\xc7\x98\xc7\t&\xe1Q9A\x17\x83m\xbd\x8b\xe7\xf1\xcb\x03\xdaO\x98\x87\x90-*\xbf\x06\x03\xb5\x99\xe3\x9cl\xe4\x89\xd9\x85GCo\x0cC\x9e\xbe\xf0*\xdb\xea}\xbc\x8b\'\x17\xe2\x1at\x1fp1D\x08\xe1\xd1\xe7W\xfa\xad\xf2\x8a[\xd8\'\x85\xbd\xfc\xd9\xc8o\xfc\x00g\x04\xb4\xa0\x98\x9f\xfe\xd4\xe4T^\xfb\x1f&\xc0|\x97^\xe4J\x9b\xa7\xe6\xc2(\x8b\xccZv\xa6n\x1fCEL\xa3\xac\x10Y\xa3\x97@\xd6\x8d\xf6\xce\x9b\x85\x06\xb2]#\xc7fR\x9c=\x82\xd7\xf4\x17@Z\xf2Q\x99\x9b\xc5*sA\xb2]\xe5\xce%A6\xbb\xb0\xa22\xed\xcc\xef\xb0L\xe9\x92\xcbM\xca0\xe7\xe6\xd0"i&L\xbdR\x1a\x1c\xf0~)\xcc\x13W\xba\xa7q\xe6\xff\xfaC\x8e\xe2o\x15\xa66\xdaM9.\x02\xee\xca\xa79\xf6\xf1b\x07t\xe8\x95\xdc\xfc\xf8\x06\xcc6;\xf3\x03\x02\x03\x01\x00\x01') +type(y) is PubKeyRSA + += PubKey class : key format is DER +y.frmt == "DER" + += PubKey class : checking modulus value +x.modulus == y.modulus and x.modulus == 19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163L + += PubKey class : checking public exponent value +x.pubExp == y.pubExp and x.pubExp == 65537L + += PubKey class : Importing PEM-encoded ECDSA public key +z = PubKey(""" +-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE55WjbZjS/88K1kYagsO9wtKifw0IKLp4 +Jd5qtmDF2Zu+xrwrBRT0HBnPweDU+RsFxcyU/QxD9WYORzYarqxbcA== +-----END PUBLIC KEY----- +""") +type(z) is PubKeyECDSA + += PubKey class : checking curve +z.key.curve.name == "SECP256k1" + += PubKey class : checking point value +z.key.pubkey.point.x() == 104748656174769496952370005421566518252704263000192720134585149244759951661467L + + +########### PrivKey class ############################################### + ++ PrivKey class tests + += PrivKey class : Importing PEM-encoded RSA private key +x = PrivKey(""" +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAmFdqP+nTEZukS0lLP+yj1gNImsEIf7P2ySTunceYxwkm4VE5 +QReDbb2L5/HLA9pPmIeQLSq/BgO1meOcbOSJ2YVHQ28MQ56+8Crb6n28iycX4hp0 +H3AxRAjh0edX+q3yilvYJ4W9/NnIb/wAZwS0oJif/tTkVF77HybAfJde5Eqbp+bC +KIvMWnambh9DRUyjrBBZo5dA1o32zpuFBrJdI8dmUpw9gtf0F0Ba8lGZm8Uqc0Gy +XeXOJUE2u7CiMu3M77BM6ZLLTcow5+bQImkmTL1SGhzwfinME1e6p3Hm//pDjuJv +FaY22k05LgLuyqc59vFiB3Toldz8+AbMNjvzAwIDAQABAoIBAH3KeJZL2hhI/1GX +NMaU/PfDgFkgmYbxMA8JKusnm/SFjxAwBGnGI6UjBXpBgpQs2Nqm3ZseF9u8hmCK +vGiCEX2GesCo2mSfmSQxD6RBrMTuQ99UXpxzBIscFnM/Zrs8lPBARGzmF2nI3qPx +Xtex4ABX5o0Cd4NfZlZjpj96skUoO8+bd3I4OPUFYFFFuv81LoSQ6Hew0a8xtJXt +KkDp9h1jTGGUOc189WACNoBLH0MGeVoSUfc1++RcC3cypUZ8fNP1OO6GBfv06f5o +XES4ZbxGYpa+nCfNwb6V2gWbkvaYm7aFn0KWGNZXS1P3OcWv6IWdOmg2CI7MMBLJ +0LyWVCECgYEAyMJYw195mvHl8VyxJ3HkxeQaaozWL4qhNQ0Kaw+mzD+jYdkbHb3a +BYghsgEDZjnyOVblC7I+4smvAZJLWJaf6sZ5HAw3zmj1ibCkXx7deoRc/QVcOikl +3dE/ymO0KGJNiGzJZmxbRS3hTokmVPuxSWW4p5oSiMupFHKa18Uv8DECgYEAwkJ7 +iTOUL6b4e3lQuHQnJbsiQpd+P/bsIPP7kaaHObewfHpfOOtIdtN4asxVFf/PgW5u +WmBllqAHZYR14DEYIdL+hdLrdvk5nYQ3YfhOnp+haHUPCdEiXrRZuGXjmMA4V0hL +3HPF5ZM8H80fLnN8Pgn2rIC7CZQ46y4PnoV1nXMCgYBBwCUCF8rkDEWa/ximKo8a +oNJmAypC98xEa7j1x3KBgnYoHcrbusok9ajTe7F5UZEbZnItmnsuG4/Nm/RBV1OY +uNgBb573YzjHl6q93IX9EkzCMXc7NS7JrzaNOopOj6OFAtwTR3m89oHMDu8W9jfi +KgaIHdXkJ4+AuugrstE4gQKBgFK0d1/8g7SeA+Cdz84YNaqMt5NeaDPXbsTA23Qx +UBU0rYDxoKTdFybv9a6SfA83sCLM31K/A8FTNJL2CDGA9WNBL3fOSs2GYg88AVBG +pUJHeDK+0748OcPUSPaG+pVIETSn5RRgffq16r0nWYUvSdAn8cuTqw3y+yC1pZS6 +AU8dAoGBAL5QCi0dTWKN3kf3cXaCAnYiWe4Qg2S+SgLE+F1U4Xws2rqAuSvIiuT5 +i5+Mqk9ZCGdoReVbAovJFoRqe7Fj9yWM+b1awGjL0bOTtnqx0iljob6uFyhpl1xg +W3a3ICJ/ZYLvkgb4IBEteOwWpp37fX57vzhW8EmUV2UX7ve1uNRI +-----END RSA PRIVATE KEY----- +""") +type(x) is PrivKeyRSA + += PrivKey class : checking public attributes +assert(x.modulus == 19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163L) +x.pubExp == 65537L + += PrivKey class : checking private attributes +assert(x.prime1 == 140977881300857803928857666115326329496639762170623218602431133528876162476487960230341078724702018316260690172014674492782486113504117653531825010840338251572887403113276393351318549036549656895326851872473595350667293402676143426484331639796163189182788306480699144107905869179435145810212051656274284113969L) +assert(x.prime2 == 136413798668820291889092636919077529673097927884427227010121877374504825870002258140616512268521246045642663981036167305976907058413796938050224182519965099316625879807962173794483933183111515251808827349718943344770056106787713032506379905031673992574818291891535689493330517205396872699985860522390496583027L) +assert(x.exponent1 == 46171616708754015342920807261537213121074749458020000367465429453038710215532257783908950878847126373502288079285334594398328912526548076894076506899568491565992572446455658740752572386903609191774044411412991906964352741123956581870694330173563737928488765282233340389888026245745090096745219902501964298369L) +assert(x.exponent2 == 58077388505079936284685944662039782610415160654764308528562806086690474868010482729442634318267235411531220690585030443434512729356878742778542733733189895801341155353491318998637269079682889033003797865508917973141494201620317820971253064836562060222814287812344611566640341960495346782352037479526674026269L) +x.privExp == 15879630313397508329451198152673380989865598204237760057319927734227125481903063742175442230739018051313441697936698689753842471306305671266572085925009572141819112648211571007521954312641597446020984266846581125287547514750428503480880603089110687015181510081018160579576523796170439894692640171752302225125980423560965987469457505107324833137678663960560798216976668670722016960863268272661588745006387723814962668678285659376534048525020951633874488845649968990679414325096323920666486328886913648207836459784281744709948801682209478580185160477801656666089536527545026197569990716720623647770979759861119273292833L + += PrivKey class : Importing PEM-encoded ECDSA private key +y = PrivKey(""" +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIMiRlFoy6046m1NXu911ukXyjDLVgmOXWCKWdQMd8gCRoAcGBSuBBAAK +oUQDQgAE55WjbZjS/88K1kYagsO9wtKifw0IKLp4Jd5qtmDF2Zu+xrwrBRT0HBnP +weDU+RsFxcyU/QxD9WYORzYarqxbcA== +-----END EC PRIVATE KEY----- +""") +type(y) is PrivKeyECDSA + += PrivKey class : checking public attributes +assert(y.key.curve.name == "SECP256k1") +y.key.privkey.public_key.point.y() == 86290575637772818452062569410092503179882738810918951913926481113065456425840L + += PrivKey class : checking private attributes +y.key.privkey.secret_multiplier == 90719786431263082134670936670180839782031078050773732489701961692235185651857L + + +########### Cert class ############################################## + ++ Cert class tests + += Cert class : Importing PEM-encoded X.509 Certificate +x = Cert(""" +-----BEGIN CERTIFICATE----- +MIIFEjCCA/qgAwIBAgIJALRecEPnCQtxMA0GCSqGSIb3DQEBBQUAMIG2MQswCQYD +VQQGEwJGUjEOMAwGA1UECBMFUGFyaXMxDjAMBgNVBAcTBVBhcmlzMRcwFQYDVQQK +Ew5NdXNocm9vbSBDb3JwLjEeMBwGA1UECxMVTXVzaHJvb20gVlBOIFNlcnZpY2Vz +MSUwIwYDVQQDExxJS0V2MiBYLjUwOSBUZXN0IGNlcnRpZmljYXRlMScwJQYJKoZI +hvcNAQkBFhhpa2V2Mi10ZXN0QG11c2hyb29tLmNvcnAwHhcNMDYwNzEzMDczODU5 +WhcNMjYwMzMwMDczODU5WjCBtjELMAkGA1UEBhMCRlIxDjAMBgNVBAgTBVBhcmlz +MQ4wDAYDVQQHEwVQYXJpczEXMBUGA1UEChMOTXVzaHJvb20gQ29ycC4xHjAcBgNV +BAsTFU11c2hyb29tIFZQTiBTZXJ2aWNlczElMCMGA1UEAxMcSUtFdjIgWC41MDkg +VGVzdCBjZXJ0aWZpY2F0ZTEnMCUGCSqGSIb3DQEJARYYaWtldjItdGVzdEBtdXNo +cm9vbS5jb3JwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmFdqP+nT +EZukS0lLP+yj1gNImsEIf7P2ySTunceYxwkm4VE5QReDbb2L5/HLA9pPmIeQLSq/ +BgO1meOcbOSJ2YVHQ28MQ56+8Crb6n28iycX4hp0H3AxRAjh0edX+q3yilvYJ4W9 +/NnIb/wAZwS0oJif/tTkVF77HybAfJde5Eqbp+bCKIvMWnambh9DRUyjrBBZo5dA +1o32zpuFBrJdI8dmUpw9gtf0F0Ba8lGZm8Uqc0GyXeXOJUE2u7CiMu3M77BM6ZLL +Tcow5+bQImkmTL1SGhzwfinME1e6p3Hm//pDjuJvFaY22k05LgLuyqc59vFiB3To +ldz8+AbMNjvzAwIDAQABo4IBHzCCARswHQYDVR0OBBYEFPPYTt6Q9+Zd0s4zzVxW +jG+XFDFLMIHrBgNVHSMEgeMwgeCAFPPYTt6Q9+Zd0s4zzVxWjG+XFDFLoYG8pIG5 +MIG2MQswCQYDVQQGEwJGUjEOMAwGA1UECBMFUGFyaXMxDjAMBgNVBAcTBVBhcmlz +MRcwFQYDVQQKEw5NdXNocm9vbSBDb3JwLjEeMBwGA1UECxMVTXVzaHJvb20gVlBO +IFNlcnZpY2VzMSUwIwYDVQQDExxJS0V2MiBYLjUwOSBUZXN0IGNlcnRpZmljYXRl +MScwJQYJKoZIhvcNAQkBFhhpa2V2Mi10ZXN0QG11c2hyb29tLmNvcnCCCQC0XnBD +5wkLcTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQA2zt0BvXofiVvH +MWlftZCstQaawej1SmxrAfDB4NUM24NsG+UZI88XA5XM6QolmfyKnNromMLC1+6C +aFxjq3jC/qdS7ifalFLQVo7ik/te0z6Olo0RkBNgyagWPX2LR5kHe9RvSDuoPIsb +SHMmJA98AZwatbvEhmzMINJNUoHVzhPeHZnIaBgUBg02XULk/ElidO51Rf3gh8dR +/kgFQSQT687vs1x9TWD00z0Q2bs2UF3Ob3+NYkEGEo5F9RePQm0mY94CT2xs6WpH +o060Fo7fVpAFktMWx1vpu+wsEbQAhgGqV0fCR2QwKDIbTrPW/p9HJtJDYVjYdAFx +r3s7V77y +-----END CERTIFICATE----- +""") + += Cert class : Checking version +x.version == 3 + += Cert class : Checking certificate serial number extraction +x.serial == 0xB45E7043E7090B71 + += Cert class : Checking signature algorithm +x.sigAlg == 'sha1-with-rsa-signature' + += Cert class : Checking issuer extraction in basic format (/C=FR ...) +x.issuer_str == '/C=FR/ST=Paris/L=Paris/O=Mushroom Corp./OU=Mushroom VPN Services/CN=IKEv2 X.509 Test certificate/emailAddress=ikev2-test@mushroom.corp' + += Cert class : Checking subject extraction in basic format (/C=FR ...) +x.subject_str == '/C=FR/ST=Paris/L=Paris/O=Mushroom Corp./OU=Mushroom VPN Services/CN=IKEv2 X.509 Test certificate/emailAddress=ikev2-test@mushroom.corp' + += Cert class : Checking start date extraction in simple and tuple formats +assert(x.notBefore_str_simple == '07/13/06') +x.notBefore == (2006, 7, 13, 7, 38, 59, 3, 194, -1) + += Cert class : Checking end date extraction in simple and tuple formats +assert(x.notAfter_str_simple == '03/30/26') +x.notAfter == (2026, 3, 30, 7, 38, 59, 0, 89, -1) + += Cert class : Checking RSA public key +assert(type(x.pubKey) is PubKeyRSA) +assert(x.pubKey.modulus == 19231328316532061413420367242571475005688288081144416166988378525696075445024135424022026378563116068168327239354659928492979285632474448448624869172454076124150405352043642781483254546569202103296262513098482624188672299255268092629150366527784294463900039290024710152521604731213565912934889752122898104556895316819303096201441834849255370122572613047779766933573375974464479123135292080801384304131606933504677232323037116557327478512106367095125103346134248056463878553619525193565824925835325216545121044922690971718737998420984924512388011040969150550056783451476150234324593710633552558175109683813482739004163L) +x.pubKey.pubExp == 0x10001 + += Cert class : Checking extensions +assert(x.cA) +assert(x.authorityKeyID == '\xf3\xd8N\xde\x90\xf7\xe6]\xd2\xce3\xcd\\V\x8co\x97\x141K') +not hasattr(x, "keyUsage") + += Cert class : Importing another PEM-encoded X.509 Certificate +y = Cert(""" +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- +""") + += Cert class : Checking ECDSA public key +assert(type(y.pubKey) is PubKeyECDSA) +assert(y.pubKey.key.curve.name == 'SECP384r1') +y.pubKey.key.pubkey.point.x() == 3987178688175281746349180015490646948656137448666005327832107126183726641822596270780616285891030558662603987311874L + += Cert class : Checking ECDSA signature +y.signatureValue == '0d\x020%\xa4\x81E\x02k\x12KutO\xc8#\xe3p\xf2ur\xde|\x89\xf0\xcf\x91ra\x9e^\x10\x92YV\xb9\x83\xc7\x10\xe78\xe9X&6}\xd5\xe44\x869\x020|6S\xf00\xe5bc:\x99\xe2\xb6\xa3;\x9b4\xfa\x1e\xda\x10\x92q^\x91\x13\xa7\xdd\xa4n\x92\xcc2\xd6\xf5!f\xc7/\xea\x96cjeE\x92\x95\x01\xb4' + + +########### CRL class ############################################### + ++ CRL class tests + += CRL class : Importing PEM-encoded CRL +x = CRL(""" +-----BEGIN X509 CRL----- +MIICHjCCAYcwDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMxFzAVBgNVBAoT +DlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmltYXJ5 +IENlcnRpZmljYXRpb24gQXV0aG9yaXR5Fw0wNjExMDIwMDAwMDBaFw0wNzAyMTcy +MzU5NTlaMIH2MCECECzSS2LEl6QXzW6jyJx6LcgXDTA0MDQwMTE3NTYxNVowIQIQ +OkXeVssCzdzcTndjIhvU1RcNMDEwNTA4MTkyMjM0WjAhAhBBXYg2gRUg1YCDRqhZ +kngsFw0wMTA3MDYxNjU3MjNaMCECEEc5gf/9hIHxlfnrGMJ8DfEXDTAzMDEwOTE4 +MDYxMlowIQIQcFR+auK62HZ/R6mZEEFeZxcNMDIwOTIzMTcwMDA4WjAhAhB+C13e +GPI5ZoKmj2UiOCPIFw0wMTA1MDgxOTA4MjFaMCICEQDQVEhgGGfTrTXKLw1KJ5Ve +Fw0wMTEyMTExODI2MjFaMA0GCSqGSIb3DQEBBQUAA4GBACLJ9rsdoaU9JMf/sCIR +s3AGW8VV3TN2oJgiCGNEac9PRyV3mRKE0hmuIJTKLFSaa4HSAzimWpWNKuJhztsZ +zXUnWSZ8VuHkgHEaSbKqzUlb2g+o/848CvzJrcbeyEBkDCYJI5C3nLlQA49LGJ+w +4GUPYBwaZ+WFxCX1C8kzglLm +-----END X509 CRL----- +""") + += CRL class : Checking version +x.version == 1 + += CRL class : Checking issuer extraction in basic format (/C=FR ...) +x.issuer_str == '/C=US/O=VeriSign, Inc./OU=Class 1 Public Primary Certification Authority' + += CRL class : Checking lastUpdate date extraction in tuple format +x.lastUpdate == (2006, 11, 2, 0, 0, 0, 3, 306, -1) + += CRL class : Checking nextUpdate date extraction in tuple format +x.nextUpdate == (2007, 2, 17, 23, 59, 59, 5, 48, -1) + += CRL class : Checking number of revoked certificates +len(x.revoked_cert_serials) == 7 + += CRL class : Checking presence of one revoked certificate +(94673785334145723688625287778885438961L, '030109180612') in x.revoked_cert_serials + +########### High-level methods ############################################### + += Cert class : Checking isIssuerCert() +c0 = Cert(""" +-----BEGIN CERTIFICATE----- +MIIFVjCCBD6gAwIBAgIJAJmDv7HOC+iUMA0GCSqGSIb3DQEBCwUAMIHGMQswCQYD +VQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEzMDEGA1UECxMq +aHR0cDovL2NlcnRzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkvMTQwMgYD +VQQDEytTdGFyZmllbGQgU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcy +MB4XDTE1MTAxMzE2NDIzOFoXDTE2MTEzMDIzMzQxOVowPjEhMB8GA1UECxMYRG9t +YWluIENvbnRyb2wgVmFsaWRhdGVkMRkwFwYDVQQDDBAqLnRvb2xzLmlldGYub3Jn +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAseE36OuC1on62/XCS3fw +LErecm4+E2DRqGYexK09MmDl8Jm19Hp6SFUh7g45EvnODcr1aWHHBO1uDx07HlCI +eToOMUEW8bECZGilzfVKCsqZljUIw34nXdCpz/PnKK832LZ73fN+rm6Xf/fKaU7M +0AbfXSebOxLn5v4Ia1J7ghF8crNG68HoeLgPy+HrvQZEWNyDULKgYlvcgbg24558 +ebKpU4rgC8lKKhM5MRO9LM+ocM+MjT0Bo4iuEgA2HR4kK9152FMBJu0oT8mGlINO +yOEULoWzr9Ru3WlGr0ElDnqti/KSynnZezJP93fo+bRPI1zUXAOu2Ks6yhNfXV1d +oQIDAQABo4IBzDCCAcgwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcD +AQYIKwYBBQUHAwIwDgYDVR0PAQH/BAQDAgWgMDwGA1UdHwQ1MDMwMaAvoC2GK2h0 +dHA6Ly9jcmwuc3RhcmZpZWxkdGVjaC5jb20vc2ZpZzJzMS0xNy5jcmwwWQYDVR0g +BFIwUDBOBgtghkgBhv1uAQcXATA/MD0GCCsGAQUFBwIBFjFodHRwOi8vY2VydGlm +aWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkvMIGCBggrBgEFBQcB +AQR2MHQwKgYIKwYBBQUHMAGGHmh0dHA6Ly9vY3NwLnN0YXJmaWVsZHRlY2guY29t +LzBGBggrBgEFBQcwAoY6aHR0cDovL2NlcnRpZmljYXRlcy5zdGFyZmllbGR0ZWNo +LmNvbS9yZXBvc2l0b3J5L3NmaWcyLmNydDAfBgNVHSMEGDAWgBQlRYFoUCY4PTst +LL7Natm2PbNmYzArBgNVHREEJDAighAqLnRvb2xzLmlldGYub3Jngg50b29scy5p +ZXRmLm9yZzAdBgNVHQ4EFgQUrYq0HAdR15KJB7C3hGIvNlV6X00wDQYJKoZIhvcN +AQELBQADggEBAAxfzShHiatHrWnTGuRX9BmFpHOFGmLs3PtRRPoOUEbZrcTbaJ+i +EZpjj4R3eiLITgObcib8+NR1eZsN6VkswZ+rr54aeQ1WzWlsVwBP1t0h9lIbaonD +wDV6ME3KzfFwwsZWqMBgLin8TcoMadAkXhdfcEKNndKSMsowgEjigP677l24nHf/ +OcnMftgErmTm+jEdW1wUooJoWgbt8TT2uWD8MC62sIIgSQ6miKtg7LhCC1ScyVuN +Erk3YzF8mPwouOcnNOKsUnkDXLA2REMedVp48c4ikjLClu6AcIg03ZU+o8fLNqcZ +zd1s7DbacrRSSQ+nXDTodqw1HB+77u0RFs0= +-----END CERTIFICATE----- +""") +c1 = Cert(""" +-----BEGIN CERTIFICATE----- +MIIFADCCA+igAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTExMDUwMzA3MDAw +MFoXDTMxMDUwMzA3MDAwMFowgcYxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTMwMQYDVQQLEypodHRwOi8vY2VydHMuc3RhcmZpZWxk +dGVjaC5jb20vcmVwb3NpdG9yeS8xNDAyBgNVBAMTK1N0YXJmaWVsZCBTZWN1cmUg +Q2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDlkGZL7PlGcakgg77pbL9KyUhpgXVObST2yxcT+LBxWYR6ayuF +pDS1FuXLzOlBcCykLtb6Mn3hqN6UEKwxwcDYav9ZJ6t21vwLdGu4p64/xFT0tDFE +3ZNWjKRMXpuJyySDm+JXfbfYEh/JhW300YDxUJuHrtQLEAX7J7oobRfpDtZNuTlV +Bv8KJAV+L8YdcmzUiymMV33a2etmGtNPp99/UsQwxaXJDgLFU793OGgGJMNmyDd+ +MB5FcSM1/5DYKp2N57CSTTx/KgqT3M0WRmX3YISLdkuRJ3MUkuDq7o8W6o0OPnYX +v32JgIBEQ+ct4EMJddo26K3biTr1XRKOIwSDAgMBAAGjggEsMIIBKDAPBgNVHRMB +Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUJUWBaFAmOD07LSy+ +zWrZtj2zZmMwHwYDVR0jBBgwFoAUfAwyH6fZMH/EfWijYqihzqsHWycwOgYIKwYB +BQUHAQEELjAsMCoGCCsGAQUFBzABhh5odHRwOi8vb2NzcC5zdGFyZmllbGR0ZWNo +LmNvbS8wOwYDVR0fBDQwMjAwoC6gLIYqaHR0cDovL2NybC5zdGFyZmllbGR0ZWNo +LmNvbS9zZnJvb3QtZzIuY3JsMEwGA1UdIARFMEMwQQYEVR0gADA5MDcGCCsGAQUF +BwIBFitodHRwczovL2NlcnRzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkv +MA0GCSqGSIb3DQEBCwUAA4IBAQBWZcr+8z8KqJOLGMfeQ2kTNCC+Tl94qGuc22pN +QdvBE+zcMQAiXvcAngzgNGU0+bE6TkjIEoGIXFs+CFN69xpk37hQYcxTUUApS8L0 +rjpf5MqtJsxOYUPl/VemN3DOQyuwlMOS6eFfqhBJt2nk4NAfZKQrzR9voPiEJBjO +eT2pkb9UGBOJmVQRDVXFJgt5T1ocbvlj2xSApAer+rKluYjdkf5lO6Sjeb6JTeHQ +sPTIFwwKlhR8Cbds4cLYVdQYoKpBaXAko7nv6VrcPuuUSvC33l8Odvr7+2kDRUBQ +7nIMpBKGgc0T0U7EPMpODdIm8QC3tKai4W56gf0wrHofx1l7 +-----END CERTIFICATE----- +""") +c2 = Cert(""" +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- +""") +c0.isIssuerCert(c1) and c1.isIssuerCert(c2) and not c0.isIssuerCert(c2) + += Cert class : Checking isSelfSigned() +c2.isSelfSigned() and not c1.isSelfSigned() and not c0.isSelfSigned() + += PubKey class : Checking verifyCert() +c2.pubKey.verifyCert(c2) and c1.pubKey.verifyCert(c0) + += Chain class : Checking chain construction +assert(len(Chain([c0, c1, c2])) == 3) +assert(len(Chain([c0], c1)) == 2) +len(Chain([c0], c2)) == 1 + += Chain class : Checking chain verification +assert(Chain([], c0).verifyChain([c2], [c1])) +not Chain([c1]).verifyChain([c0]) +