electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit e8d30129eae10b5efeaa9e95a900576e41dd2bf1
parent 2ba07377daafa337225b4814656b7a9613fd6540
Author: ThomasV <thomasv@gitorious>
Date:   Tue,  4 Aug 2015 18:16:06 +0200

replace TLSLite dependency with minimal RSA implementation

Diffstat:
MRELEASE-NOTES | 1+
Mlib/asn1tinydecoder.py | 47+++++++++++++++++++++++++++++++++++++++++++++++
Mlib/dnssec.py | 16++++++++--------
Mlib/paymentrequest.py | 29+++++++++++++++++------------
Alib/pem.py | 164+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/rsakey.py | 517+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/x509.py | 90+++++++++++++++++++++++--------------------------------------------------------
Msetup.py | 1-
8 files changed, 780 insertions(+), 85 deletions(-)

diff --git a/RELEASE-NOTES b/RELEASE-NOTES @@ -1,6 +1,7 @@ # Release 2.4.1 * Use ssl.PROTOCOL_TLSv1 * Fix DNSSEC issues with ECDSA signatures + * Replace TLSLite dependency with minimal RSA implementation # Release 2.4 * Payment to DNS names storing a Bitcoin addresses (OpenAlias) is diff --git a/lib/asn1tinydecoder.py b/lib/asn1tinydecoder.py @@ -121,3 +121,50 @@ def asn1_read_length(der,ix): ####################### END ASN1 DECODER ############################ +def decode_OID(s): + s = map(ord, s) + r = [] + r.append(s[0] / 40) + r.append(s[0] % 40) + k = 0 + for i in s[1:]: + if i < 128: + r.append(i + 128*k) + k = 0 + else: + k = (i - 128) + 128*k + return '.'.join(map(str, r)) + +def encode_OID(oid): + x = map(int, oid.split('.')) + s = chr(x[0]*40 + x[1]) + for i in x[2:]: + ss = chr(i % 128) + while i > 128: + i = i / 128 + ss = chr(128 + i % 128) + ss + s += ss + return s + +def asn1_get_children(der, i): + nodes = [] + ii = asn1_node_first_child(der,i) + nodes.append(ii) + while ii[2]<i[2]: + ii = asn1_node_next(der,ii) + nodes.append(ii) + return nodes + +def asn1_get_sequence(s): + return map(lambda j: asn1_get_value(s, j), asn1_get_children(s, asn1_node_root(s))) + +def asn1_get_dict(der, i): + p = {} + for ii in asn1_get_children(der, i): + for iii in asn1_get_children(der, ii): + iiii = asn1_node_first_child(der, iii) + oid = decode_OID(asn1_get_value_of_type(der, iiii, 'OBJECT IDENTIFIER')) + iiii = asn1_node_next(der, iiii) + value = asn1_get_value(der, iiii) + p[oid] = value + return p diff --git a/lib/dnssec.py b/lib/dnssec.py @@ -57,16 +57,16 @@ from dns.exception import DNSException """ Pure-Python version of dns.dnssec._validate_rsig -Uses tlslite instead of PyCrypto """ + +import ecdsa +import rsakey + + def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None): from dns.dnssec import ValidationFailure, ECDSAP256SHA256, ECDSAP384SHA384 from dns.dnssec import _find_candidate_keys, _make_hash, _is_ecdsa, _is_rsa, _to_rdata, _make_algorithm_id - import ecdsa - from tlslite.utils.keyfactory import _createPublicRSAKey - from tlslite.utils.cryptomath import bytesToNumber - if isinstance(origin, (str, unicode)): origin = dns.name.from_text(origin, dns.name.root) @@ -101,9 +101,9 @@ def python_validate_rrsig(rrset, rrsig, keys, origin=None, now=None): keyptr = keyptr[2:] rsa_e = keyptr[0:bytes] rsa_n = keyptr[bytes:] - n = bytesToNumber(bytearray(rsa_n)) - e = bytesToNumber(bytearray(rsa_e)) - pubkey = _createPublicRSAKey(n, e) + n = ecdsa.util.string_to_number(rsa_n) + e = ecdsa.util.string_to_number(rsa_e) + pubkey = rsakey.RSAKey(n, e) sig = rrsig.signature elif _is_ecdsa(rrsig.algorithm): diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py @@ -25,6 +25,7 @@ import threading import time import traceback import urlparse +import json import requests try: @@ -34,9 +35,11 @@ except ImportError: import bitcoin import util +from util import print_error import transaction import x509 -from util import print_error +import rsakey + REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'} ACK_HEADERS = {'Content-Type':'application/bitcoin-payment','Accept':'application/bitcoin-paymentack','User-Agent':'Electrum'} @@ -52,7 +55,6 @@ PR_UNKNOWN = 2 # sent but not propagated PR_PAID = 3 # send and propagated PR_ERROR = 4 # could not parse -import json def get_payment_request(url): @@ -163,7 +165,9 @@ class PaymentRequest: prev_x = x509_chain[i-1] algo, sig, data = prev_x.get_signature() sig = bytearray(sig) - pubkey = x.publicKey + + pubkey = rsakey.RSAKey(x.modulus, x.exponent) + if algo == x509.ALGO_RSA_SHA1: verify = pubkey.hashAndVerify(sig, data) elif algo == x509.ALGO_RSA_SHA256: @@ -183,7 +187,8 @@ class PaymentRequest: self.error = "Certificate not Signed by Provided CA Certificate Chain" return False # verify the BIP70 signature - pubkey0 = x509_chain[0].publicKey + x = x509_chain[0] + pubkey0 = rsakey.RSAKey(x.modulus, x.exponent) sig = paymntreq.signature paymntreq.signature = '' s = paymntreq.SerializeToString() @@ -324,20 +329,22 @@ def sign_request_with_alias(pr, alias, alias_privkey): pr.signature = ec_key.sign_message(message, compressed, address) + def sign_request_with_x509(pr, key_path, cert_path): - import tlslite + import pem with open(key_path, 'r') as f: - rsakey = tlslite.utils.python_rsakey.Python_RSAKey.parsePEM(f.read()) + params = pem.parse_private_key(f.read()) + privkey = rsakey.RSAKey(*params) with open(cert_path, 'r') as f: - chain = tlslite.X509CertChain() - chain.parsePemList(f.read()) + s = f.read() + bList = pem.dePemList(s, "CERTIFICATE") certificates = pb2.X509Certificates() - certificates.certificate.extend(map(lambda x: str(x.bytes), chain.x509List)) + certificates.certificate.extend(map(str, bList)) pr.pki_type = 'x509+sha256' pr.pki_data = certificates.SerializeToString() msgBytes = bytearray(pr.SerializeToString()) hashBytes = bytearray(hashlib.sha256(msgBytes).digest()) - sig = rsakey.sign(x509.PREFIX_RSA_SHA256 + hashBytes) + sig = privkey.sign(x509.PREFIX_RSA_SHA256 + hashBytes) pr.signature = bytes(sig) @@ -362,8 +369,6 @@ def make_request(config, req): - - class InvoiceStore(object): def __init__(self, config): diff --git a/lib/pem.py b/lib/pem.py @@ -0,0 +1,164 @@ +# This module uses code from TLSLlite +# TLSLite Author: Trevor Perrin) + + +import binascii + +from asn1tinydecoder import * + + +def a2b_base64(s): + try: + b = bytearray(binascii.a2b_base64(s)) + except Exception as e: + raise SyntaxError("base64 error: %s" % e) + return b + +def b2a_base64(b): + return binascii.b2a_base64(b) + + +def dePem(s, name): + """Decode a PEM string into a bytearray of its payload. + + The input must contain an appropriate PEM prefix and postfix + based on the input name string, e.g. for name="CERTIFICATE": + + -----BEGIN CERTIFICATE----- + MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL + ... + KoZIhvcNAQEFBQADAwA5kw== + -----END CERTIFICATE----- + + The first such PEM block in the input will be found, and its + payload will be base64 decoded and returned. + """ + prefix = "-----BEGIN %s-----" % name + postfix = "-----END %s-----" % name + start = s.find(prefix) + if start == -1: + raise SyntaxError("Missing PEM prefix") + end = s.find(postfix, start+len(prefix)) + if end == -1: + raise SyntaxError("Missing PEM postfix") + s = s[start+len("-----BEGIN %s-----" % name) : end] + retBytes = a2b_base64(s) # May raise SyntaxError + return retBytes + +def dePemList(s, name): + """Decode a sequence of PEM blocks into a list of bytearrays. + + The input must contain any number of PEM blocks, each with the appropriate + PEM prefix and postfix based on the input name string, e.g. for + name="TACK BREAK SIG". Arbitrary text can appear between and before and + after the PEM blocks. For example: + + " Created by TACK.py 0.9.3 Created at 2012-02-01T00:30:10Z -----BEGIN TACK + BREAK SIG----- + ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv + YMEBdw69PUP8JB4AdqA3K6Ap0Fgd9SSTOECeAKOUAym8zcYaXUwpk0+WuPYa7Zmm + SkbOlK4ywqt+amhWbg9txSGUwFO5tWUHT3QrnRlE/e3PeNFXLx5Bckg= -----END TACK + BREAK SIG----- Created by TACK.py 0.9.3 Created at 2012-02-01T00:30:11Z + -----BEGIN TACK BREAK SIG----- + ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv + YMEBdw69PUP8JB4AdqA3K6BVCWfcjN36lx6JwxmZQncS6sww7DecFO/qjSePCxwM + +kdDqX/9/183nmjx6bf0ewhPXkA0nVXsDYZaydN8rJU1GaMlnjcIYxY= -----END TACK + BREAK SIG----- " + + All such PEM blocks will be found, decoded, and return in an ordered list + of bytearrays, which may have zero elements if not PEM blocks are found. + """ + bList = [] + prefix = "-----BEGIN %s-----" % name + postfix = "-----END %s-----" % name + while 1: + start = s.find(prefix) + if start == -1: + return bList + end = s.find(postfix, start+len(prefix)) + if end == -1: + raise SyntaxError("Missing PEM postfix") + s2 = s[start+len(prefix) : end] + retBytes = a2b_base64(s2) # May raise SyntaxError + bList.append(retBytes) + s = s[end+len(postfix) : ] + +def pem(b, name): + """Encode a payload bytearray into a PEM string. + + The input will be base64 encoded, then wrapped in a PEM prefix/postfix + based on the name string, e.g. for name="CERTIFICATE": + + -----BEGIN CERTIFICATE----- + MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL + ... + KoZIhvcNAQEFBQADAwA5kw== + -----END CERTIFICATE----- + """ + s1 = b2a_base64(b)[:-1] # remove terminating \n + s2 = "" + while s1: + s2 += s1[:64] + "\n" + s1 = s1[64:] + s = ("-----BEGIN %s-----\n" % name) + s2 + \ + ("-----END %s-----\n" % name) + return s + +def pemSniff(inStr, name): + searchStr = "-----BEGIN %s-----" % name + return searchStr in inStr + + +def parse_private_key(s): + """Parse a string containing a PEM-encoded <privateKey>.""" + if pemSniff(s, "PRIVATE KEY"): + bytes = dePem(s, "PRIVATE KEY") + return _parsePKCS8(bytes) + elif pemSniff(s, "RSA PRIVATE KEY"): + bytes = dePem(s, "RSA PRIVATE KEY") + return _parseSSLeay(bytes) + else: + raise SyntaxError("Not a PEM private key file") + + +def _parsePKCS8(bytes): + s = str(bytes) + root = asn1_node_root(s) + version_node = asn1_node_first_child(s, root) + version = bytestr_to_int(asn1_get_value_of_type(s, version_node, 'INTEGER')) + if version != 0: + raise SyntaxError("Unrecognized PKCS8 version") + rsaOID_node = asn1_node_next(s, version_node) + ii = asn1_node_first_child(s, rsaOID_node) + rsaOID = decode_OID(asn1_get_value_of_type(s, ii, 'OBJECT IDENTIFIER')) + if rsaOID != '1.2.840.113549.1.1.1': + raise SyntaxError("Unrecognized AlgorithmIdentifier") + privkey_node = asn1_node_next(s, rsaOID_node) + value = asn1_get_value_of_type(s, privkey_node, 'OCTET STRING') + return _parseASN1PrivateKey(value) + + +def _parseSSLeay(bytes): + return _parseASN1PrivateKey(str(bytes)) + + +def bytesToNumber(s): + return int(binascii.hexlify(s), 16) + + +def _parseASN1PrivateKey(s): + root = asn1_node_root(s) + version_node = asn1_node_first_child(s, root) + version = bytestr_to_int(asn1_get_value_of_type(s, version_node, 'INTEGER')) + if version != 0: + raise SyntaxError("Unrecognized RSAPrivateKey version") + n = asn1_node_next(s, version_node) + e = asn1_node_next(s, n) + d = asn1_node_next(s, e) + p = asn1_node_next(s, d) + q = asn1_node_next(s, p) + dP = asn1_node_next(s, q) + dQ = asn1_node_next(s, dP) + qInv = asn1_node_next(s, dQ) + return map(lambda x: bytesToNumber(asn1_get_value_of_type(s, x, 'INTEGER')), [n, e, d, p, q, dP, dQ, qInv]) + diff --git a/lib/rsakey.py b/lib/rsakey.py @@ -0,0 +1,517 @@ +# This module uses functions from TLSLite (public domain) +# +# TLSLite Authors: +# Trevor Perrin +# Martin von Loewis - python 3 port +# Yngve Pettersen (ported by Paul Sokolovsky) - TLS 1.2 +# + +"""Pure-Python RSA implementation.""" + + +from __future__ import print_function +import os +import math +import base64 +import binascii + +from pem import * + + +# ************************************************************************** +# PRNG Functions +# ************************************************************************** + +# Check that os.urandom works +import zlib +length = len(zlib.compress(os.urandom(1000))) +assert(length > 900) + +def getRandomBytes(howMany): + b = bytearray(os.urandom(howMany)) + assert(len(b) == howMany) + return b + +prngName = "os.urandom" + + +# ************************************************************************** +# Converter Functions +# ************************************************************************** + +def bytesToNumber(b): + total = 0 + multiplier = 1 + for count in range(len(b)-1, -1, -1): + byte = b[count] + total += multiplier * byte + multiplier *= 256 + return total + +def numberToByteArray(n, howManyBytes=None): + """Convert an integer into a bytearray, zero-pad to howManyBytes. + + The returned bytearray may be smaller than howManyBytes, but will + not be larger. The returned bytearray will contain a big-endian + encoding of the input integer (n). + """ + if howManyBytes == None: + howManyBytes = numBytes(n) + b = bytearray(howManyBytes) + for count in range(howManyBytes-1, -1, -1): + b[count] = int(n % 256) + n >>= 8 + return b + +def mpiToNumber(mpi): #mpi is an openssl-format bignum string + if (ord(mpi[4]) & 0x80) !=0: #Make sure this is a positive number + raise AssertionError() + b = bytearray(mpi[4:]) + return bytesToNumber(b) + +def numberToMPI(n): + b = numberToByteArray(n) + ext = 0 + #If the high-order bit is going to be set, + #add an extra byte of zeros + if (numBits(n) & 0x7)==0: + ext = 1 + length = numBytes(n) + ext + b = bytearray(4+ext) + b + b[0] = (length >> 24) & 0xFF + b[1] = (length >> 16) & 0xFF + b[2] = (length >> 8) & 0xFF + b[3] = length & 0xFF + return bytes(b) + + +# ************************************************************************** +# Misc. Utility Functions +# ************************************************************************** + +def numBits(n): + if n==0: + return 0 + s = "%x" % n + return ((len(s)-1)*4) + \ + {'0':0, '1':1, '2':2, '3':2, + '4':3, '5':3, '6':3, '7':3, + '8':4, '9':4, 'a':4, 'b':4, + 'c':4, 'd':4, 'e':4, 'f':4, + }[s[0]] + return int(math.floor(math.log(n, 2))+1) + +def numBytes(n): + if n==0: + return 0 + bits = numBits(n) + return int(math.ceil(bits / 8.0)) + +# ************************************************************************** +# Big Number Math +# ************************************************************************** + +def getRandomNumber(low, high): + if low >= high: + raise AssertionError() + howManyBits = numBits(high) + howManyBytes = numBytes(high) + lastBits = howManyBits % 8 + while 1: + bytes = getRandomBytes(howManyBytes) + if lastBits: + bytes[0] = bytes[0] % (1 << lastBits) + n = bytesToNumber(bytes) + if n >= low and n < high: + return n + +def gcd(a,b): + a, b = max(a,b), min(a,b) + while b: + a, b = b, a % b + return a + +def lcm(a, b): + return (a * b) // gcd(a, b) + +#Returns inverse of a mod b, zero if none +#Uses Extended Euclidean Algorithm +def invMod(a, b): + c, d = a, b + uc, ud = 1, 0 + while c != 0: + q = d // c + c, d = d-(q*c), c + uc, ud = ud - (q * uc), uc + if d == 1: + return ud % b + return 0 + + +def powMod(base, power, modulus): + if power < 0: + result = pow(base, power*-1, modulus) + result = invMod(result, modulus) + return result + else: + return pow(base, power, modulus) + +#Pre-calculate a sieve of the ~100 primes < 1000: +def makeSieve(n): + sieve = list(range(n)) + for count in range(2, int(math.sqrt(n))+1): + if sieve[count] == 0: + continue + x = sieve[count] * 2 + while x < len(sieve): + sieve[x] = 0 + x += sieve[count] + sieve = [x for x in sieve[2:] if x] + return sieve + +sieve = makeSieve(1000) + +def isPrime(n, iterations=5, display=False): + #Trial division with sieve + for x in sieve: + if x >= n: return True + if n % x == 0: return False + #Passed trial division, proceed to Rabin-Miller + #Rabin-Miller implemented per Ferguson & Schneier + #Compute s, t for Rabin-Miller + if display: print("*", end=' ') + s, t = n-1, 0 + while s % 2 == 0: + s, t = s//2, t+1 + #Repeat Rabin-Miller x times + a = 2 #Use 2 as a base for first iteration speedup, per HAC + for count in range(iterations): + v = powMod(a, s, n) + if v==1: + continue + i = 0 + while v != n-1: + if i == t-1: + return False + else: + v, i = powMod(v, 2, n), i+1 + a = getRandomNumber(2, n) + return True + +def getRandomPrime(bits, display=False): + if bits < 10: + raise AssertionError() + #The 1.5 ensures the 2 MSBs are set + #Thus, when used for p,q in RSA, n will have its MSB set + # + #Since 30 is lcm(2,3,5), we'll set our test numbers to + #29 % 30 and keep them there + low = ((2 ** (bits-1)) * 3) // 2 + high = 2 ** bits - 30 + p = getRandomNumber(low, high) + p += 29 - (p % 30) + while 1: + if display: print(".", end=' ') + p += 30 + if p >= high: + p = getRandomNumber(low, high) + p += 29 - (p % 30) + if isPrime(p, display=display): + return p + +#Unused at the moment... +def getRandomSafePrime(bits, display=False): + if bits < 10: + raise AssertionError() + #The 1.5 ensures the 2 MSBs are set + #Thus, when used for p,q in RSA, n will have its MSB set + # + #Since 30 is lcm(2,3,5), we'll set our test numbers to + #29 % 30 and keep them there + low = (2 ** (bits-2)) * 3//2 + high = (2 ** (bits-1)) - 30 + q = getRandomNumber(low, high) + q += 29 - (q % 30) + while 1: + if display: print(".", end=' ') + q += 30 + if (q >= high): + q = getRandomNumber(low, high) + q += 29 - (q % 30) + #Ideas from Tom Wu's SRP code + #Do trial division on p and q before Rabin-Miller + if isPrime(q, 0, display=display): + p = (2 * q) + 1 + if isPrime(p, display=display): + if isPrime(q, display=display): + return p + + +class RSAKey(object): + + def __init__(self, n=0, e=0, d=0, p=0, q=0, dP=0, dQ=0, qInv=0): + if (n and not e) or (e and not n): + raise AssertionError() + self.n = n + self.e = e + self.d = d + self.p = p + self.q = q + self.dP = dP + self.dQ = dQ + self.qInv = qInv + self.blinder = 0 + self.unblinder = 0 + + def __len__(self): + """Return the length of this key in bits. + + @rtype: int + """ + return numBits(self.n) + + def hasPrivateKey(self): + return self.d != 0 + + def hashAndSign(self, bytes): + """Hash and sign the passed-in bytes. + + This requires the key to have a private component. It performs + a PKCS1-SHA1 signature on the passed-in data. + + @type bytes: str or L{bytearray} of unsigned bytes + @param bytes: The value which will be hashed and signed. + + @rtype: L{bytearray} of unsigned bytes. + @return: A PKCS1-SHA1 signature on the passed-in data. + """ + hashBytes = SHA1(bytearray(bytes)) + prefixedHashBytes = self._addPKCS1SHA1Prefix(hashBytes) + sigBytes = self.sign(prefixedHashBytes) + return sigBytes + + def hashAndVerify(self, sigBytes, bytes): + """Hash and verify the passed-in bytes with the signature. + + This verifies a PKCS1-SHA1 signature on the passed-in data. + + @type sigBytes: L{bytearray} of unsigned bytes + @param sigBytes: A PKCS1-SHA1 signature. + + @type bytes: str or L{bytearray} of unsigned bytes + @param bytes: The value which will be hashed and verified. + + @rtype: bool + @return: Whether the signature matches the passed-in data. + """ + hashBytes = SHA1(bytearray(bytes)) + + # Try it with/without the embedded NULL + prefixedHashBytes1 = self._addPKCS1SHA1Prefix(hashBytes, False) + prefixedHashBytes2 = self._addPKCS1SHA1Prefix(hashBytes, True) + result1 = self.verify(sigBytes, prefixedHashBytes1) + result2 = self.verify(sigBytes, prefixedHashBytes2) + return (result1 or result2) + + def sign(self, bytes): + """Sign the passed-in bytes. + + This requires the key to have a private component. It performs + a PKCS1 signature on the passed-in data. + + @type bytes: L{bytearray} of unsigned bytes + @param bytes: The value which will be signed. + + @rtype: L{bytearray} of unsigned bytes. + @return: A PKCS1 signature on the passed-in data. + """ + if not self.hasPrivateKey(): + raise AssertionError() + paddedBytes = self._addPKCS1Padding(bytes, 1) + m = bytesToNumber(paddedBytes) + if m >= self.n: + raise ValueError() + c = self._rawPrivateKeyOp(m) + sigBytes = numberToByteArray(c, numBytes(self.n)) + return sigBytes + + def verify(self, sigBytes, bytes): + """Verify the passed-in bytes with the signature. + + This verifies a PKCS1 signature on the passed-in data. + + @type sigBytes: L{bytearray} of unsigned bytes + @param sigBytes: A PKCS1 signature. + + @type bytes: L{bytearray} of unsigned bytes + @param bytes: The value which will be verified. + + @rtype: bool + @return: Whether the signature matches the passed-in data. + """ + if len(sigBytes) != numBytes(self.n): + return False + paddedBytes = self._addPKCS1Padding(bytes, 1) + c = bytesToNumber(sigBytes) + if c >= self.n: + return False + m = self._rawPublicKeyOp(c) + checkBytes = numberToByteArray(m, numBytes(self.n)) + return checkBytes == paddedBytes + + def encrypt(self, bytes): + """Encrypt the passed-in bytes. + + This performs PKCS1 encryption of the passed-in data. + + @type bytes: L{bytearray} of unsigned bytes + @param bytes: The value which will be encrypted. + + @rtype: L{bytearray} of unsigned bytes. + @return: A PKCS1 encryption of the passed-in data. + """ + paddedBytes = self._addPKCS1Padding(bytes, 2) + m = bytesToNumber(paddedBytes) + if m >= self.n: + raise ValueError() + c = self._rawPublicKeyOp(m) + encBytes = numberToByteArray(c, numBytes(self.n)) + return encBytes + + def decrypt(self, encBytes): + """Decrypt the passed-in bytes. + + This requires the key to have a private component. It performs + PKCS1 decryption of the passed-in data. + + @type encBytes: L{bytearray} of unsigned bytes + @param encBytes: The value which will be decrypted. + + @rtype: L{bytearray} of unsigned bytes or None. + @return: A PKCS1 decryption of the passed-in data or None if + the data is not properly formatted. + """ + if not self.hasPrivateKey(): + raise AssertionError() + if len(encBytes) != numBytes(self.n): + return None + c = bytesToNumber(encBytes) + if c >= self.n: + return None + m = self._rawPrivateKeyOp(c) + decBytes = numberToByteArray(m, numBytes(self.n)) + #Check first two bytes + if decBytes[0] != 0 or decBytes[1] != 2: + return None + #Scan through for zero separator + for x in range(1, len(decBytes)-1): + if decBytes[x]== 0: + break + else: + return None + return decBytes[x+1:] #Return everything after the separator + + + + + # ************************************************************************** + # Helper Functions for RSA Keys + # ************************************************************************** + + def _addPKCS1SHA1Prefix(self, bytes, withNULL=True): + # There is a long history of confusion over whether the SHA1 + # algorithmIdentifier should be encoded with a NULL parameter or + # with the parameter omitted. While the original intention was + # apparently to omit it, many toolkits went the other way. TLS 1.2 + # specifies the NULL should be included, and this behavior is also + # mandated in recent versions of PKCS #1, and is what tlslite has + # always implemented. Anyways, verification code should probably + # accept both. However, nothing uses this code yet, so this is + # all fairly moot. + if not withNULL: + prefixBytes = bytearray(\ + [0x30,0x1f,0x30,0x07,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x04,0x14]) + else: + prefixBytes = bytearray(\ + [0x30,0x21,0x30,0x09,0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a,0x05,0x00,0x04,0x14]) + prefixedBytes = prefixBytes + bytes + return prefixedBytes + + def _addPKCS1Padding(self, bytes, blockType): + padLength = (numBytes(self.n) - (len(bytes)+3)) + if blockType == 1: #Signature padding + pad = [0xFF] * padLength + elif blockType == 2: #Encryption padding + pad = bytearray(0) + while len(pad) < padLength: + padBytes = getRandomBytes(padLength * 2) + pad = [b for b in padBytes if b != 0] + pad = pad[:padLength] + else: + raise AssertionError() + + padding = bytearray([0,blockType] + pad + [0]) + paddedBytes = padding + bytes + return paddedBytes + + + + + def _rawPrivateKeyOp(self, m): + #Create blinding values, on the first pass: + if not self.blinder: + self.unblinder = getRandomNumber(2, self.n) + self.blinder = powMod(invMod(self.unblinder, self.n), self.e, + self.n) + + #Blind the input + m = (m * self.blinder) % self.n + + #Perform the RSA operation + c = self._rawPrivateKeyOpHelper(m) + + #Unblind the output + c = (c * self.unblinder) % self.n + + #Update blinding values + self.blinder = (self.blinder * self.blinder) % self.n + self.unblinder = (self.unblinder * self.unblinder) % self.n + + #Return the output + return c + + + def _rawPrivateKeyOpHelper(self, m): + #Non-CRT version + #c = powMod(m, self.d, self.n) + + #CRT version (~3x faster) + s1 = powMod(m, self.dP, self.p) + s2 = powMod(m, self.dQ, self.q) + h = ((s1 - s2) * self.qInv) % self.p + c = s2 + self.q * h + return c + + def _rawPublicKeyOp(self, c): + m = powMod(c, self.e, self.n) + return m + + def acceptsPassword(self): + return False + + def generate(bits): + key = Python_RSAKey() + p = getRandomPrime(bits//2, False) + q = getRandomPrime(bits//2, False) + t = lcm(p-1, q-1) + key.n = p * q + key.e = 65537 + key.d = invMod(key.e, t) + key.p = p + key.q = q + key.dP = key.d % (p-1) + key.dQ = key.d % (q-1) + key.qInv = invMod(q, p) + return key + generate = staticmethod(generate) + diff --git a/lib/x509.py b/lib/x509.py @@ -20,17 +20,12 @@ from datetime import datetime import sys -import tlslite import util from util import profiler, print_error -from asn1tinydecoder import asn1_node_root, asn1_get_all, asn1_get_value, \ - asn1_get_value_of_type, asn1_node_next, asn1_node_first_child, \ - asn1_read_length, asn1_node_is_child_of, \ - bytestr_to_int, bitstr_to_bytestr - -# workaround https://github.com/trevp/tlslite/issues/15 -tlslite.utils.cryptomath.pycryptoLoaded = False +from asn1tinydecoder import * +import ecdsa +import hashlib # algo OIDs @@ -50,61 +45,13 @@ class CertificateError(Exception): pass -def decode_OID(s): - s = map(ord, s) - r = [] - r.append(s[0] / 40) - r.append(s[0] % 40) - k = 0 - for i in s[1:]: - if i < 128: - r.append(i + 128*k) - k = 0 - else: - k = (i - 128) + 128*k - return '.'.join(map(str, r)) - -def encode_OID(oid): - x = map(int, oid.split('.')) - s = chr(x[0]*40 + x[1]) - for i in x[2:]: - ss = chr(i % 128) - while i > 128: - i = i / 128 - ss = chr(128 + i % 128) + ss - s += ss - return s - -def asn1_get_children(der, i): - nodes = [] - ii = asn1_node_first_child(der,i) - nodes.append(ii) - while ii[2]<i[2]: - ii = asn1_node_next(der,ii) - nodes.append(ii) - return nodes - -def asn1_get_sequence(s): - return map(lambda j: asn1_get_value(s, j), asn1_get_children(s, asn1_node_root(s))) - -def asn1_get_dict(der, i): - p = {} - for ii in asn1_get_children(der, i): - for iii in asn1_get_children(der, ii): - iiii = asn1_node_first_child(der, iii) - oid = decode_OID(asn1_get_value_of_type(der, iiii, 'OBJECT IDENTIFIER')) - iiii = asn1_node_next(der, iiii) - value = asn1_get_value(der, iiii) - p[oid] = value - return p -class X509(tlslite.X509): +class X509(object): def parseBinary(self, b): - # call tlslite method first - tlslite.X509.parseBinary(self, b) + self.bytes = bytearray(b) der = str(b) root = asn1_node_root(der) @@ -139,8 +86,25 @@ class X509(tlslite.X509): # subject subject = asn1_node_next(der, validity) self.subject = asn1_get_dict(der, subject) + subject_pki = asn1_node_next(der, subject) + public_key_algo = asn1_node_first_child(der, subject_pki) + ii = asn1_node_first_child(der, public_key_algo) + self.public_key_algo = decode_OID(asn1_get_value_of_type(der, ii, 'OBJECT IDENTIFIER')) + + # pubkey modulus and exponent + subject_public_key = asn1_node_next(der, public_key_algo) + spk = asn1_get_value_of_type(der, subject_public_key, 'BIT STRING') + spk = bitstr_to_bytestr(spk) + r = asn1_node_root(spk) + modulus = asn1_node_first_child(spk, r) + exponent = asn1_node_next(spk, modulus) + rsa_n = asn1_get_value_of_type(spk, modulus, 'INTEGER') + rsa_e = asn1_get_value_of_type(spk, exponent, 'INTEGER') + self.modulus = ecdsa.util.string_to_number(rsa_n) + self.exponent = ecdsa.util.string_to_number(rsa_e) + # extensions self.CA = False self.AKI = None @@ -198,10 +162,8 @@ class X509(tlslite.X509): if not_after <= now: raise CertificateError('Certificate has expired.') - - -class X509CertChain(tlslite.X509CertChain): - pass + def getFingerprint(self): + return hashlib.sha1(self.bytes).digest() @@ -209,11 +171,12 @@ class X509CertChain(tlslite.X509CertChain): @profiler def load_certificates(ca_path): + import pem ca_list = {} ca_keyID = {} with open(ca_path, 'r') as f: s = f.read() - bList = tlslite.utils.pem.dePemList(s, "CERTIFICATE") + bList = pem.dePemList(s, "CERTIFICATE") for b in bList: x = X509() try: @@ -238,7 +201,6 @@ def int_to_bytestr(i): return s def create_csr(commonName, challenge, k): - import ecdsa, hashlib from bitcoin import point_to_ser private_key = ecdsa.SigningKey.from_string(k, curve = ecdsa.SECP256k1) public_key = private_key.get_verifying_key() diff --git a/setup.py b/setup.py @@ -35,7 +35,6 @@ setup( 'requests', 'qrcode', 'protobuf', - 'tlslite', 'dnspython', ], package_dir={