Added cert.py: X.509 Cert and Key module based on OpenSSL binary
authorArnaud Ebalard <arno@natisbad.org>
Mon Nov 02 22:09:11 2009 +0100 (2009-11-02)
changeset 115342db888aaf7b
parent 1152 0bbaf2ce83bc
child 1156 e675af7207d2
Added cert.py: X.509 Cert and Key module based on OpenSSL binary
scapy/crypto/__init__.py
scapy/crypto/cert.py
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/scapy/crypto/__init__.py	Mon Nov 02 22:09:11 2009 +0100
     1.3 @@ -0,0 +1,6 @@
     1.4 +## This file is part of Scapy
     1.5 +## See http://www.secdev.org/projects/scapy for more informations
     1.6 +## Copyright (C) Arnaud Ebalard <arno@natisbad.org>
     1.7 +## This program is published under a GPLv2 license
     1.8 +
     1.9 +__all__ = ["cert"]
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/scapy/crypto/cert.py	Mon Nov 02 22:09:11 2009 +0100
     2.3 @@ -0,0 +1,2470 @@
     2.4 +## This file is part of Scapy
     2.5 +## See http://www.secdev.org/projects/scapy for more informations
     2.6 +## Copyright (C) Arnaud Ebalard <arno@natisbad.org>
     2.7 +## This program is published under a GPLv2 license
     2.8 +
     2.9 +import os, sys, math, socket, struct, sha, hmac, string, time
    2.10 +import random, popen2, tempfile
    2.11 +from scapy.utils import strxor
    2.12 +try:
    2.13 +    HAS_HASHLIB=True
    2.14 +    import hashlib
    2.15 +except:
    2.16 +    HAS_HASHLIB=False
    2.17 +
    2.18 +from Crypto.PublicKey import *
    2.19 +from Crypto.Cipher import *
    2.20 +from Crypto.Hash import *
    2.21 +
    2.22 +# Maximum allowed size in bytes for a certificate file, to avoid
    2.23 +# loading huge file when importing a cert
    2.24 +MAX_KEY_SIZE=50*1024
    2.25 +MAX_CERT_SIZE=50*1024
    2.26 +MAX_CRL_SIZE=10*1024*1024   # some are that big
    2.27 +
    2.28 +#####################################################################
    2.29 +# Some helpers
    2.30 +#####################################################################
    2.31 +
    2.32 +def warning(m):
    2.33 +    print "WARNING: %s" % m
    2.34 +
    2.35 +def randstring(l):
    2.36 +    """
    2.37 +    Returns a random string of length l (l >= 0)
    2.38 +    """
    2.39 +    tmp = map(lambda x: struct.pack("B", random.randrange(0, 256, 1)), [""]*l)
    2.40 +    return "".join(tmp)
    2.41 +
    2.42 +def zerofree_randstring(l):
    2.43 +    """
    2.44 +    Returns a random string of length l (l >= 0) without zero in it. 
    2.45 +    """
    2.46 +    tmp = map(lambda x: struct.pack("B", random.randrange(1, 256, 1)), [""]*l)
    2.47 +    return "".join(tmp)
    2.48 +
    2.49 +def strand(s1, s2):
    2.50 +    """
    2.51 +    Returns the binary AND of the 2 provided strings s1 and s2. s1 and s2
    2.52 +    must be of same length.
    2.53 +    """
    2.54 +    return "".join(map(lambda x,y:chr(ord(x)&ord(y)), s1, s2))
    2.55 +
    2.56 +# OS2IP function defined in RFC 3447 for octet string to integer conversion
    2.57 +def pkcs_os2ip(x):
    2.58 +    """
    2.59 +    Accepts a byte string as input parameter and return the associated long
    2.60 +    value:
    2.61 +
    2.62 +    Input : x        octet string to be converted
    2.63 +
    2.64 +    Output: x        corresponding nonnegative integer
    2.65 +
    2.66 +    Reverse function is pkcs_i2osp()
    2.67 +    """
    2.68 +    return RSA.number.bytes_to_long(x) 
    2.69 +
    2.70 +# IP2OS function defined in RFC 3447 for octet string to integer conversion
    2.71 +def pkcs_i2osp(x,xLen):
    2.72 +    """
    2.73 +    Converts a long (the first parameter) to the associated byte string
    2.74 +    representation of length l (second parameter). Basically, the length
    2.75 +    parameters allow the function to perform the associated padding.
    2.76 +
    2.77 +    Input : x        nonnegative integer to be converted
    2.78 +            xLen     intended length of the resulting octet string
    2.79 +
    2.80 +    Output: x        corresponding nonnegative integer
    2.81 +
    2.82 +    Reverse function is pkcs_os2ip().
    2.83 +    """
    2.84 +    z = RSA.number.long_to_bytes(x)
    2.85 +    padlen = max(0, xLen-len(z))
    2.86 +    return '\x00'*padlen + z
    2.87 +
    2.88 +# for every hash function a tuple is provided, giving access to 
    2.89 +# - hash output length in byte
    2.90 +# - associated hash function that take data to be hashed as parameter
    2.91 +#   XXX I do not provide update() at the moment.
    2.92 +# - DER encoding of the leading bits of digestInfo (the hash value
    2.93 +#   will be concatenated to create the complete digestInfo).
    2.94 +# 
    2.95 +# Notes:
    2.96 +# - MD4 asn.1 value should be verified. Also, as stated in 
    2.97 +#   PKCS#1 v2.1, MD4 should not be used.
    2.98 +# - hashlib is available from http://code.krypto.org/python/hashlib/
    2.99 +# - 'tls' one is the concatenation of both md5 and sha1 hashes used
   2.100 +#   by SSL/TLS when signing/verifying things
   2.101 +_hashFuncParams = {
   2.102 +    "md2"    : (16, 
   2.103 +                lambda x: MD2.new(x).digest(), 
   2.104 +                '\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x02\x05\x00\x04\x10'),
   2.105 +    "md4"    : (16, 
   2.106 +                lambda x: MD4.new(x).digest(), 
   2.107 +                '\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x04\x05\x00\x04\x10'), # is that right ?
   2.108 +    "md5"    : (16, 
   2.109 +                lambda x: MD5.new(x).digest(), 
   2.110 +                '\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10'),
   2.111 +    "sha1"   : (20,
   2.112 +                lambda x: SHA.new(x).digest(), 
   2.113 +                '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'),
   2.114 +    "tls"    : (36,
   2.115 +                lambda x: MD5.new(x).digest() + SHA.new(x).digest(),
   2.116 +                '') }
   2.117 +
   2.118 +if HAS_HASHLIB:
   2.119 +    _hashFuncParams["sha224"] = (28, 
   2.120 +                lambda x: hashlib.sha224(x).digest(),
   2.121 +                '\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c')
   2.122 +    _hashFuncParams["sha256"] = (32, 
   2.123 +                lambda x: hashlib.sha256(x).digest(), 
   2.124 +                '\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20')
   2.125 +    _hashFuncParams["sha384"] = (48, 
   2.126 +                lambda x: hashlib.sha384(x).digest(),
   2.127 +               '\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30')
   2.128 +    _hashFuncParams["sha512"] = (64, 
   2.129 +               lambda x: hashlib.sha512(x).digest(),
   2.130 +               '\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40')
   2.131 +else:
   2.132 +    warning("hashlib support is not available. Consider installing it")
   2.133 +    warning("if you need sha224, sha256, sha384 and sha512 algs.")
   2.134 +    
   2.135 +def pkcs_mgf1(mgfSeed, maskLen, h):
   2.136 +    """
   2.137 +    Implements generic MGF1 Mask Generation function as described in
   2.138 +    Appendix B.2.1 of RFC 3447. The hash function is passed by name.
   2.139 +    valid values are 'md2', 'md4', 'md5', 'sha1', 'tls, 'sha256',
   2.140 +    'sha384' and 'sha512'. Returns None on error.
   2.141 +
   2.142 +    Input:
   2.143 +       mgfSeed: seed from which mask is generated, an octet string
   2.144 +       maskLen: intended length in octets of the mask, at most 2^32 * hLen
   2.145 +                hLen (see below)
   2.146 +       h      : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls',
   2.147 +                'sha256', 'sha384'). hLen denotes the length in octets of
   2.148 +                the hash function output.
   2.149 +
   2.150 +    Output:
   2.151 +       an octet string of length maskLen
   2.152 +    """
   2.153 +
   2.154 +    # steps are those of Appendix B.2.1
   2.155 +    if not _hashFuncParams.has_key(h):
   2.156 +        warning("pkcs_mgf1: invalid hash (%s) provided")
   2.157 +        return None
   2.158 +    hLen = _hashFuncParams[h][0]
   2.159 +    hFunc = _hashFuncParams[h][1]
   2.160 +    if maskLen > 2**32 * hLen:                               # 1)
   2.161 +        warning("pkcs_mgf1: maskLen > 2**32 * hLen")         
   2.162 +        return None
   2.163 +    T = ""                                                   # 2)
   2.164 +    maxCounter = math.ceil(float(maskLen) / float(hLen))     # 3)
   2.165 +    counter = 0
   2.166 +    while counter < maxCounter:
   2.167 +        C = pkcs_i2osp(counter, 4)
   2.168 +        T += hFunc(mgfSeed + C)
   2.169 +        counter += 1
   2.170 +    return T[:maskLen]
   2.171 +
   2.172 +
   2.173 +def pkcs_emsa_pss_encode(M, emBits, h, mgf, sLen): 
   2.174 +    """
   2.175 +    Implements EMSA-PSS-ENCODE() function described in Sect. 9.1.1 of RFC 3447
   2.176 +
   2.177 +    Input:
   2.178 +       M     : message to be encoded, an octet string
   2.179 +       emBits: maximal bit length of the integer resulting of pkcs_os2ip(EM),
   2.180 +               where EM is the encoded message, output of the function.
   2.181 +       h     : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls',
   2.182 +               'sha256', 'sha384'). hLen denotes the length in octets of
   2.183 +               the hash function output. 
   2.184 +       mgf   : the mask generation function f : seed, maskLen -> mask
   2.185 +       sLen  : intended length in octets of the salt
   2.186 +
   2.187 +    Output:
   2.188 +       encoded message, an octet string of length emLen = ceil(emBits/8)
   2.189 +
   2.190 +    On error, None is returned.
   2.191 +    """
   2.192 +
   2.193 +    # 1) is not done
   2.194 +    hLen = _hashFuncParams[h][0]                             # 2)
   2.195 +    hFunc = _hashFuncParams[h][1]
   2.196 +    mHash = hFunc(M)
   2.197 +    emLen = int(math.ceil(emBits/8.))
   2.198 +    if emLen < hLen + sLen + 2:                              # 3)
   2.199 +        warning("encoding error (emLen < hLen + sLen + 2)")
   2.200 +        return None
   2.201 +    salt = randstring(sLen)                                  # 4)
   2.202 +    MPrime = '\x00'*8 + mHash + salt                         # 5)
   2.203 +    H = hFunc(MPrime)                                        # 6)
   2.204 +    PS = '\x00'*(emLen - sLen - hLen - 2)                    # 7)
   2.205 +    DB = PS + '\x01' + salt                                  # 8)
   2.206 +    dbMask = mgf(H, emLen - hLen - 1)                        # 9)
   2.207 +    maskedDB = strxor(DB, dbMask)                            # 10)
   2.208 +    l = (8*emLen - emBits)/8                                 # 11)
   2.209 +    rem = 8*emLen - emBits - 8*l # additionnal bits
   2.210 +    andMask = l*'\x00'
   2.211 +    if rem:
   2.212 +        j = chr(reduce(lambda x,y: x+y, map(lambda x: 1<<x, range(8-rem))))
   2.213 +        andMask += j
   2.214 +        l += 1
   2.215 +    maskedDB = strand(maskedDB[:l], andMask) + maskedDB[l:]
   2.216 +    EM = maskedDB + H + '\xbc'                               # 12)
   2.217 +    return EM                                                # 13)
   2.218 +
   2.219 +
   2.220 +def pkcs_emsa_pss_verify(M, EM, emBits, h, mgf, sLen):
   2.221 +    """
   2.222 +    Implements EMSA-PSS-VERIFY() function described in Sect. 9.1.2 of RFC 3447
   2.223 +
   2.224 +    Input:
   2.225 +       M     : message to be encoded, an octet string
   2.226 +       EM    : encoded message, an octet string of length emLen = ceil(emBits/8)
   2.227 +       emBits: maximal bit length of the integer resulting of pkcs_os2ip(EM)
   2.228 +       h     : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls',
   2.229 +               'sha256', 'sha384'). hLen denotes the length in octets of
   2.230 +               the hash function output.
   2.231 +       mgf   : the mask generation function f : seed, maskLen -> mask
   2.232 +       sLen  : intended length in octets of the salt
   2.233 +
   2.234 +    Output:
   2.235 +       True if the verification is ok, False otherwise.
   2.236 +    """
   2.237 +    
   2.238 +    # 1) is not done
   2.239 +    hLen = _hashFuncParams[h][0]                             # 2)
   2.240 +    hFunc = _hashFuncParams[h][1]
   2.241 +    mHash = hFunc(M)
   2.242 +    emLen = int(math.ceil(emBits/8.))                        # 3)
   2.243 +    if emLen < hLen + sLen + 2:
   2.244 +        return False
   2.245 +    if EM[-1] != '\xbc':                                     # 4)
   2.246 +        return False
   2.247 +    l = emLen - hLen - 1                                     # 5)
   2.248 +    maskedDB = EM[:l]
   2.249 +    H = EM[l:l+hLen]
   2.250 +    l = (8*emLen - emBits)/8                                 # 6)
   2.251 +    rem = 8*emLen - emBits - 8*l # additionnal bits
   2.252 +    andMask = l*'\xff'
   2.253 +    if rem:
   2.254 +        val = reduce(lambda x,y: x+y, map(lambda x: 1<<x, range(8-rem)))
   2.255 +        j = chr(~val & 0xff)
   2.256 +        andMask += j
   2.257 +        l += 1
   2.258 +    if strand(maskedDB[:l], andMask) != '\x00'*l:
   2.259 +        return False
   2.260 +    dbMask = mgf(H, emLen - hLen - 1)                        # 7)
   2.261 +    DB = strxor(maskedDB, dbMask)                            # 8)
   2.262 +    l = (8*emLen - emBits)/8                                 # 9)
   2.263 +    rem = 8*emLen - emBits - 8*l # additionnal bits
   2.264 +    andMask = l*'\x00'
   2.265 +    if rem:
   2.266 +        j = chr(reduce(lambda x,y: x+y, map(lambda x: 1<<x, range(8-rem))))
   2.267 +        andMask += j
   2.268 +        l += 1
   2.269 +    DB = strand(DB[:l], andMask) + DB[l:]
   2.270 +    l = emLen - hLen - sLen - 1                              # 10)
   2.271 +    if DB[:l] != '\x00'*(l-1) + '\x01':
   2.272 +        return False
   2.273 +    salt = DB[-sLen:]                                        # 11)
   2.274 +    MPrime = '\x00'*8 + mHash + salt                         # 12)
   2.275 +    HPrime = hFunc(MPrime)                                   # 13)
   2.276 +    return H == HPrime                                       # 14)
   2.277 +
   2.278 +
   2.279 +def pkcs_emsa_pkcs1_v1_5_encode(M, emLen, h): # section 9.2 of RFC 3447
   2.280 +    """
   2.281 +    Implements EMSA-PKCS1-V1_5-ENCODE() function described in Sect.
   2.282 +    9.2 of RFC 3447.
   2.283 +
   2.284 +    Input:
   2.285 +       M    : message to be encode, an octet string
   2.286 +       emLen: intended length in octets of the encoded message, at least
   2.287 +              tLen + 11, where tLen is the octet length of the DER encoding
   2.288 +              T of a certain value computed during the encoding operation.
   2.289 +       h    : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls',
   2.290 +              'sha256', 'sha384'). hLen denotes the length in octets of
   2.291 +              the hash function output.
   2.292 +
   2.293 +    Output:
   2.294 +       encoded message, an octet string of length emLen
   2.295 +
   2.296 +    On error, None is returned.
   2.297 +    """
   2.298 +    hLen = _hashFuncParams[h][0]                             # 1)
   2.299 +    hFunc = _hashFuncParams[h][1]
   2.300 +    H = hFunc(M)
   2.301 +    hLeadingDigestInfo = _hashFuncParams[h][2]               # 2)
   2.302 +    T = hLeadingDigestInfo + H
   2.303 +    tLen = len(T)
   2.304 +    if emLen < tLen + 11:                                    # 3)
   2.305 +        warning("pkcs_emsa_pkcs1_v1_5_encode: intended encoded message length too short")
   2.306 +        return None
   2.307 +    PS = '\xff'*(emLen - tLen - 3)                           # 4)
   2.308 +    EM = '\x00' + '\x01' + PS + '\x00' + T                   # 5)
   2.309 +    return EM                                                # 6)
   2.310 +
   2.311 +
   2.312 +# XXX should add other pgf1 instance in a better fashion.
   2.313 +
   2.314 +def create_ca_file(anchor_list, filename):
   2.315 +    """
   2.316 +    Concatenate all the certificates (PEM format for the export) in
   2.317 +    'anchor_list' and write the result to file 'filename'. On success
   2.318 +    'filename' is returned, None otherwise.
   2.319 +
   2.320 +    If you are used to OpenSSL tools, this function builds a CAfile
   2.321 +    that can be used for certificate and CRL check.
   2.322 +
   2.323 +    Also see create_temporary_ca_file().
   2.324 +    """
   2.325 +    try:
   2.326 +        f = open(filename, "w")
   2.327 +        for a in anchor_list:
   2.328 +            s = a.output(fmt="PEM")
   2.329 +            f.write(s)
   2.330 +        f.close()
   2.331 +    except:
   2.332 +        return None
   2.333 +    return filename
   2.334 +
   2.335 +def create_temporary_ca_file(anchor_list):
   2.336 +    """
   2.337 +    Concatenate all the certificates (PEM format for the export) in
   2.338 +    'anchor_list' and write the result to file to a temporary file
   2.339 +    using mkstemp() from tempfile module. On success 'filename' is
   2.340 +    returned, None otherwise.
   2.341 +
   2.342 +    If you are used to OpenSSL tools, this function builds a CAfile
   2.343 +    that can be used for certificate and CRL check.
   2.344 +
   2.345 +    Also see create_temporary_ca_file().
   2.346 +    """
   2.347 +    try:
   2.348 +        f, fname = tempfile.mkstemp()
   2.349 +        for a in anchor_list:
   2.350 +            s = a.output(fmt="PEM")
   2.351 +            l = os.write(f, s)
   2.352 +        os.close(f)
   2.353 +    except:
   2.354 +        return None
   2.355 +    return fname
   2.356 +
   2.357 +def create_temporary_ca_path(anchor_list, folder):
   2.358 +    """
   2.359 +    Create a CA path folder as defined in OpenSSL terminology, by
   2.360 +    storing all certificates in 'anchor_list' list in PEM format
   2.361 +    under provided 'folder' and then creating the associated links
   2.362 +    using the hash as usually done by c_rehash.
   2.363 +
   2.364 +    Note that you can also include CRL in 'anchor_list'. In that
   2.365 +    case, they will also be stored under 'folder' and associated
   2.366 +    links will be created.
   2.367 +
   2.368 +    In folder, the files are created with names of the form
   2.369 +    0...ZZ.pem. If you provide an empty list, folder will be created
   2.370 +    if it does not already exist, but that's all.
   2.371 +
   2.372 +    The number of certificates written to folder is returned on
   2.373 +    success, None on error.
   2.374 +    """
   2.375 +    # We should probably avoid writing duplicate anchors and also
   2.376 +    # check if they are all certs.
   2.377 +    try:
   2.378 +        if not os.path.isdir(folder):
   2.379 +            os.makedirs(folder)
   2.380 +    except:
   2.381 +        return None
   2.382 +    
   2.383 +    l = len(anchor_list)
   2.384 +    if l == 0:
   2.385 +        return None
   2.386 +    fmtstr = "%%0%sd.pem" % math.ceil(math.log(l, 10))
   2.387 +    i = 0
   2.388 +    try:
   2.389 +        for a in anchor_list:
   2.390 +            fname = os.path.join(folder, fmtstr % i)
   2.391 +            f = open(fname, "w")
   2.392 +            s = a.output(fmt="PEM")
   2.393 +            f.write(s)
   2.394 +            f.close()
   2.395 +            i += 1
   2.396 +    except:
   2.397 +        return None
   2.398 +
   2.399 +    r,w=popen2.popen2("c_rehash %s" % folder)
   2.400 +    r.close(); w.close()
   2.401 +
   2.402 +    return l
   2.403 +
   2.404 +
   2.405 +#####################################################################
   2.406 +# Public Key Cryptography related stuff
   2.407 +#####################################################################
   2.408 +
   2.409 +class OSSLHelper:
   2.410 +    def _apply_ossl_cmd(self, osslcmd, rawdata):
   2.411 +	r,w=popen2.popen2(osslcmd)
   2.412 +	w.write(rawdata)
   2.413 +	w.close()
   2.414 +	res = r.read()
   2.415 +	r.close()
   2.416 +	return res
   2.417 +
   2.418 +class _EncryptAndVerify:
   2.419 +    ### Below are encryption methods
   2.420 +
   2.421 +    def _rsaep(self, m):
   2.422 +        """
   2.423 +        Internal method providing raw RSA encryption, i.e. simple modular
   2.424 +        exponentiation of the given message representative 'm', a long
   2.425 +        between 0 and n-1.
   2.426 +
   2.427 +        This is the encryption primitive RSAEP described in PKCS#1 v2.1,
   2.428 +        i.e. RFC 3447 Sect. 5.1.1.
   2.429 +
   2.430 +        Input:
   2.431 +           m: message representative, a long between 0 and n-1, where
   2.432 +              n is the key modulus.
   2.433 +
   2.434 +        Output:
   2.435 +           ciphertext representative, a long between 0 and n-1
   2.436 +
   2.437 +        Not intended to be used directly. Please, see encrypt() method.
   2.438 +        """
   2.439 +
   2.440 +        n = self.modulus
   2.441 +        if type(m) is int:
   2.442 +            m = long(m)
   2.443 +        if type(m) is not long or m > n-1:
   2.444 +            warning("Key._rsaep() expects a long between 0 and n-1")
   2.445 +            return None
   2.446 +
   2.447 +        return self.key.encrypt(m, "")[0]
   2.448 +
   2.449 +
   2.450 +    def _rsaes_pkcs1_v1_5_encrypt(self, M):
   2.451 +        """
   2.452 +        Implements RSAES-PKCS1-V1_5-ENCRYPT() function described in section
   2.453 +        7.2.1 of RFC 3447.
   2.454 +
   2.455 +        Input:
   2.456 +           M: message to be encrypted, an octet string of length mLen, where
   2.457 +              mLen <= k - 11 (k denotes the length in octets of the key modulus)
   2.458 +
   2.459 +        Output:
   2.460 +           ciphertext, an octet string of length k
   2.461 +
   2.462 +        On error, None is returned.
   2.463 +        """
   2.464 +
   2.465 +        # 1) Length checking
   2.466 +        mLen = len(M)
   2.467 +        k = self.modulusLen / 8
   2.468 +        if mLen > k - 11:
   2.469 +            warning("Key._rsaes_pkcs1_v1_5_encrypt(): message too "
   2.470 +                    "long (%d > %d - 11)" % (mLen, k))
   2.471 +            return None
   2.472 +
   2.473 +        # 2) EME-PKCS1-v1_5 encoding
   2.474 +        PS = zerofree_randstring(k - mLen - 3)      # 2.a)
   2.475 +        EM = '\x00' + '\x02' + PS + '\x00' + M      # 2.b)
   2.476 +
   2.477 +        # 3) RSA encryption
   2.478 +        m = pkcs_os2ip(EM)                          # 3.a)
   2.479 +        c = self._rsaep(m)                          # 3.b)
   2.480 +        C = pkcs_i2osp(c, k)                        # 3.c)
   2.481 +
   2.482 +        return C                                    # 4)
   2.483 +
   2.484 +
   2.485 +    def _rsaes_oaep_encrypt(self, M, h=None, mgf=None, L=None):
   2.486 +        """
   2.487 +        Internal method providing RSAES-OAEP-ENCRYPT as defined in Sect.
   2.488 +        7.1.1 of RFC 3447. Not intended to be used directly. Please, see
   2.489 +        encrypt() method for type "OAEP".
   2.490 +
   2.491 +
   2.492 +        Input:
   2.493 +           M  : message to be encrypted, an octet string of length mLen
   2.494 +                where mLen <= k - 2*hLen - 2 (k denotes the length in octets
   2.495 +                of the RSA modulus and hLen the length in octets of the hash
   2.496 +                function output)
   2.497 +           h  : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls',
   2.498 +                'sha256', 'sha384'). hLen denotes the length in octets of
   2.499 +                the hash function output. 'sha1' is used by default if not
   2.500 +                provided.
   2.501 +           mgf: the mask generation function f : seed, maskLen -> mask
   2.502 +           L  : optional label to be associated with the message; the default
   2.503 +                value for L, if not provided is the empty string
   2.504 +
   2.505 +        Output:
   2.506 +           ciphertext, an octet string of length k
   2.507 +
   2.508 +        On error, None is returned.
   2.509 +        """
   2.510 +        # The steps below are the one described in Sect. 7.1.1 of RFC 3447.
   2.511 +        # 1) Length Checking
   2.512 +                                                    # 1.a) is not done
   2.513 +        mLen = len(M)
   2.514 +        if h is None:
   2.515 +            h = "sha1"
   2.516 +        if not _hashFuncParams.has_key(h):
   2.517 +            warning("Key._rsaes_oaep_encrypt(): unknown hash function %s.", h)
   2.518 +            return None
   2.519 +        hLen = _hashFuncParams[h][0]
   2.520 +        hFun = _hashFuncParams[h][1]
   2.521 +        k = self.modulusLen / 8
   2.522 +        if mLen > k - 2*hLen - 2:                   # 1.b)
   2.523 +            warning("Key._rsaes_oaep_encrypt(): message too long.")
   2.524 +            return None
   2.525 +        
   2.526 +        # 2) EME-OAEP encoding
   2.527 +        if L is None:                               # 2.a)
   2.528 +            L = ""
   2.529 +        lHash = hFun(L)
   2.530 +        PS = '\x00'*(k - mLen - 2*hLen - 2)         # 2.b)
   2.531 +        DB = lHash + PS + '\x01' + M                # 2.c)
   2.532 +        seed = randstring(hLen)                     # 2.d)
   2.533 +        if mgf is None:                             # 2.e)
   2.534 +            mgf = lambda x,y: pkcs_mgf1(x,y,h)
   2.535 +        dbMask = mgf(seed, k - hLen - 1)
   2.536 +        maskedDB = strxor(DB, dbMask)               # 2.f)
   2.537 +        seedMask = mgf(maskedDB, hLen)              # 2.g)
   2.538 +        maskedSeed = strxor(seed, seedMask)         # 2.h)
   2.539 +        EM = '\x00' + maskedSeed + maskedDB         # 2.i)
   2.540 +
   2.541 +        # 3) RSA Encryption
   2.542 +        m = pkcs_os2ip(EM)                          # 3.a)
   2.543 +        c = self._rsaep(m)                          # 3.b)
   2.544 +        C = pkcs_i2osp(c, k)                        # 3.c)
   2.545 +
   2.546 +        return C                                    # 4)
   2.547 +
   2.548 +
   2.549 +    def encrypt(self, m, t=None, h=None, mgf=None, L=None):
   2.550 +        """
   2.551 +        Encrypt message 'm' using 't' encryption scheme where 't' can be:
   2.552 +
   2.553 +        - None: the message 'm' is directly applied the RSAEP encryption
   2.554 +                primitive, as described in PKCS#1 v2.1, i.e. RFC 3447
   2.555 +                Sect 5.1.1. Simply put, the message undergo a modular
   2.556 +                exponentiation using the public key. Additionnal method
   2.557 +                parameters are just ignored.
   2.558 +
   2.559 +        - 'pkcs': the message 'm' is applied RSAES-PKCS1-V1_5-ENCRYPT encryption
   2.560 +                scheme as described in section 7.2.1 of RFC 3447. In that
   2.561 +                context, other parameters ('h', 'mgf', 'l') are not used.
   2.562 +
   2.563 +        - 'oaep': the message 'm' is applied the RSAES-OAEP-ENCRYPT encryption
   2.564 +                scheme, as described in PKCS#1 v2.1, i.e. RFC 3447 Sect
   2.565 +                7.1.1. In that context,
   2.566 +
   2.567 +                o 'h' parameter provides the name of the hash method to use.
   2.568 +                  Possible values are "md2", "md4", "md5", "sha1", "tls",
   2.569 +                  "sha224", "sha256", "sha384" and "sha512". if none is provided,
   2.570 +                  sha1 is used.
   2.571 +
   2.572 +                o 'mgf' is the mask generation function. By default, mgf
   2.573 +                  is derived from the provided hash function using the
   2.574 +                  generic MGF1 (see pkcs_mgf1() for details).
   2.575 +
   2.576 +                o 'L' is the optional label to be associated with the
   2.577 +                  message. If not provided, the default value is used, i.e
   2.578 +                  the empty string. No check is done on the input limitation
   2.579 +                  of the hash function regarding the size of 'L' (for
   2.580 +                  instance, 2^61 - 1 for SHA-1). You have been warned.
   2.581 +        """
   2.582 +
   2.583 +        if t is None: # Raw encryption
   2.584 +            m = pkcs_os2ip(m)
   2.585 +            c = self._rsaep(m)
   2.586 +            return pkcs_i2osp(c, self.modulusLen/8)
   2.587 +        
   2.588 +        elif t == "pkcs":
   2.589 +            return self._rsaes_pkcs1_v1_5_encrypt(m)
   2.590 +        
   2.591 +        elif t == "oaep":
   2.592 +            return self._rsaes_oaep_encrypt(m, h, mgf, L)
   2.593 +
   2.594 +        else:
   2.595 +            warning("Key.encrypt(): Unknown encryption type (%s) provided" % t)
   2.596 +            return None
   2.597 +
   2.598 +    ### Below are verification related methods
   2.599 +
   2.600 +    def _rsavp1(self, s):
   2.601 +        """
   2.602 +        Internal method providing raw RSA verification, i.e. simple modular
   2.603 +        exponentiation of the given signature representative 'c', an integer
   2.604 +        between 0 and n-1.
   2.605 +
   2.606 +        This is the signature verification primitive RSAVP1 described in
   2.607 +        PKCS#1 v2.1, i.e. RFC 3447 Sect. 5.2.2.
   2.608 +
   2.609 +        Input:
   2.610 +          s: signature representative, an integer between 0 and n-1,
   2.611 +             where n is the key modulus.
   2.612 +
   2.613 +        Output:
   2.614 +           message representative, an integer between 0 and n-1
   2.615 +
   2.616 +        Not intended to be used directly. Please, see verify() method.
   2.617 +        """
   2.618 +        return self._rsaep(s)
   2.619 +
   2.620 +    def _rsassa_pss_verify(self, M, S, h=None, mgf=None, sLen=None):
   2.621 +        """
   2.622 +        Implements RSASSA-PSS-VERIFY() function described in Sect 8.1.2
   2.623 +        of RFC 3447
   2.624 +
   2.625 +        Input:
   2.626 +           M: message whose signature is to be verified
   2.627 +           S: signature to be verified, an octet string of length k, where k
   2.628 +              is the length in octets of the RSA modulus n.
   2.629 +
   2.630 +        Output:
   2.631 +           True is the signature is valid. False otherwise.
   2.632 +        """
   2.633 +
   2.634 +        # Set default parameters if not provided
   2.635 +        if h is None: # By default, sha1
   2.636 +            h = "sha1"
   2.637 +        if not _hashFuncParams.has_key(h):
   2.638 +            warning("Key._rsassa_pss_verify(): unknown hash function "
   2.639 +                    "provided (%s)" % h)
   2.640 +            return False
   2.641 +        if mgf is None: # use mgf1 with underlying hash function
   2.642 +            mgf = lambda x,y: pkcs_mgf1(x, y, h)
   2.643 +        if sLen is None: # use Hash output length (A.2.3 of RFC 3447)
   2.644 +            hLen = _hashFuncParams[h][0]
   2.645 +            sLen = hLen
   2.646 +
   2.647 +        # 1) Length checking
   2.648 +        modBits = self.modulusLen
   2.649 +        k = modBits / 8
   2.650 +        if len(S) != k:
   2.651 +            return False
   2.652 +
   2.653 +        # 2) RSA verification
   2.654 +        s = pkcs_os2ip(S)                           # 2.a)
   2.655 +        m = self._rsavp1(s)                         # 2.b)
   2.656 +        emLen = math.ceil((modBits - 1) / 8.)       # 2.c)
   2.657 +        EM = pkcs_i2osp(m, emLen) 
   2.658 +
   2.659 +        # 3) EMSA-PSS verification
   2.660 +        Result = pkcs_emsa_pss_verify(M, EM, modBits - 1, h, mgf, sLen)
   2.661 +
   2.662 +        return Result                               # 4)
   2.663 +
   2.664 +
   2.665 +    def _rsassa_pkcs1_v1_5_verify(self, M, S, h):
   2.666 +        """
   2.667 +        Implements RSASSA-PKCS1-v1_5-VERIFY() function as described in
   2.668 +        Sect. 8.2.2 of RFC 3447.
   2.669 +
   2.670 +        Input:
   2.671 +           M: message whose signature is to be verified, an octet string
   2.672 +           S: signature to be verified, an octet string of length k, where
   2.673 +              k is the length in octets of the RSA modulus n
   2.674 +           h: hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls',
   2.675 +                'sha256', 'sha384').
   2.676 +           
   2.677 +        Output:
   2.678 +           True if the signature is valid. False otherwise.
   2.679 +        """
   2.680 +
   2.681 +        # 1) Length checking
   2.682 +        k = self.modulusLen / 8
   2.683 +        if len(S) != k:
   2.684 +            warning("invalid signature (len(S) != k)")
   2.685 +            return False
   2.686 +
   2.687 +        # 2) RSA verification
   2.688 +        s = pkcs_os2ip(S)                           # 2.a)
   2.689 +        m = self._rsavp1(s)                         # 2.b)
   2.690 +        EM = pkcs_i2osp(m, k)                       # 2.c)
   2.691 +
   2.692 +        # 3) EMSA-PKCS1-v1_5 encoding
   2.693 +        EMPrime = pkcs_emsa_pkcs1_v1_5_encode(M, k, h)
   2.694 +        if EMPrime is None:
   2.695 +            warning("Key._rsassa_pkcs1_v1_5_verify(): unable to encode.")
   2.696 +            return False
   2.697 +
   2.698 +        # 4) Comparison
   2.699 +        return EM == EMPrime
   2.700 +
   2.701 +
   2.702 +    def verify(self, M, S, t=None, h=None, mgf=None, sLen=None):
   2.703 +        """
   2.704 +        Verify alleged signature 'S' is indeed the signature of message 'M' using
   2.705 +        't' signature scheme where 't' can be:
   2.706 +
   2.707 +        - None: the alleged signature 'S' is directly applied the RSAVP1 signature
   2.708 +                primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 Sect
   2.709 +                5.2.1. Simply put, the provided signature is applied a moular
   2.710 +                exponentiation using the public key. Then, a comparison of the
   2.711 +                result is done against 'M'. On match, True is returned.
   2.712 +                Additionnal method parameters are just ignored.
   2.713 +
   2.714 +        - 'pkcs': the alleged signature 'S' and message 'M' are applied
   2.715 +                RSASSA-PKCS1-v1_5-VERIFY signature verification scheme as
   2.716 +                described in Sect. 8.2.2 of RFC 3447. In that context,
   2.717 +                the hash function name is passed using 'h'. Possible values are
   2.718 +                "md2", "md4", "md5", "sha1", "tls", "sha224", "sha256", "sha384"
   2.719 +                and "sha512". If none is provided, sha1 is used. Other additionnal
   2.720 +                parameters are ignored.
   2.721 +
   2.722 +        - 'pss': the alleged signature 'S' and message 'M' are applied
   2.723 +                RSASSA-PSS-VERIFY signature scheme as described in Sect. 8.1.2.
   2.724 +                of RFC 3447. In that context,
   2.725 +
   2.726 +                o 'h' parameter provides the name of the hash method to use.
   2.727 +                   Possible values are "md2", "md4", "md5", "sha1", "tls", "sha224",
   2.728 +                   "sha256", "sha384" and "sha512". if none is provided, sha1
   2.729 +                   is used. 
   2.730 +
   2.731 +                o 'mgf' is the mask generation function. By default, mgf
   2.732 +                   is derived from the provided hash function using the
   2.733 +                   generic MGF1 (see pkcs_mgf1() for details).
   2.734 +
   2.735 +                o 'sLen' is the length in octet of the salt. You can overload the
   2.736 +                  default value (the octet length of the hash value for provided
   2.737 +                  algorithm) by providing another one with that parameter.
   2.738 +        """
   2.739 +        if t is None: # RSAVP1
   2.740 +            S = pkcs_os2ip(S)
   2.741 +            n = self.modulus
   2.742 +            if S > n-1:
   2.743 +                warning("Signature to be verified is too long for key modulus")
   2.744 +                return False
   2.745 +            m = self._rsavp1(S)
   2.746 +            if m is None:
   2.747 +                return False
   2.748 +            l = int(math.ceil(math.log(m, 2) / 8.)) # Hack
   2.749 +            m = pkcs_i2osp(m, l)
   2.750 +            return M == m
   2.751 +
   2.752 +        elif t == "pkcs": # RSASSA-PKCS1-v1_5-VERIFY
   2.753 +            if h is None:
   2.754 +                h = "sha1"
   2.755 +            return self._rsassa_pkcs1_v1_5_verify(M, S, h)
   2.756 +
   2.757 +        elif t == "pss": # RSASSA-PSS-VERIFY
   2.758 +            return self._rsassa_pss_verify(M, S, h, mgf, sLen)
   2.759 +
   2.760 +        else:
   2.761 +            warning("Key.verify(): Unknown signature type (%s) provided" % t)
   2.762 +            return None
   2.763 +    
   2.764 +class _DecryptAndSignMethods(OSSLHelper):
   2.765 +    ### Below are decryption related methods. Encryption ones are inherited
   2.766 +    ### from PubKey
   2.767 +
   2.768 +    def _rsadp(self, c):
   2.769 +        """
   2.770 +        Internal method providing raw RSA decryption, i.e. simple modular
   2.771 +        exponentiation of the given ciphertext representative 'c', a long
   2.772 +        between 0 and n-1.
   2.773 +
   2.774 +        This is the decryption primitive RSADP described in PKCS#1 v2.1,
   2.775 +        i.e. RFC 3447 Sect. 5.1.2.
   2.776 +
   2.777 +        Input:
   2.778 +           c: ciphertest representative, a long between 0 and n-1, where
   2.779 +              n is the key modulus.
   2.780 +
   2.781 +        Output:
   2.782 +           ciphertext representative, a long between 0 and n-1
   2.783 +
   2.784 +        Not intended to be used directly. Please, see encrypt() method.
   2.785 +        """
   2.786 +
   2.787 +        n = self.modulus
   2.788 +        if type(c) is int:
   2.789 +            c = long(c)        
   2.790 +        if type(c) is not long or c > n-1:
   2.791 +            warning("Key._rsaep() expects a long between 0 and n-1")
   2.792 +            return None
   2.793 +
   2.794 +        return self.key.decrypt(c)    
   2.795 +
   2.796 +
   2.797 +    def _rsaes_pkcs1_v1_5_decrypt(self, C):
   2.798 +        """
   2.799 +        Implements RSAES-PKCS1-V1_5-DECRYPT() function described in section
   2.800 +        7.2.2 of RFC 3447.
   2.801 +
   2.802 +        Input:
   2.803 +           C: ciphertext to be decrypted, an octet string of length k, where
   2.804 +              k is the length in octets of the RSA modulus n.
   2.805 +
   2.806 +        Output:
   2.807 +           an octet string of length k at most k - 11
   2.808 +
   2.809 +        on error, None is returned.
   2.810 +        """
   2.811 +        
   2.812 +        # 1) Length checking
   2.813 +        cLen = len(C)
   2.814 +        k = self.modulusLen / 8
   2.815 +        if cLen != k or k < 11:
   2.816 +            warning("Key._rsaes_pkcs1_v1_5_decrypt() decryption error "
   2.817 +                    "(cLen != k or k < 11)")
   2.818 +            return None
   2.819 +
   2.820 +        # 2) RSA decryption
   2.821 +        c = pkcs_os2ip(C)                           # 2.a)
   2.822 +        m = self._rsadp(c)                          # 2.b)
   2.823 +        EM = pkcs_i2osp(m, k)                       # 2.c)
   2.824 +
   2.825 +        # 3) EME-PKCS1-v1_5 decoding
   2.826 +
   2.827 +        # I am aware of the note at the end of 7.2.2 regarding error
   2.828 +        # conditions reporting but the one provided below are for _local_
   2.829 +        # debugging purposes. --arno
   2.830 +        
   2.831 +        if EM[0] != '\x00':
   2.832 +            warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error "
   2.833 +                    "(first byte is not 0x00)")
   2.834 +            return None
   2.835 +
   2.836 +        if EM[1] != '\x02':
   2.837 +            warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error "
   2.838 +                    "(second byte is not 0x02)")
   2.839 +            return None
   2.840 +
   2.841 +        tmp = EM[2:].split('\x00', 1)
   2.842 +        if len(tmp) != 2:
   2.843 +            warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error "
   2.844 +                    "(no 0x00 to separate PS from M)")
   2.845 +            return None
   2.846 +
   2.847 +        PS, M = tmp
   2.848 +        if len(PS) < 8:
   2.849 +            warning("Key._rsaes_pkcs1_v1_5_decrypt(): decryption error "
   2.850 +                    "(PS is less than 8 byte long)")
   2.851 +            return None
   2.852 +
   2.853 +        return M                                    # 4)
   2.854 +
   2.855 +
   2.856 +    def _rsaes_oaep_decrypt(self, C, h=None, mgf=None, L=None):
   2.857 +        """
   2.858 +        Internal method providing RSAES-OAEP-DECRYPT as defined in Sect.
   2.859 +        7.1.2 of RFC 3447. Not intended to be used directly. Please, see
   2.860 +        encrypt() method for type "OAEP".
   2.861 +
   2.862 +
   2.863 +        Input:
   2.864 +           C  : ciphertext to be decrypted, an octet string of length k, where
   2.865 +                k = 2*hLen + 2 (k denotes the length in octets of the RSA modulus
   2.866 +                and hLen the length in octets of the hash function output)
   2.867 +           h  : hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls',
   2.868 +                'sha256', 'sha384'). 'sha1' is used if none is provided.
   2.869 +           mgf: the mask generation function f : seed, maskLen -> mask
   2.870 +           L  : optional label whose association with the message is to be
   2.871 +                verified; the default value for L, if not provided is the empty
   2.872 +                string.
   2.873 +
   2.874 +        Output:
   2.875 +           message, an octet string of length k mLen, where mLen <= k - 2*hLen - 2
   2.876 +
   2.877 +        On error, None is returned.
   2.878 +        """
   2.879 +        # The steps below are the one described in Sect. 7.1.2 of RFC 3447.
   2.880 +
   2.881 +        # 1) Length Checking
   2.882 +                                                    # 1.a) is not done
   2.883 +        if h is None:
   2.884 +            h = "sha1"
   2.885 +        if not _hashFuncParams.has_key(h):
   2.886 +            warning("Key._rsaes_oaep_decrypt(): unknown hash function %s.", h)
   2.887 +            return None
   2.888 +        hLen = _hashFuncParams[h][0]
   2.889 +        hFun = _hashFuncParams[h][1]
   2.890 +        k = self.modulusLen / 8
   2.891 +        cLen = len(C)
   2.892 +        if cLen != k:                               # 1.b)
   2.893 +            warning("Key._rsaes_oaep_decrypt(): decryption error. "
   2.894 +                    "(cLen != k)")
   2.895 +            return None
   2.896 +        if k < 2*hLen + 2:
   2.897 +            warning("Key._rsaes_oaep_decrypt(): decryption error. "
   2.898 +                    "(k < 2*hLen + 2)")
   2.899 +            return None
   2.900 +
   2.901 +        # 2) RSA decryption
   2.902 +        c = pkcs_os2ip(C)                           # 2.a)
   2.903 +        m = self._rsadp(c)                          # 2.b)
   2.904 +        EM = pkcs_i2osp(m, k)                       # 2.c)
   2.905 +
   2.906 +        # 3) EME-OAEP decoding
   2.907 +        if L is None:                               # 3.a)
   2.908 +            L = ""
   2.909 +        lHash = hFun(L)
   2.910 +        Y = EM[:1]                                  # 3.b)
   2.911 +        if Y != '\x00':
   2.912 +            warning("Key._rsaes_oaep_decrypt(): decryption error. "
   2.913 +                    "(Y is not zero)")
   2.914 +            return None
   2.915 +        maskedSeed = EM[1:1+hLen]
   2.916 +        maskedDB = EM[1+hLen:]
   2.917 +        if mgf is None:
   2.918 +            mgf = lambda x,y: pkcs_mgf1(x, y, h)
   2.919 +        seedMask = mgf(maskedDB, hLen)              # 3.c)
   2.920 +        seed = strxor(maskedSeed, seedMask)         # 3.d)
   2.921 +        dbMask = mgf(seed, k - hLen - 1)            # 3.e)
   2.922 +        DB = strxor(maskedDB, dbMask)               # 3.f)
   2.923 +
   2.924 +        # I am aware of the note at the end of 7.1.2 regarding error
   2.925 +        # conditions reporting but the one provided below are for _local_
   2.926 +        # debugging purposes. --arno
   2.927 +
   2.928 +        lHashPrime = DB[:hLen]                      # 3.g)
   2.929 +        tmp = DB[hLen:].split('\x01', 1)
   2.930 +        if len(tmp) != 2:
   2.931 +            warning("Key._rsaes_oaep_decrypt(): decryption error. "
   2.932 +                    "(0x01 separator not found)")
   2.933 +            return None
   2.934 +        PS, M = tmp
   2.935 +        if PS != '\x00'*len(PS):
   2.936 +            warning("Key._rsaes_oaep_decrypt(): decryption error. "
   2.937 +                    "(invalid padding string)")
   2.938 +            return None
   2.939 +        if lHash != lHashPrime:
   2.940 +            warning("Key._rsaes_oaep_decrypt(): decryption error. "
   2.941 +                    "(invalid hash)")
   2.942 +            return None            
   2.943 +        return M                                    # 4)
   2.944 +
   2.945 +
   2.946 +    def decrypt(self, C, t=None, h=None, mgf=None, L=None):
   2.947 +        """
   2.948 +        Decrypt ciphertext 'C' using 't' decryption scheme where 't' can be:
   2.949 +
   2.950 +        - None: the ciphertext 'C' is directly applied the RSADP decryption
   2.951 +                primitive, as described in PKCS#1 v2.1, i.e. RFC 3447
   2.952 +                Sect 5.1.2. Simply, put the message undergo a modular
   2.953 +                exponentiation using the private key. Additionnal method
   2.954 +                parameters are just ignored.
   2.955 +
   2.956 +        - 'pkcs': the ciphertext 'C' is applied RSAES-PKCS1-V1_5-DECRYPT
   2.957 +                decryption scheme as described in section 7.2.2 of RFC 3447.
   2.958 +                In that context, other parameters ('h', 'mgf', 'l') are not
   2.959 +                used.
   2.960 +
   2.961 +        - 'oaep': the ciphertext 'C' is applied the RSAES-OAEP-DECRYPT decryption
   2.962 +                scheme, as described in PKCS#1 v2.1, i.e. RFC 3447 Sect
   2.963 +                7.1.2. In that context,
   2.964 +
   2.965 +                o 'h' parameter provides the name of the hash method to use.
   2.966 +                  Possible values are "md2", "md4", "md5", "sha1", "tls",
   2.967 +                  "sha224", "sha256", "sha384" and "sha512". if none is provided,
   2.968 +                  sha1 is used by default.
   2.969 +
   2.970 +                o 'mgf' is the mask generation function. By default, mgf
   2.971 +                  is derived from the provided hash function using the
   2.972 +                  generic MGF1 (see pkcs_mgf1() for details).
   2.973 +
   2.974 +                o 'L' is the optional label to be associated with the
   2.975 +                  message. If not provided, the default value is used, i.e
   2.976 +                  the empty string. No check is done on the input limitation
   2.977 +                  of the hash function regarding the size of 'L' (for
   2.978 +                  instance, 2^61 - 1 for SHA-1). You have been warned.        
   2.979 +        """
   2.980 +        if t is None:
   2.981 +            C = pkcs_os2ip(C)
   2.982 +            c = self._rsadp(C)
   2.983 +            l = int(math.ceil(math.log(c, 2) / 8.)) # Hack
   2.984 +            return pkcs_i2osp(c, l)
   2.985 +
   2.986 +        elif t == "pkcs":
   2.987 +            return self._rsaes_pkcs1_v1_5_decrypt(C)
   2.988 +
   2.989 +        elif t == "oaep":
   2.990 +            return self._rsaes_oaep_decrypt(C, h, mgf, L)
   2.991 +
   2.992 +        else:
   2.993 +            warning("Key.decrypt(): Unknown decryption type (%s) provided" % t)
   2.994 +            return None
   2.995 +
   2.996 +    ### Below are signature related methods. Verification ones are inherited from
   2.997 +    ### PubKey
   2.998 +
   2.999 +    def _rsasp1(self, m):
  2.1000 +        """
  2.1001 +        Internal method providing raw RSA signature, i.e. simple modular
  2.1002 +        exponentiation of the given message representative 'm', an integer
  2.1003 +        between 0 and n-1.
  2.1004 +
  2.1005 +        This is the signature primitive RSASP1 described in PKCS#1 v2.1,
  2.1006 +        i.e. RFC 3447 Sect. 5.2.1.
  2.1007 +
  2.1008 +        Input:
  2.1009 +           m: message representative, an integer between 0 and n-1, where
  2.1010 +              n is the key modulus.
  2.1011 +
  2.1012 +        Output:
  2.1013 +           signature representative, an integer between 0 and n-1
  2.1014 +
  2.1015 +        Not intended to be used directly. Please, see sign() method.
  2.1016 +        """
  2.1017 +        return self._rsadp(m)
  2.1018 +
  2.1019 +
  2.1020 +    def _rsassa_pss_sign(self, M, h=None, mgf=None, sLen=None):
  2.1021 +        """
  2.1022 +        Implements RSASSA-PSS-SIGN() function described in Sect. 8.1.1 of
  2.1023 +        RFC 3447.
  2.1024 +
  2.1025 +        Input:
  2.1026 +           M: message to be signed, an octet string
  2.1027 +
  2.1028 +        Output:
  2.1029 +           signature, an octet string of length k, where k is the length in
  2.1030 +           octets of the RSA modulus n.
  2.1031 +
  2.1032 +        On error, None is returned.
  2.1033 +        """
  2.1034 +
  2.1035 +        # Set default parameters if not provided
  2.1036 +        if h is None: # By default, sha1
  2.1037 +            h = "sha1"
  2.1038 +        if not _hashFuncParams.has_key(h):
  2.1039 +            warning("Key._rsassa_pss_sign(): unknown hash function "
  2.1040 +                    "provided (%s)" % h)
  2.1041 +            return None
  2.1042 +        if mgf is None: # use mgf1 with underlying hash function
  2.1043 +            mgf = lambda x,y: pkcs_mgf1(x, y, h)
  2.1044 +        if sLen is None: # use Hash output length (A.2.3 of RFC 3447)
  2.1045 +            hLen = _hashFuncParams[h][0]
  2.1046 +            sLen = hLen
  2.1047 +
  2.1048 +        # 1) EMSA-PSS encoding
  2.1049 +        modBits = self.modulusLen
  2.1050 +        k = modBits / 8
  2.1051 +        EM = pkcs_emsa_pss_encode(M, modBits - 1, h, mgf, sLen)
  2.1052 +        if EM is None:
  2.1053 +            warning("Key._rsassa_pss_sign(): unable to encode")
  2.1054 +            return None
  2.1055 +
  2.1056 +        # 2) RSA signature
  2.1057 +        m = pkcs_os2ip(EM)                          # 2.a)
  2.1058 +        s = self._rsasp1(m)                         # 2.b)
  2.1059 +        S = pkcs_i2osp(s, k)                        # 2.c)
  2.1060 +
  2.1061 +        return S                                    # 3)
  2.1062 +
  2.1063 +
  2.1064 +    def _rsassa_pkcs1_v1_5_sign(self, M, h):
  2.1065 +        """
  2.1066 +        Implements RSASSA-PKCS1-v1_5-SIGN() function as described in
  2.1067 +        Sect. 8.2.1 of RFC 3447.
  2.1068 +
  2.1069 +        Input:
  2.1070 +           M: message to be signed, an octet string
  2.1071 +           h: hash function name (in 'md2', 'md4', 'md5', 'sha1', 'tls'
  2.1072 +                'sha256', 'sha384').
  2.1073 +           
  2.1074 +        Output:
  2.1075 +           the signature, an octet string.
  2.1076 +        """
  2.1077 +        
  2.1078 +        # 1) EMSA-PKCS1-v1_5 encoding
  2.1079 +        k = self.modulusLen / 8
  2.1080 +        EM = pkcs_emsa_pkcs1_v1_5_encode(M, k, h)
  2.1081 +        if EM is None:
  2.1082 +            warning("Key._rsassa_pkcs1_v1_5_sign(): unable to encode")
  2.1083 +            return None
  2.1084 +
  2.1085 +        # 2) RSA signature
  2.1086 +        m = pkcs_os2ip(EM)                          # 2.a)
  2.1087 +        s = self._rsasp1(m)                         # 2.b)
  2.1088 +        S = pkcs_i2osp(s, k)                        # 2.c)
  2.1089 +
  2.1090 +        return S                                    # 3)
  2.1091 +
  2.1092 +
  2.1093 +    def sign(self, M, t=None, h=None, mgf=None, sLen=None):
  2.1094 +        """
  2.1095 +        Sign message 'M' using 't' signature scheme where 't' can be:
  2.1096 +
  2.1097 +        - None: the message 'M' is directly applied the RSASP1 signature
  2.1098 +                primitive, as described in PKCS#1 v2.1, i.e. RFC 3447 Sect
  2.1099 +                5.2.1. Simply put, the message undergo a modular exponentiation
  2.1100 +                using the private key. Additionnal method parameters are just
  2.1101 +                ignored.
  2.1102 +
  2.1103 +        - 'pkcs': the message 'M' is applied RSASSA-PKCS1-v1_5-SIGN signature
  2.1104 +                scheme as described in Sect. 8.2.1 of RFC 3447. In that context,
  2.1105 +                the hash function name is passed using 'h'. Possible values are
  2.1106 +                "md2", "md4", "md5", "sha1", "tls", "sha224", "sha256", "sha384"
  2.1107 +                and "sha512". If none is provided, sha1 is used. Other additionnal 
  2.1108 +                parameters are ignored.
  2.1109 +
  2.1110 +        - 'pss' : the message 'M' is applied RSASSA-PSS-SIGN signature scheme as
  2.1111 +                described in Sect. 8.1.1. of RFC 3447. In that context,
  2.1112 +
  2.1113 +                o 'h' parameter provides the name of the hash method to use.
  2.1114 +                   Possible values are "md2", "md4", "md5", "sha1", "tls", "sha224",
  2.1115 +                   "sha256", "sha384" and "sha512". if none is provided, sha1
  2.1116 +                   is used. 
  2.1117 +
  2.1118 +                o 'mgf' is the mask generation function. By default, mgf
  2.1119 +                   is derived from the provided hash function using the
  2.1120 +                   generic MGF1 (see pkcs_mgf1() for details).
  2.1121 +
  2.1122 +                o 'sLen' is the length in octet of the salt. You can overload the
  2.1123 +                  default value (the octet length of the hash value for provided
  2.1124 +                  algorithm) by providing another one with that parameter.
  2.1125 +        """
  2.1126 +
  2.1127 +        if t is None: # RSASP1
  2.1128 +            M = pkcs_os2ip(M)
  2.1129 +            n = self.modulus
  2.1130 +            if M > n-1:
  2.1131 +                warning("Message to be signed is too long for key modulus")
  2.1132 +                return None
  2.1133 +            s = self._rsasp1(M)
  2.1134 +            if s is None:
  2.1135 +                return None
  2.1136 +            return pkcs_i2osp(s, self.modulusLen/8)
  2.1137 +        
  2.1138 +        elif t == "pkcs": # RSASSA-PKCS1-v1_5-SIGN
  2.1139 +            if h is None:
  2.1140 +                h = "sha1"
  2.1141 +            return self._rsassa_pkcs1_v1_5_sign(M, h)
  2.1142 +        
  2.1143 +        elif t == "pss": # RSASSA-PSS-SIGN
  2.1144 +            return self._rsassa_pss_sign(M, h, mgf, sLen)
  2.1145 +
  2.1146 +        else:
  2.1147 +            warning("Key.sign(): Unknown signature type (%s) provided" % t)
  2.1148 +            return None
  2.1149 +
  2.1150 +
  2.1151 +
  2.1152 +
  2.1153 +class PubKey(OSSLHelper, _EncryptAndVerify):
  2.1154 +    # Below are the fields we recognize in the -text output of openssl
  2.1155 +    # and from which we extract information. We expect them in that
  2.1156 +    # order. Number of spaces does matter.
  2.1157 +    possible_fields = [ "Modulus (",
  2.1158 +                        "Exponent:" ]
  2.1159 +    possible_fields_count = len(possible_fields)
  2.1160 +    
  2.1161 +    def __init__(self, keypath):
  2.1162 +        error_msg = "Unable to import key."
  2.1163 +
  2.1164 +        # XXX Temporary hack to use PubKey inside Cert
  2.1165 +        if type(keypath) is tuple:
  2.1166 +            e, m, mLen = keypath
  2.1167 +            self.modulus = m
  2.1168 +            self.modulusLen = mLen
  2.1169 +            self.pubExp = e
  2.1170 +            return
  2.1171 +
  2.1172 +        fields_dict = {}
  2.1173 +        for k in self.possible_fields:
  2.1174 +            fields_dict[k] = None
  2.1175 +
  2.1176 +        self.keypath = None
  2.1177 +        rawkey = None
  2.1178 +
  2.1179 +        if (not '\x00' in keypath) and os.path.isfile(keypath): # file
  2.1180 +            self.keypath = keypath
  2.1181 +            key_size = os.path.getsize(keypath)
  2.1182 +            if key_size > MAX_KEY_SIZE:
  2.1183 +                raise Exception(error_msg)
  2.1184 +            try:
  2.1185 +                f = open(keypath)
  2.1186 +                rawkey = f.read()
  2.1187 +                f.close()
  2.1188 +            except:
  2.1189 +    		raise Exception(error_msg)     
  2.1190 +        else:
  2.1191 +            rawkey = keypath
  2.1192 +
  2.1193 +	if rawkey is None:
  2.1194 +	    raise Exception(error_msg)
  2.1195 +
  2.1196 +	self.rawkey = rawkey
  2.1197 +
  2.1198 +        # Let's try to get file format : PEM or DER.
  2.1199 +        fmtstr = 'openssl rsa -text -pubin -inform %s -noout '
  2.1200 +        convertstr = 'openssl rsa -pubin -inform %s -outform %s 2>/dev/null'
  2.1201 +        key_header = "-----BEGIN PUBLIC KEY-----"
  2.1202 +        key_footer = "-----END PUBLIC KEY-----"
  2.1203 +        l = rawkey.split(key_header, 1)
  2.1204 +        if len(l) == 2: # looks like PEM
  2.1205 +            tmp = l[1]
  2.1206 +            l = tmp.split(key_footer, 1)
  2.1207 +            if len(l) == 2:
  2.1208 +                tmp = l[0]
  2.1209 +                rawkey = "%s%s%s\n" % (key_header, tmp, key_footer)
  2.1210 +            else:
  2.1211 +                raise Exception(error_msg)
  2.1212 +            r,w,e = popen2.popen3(fmtstr % "PEM")
  2.1213 +            w.write(rawkey)
  2.1214 +            w.close()
  2.1215 +            textkey = r.read()
  2.1216 +            r.close()
  2.1217 +            res = e.read()
  2.1218 +            e.close()
  2.1219 +            if res == '':
  2.1220 +                self.format = "PEM"
  2.1221 +                self.pemkey = rawkey
  2.1222 +                self.textkey = textkey
  2.1223 +                cmd = convertstr % ("PEM", "DER")
  2.1224 +                self.derkey = self._apply_ossl_cmd(cmd, rawkey)
  2.1225 +            else:
  2.1226 +                raise Exception(error_msg)
  2.1227 +        else: # not PEM, try DER
  2.1228 +            r,w,e = popen2.popen3(fmtstr % "DER")            
  2.1229 +            w.write(rawkey)
  2.1230 +            w.close()
  2.1231 +            textkey = r.read()
  2.1232 +            r.close()
  2.1233 +            res = e.read()
  2.1234 +	    if res == '':
  2.1235 +		self.format = "DER"
  2.1236 +                self.derkey = rawkey
  2.1237 +                self.textkey = textkey
  2.1238 +                cmd = convertstr % ("DER", "PEM")
  2.1239 +                self.pemkey = self._apply_ossl_cmd(cmd, rawkey)
  2.1240 +                cmd = convertstr % ("DER", "DER")
  2.1241 +                self.derkey = self._apply_ossl_cmd(cmd, rawkey)                
  2.1242 +	    else:
  2.1243 +                try: # Perhaps it is a cert
  2.1244 +                    c = Cert(keypath)
  2.1245 +                except:
  2.1246 +                    raise Exception(error_msg)
  2.1247 +                # TODO:
  2.1248 +                # Reconstruct a key (der and pem) and provide:
  2.1249 +                # self.format
  2.1250 +                # self.derkey
  2.1251 +                # self.pemkey
  2.1252 +                # self.textkey
  2.1253 +                # self.keypath
  2.1254 +
  2.1255 +        self.osslcmdbase = 'openssl rsa -pubin -inform %s ' % self.format
  2.1256 +
  2.1257 +        self.keypath = keypath
  2.1258 +
  2.1259 +        # Parse the -text output of openssl to make things available
  2.1260 +        l = self.textkey.split('\n', 1)
  2.1261 +        if len(l) != 2:
  2.1262 +            raise Exception(error_msg)
  2.1263 +        cur, tmp = l
  2.1264 +        i = 0
  2.1265 +        k = self.possible_fields[i] # Modulus (
  2.1266 +        cur = cur[len(k):] + '\n'
  2.1267 +        while k:
  2.1268 +            l = tmp.split('\n', 1)
  2.1269 +            if len(l) != 2: # Over
  2.1270 +                fields_dict[k] = cur
  2.1271 +                break
  2.1272 +            l, tmp = l
  2.1273 +
  2.1274 +            newkey = 0
  2.1275 +            # skip fields we have already seen, this is the purpose of 'i'
  2.1276 +            for j in range(i, self.possible_fields_count):
  2.1277 +                f = self.possible_fields[j]
  2.1278 +                if l.startswith(f):
  2.1279 +                    fields_dict[k] = cur
  2.1280 +                    cur = l[len(f):] + '\n'
  2.1281 +                    k = f
  2.1282 +                    newkey = 1
  2.1283 +                    i = j+1
  2.1284 +                    break
  2.1285 +            if newkey == 1:
  2.1286 +                continue
  2.1287 +            cur += l + '\n'
  2.1288 +
  2.1289 +        # modulus and modulus length
  2.1290 +        v = fields_dict["Modulus ("]
  2.1291 +        self.modulusLen = None
  2.1292 +        if v:
  2.1293 +            v, rem = v.split(' bit):', 1)
  2.1294 +            self.modulusLen = int(v)
  2.1295 +            rem = rem.replace('\n','').replace(' ','').replace(':','')
  2.1296 +            self.modulus = long(rem, 16)
  2.1297 +        if self.modulus is None:
  2.1298 +            raise Exception(error_msg)
  2.1299 +        
  2.1300 +        # public exponent
  2.1301 +        v = fields_dict["Exponent:"]
  2.1302 +        self.pubExp = None
  2.1303 +        if v:
  2.1304 +            self.pubExp = long(v.split('(', 1)[0])
  2.1305 +        if self.pubExp is None:
  2.1306 +            raise Exception(error_msg)
  2.1307 +
  2.1308 +        self.key = RSA.construct((self.modulus, self.pubExp, ))
  2.1309 +
  2.1310 +    def __str__(self):
  2.1311 +        return self.derkey
  2.1312 +
  2.1313 +
  2.1314 +class Key(OSSLHelper, _DecryptAndSignMethods, _EncryptAndVerify):
  2.1315 +    # Below are the fields we recognize in the -text output of openssl
  2.1316 +    # and from which we extract information. We expect them in that
  2.1317 +    # order. Number of spaces does matter.
  2.1318 +    possible_fields = [ "Private-Key: (",
  2.1319 +                        "modulus:",
  2.1320 +                        "publicExponent:",
  2.1321 +                        "privateExponent:",
  2.1322 +                        "prime1:",
  2.1323 +                        "prime2:",
  2.1324 +                        "exponent1:",
  2.1325 +                        "exponent2:",
  2.1326 +                        "coefficient:" ]
  2.1327 +    possible_fields_count = len(possible_fields)
  2.1328 +    
  2.1329 +    def __init__(self, keypath):
  2.1330 +        error_msg = "Unable to import key."
  2.1331 +
  2.1332 +        fields_dict = {}
  2.1333 +        for k in self.possible_fields:
  2.1334 +            fields_dict[k] = None
  2.1335 +
  2.1336 +        self.keypath = None
  2.1337 +        rawkey = None
  2.1338 +
  2.1339 +        if (not '\x00' in keypath) and os.path.isfile(keypath):
  2.1340 +            self.keypath = keypath
  2.1341 +            key_size = os.path.getsize(keypath)
  2.1342 +            if key_size > MAX_KEY_SIZE:
  2.1343 +                raise Exception(error_msg)
  2.1344 +            try:
  2.1345 +                f = open(keypath)
  2.1346 +                rawkey = f.read()
  2.1347 +                f.close()
  2.1348 +            except:
  2.1349 +    		raise Exception(error_msg)     
  2.1350 +        else:
  2.1351 +            rawkey = keypath
  2.1352 +
  2.1353 +	if rawkey is None:
  2.1354 +	    raise Exception(error_msg)
  2.1355 +
  2.1356 +	self.rawkey = rawkey
  2.1357 +
  2.1358 +        # Let's try to get file format : PEM or DER.
  2.1359 +        fmtstr = 'openssl rsa -text -inform %s -noout '
  2.1360 +        convertstr = 'openssl rsa -inform %s -outform %s 2>/dev/null'
  2.1361 +        key_header = "-----BEGIN RSA PRIVATE KEY-----"
  2.1362 +        key_footer = "-----END RSA PRIVATE KEY-----"
  2.1363 +        l = rawkey.split(key_header, 1)
  2.1364 +        if len(l) == 2: # looks like PEM
  2.1365 +            tmp = l[1]
  2.1366 +            l = tmp.split(key_footer, 1)
  2.1367 +            if len(l) == 2:
  2.1368 +                tmp = l[0]
  2.1369 +                rawkey = "%s%s%s\n" % (key_header, tmp, key_footer)
  2.1370 +            else:
  2.1371 +                raise Exception(error_msg)
  2.1372 +            r,w,e = popen2.popen3(fmtstr % "PEM")
  2.1373 +            w.write(rawkey)
  2.1374 +            w.close()
  2.1375 +            textkey = r.read()
  2.1376 +            r.close()
  2.1377 +            res = e.read()
  2.1378 +            e.close()
  2.1379 +            if res == '':
  2.1380 +                self.format = "PEM"
  2.1381 +                self.pemkey = rawkey
  2.1382 +                self.textkey = textkey
  2.1383 +                cmd = convertstr % ("PEM", "DER")
  2.1384 +                self.derkey = self._apply_ossl_cmd(cmd, rawkey)
  2.1385 +            else:
  2.1386 +                raise Exception(error_msg)
  2.1387 +        else: # not PEM, try DER
  2.1388 +            r,w,e = popen2.popen3(fmtstr % "DER")            
  2.1389 +            w.write(rawkey)
  2.1390 +            w.close()
  2.1391 +            textkey = r.read()
  2.1392 +            r.close()
  2.1393 +            res = e.read()
  2.1394 +	    if res == '':
  2.1395 +		self.format = "DER"
  2.1396 +                self.derkey = rawkey
  2.1397 +                self.textkey = textkey
  2.1398 +                cmd = convertstr % ("DER", "PEM")
  2.1399 +                self.pemkey = self._apply_ossl_cmd(cmd, rawkey)
  2.1400 +                cmd = convertstr % ("DER", "DER")
  2.1401 +                self.derkey = self._apply_ossl_cmd(cmd, rawkey)
  2.1402 +	    else:
  2.1403 +		raise Exception(error_msg)     
  2.1404 +
  2.1405 +        self.osslcmdbase = 'openssl rsa -inform %s ' % self.format
  2.1406 +
  2.1407 +        r,w,e = popen2.popen3('openssl asn1parse -inform DER ')
  2.1408 +        w.write(self.derkey)
  2.1409 +        w.close()
  2.1410 +        self.asn1parsekey = r.read()
  2.1411 +        r.close()
  2.1412 +        res = e.read()
  2.1413 +        e.close()
  2.1414 +        if res != '':
  2.1415 +            raise Exception(error_msg)
  2.1416 +
  2.1417 +        self.keypath = keypath
  2.1418 +
  2.1419 +        # Parse the -text output of openssl to make things available
  2.1420 +        l = self.textkey.split('\n', 1)
  2.1421 +        if len(l) != 2:
  2.1422 +            raise Exception(error_msg)
  2.1423 +        cur, tmp = l
  2.1424 +        i = 0
  2.1425 +        k = self.possible_fields[i] # Private-Key: (
  2.1426 +        cur = cur[len(k):] + '\n'
  2.1427 +        while k:
  2.1428 +            l = tmp.split('\n', 1)
  2.1429 +            if len(l) != 2: # Over
  2.1430 +                fields_dict[k] = cur
  2.1431 +                break
  2.1432 +            l, tmp = l
  2.1433 +
  2.1434 +            newkey = 0
  2.1435 +            # skip fields we have already seen, this is the purpose of 'i'
  2.1436 +            for j in range(i, self.possible_fields_count):
  2.1437 +                f = self.possible_fields[j]
  2.1438 +                if l.startswith(f):
  2.1439 +                    fields_dict[k] = cur
  2.1440 +                    cur = l[len(f):] + '\n'
  2.1441 +                    k = f
  2.1442 +                    newkey = 1
  2.1443 +                    i = j+1
  2.1444 +                    break
  2.1445 +            if newkey == 1:
  2.1446 +                continue
  2.1447 +            cur += l + '\n'
  2.1448 +
  2.1449 +        # modulus length
  2.1450 +        v = fields_dict["Private-Key: ("]
  2.1451 +        self.modulusLen = None
  2.1452 +        if v:
  2.1453 +            self.modulusLen = int(v.split(' bit', 1)[0])
  2.1454 +        if self.modulusLen is None:
  2.1455 +            raise Exception(error_msg)
  2.1456 +        
  2.1457 +        # public exponent
  2.1458 +        v = fields_dict["publicExponent:"]
  2.1459 +        self.pubExp = None
  2.1460 +        if v:
  2.1461 +            self.pubExp = long(v.split('(', 1)[0])
  2.1462 +        if self.pubExp is None:
  2.1463 +            raise Exception(error_msg)
  2.1464 +
  2.1465 +        tmp = {}
  2.1466 +        for k in ["modulus:", "privateExponent:", "prime1:", "prime2:",
  2.1467 +                  "exponent1:", "exponent2:", "coefficient:"]:
  2.1468 +            v = fields_dict[k]
  2.1469 +            if v:
  2.1470 +                s = v.replace('\n', '').replace(' ', '').replace(':', '')
  2.1471 +                tmp[k] = long(s, 16)
  2.1472 +            else:
  2.1473 +                raise Exception(error_msg)
  2.1474 +
  2.1475 +        self.modulus     = tmp["modulus:"]
  2.1476 +        self.privExp     = tmp["privateExponent:"]
  2.1477 +        self.prime1      = tmp["prime1:"]
  2.1478 +        self.prime2      = tmp["prime2:"] 
  2.1479 +        self.exponent1   = tmp["exponent1:"]
  2.1480 +        self.exponent2   = tmp["exponent2:"]
  2.1481 +        self.coefficient = tmp["coefficient:"]
  2.1482 +
  2.1483 +        self.key = RSA.construct((self.modulus, self.pubExp, self.privExp))
  2.1484 +
  2.1485 +    def __str__(self):
  2.1486 +        return self.derkey
  2.1487 +
  2.1488 +
  2.1489 +# We inherit from PubKey to get access to all encryption and verification
  2.1490 +# methods. To have that working, we simply need Cert to provide 
  2.1491 +# modulusLen and key attribute.
  2.1492 +# XXX Yes, it is a hack.
  2.1493 +class Cert(OSSLHelper, _EncryptAndVerify):
  2.1494 +    # Below are the fields we recognize in the -text output of openssl
  2.1495 +    # and from which we extract information. We expect them in that
  2.1496 +    # order. Number of spaces does matter.
  2.1497 +    possible_fields = [ "        Version:",
  2.1498 +                        "        Serial Number:",
  2.1499 +                        "        Signature Algorithm:",
  2.1500 +                        "        Issuer:",
  2.1501 +                        "            Not Before:",
  2.1502 +                        "            Not After :",
  2.1503 +                        "        Subject:",
  2.1504 +                        "            Public Key Algorithm:",
  2.1505 +                        "                Modulus (",
  2.1506 +                        "                Exponent:",
  2.1507 +                        "            X509v3 Subject Key Identifier:",
  2.1508 +                        "            X509v3 Authority Key Identifier:",
  2.1509 +                        "                keyid:",
  2.1510 +                        "                DirName:",
  2.1511 +                        "                serial:",
  2.1512 +                        "            X509v3 Basic Constraints:",
  2.1513 +                        "            X509v3 Key Usage:",
  2.1514 +                        "            X509v3 Extended Key Usage:",
  2.1515 +                        "            X509v3 CRL Distribution Points:",
  2.1516 +                        "            Authority Information Access:",
  2.1517 +                        "    Signature Algorithm:" ]
  2.1518 +    possible_fields_count = len(possible_fields)
  2.1519 +    
  2.1520 +    def __init__(self, certpath):
  2.1521 +        error_msg = "Unable to import certificate."
  2.1522 +
  2.1523 +        fields_dict = {}
  2.1524 +        for k in self.possible_fields:
  2.1525 +            fields_dict[k] = None
  2.1526 +
  2.1527 +        self.certpath = None
  2.1528 +        rawcert = None
  2.1529 +
  2.1530 +        if (not '\x00' in certpath) and os.path.isfile(certpath): # file
  2.1531 +            self.certpath = certpath
  2.1532 +            cert_size = os.path.getsize(certpath)
  2.1533 +            if cert_size > MAX_CERT_SIZE:
  2.1534 +                raise Exception(error_msg)
  2.1535 +            try:
  2.1536 +                f = open(certpath)
  2.1537 +                rawcert = f.read()
  2.1538 +                f.close()
  2.1539 +            except:
  2.1540 +    		raise Exception(error_msg)     
  2.1541 +        else:
  2.1542 +            rawcert = certpath
  2.1543 +            
  2.1544 +	if rawcert is None:
  2.1545 +	    raise Exception(error_msg)
  2.1546 +
  2.1547 +	self.rawcert = rawcert
  2.1548 +
  2.1549 +        # Let's try to get file format : PEM or DER.
  2.1550 +        fmtstr = 'openssl x509 -text -inform %s -noout '
  2.1551 +        convertstr = 'openssl x509 -inform %s -outform %s '
  2.1552 +        cert_header = "-----BEGIN CERTIFICATE-----"
  2.1553 +        cert_footer = "-----END CERTIFICATE-----"
  2.1554 +        l = rawcert.split(cert_header, 1)
  2.1555 +        if len(l) == 2: # looks like PEM
  2.1556 +            tmp = l[1]
  2.1557 +            l = tmp.split(cert_footer, 1)
  2.1558 +            if len(l) == 2:
  2.1559 +                tmp = l[0]
  2.1560 +                rawcert = "%s%s%s\n" % (cert_header, tmp, cert_footer)
  2.1561 +            else:
  2.1562 +                raise Exception(error_msg)
  2.1563 +            r,w,e = popen2.popen3(fmtstr % "PEM")
  2.1564 +            w.write(rawcert)
  2.1565 +            w.close()
  2.1566 +            textcert = r.read()
  2.1567 +            r.close()
  2.1568 +            res = e.read()
  2.1569 +            e.close()
  2.1570 +            if res == '':
  2.1571 +                self.format = "PEM"
  2.1572 +                self.pemcert = rawcert
  2.1573 +                self.textcert = textcert
  2.1574 +                cmd = convertstr % ("PEM", "DER")
  2.1575 +                self.dercert = self._apply_ossl_cmd(cmd, rawcert)
  2.1576 +            else:
  2.1577 +                raise Exception(error_msg)
  2.1578 +        else: # not PEM, try DER
  2.1579 +            r,w,e = popen2.popen3(fmtstr % "DER")            
  2.1580 +            w.write(rawcert)
  2.1581 +            w.close()
  2.1582 +            textcert = r.read()
  2.1583 +            r.close()
  2.1584 +            res = e.read()
  2.1585 +	    if res == '':
  2.1586 +		self.format = "DER"
  2.1587 +                self.dercert = rawcert
  2.1588 +                self.textcert = textcert
  2.1589 +                cmd = convertstr % ("DER", "PEM")
  2.1590 +                self.pemcert = self._apply_ossl_cmd(cmd, rawcert)
  2.1591 +                cmd = convertstr % ("DER", "DER")                
  2.1592 +                self.dercert = self._apply_ossl_cmd(cmd, rawcert)
  2.1593 +	    else:
  2.1594 +		raise Exception(error_msg)
  2.1595 +
  2.1596 +        self.osslcmdbase = 'openssl x509 -inform %s ' % self.format
  2.1597 +                                                  
  2.1598 +        r,w,e = popen2.popen3('openssl asn1parse -inform DER ')
  2.1599 +        w.write(self.dercert)
  2.1600 +        w.close()
  2.1601 +        self.asn1parsecert = r.read()
  2.1602 +        r.close()
  2.1603 +        res = e.read()
  2.1604 +        e.close()
  2.1605 +        if res != '':
  2.1606 +            raise Exception(error_msg)
  2.1607 +        
  2.1608 +        # Grab _raw_ X509v3 Authority Key Identifier, if any.
  2.1609 +        tmp = self.asn1parsecert.split(":X509v3 Authority Key Identifier", 1)
  2.1610 +        self.authorityKeyID = None
  2.1611 +        if len(tmp) == 2:
  2.1612 +            tmp = tmp[1]
  2.1613 +            tmp = tmp.split("[HEX DUMP]:", 1)[1]
  2.1614 +            self.authorityKeyID=tmp.split('\n',1)[0]
  2.1615 +
  2.1616 +        # Grab _raw_ X509v3 Subject Key Identifier, if any.
  2.1617 +        tmp = self.asn1parsecert.split(":X509v3 Subject Key Identifier", 1)
  2.1618 +        self.subjectKeyID = None
  2.1619 +        if len(tmp) == 2:
  2.1620 +            tmp = tmp[1]
  2.1621 +            tmp = tmp.split("[HEX DUMP]:", 1)[1]
  2.1622 +            self.subjectKeyID=tmp.split('\n',1)[0]            
  2.1623 +
  2.1624 +        # Get tbsCertificate using the worst hack. output of asn1parse
  2.1625 +        # looks like that:
  2.1626 +        #
  2.1627 +        # 0:d=0  hl=4 l=1298 cons: SEQUENCE          
  2.1628 +        # 4:d=1  hl=4 l=1018 cons: SEQUENCE          
  2.1629 +        # ...
  2.1630 +        #
  2.1631 +        l1,l2 = self.asn1parsecert.split('\n', 2)[:2]
  2.1632 +        hl1 = int(l1.split("hl=",1)[1].split("l=",1)[0])
  2.1633 +        rem = l2.split("hl=",1)[1]
  2.1634 +        hl2, rem = rem.split("l=",1)
  2.1635 +        hl2 = int(hl2)
  2.1636 +        l = int(rem.split("cons",1)[0])
  2.1637 +        self.tbsCertificate = self.dercert[hl1:hl1+hl2+l]
  2.1638 +
  2.1639 +        # Parse the -text output of openssl to make things available
  2.1640 +        tmp = self.textcert.split('\n', 2)[2]
  2.1641 +        l = tmp.split('\n', 1)
  2.1642 +        if len(l) != 2:
  2.1643 +            raise Exception(error_msg)
  2.1644 +        cur, tmp = l
  2.1645 +        i = 0
  2.1646 +        k = self.possible_fields[i] # Version:
  2.1647 +        cur = cur[len(k):] + '\n'
  2.1648 +        while k:
  2.1649 +            l = tmp.split('\n', 1)
  2.1650 +            if len(l) != 2: # Over
  2.1651 +                fields_dict[k] = cur
  2.1652 +                break
  2.1653 +            l, tmp = l
  2.1654 +
  2.1655 +            newkey = 0
  2.1656 +            # skip fields we have already seen, this is the purpose of 'i'
  2.1657 +            for j in range(i, self.possible_fields_count):
  2.1658 +                f = self.possible_fields[j]
  2.1659 +                if l.startswith(f):
  2.1660 +                    fields_dict[k] = cur
  2.1661 +                    cur = l[len(f):] + '\n'
  2.1662 +                    k = f
  2.1663 +                    newkey = 1
  2.1664 +                    i = j+1
  2.1665 +                    break
  2.1666 +            if newkey == 1:
  2.1667 +                continue
  2.1668 +            cur += l + '\n'
  2.1669 +
  2.1670 +        # version
  2.1671 +        v = fields_dict["        Version:"]
  2.1672 +        self.version = None
  2.1673 +        if v:
  2.1674 +            self.version = int(v[1:2])
  2.1675 +        if self.version is None:
  2.1676 +            raise Exception(error_msg)
  2.1677 +
  2.1678 +        # serial number
  2.1679 +        v = fields_dict["        Serial Number:"]
  2.1680 +        self.serial = None
  2.1681 +        if v:
  2.1682 +            v = v.replace('\n', '').strip()
  2.1683 +            if "0x" in v:
  2.1684 +                v = v.split("0x", 1)[1].split(')', 1)[0]
  2.1685 +            v = v.replace(':', '').upper()
  2.1686 +            if len(v) % 2:
  2.1687 +                v = '0' + v
  2.1688 +            self.serial = v
  2.1689 +        if self.serial is None:
  2.1690 +            raise Exception(error_msg)
  2.1691 +
  2.1692 +        # Signature Algorithm        
  2.1693 +        v = fields_dict["        Signature Algorithm:"]
  2.1694 +        self.sigAlg = None
  2.1695 +        if v:
  2.1696 +            v = v.split('\n',1)[0]
  2.1697 +            v = v.strip()
  2.1698 +            self.sigAlg = v
  2.1699 +        if self.sigAlg is None:
  2.1700 +            raise Exception(error_msg)
  2.1701 +        
  2.1702 +        # issuer
  2.1703 +        v = fields_dict["        Issuer:"]
  2.1704 +        self.issuer = None
  2.1705 +        if v:
  2.1706 +            v = v.split('\n',1)[0]
  2.1707 +            v = v.strip()
  2.1708 +            self.issuer = v
  2.1709 +        if self.issuer is None:
  2.1710 +            raise Exception(error_msg)
  2.1711 +
  2.1712 +        # not before
  2.1713 +        v = fields_dict["            Not Before:"]
  2.1714 +        self.notBefore_str = None
  2.1715 +        if v:
  2.1716 +            v = v.split('\n',1)[0]
  2.1717 +            v = v.strip()
  2.1718 +            self.notBefore_str = v
  2.1719 +        if self.notBefore_str is None:
  2.1720 +            raise Exception(error_msg)
  2.1721 +        self.notBefore = time.strptime(self.notBefore_str,
  2.1722 +                                       "%b %d %H:%M:%S %Y %Z")
  2.1723 +        self.notBefore_str_simple = time.strftime("%x", self.notBefore)
  2.1724 +        
  2.1725 +        # not after
  2.1726 +        v = fields_dict["            Not After :"]
  2.1727 +        self.notAfter_str = None
  2.1728 +        if v:
  2.1729 +            v = v.split('\n',1)[0]
  2.1730 +            v = v.strip()
  2.1731 +            self.notAfter_str = v
  2.1732 +        if self.notAfter_str is None:
  2.1733 +            raise Exception(error_msg)
  2.1734 +        self.notAfter = time.strptime(self.notAfter_str,
  2.1735 +                                      "%b %d %H:%M:%S %Y %Z")
  2.1736 +        self.notAfter_str_simple = time.strftime("%x", self.notAfter)
  2.1737 +        
  2.1738 +        # subject
  2.1739 +        v = fields_dict["        Subject:"]
  2.1740 +        self.subject = None
  2.1741 +        if v:
  2.1742 +            v = v.split('\n',1)[0]
  2.1743 +            v = v.strip()
  2.1744 +            self.subject = v
  2.1745 +        if self.subject is None:
  2.1746 +            raise Exception(error_msg)
  2.1747 +        
  2.1748 +        # Public Key Algorithm
  2.1749 +        v = fields_dict["            Public Key Algorithm:"]
  2.1750 +        self.pubKeyAlg = None
  2.1751 +        if v:
  2.1752 +            v = v.split('\n',1)[0]
  2.1753 +            v = v.strip()
  2.1754 +            self.pubKeyAlg = v
  2.1755 +        if self.pubKeyAlg is None:
  2.1756 +            raise Exception(error_msg)
  2.1757 +        
  2.1758 +        # Modulus
  2.1759 +        v = fields_dict["                Modulus ("]
  2.1760 +        self.modulus = None
  2.1761 +        if v:
  2.1762 +            v,t = v.split(' bit):',1)
  2.1763 +            self.modulusLen = int(v)
  2.1764 +            t = t.replace(' ', '').replace('\n', ''). replace(':', '')
  2.1765 +            self.modulus_hexdump = t
  2.1766 +            self.modulus = long(t, 16)
  2.1767 +        if self.modulus is None:
  2.1768 +            raise Exception(error_msg)
  2.1769 +
  2.1770 +        # Exponent
  2.1771 +        v = fields_dict["                Exponent:"]
  2.1772 +        self.exponent = None
  2.1773 +        if v:
  2.1774 +            v = v.split('(',1)[0]
  2.1775 +            self.exponent = long(v)
  2.1776 +        if self.exponent is None:
  2.1777 +            raise Exception(error_msg)
  2.1778 +
  2.1779 +        # Public Key instance
  2.1780 +        self.key = RSA.construct((self.modulus, self.exponent, ))
  2.1781 +        
  2.1782 +        # Subject Key Identifier
  2.1783 +
  2.1784 +        # Authority Key Identifier: keyid, dirname and serial
  2.1785 +        self.authorityKeyID_keyid   = None
  2.1786 +        self.authorityKeyID_dirname = None
  2.1787 +        self.authorityKeyID_serial  = None
  2.1788 +        if self.authorityKeyID: # (hex version already done using asn1parse)
  2.1789 +            v = fields_dict["                keyid:"]
  2.1790 +            if v:
  2.1791 +                v = v.split('\n',1)[0]
  2.1792 +                v = v.strip().replace(':', '')
  2.1793 +                self.authorityKeyID_keyid = v
  2.1794 +            v = fields_dict["                DirName:"]
  2.1795 +            if v:
  2.1796 +                v = v.split('\n',1)[0]
  2.1797 +                self.authorityKeyID_dirname = v
  2.1798 +            v = fields_dict["                serial:"]
  2.1799 +            if v:
  2.1800 +                v = v.split('\n',1)[0]
  2.1801 +                v = v.strip().replace(':', '')
  2.1802 +                self.authorityKeyID_serial = v                
  2.1803 +
  2.1804 +        # Basic constraints
  2.1805 +        self.basicConstraintsCritical = False
  2.1806 +        self.basicConstraints=None
  2.1807 +        v = fields_dict["            X509v3 Basic Constraints:"]
  2.1808 +        if v:
  2.1809 +            self.basicConstraints = {}
  2.1810 +            v,t = v.split('\n',2)[:2]
  2.1811 +            if "critical" in v:
  2.1812 +                self.basicConstraintsCritical = True
  2.1813 +            if "CA:" in t:
  2.1814 +                self.basicConstraints["CA"] = t.split('CA:')[1][:4] == "TRUE"
  2.1815 +            if "pathlen:" in t:
  2.1816 +                self.basicConstraints["pathlen"] = int(t.split('pathlen:')[1])
  2.1817 +
  2.1818 +        # X509v3 Key Usage
  2.1819 +        self.keyUsage = []
  2.1820 +        v = fields_dict["            X509v3 Key Usage:"]
  2.1821 +        if v:	
  2.1822 +            # man 5 x509v3_config
  2.1823 +            ku_mapping = {"Digital Signature": "digitalSignature",
  2.1824 +                          "Non Repudiation": "nonRepudiation",
  2.1825 +                          "Key Encipherment": "keyEncipherment",
  2.1826 +                          "Data Encipherment": "dataEncipherment",
  2.1827 +                          "Key Agreement": "keyAgreement",
  2.1828 +                          "Certificate Sign": "keyCertSign",
  2.1829 +                          "CRL Sign": "cRLSign",
  2.1830 +                          "Encipher Only": "encipherOnly",
  2.1831 +                          "Decipher Only": "decipherOnly"}
  2.1832 +            v = v.split('\n',2)[1]
  2.1833 +            l = map(lambda x: x.strip(), v.split(','))
  2.1834 +            while l:
  2.1835 +                c = l.pop()
  2.1836 +                if ku_mapping.has_key(c):
  2.1837 +                    self.keyUsage.append(ku_mapping[c])
  2.1838 +                else:
  2.1839 +                    self.keyUsage.append(c) # Add it anyway
  2.1840 +                    print "Found unknown X509v3 Key Usage: '%s'" % c
  2.1841 +                    print "Report it to arno (at) natisbad.org for addition"
  2.1842 +
  2.1843 +        # X509v3 Extended Key Usage
  2.1844 +        self.extKeyUsage = []
  2.1845 +        v = fields_dict["            X509v3 Extended Key Usage:"]
  2.1846 +        if v:	
  2.1847 +            # man 5 x509v3_config:
  2.1848 +            eku_mapping = {"TLS Web Server Authentication": "serverAuth",
  2.1849 +                           "TLS Web Client Authentication": "clientAuth",
  2.1850 +                           "Code Signing": "codeSigning",
  2.1851 +                           "E-mail Protection": "emailProtection",
  2.1852 +                           "Time Stamping": "timeStamping",
  2.1853 +                           "Microsoft Individual Code Signing": "msCodeInd",
  2.1854 +                           "Microsoft Commercial Code Signing": "msCodeCom",
  2.1855 +                           "Microsoft Trust List Signing": "msCTLSign",
  2.1856 +                           "Microsoft Encrypted File System": "msEFS",
  2.1857 +                           "Microsoft Server Gated Crypto": "msSGC",
  2.1858 +                           "Netscape Server Gated Crypto": "nsSGC",
  2.1859 +                           "IPSec End System": "iPsecEndSystem",
  2.1860 +                           "IPSec Tunnel": "iPsecTunnel",
  2.1861 +                           "IPSec User": "iPsecUser"}
  2.1862 +            v = v.split('\n',2)[1]
  2.1863 +            l = map(lambda x: x.strip(), v.split(','))
  2.1864 +            while l:
  2.1865 +                c = l.pop()
  2.1866 +                if eku_mapping.has_key(c):
  2.1867 +                    self.extKeyUsage.append(eku_mapping[c])
  2.1868 +                else:
  2.1869 +                    self.extKeyUsage.append(c) # Add it anyway
  2.1870 +                    print "Found unknown X509v3 Extended Key Usage: '%s'" % c
  2.1871 +                    print "Report it to arno (at) natisbad.org for addition"
  2.1872 +
  2.1873 +        # CRL Distribution points
  2.1874 +        self.cRLDistributionPoints = []
  2.1875 +        v = fields_dict["            X509v3 CRL Distribution Points:"]
  2.1876 +        if v:
  2.1877 +            v = v.split("\n\n", 1)[0]
  2.1878 +            v = v.split("URI:")[1:]
  2.1879 +            self.CRLDistributionPoints = map(lambda x: x.strip(), v)
  2.1880 +            
  2.1881 +        # Authority Information Access: list of tuples ("method", "location")
  2.1882 +        self.authorityInfoAccess = []
  2.1883 +        v = fields_dict["            Authority Information Access:"]
  2.1884 +        if v:
  2.1885 +            v = v.split("\n\n", 1)[0]
  2.1886 +            v = v.split("\n")[1:]
  2.1887 +            for e in v:
  2.1888 +                method, location = map(lambda x: x.strip(), e.split(" - ", 1))
  2.1889 +                self.authorityInfoAccess.append((method, location))
  2.1890 +
  2.1891 +        # signature field
  2.1892 +        v = fields_dict["    Signature Algorithm:" ]
  2.1893 +        self.sig = None
  2.1894 +        if v:
  2.1895 +            v = v.split('\n',1)[1]
  2.1896 +            v = v.replace(' ', '').replace('\n', '')
  2.1897 +            self.sig = "".join(map(lambda x: chr(int(x, 16)), v.split(':')))
  2.1898 +            self.sigLen = len(self.sig)
  2.1899 +        if self.sig is None:
  2.1900 +            raise Exception(error_msg)
  2.1901 +
  2.1902 +    def isIssuerCert(self, other):
  2.1903 +        """
  2.1904 +        True if 'other' issued 'self', i.e.:
  2.1905 +          - self.issuer == other.subject
  2.1906 +          - self is signed by other
  2.1907 +        """
  2.1908 +        # XXX should be done on raw values, instead of their textual repr
  2.1909 +        if self.issuer != other.subject:
  2.1910 +            return False
  2.1911 +
  2.1912 +        # Sanity check regarding modulus length and the
  2.1913 +        # signature length
  2.1914 +        keyLen = (other.modulusLen + 7)/8
  2.1915 +        if keyLen != self.sigLen:
  2.1916 +            return False
  2.1917 +
  2.1918 +        unenc = other.encrypt(self.sig) # public key encryption, i.e. decrypt
  2.1919 +
  2.1920 +        # XXX Check block type (00 or 01 and type of padding)
  2.1921 +        unenc = unenc[1:]
  2.1922 +        if not '\x00' in unenc:
  2.1923 +            return False
  2.1924 +        pos = unenc.index('\x00')
  2.1925 +        unenc = unenc[pos+1:]
  2.1926 +
  2.1927 +        found = None
  2.1928 +        for k in _hashFuncParams.keys():
  2.1929 +            if self.sigAlg.startswith(k):
  2.1930 +                found = k
  2.1931 +                break
  2.1932 +        if not found:
  2.1933 +            return False
  2.1934 +        hlen, hfunc, digestInfo =  _hashFuncParams[k]
  2.1935 +        
  2.1936 +        if len(unenc) != (hlen+len(digestInfo)):
  2.1937 +            return False
  2.1938 +
  2.1939 +        if not unenc.startswith(digestInfo):
  2.1940 +            return False
  2.1941 +
  2.1942 +        h = unenc[-hlen:]
  2.1943 +        myh = hfunc(self.tbsCertificate)
  2.1944 +
  2.1945 +        return h == myh
  2.1946 +
  2.1947 +    def chain(self, certlist):
  2.1948 +        """
  2.1949 +        Construct the chain of certificates leading from 'self' to the
  2.1950 +        self signed root using the certificates in 'certlist'. If the
  2.1951 +        list does not provide all the required certs to go to the root
  2.1952 +        the function returns a incomplete chain starting with the
  2.1953 +        certificate. This fact can be tested by tchecking if the last
  2.1954 +        certificate of the returned chain is self signed (if c is the
  2.1955 +        result, c[-1].isSelfSigned())
  2.1956 +        """
  2.1957 +        d = {}
  2.1958 +        for c in certlist:
  2.1959 +            # XXX we should check if we have duplicate
  2.1960 +            d[c.subject] = c
  2.1961 +        res = [self]
  2.1962 +        cur = self
  2.1963 +        while not cur.isSelfSigned():
  2.1964 +            if d.has_key(cur.issuer):
  2.1965 +                possible_issuer = d[cur.issuer]
  2.1966 +                if cur.isIssuerCert(possible_issuer):
  2.1967 +                    res.append(possible_issuer)
  2.1968 +                    cur = possible_issuer
  2.1969 +                else:
  2.1970 +                    break
  2.1971 +        return res
  2.1972 +
  2.1973 +    def remainingDays(self, now=None):
  2.1974 +        """
  2.1975 +        Based on the value of notBefore field, returns the number of
  2.1976 +        days the certificate will still be valid. The date used for the
  2.1977 +        comparison is the current and local date, as returned by 
  2.1978 +        time.localtime(), except if 'now' argument is provided another
  2.1979 +        one. 'now' argument can be given as either a time tuple or a string
  2.1980 +        representing the date. Accepted format for the string version
  2.1981 +        are:
  2.1982 +        
  2.1983 +         - '%b %d %H:%M:%S %Y %Z' e.g. 'Jan 30 07:38:59 2008 GMT'
  2.1984 +         - '%m/%d/%y' e.g. '01/30/08' (less precise)
  2.1985 +
  2.1986 +        If the certificate is no more valid at the date considered, then,
  2.1987 +        a negative value is returned representing the number of days
  2.1988 +        since it has expired.
  2.1989 +        
  2.1990 +        The number of days is returned as a float to deal with the unlikely
  2.1991 +        case of certificates that are still just valid.
  2.1992 +        """
  2.1993 +        if now is None:
  2.1994 +            now = time.localtime()
  2.1995 +        elif type(now) is str:
  2.1996 +            try:
  2.1997 +                if '/' in now:
  2.1998 +                    now = time.strptime(now, '%m/%d/%y')
  2.1999 +                else:
  2.2000 +                    now = time.strptime(now, '%b %d %H:%M:%S %Y %Z')
  2.2001 +            except:
  2.2002 +                warning("Bad time string provided '%s'. Using current time" % now)
  2.2003 +                now = time.localtime()
  2.2004 +
  2.2005 +        now = time.mktime(now)
  2.2006 +        nft = time.mktime(self.notAfter)
  2.2007 +        diff = (nft - now)/(24.*3600)
  2.2008 +        return diff
  2.2009 +
  2.2010 +
  2.2011 +    # return SHA-1 hash of cert embedded public key
  2.2012 +    # !! At the moment, the trailing 0 is in the hashed string if any
  2.2013 +    def keyHash(self):
  2.2014 +	m = self.modulus_hexdump
  2.2015 +        res = []
  2.2016 +        i = 0
  2.2017 +        l = len(m)
  2.2018 +        while i<l: # get a string version of modulus
  2.2019 +            res.append(struct.pack("B", int(m[i:i+2], 16)))
  2.2020 +            i += 2
  2.2021 +        return sha.new("".join(res)).digest()    
  2.2022 +
  2.2023 +    def output(self, fmt="DER"):
  2.2024 +        if fmt == "DER":
  2.2025 +            return self.dercert
  2.2026 +        elif fmt == "PEM":
  2.2027 +            return self.pemcert
  2.2028 +        elif fmt == "TXT":
  2.2029 +            return self.textcert
  2.2030 +
  2.2031 +    def export(self, filename, fmt="DER"):
  2.2032 +        """
  2.2033 +        Export certificate in 'fmt' format (PEM, DER or TXT) to file 'filename'
  2.2034 +        """
  2.2035 +        f = open(filename, "wb")
  2.2036 +        f.write(self.output(fmt))
  2.2037 +        f.close()
  2.2038 +
  2.2039 +    def isSelfSigned(self):
  2.2040 +        """
  2.2041 +        Return True if the certificate is self signed:
  2.2042 +          - issuer and subject are the same
  2.2043 +          - the signature of the certificate is valid.
  2.2044 +        """
  2.2045 +        if self.issuer == self.subject:
  2.2046 +            return self.isIssuerCert(self)
  2.2047 +        return False
  2.2048 +
  2.2049 +    # Print main informations stored in certificate
  2.2050 +    def show(self):
  2.2051 +        print "Serial: %s" % self.serial
  2.2052 +        print "Issuer: " + self.issuer
  2.2053 +        print "Subject: " + self.subject
  2.2054 +        print "Validity: %s to %s" % (self.notBefore_str_simple,
  2.2055 +                                      self.notAfter_str_simple)
  2.2056 +
  2.2057 +    def __repr__(self):
  2.2058 +        return "[X.509 Cert. Subject:%s, Issuer:%s]" % (self.subject, self.issuer)
  2.2059 +
  2.2060 +    def __str__(self):
  2.2061 +        return self.dercert
  2.2062 +
  2.2063 +    def verifychain(self, anchors, untrusted=None):
  2.2064 +        """
  2.2065 +        Perform verification of certificate chains for that certificate. The
  2.2066 +        behavior of verifychain method is mapped (and also based) on openssl
  2.2067 +        verify userland tool (man 1 verify).
  2.2068 +        A list of anchors is required. untrusted parameter can be provided 
  2.2069 +        a list of untrusted certificates that can be used to reconstruct the
  2.2070 +        chain.
  2.2071 +
  2.2072 +        If you have a lot of certificates to verify against the same
  2.2073 +        list of anchor, consider constructing this list as a cafile
  2.2074 +        and use .verifychain_from_cafile() instead.
  2.2075 +        """
  2.2076 +        cafile = create_temporary_ca_file(anchors)
  2.2077 +        if not cafile:
  2.2078 +            return False
  2.2079 +        untrusted_file = None
  2.2080 +        if untrusted:
  2.2081 +            untrusted_file = create_temporary_ca_file(untrusted) # hack
  2.2082 +            if not untrusted_file:
  2.2083 +                os.unlink(cafile)
  2.2084 +                return False
  2.2085 +        res = self.verifychain_from_cafile(cafile, 
  2.2086 +                                           untrusted_file=untrusted_file)
  2.2087 +        os.unlink(cafile)
  2.2088 +        if untrusted_file:
  2.2089 +            os.unlink(untrusted_file)
  2.2090 +        return res
  2.2091 +
  2.2092 +    def verifychain_from_cafile(self, cafile, untrusted_file=None):
  2.2093 +        """
  2.2094 +        Does the same job as .verifychain() but using the list of anchors
  2.2095 +        from the cafile. This is useful (because more efficient) if
  2.2096 +        you have a lot of certificates to verify do it that way: it
  2.2097 +        avoids the creation of a cafile from anchors at each call.
  2.2098 +
  2.2099 +        As for .verifychain(), a list of untrusted certificates can be
  2.2100 +        passed (as a file, this time)
  2.2101 +        """
  2.2102 +        u = ""
  2.2103 +        if untrusted_file:
  2.2104 +            u = "-untrusted %s" % untrusted_file
  2.2105 +        try:
  2.2106 +            cmd = "openssl verify -CAfile %s %s " % (cafile, u)
  2.2107 +            pemcert = self.output(fmt="PEM")
  2.2108 +            cmdres = self._apply_ossl_cmd(cmd, pemcert)
  2.2109 +        except:
  2.2110 +            return False
  2.2111 +        return cmdres.endswith("\nOK\n") or cmdres.endswith(": OK\n")
  2.2112 +
  2.2113 +    def verifychain_from_capath(self, capath, untrusted_file=None):
  2.2114 +        """
  2.2115 +        Does the same job as .verifychain_from_cafile() but using the list
  2.2116 +        of anchors in capath directory. The directory should contain
  2.2117 +        certificates files in PEM format with associated links as
  2.2118 +        created using c_rehash utility (man c_rehash).
  2.2119 +
  2.2120 +        As for .verifychain_from_cafile(), a list of untrusted certificates
  2.2121 +        can be passed as a file (concatenation of the certificates in
  2.2122 +        PEM format)
  2.2123 +        """
  2.2124 +        u = ""
  2.2125 +        if untrusted_file:
  2.2126 +            u = "-untrusted %s" % untrusted_file
  2.2127 +        try:
  2.2128 +            cmd = "openssl verify -CApath %s %s " % (capath, u)
  2.2129 +            pemcert = self.output(fmt="PEM")
  2.2130 +            cmdres = self._apply_ossl_cmd(cmd, pemcert)
  2.2131 +        except:
  2.2132 +            return False
  2.2133 +        return cmdres.endswith("\nOK\n") or cmdres.endswith(": OK\n")
  2.2134 +
  2.2135 +    def is_revoked(self, crl_list):
  2.2136 +        """
  2.2137 +        Given a list of trusted CRL (their signature has already been
  2.2138 +        verified with trusted anchors), this function returns True if
  2.2139 +        the certificate is marked as revoked by one of those CRL.
  2.2140 +
  2.2141 +        Note that if the Certificate was on hold in a previous CRL and
  2.2142 +        is now valid again in a new CRL and bot are in the list, it
  2.2143 +        will be considered revoked: this is because _all_ CRLs are 
  2.2144 +        checked (not only the freshest) and revocation status is not
  2.2145 +        handled.
  2.2146 +
  2.2147 +        Also note that the check on the issuer is performed on the
  2.2148 +        Authority Key Identifier if available in _both_ the CRL and the
  2.2149 +        Cert. Otherwise, the issuers are simply compared.
  2.2150 +        """
  2.2151 +        for c in crl_list:
  2.2152 +            if (self.authorityKeyID is not None and 
  2.2153 +                c.authorityKeyID is not None and
  2.2154 +                self.authorityKeyID == c.authorityKeyID):
  2.2155 +                return self.serial in map(lambda x: x[0], c.revoked_cert_serials)
  2.2156 +            elif (self.issuer == c.issuer):
  2.2157 +                return self.serial in map(lambda x: x[0], c.revoked_cert_serials)
  2.2158 +        return False
  2.2159 +
  2.2160 +def print_chain(l):
  2.2161 +    llen = len(l) - 1
  2.2162 +    if llen < 0:
  2.2163 +        return ""
  2.2164 +    c = l[llen]
  2.2165 +    llen -= 1
  2.2166 +    s = "_ "
  2.2167 +    if not c.isSelfSigned():
  2.2168 +        s = "_ ... [Missing Root]\n"
  2.2169 +    else:
  2.2170 +        s += "%s [Self Signed]\n" % c.subject
  2.2171 +    i = 1
  2.2172 +    while (llen != -1):
  2.2173 +        c = l[llen]
  2.2174 +        s += "%s\_ %s" % (" "*i, c.subject)
  2.2175 +        if llen != 0:
  2.2176 +            s += "\n"
  2.2177 +        i += 2
  2.2178 +        llen -= 1
  2.2179 +    print s
  2.2180 +
  2.2181 +# import popen2
  2.2182 +# a=popen2.Popen3("openssl crl -text -inform DER -noout ", capturestderr=True)
  2.2183 +# a.tochild.write(open("samples/klasa1.crl").read())
  2.2184 +# a.tochild.close()
  2.2185 +# a.poll()
  2.2186 +
  2.2187 +class CRL(OSSLHelper):
  2.2188 +    # Below are the fields we recognize in the -text output of openssl
  2.2189 +    # and from which we extract information. We expect them in that
  2.2190 +    # order. Number of spaces does matter.
  2.2191 +    possible_fields = [ "        Version",
  2.2192 +                        "        Signature Algorithm:",
  2.2193 +                        "        Issuer:",
  2.2194 +                        "        Last Update:",
  2.2195 +                        "        Next Update:",
  2.2196 +                        "        CRL extensions:",
  2.2197 +                        "            X509v3 Issuer Alternative Name:",
  2.2198 +                        "            X509v3 Authority Key Identifier:", 
  2.2199 +                        "                keyid:",
  2.2200 +                        "                DirName:",
  2.2201 +                        "                serial:",
  2.2202 +                        "            X509v3 CRL Number:", 
  2.2203 +                        "Revoked Certificates:",
  2.2204 +                        "No Revoked Certificates.",
  2.2205 +                        "    Signature Algorithm:" ]
  2.2206 +    possible_fields_count = len(possible_fields)
  2.2207 +
  2.2208 +    def __init__(self, crlpath):
  2.2209 +        error_msg = "Unable to import CRL."
  2.2210 +
  2.2211 +        fields_dict = {}
  2.2212 +        for k in self.possible_fields:
  2.2213 +            fields_dict[k] = None
  2.2214 +
  2.2215 +        self.crlpath = None
  2.2216 +        rawcrl = None
  2.2217 +
  2.2218 +        if (not '\x00' in crlpath) and os.path.isfile(crlpath):
  2.2219 +            self.crlpath = crlpath
  2.2220 +            cert_size = os.path.getsize(crlpath)
  2.2221 +            if cert_size > MAX_CRL_SIZE:
  2.2222 +                raise Exception(error_msg)
  2.2223 +            try:
  2.2224 +                f = open(crlpath)
  2.2225 +                rawcrl = f.read()
  2.2226 +                f.close()
  2.2227 +            except:
  2.2228 +    		raise Exception(error_msg)     
  2.2229 +        else:
  2.2230 +            rawcrl = crlpath
  2.2231 +
  2.2232 +	if rawcrl is None:
  2.2233 +	    raise Exception(error_msg)
  2.2234 +
  2.2235 +	self.rawcrl = rawcrl
  2.2236 +
  2.2237 +        # Let's try to get file format : PEM or DER.
  2.2238 +        fmtstr = 'openssl crl -text -inform %s -noout '
  2.2239 +        convertstr = 'openssl crl -inform %s -outform %s '
  2.2240 +        crl_header = "-----BEGIN X509 CRL-----"
  2.2241 +        crl_footer = "-----END X509 CRL-----"
  2.2242 +        l = rawcrl.split(crl_header, 1)
  2.2243 +        if len(l) == 2: # looks like PEM
  2.2244 +            tmp = l[1]
  2.2245 +            l = tmp.split(crl_footer, 1)
  2.2246 +            if len(l) == 2:
  2.2247 +                tmp = l[0]
  2.2248 +                rawcrl = "%s%s%s\n" % (crl_header, tmp, crl_footer)
  2.2249 +            else:
  2.2250 +                raise Exception(error_msg)
  2.2251 +            r,w,e = popen2.popen3(fmtstr % "PEM")
  2.2252 +            w.write(rawcrl)
  2.2253 +            w.close()
  2.2254 +            textcrl = r.read()
  2.2255 +            r.close()
  2.2256 +            res = e.read()
  2.2257 +            e.close()
  2.2258 +            if res == '':
  2.2259 +                self.format = "PEM"
  2.2260 +                self.pemcrl = rawcrl
  2.2261 +                self.textcrl = textcrl
  2.2262 +                cmd = convertstr % ("PEM", "DER")
  2.2263 +                self.dercrl = self._apply_ossl_cmd(cmd, rawcrl)
  2.2264 +            else:
  2.2265 +                raise Exception(error_msg)
  2.2266 +        else: # not PEM, try DER
  2.2267 +            r,w,e = popen2.popen3(fmtstr % "DER")            
  2.2268 +            w.write(rawcrl)
  2.2269 +            w.close()
  2.2270 +            textcrl = r.read()
  2.2271 +            r.close()
  2.2272 +            res = e.read()
  2.2273 +	    if res == '':
  2.2274 +		self.format = "DER"
  2.2275 +                self.dercrl = rawcrl
  2.2276 +                self.textcrl = textcrl
  2.2277 +                cmd = convertstr % ("DER", "PEM")
  2.2278 +                self.pemcrl = self._apply_ossl_cmd(cmd, rawcrl)
  2.2279 +                cmd = convertstr % ("DER", "DER")
  2.2280 +                self.dercrl = self._apply_ossl_cmd(cmd, rawcrl)
  2.2281 +	    else:
  2.2282 +		raise Exception(error_msg)
  2.2283 +
  2.2284 +        self.osslcmdbase = 'openssl crl -inform %s ' % self.format
  2.2285 +
  2.2286 +        r,w,e = popen2.popen3('openssl asn1parse -inform DER ')
  2.2287 +        w.write(self.dercrl)
  2.2288 +        w.close()
  2.2289 +        self.asn1parsecrl = r.read()
  2.2290 +        r.close()
  2.2291 +        res = e.read()
  2.2292 +        e.close()
  2.2293 +        if res != '':
  2.2294 +            raise Exception(error_msg)
  2.2295 +
  2.2296 +        # Grab _raw_ X509v3 Authority Key Identifier, if any.
  2.2297 +        tmp = self.asn1parsecrl.split(":X509v3 Authority Key Identifier", 1)
  2.2298 +        self.authorityKeyID = None
  2.2299 +        if len(tmp) == 2:
  2.2300 +            tmp = tmp[1]
  2.2301 +            tmp = tmp.split("[HEX DUMP]:", 1)[1]
  2.2302 +            self.authorityKeyID=tmp.split('\n',1)[0]
  2.2303 +
  2.2304 +        # Parse the -text output of openssl to make things available
  2.2305 +        tmp = self.textcrl.split('\n', 1)[1]
  2.2306 +        l = tmp.split('\n', 1)
  2.2307 +        if len(l) != 2:
  2.2308 +            raise Exception(error_msg)
  2.2309 +        cur, tmp = l
  2.2310 +        i = 0
  2.2311 +        k = self.possible_fields[i] # Version
  2.2312 +        cur = cur[len(k):] + '\n'
  2.2313 +        while k:
  2.2314 +            l = tmp.split('\n', 1)
  2.2315 +            if len(l) != 2: # Over
  2.2316 +                fields_dict[k] = cur
  2.2317 +                break
  2.2318 +            l, tmp = l
  2.2319 +
  2.2320 +            newkey = 0
  2.2321 +            # skip fields we have already seen, this is the purpose of 'i'
  2.2322 +            for j in range(i, self.possible_fields_count):
  2.2323 +                f = self.possible_fields[j]
  2.2324 +                if l.startswith(f):
  2.2325 +                    fields_dict[k] = cur
  2.2326 +                    cur = l[len(f):] + '\n'
  2.2327 +                    k = f
  2.2328 +                    newkey = 1
  2.2329 +                    i = j+1
  2.2330 +                    break
  2.2331 +            if newkey == 1:
  2.2332 +                continue
  2.2333 +            cur += l + '\n'
  2.2334 +
  2.2335 +        # version
  2.2336 +        v = fields_dict["        Version"]
  2.2337 +        self.version = None
  2.2338 +        if v:
  2.2339 +            self.version = int(v[1:2])
  2.2340 +        if self.version is None:
  2.2341 +            raise Exception(error_msg)
  2.2342 +
  2.2343 +        # signature algorithm
  2.2344 +        v = fields_dict["        Signature Algorithm:"]
  2.2345 +        self.sigAlg = None
  2.2346 +        if v:
  2.2347 +            v = v.split('\n',1)[0]
  2.2348 +            v = v.strip()
  2.2349 +            self.sigAlg = v
  2.2350 +        if self.sigAlg is None:
  2.2351 +            raise Exception(error_msg)
  2.2352 +
  2.2353 +        # issuer
  2.2354 +        v = fields_dict["        Issuer:"]
  2.2355 +        self.issuer = None
  2.2356 +        if v:
  2.2357 +            v = v.split('\n',1)[0]
  2.2358 +            v = v.strip()
  2.2359 +            self.issuer = v
  2.2360 +        if self.issuer is None:
  2.2361 +            raise Exception(error_msg)
  2.2362 +
  2.2363 +        # last update
  2.2364 +        v = fields_dict["        Last Update:"]
  2.2365 +        self.lastUpdate_str = None
  2.2366 +        if v:
  2.2367 +            v = v.split('\n',1)[0]
  2.2368 +            v = v.strip()
  2.2369 +            self.lastUpdate_str = v
  2.2370 +        if self.lastUpdate_str is None:
  2.2371 +            raise Exception(error_msg)
  2.2372 +        self.lastUpdate = time.strptime(self.lastUpdate_str,
  2.2373 +                                       "%b %d %H:%M:%S %Y %Z")
  2.2374 +        self.lastUpdate_str_simple = time.strftime("%x", self.lastUpdate)
  2.2375 +
  2.2376 +        # next update
  2.2377 +        v = fields_dict["        Next Update:"]
  2.2378 +        self.nextUpdate_str = None
  2.2379 +        if v:
  2.2380 +            v = v.split('\n',1)[0]
  2.2381 +            v = v.strip()
  2.2382 +            self.nextUpdate_str = v
  2.2383 +        if self.nextUpdate_str is None:
  2.2384 +            raise Exception(error_msg)
  2.2385 +        self.nextUpdate = time.strptime(self.nextUpdate_str,
  2.2386 +                                       "%b %d %H:%M:%S %Y %Z")
  2.2387 +        self.nextUpdate_str_simple = time.strftime("%x", self.nextUpdate)
  2.2388 +        
  2.2389 +        # XXX Do something for Issuer Alternative Name
  2.2390 +
  2.2391 +        # Authority Key Identifier: keyid, dirname and serial
  2.2392 +        self.authorityKeyID_keyid   = None
  2.2393 +        self.authorityKeyID_dirname = None
  2.2394 +        self.authorityKeyID_serial  = None
  2.2395 +        if self.authorityKeyID: # (hex version already done using asn1parse)
  2.2396 +            v = fields_dict["                keyid:"]
  2.2397 +            if v:
  2.2398 +                v = v.split('\n',1)[0]
  2.2399 +                v = v.strip().replace(':', '')
  2.2400 +                self.authorityKeyID_keyid = v
  2.2401 +            v = fields_dict["                DirName:"]
  2.2402 +            if v:
  2.2403 +                v = v.split('\n',1)[0]
  2.2404 +                self.authorityKeyID_dirname = v
  2.2405 +            v = fields_dict["                serial:"]
  2.2406 +            if v:
  2.2407 +                v = v.split('\n',1)[0]
  2.2408 +                v = v.strip().replace(':', '')
  2.2409 +                self.authorityKeyID_serial = v
  2.2410 +
  2.2411 +        # number
  2.2412 +        v = fields_dict["            X509v3 CRL Number:"]
  2.2413 +        self.number = None
  2.2414 +        if v:
  2.2415 +            v = v.split('\n',2)[1]
  2.2416 +            v = v.strip()
  2.2417 +            self.number = int(v)
  2.2418 +
  2.2419 +        # Get the list of serial numbers of revoked certificates
  2.2420 +        self.revoked_cert_serials = []
  2.2421 +        v = fields_dict["Revoked Certificates:"]
  2.2422 +        t = fields_dict["No Revoked Certificates."]
  2.2423 +        if (t is None and v is not None):
  2.2424 +            v = v.split("Serial Number: ")[1:]
  2.2425 +            for r in v:
  2.2426 +                s,d = r.split('\n', 1)
  2.2427 +                s = s.split('\n', 1)[0]
  2.2428 +                d = d.split("Revocation Date:", 1)[1]
  2.2429 +                d = time.strptime(d.strip(), "%b %d %H:%M:%S %Y %Z")
  2.2430 +                self.revoked_cert_serials.append((s,d))
  2.2431 +
  2.2432 +        # signature field
  2.2433 +        v = fields_dict["    Signature Algorithm:" ]
  2.2434 +        self.sig = None
  2.2435 +        if v:
  2.2436 +            v = v.split('\n',1)[1]
  2.2437 +            v = v.replace(' ', '').replace('\n', '')
  2.2438 +            self.sig = "".join(map(lambda x: chr(int(x, 16)), v.split(':')))
  2.2439 +            self.sigLen = len(self.sig)
  2.2440 +        if self.sig is None:
  2.2441 +            raise Exception(error_msg)
  2.2442 +
  2.2443 +    def __str__(self):
  2.2444 +        return self.dercrl
  2.2445 +        
  2.2446 +    # Print main informations stored in CRL
  2.2447 +    def show(self):
  2.2448 +        print "Version: %d" % self.version
  2.2449 +        print "sigAlg: " + self.sigAlg
  2.2450 +        print "Issuer: " + self.issuer
  2.2451 +        print "lastUpdate: %s" % self.lastUpdate_str_simple
  2.2452 +        print "nextUpdate: %s" % self.nextUpdate_str_simple
  2.2453 +
  2.2454 +    def verify(self, anchors):
  2.2455 +        """
  2.2456 +        Return True if the CRL is signed by one of the provided
  2.2457 +        anchors. False on error (invalid signature, missing anchorand, ...)
  2.2458 +        """
  2.2459 +        cafile = create_temporary_ca_file(anchors)
  2.2460 +        if cafile is None:
  2.2461 +            return False
  2.2462 +        try:
  2.2463 +            cmd = self.osslcmdbase + '-noout -CAfile %s 2>&1' % cafile
  2.2464 +            cmdres = self._apply_ossl_cmd(cmd, self.rawcrl)
  2.2465 +        except:
  2.2466 +            os.unlink(cafile)
  2.2467 +            return False
  2.2468 +        os.unlink(cafile)
  2.2469 +        return "verify OK" in cmdres
  2.2470 +
  2.2471 +
  2.2472 +    
  2.2473 +