summaryrefslogtreecommitdiff
path: root/lib/glider/keys.py
diff options
context:
space:
mode:
authorNick Mathewson <nickm@torproject.org>2008-10-14 05:10:30 +0000
committerNick Mathewson <nickm@torproject.org>2008-10-14 05:10:30 +0000
commitd90990ee3ecd09a2725b8051759a900ebd488b8c (patch)
treefbe68d0c4b698d45bfdcb6c91a43ea2e60c21329 /lib/glider/keys.py
parentfb5a6115a6f3ea0216e3ca0645ba1eb31fb02876 (diff)
Rename glider to thandy, based on discussions on #nottor. Please let me know ASAP if there is another program Thandy, or if it means something rude, or whatever.
git-svn-id: file:///home/or/svnrepo/updater/trunk@17085 55e972cd-5a19-0410-ae62-a4d7a52db4cd
Diffstat (limited to 'lib/glider/keys.py')
-rw-r--r--lib/glider/keys.py399
1 files changed, 0 insertions, 399 deletions
diff --git a/lib/glider/keys.py b/lib/glider/keys.py
deleted file mode 100644
index fefff6c..0000000
--- a/lib/glider/keys.py
+++ /dev/null
@@ -1,399 +0,0 @@
-
-# These require PyCrypto.
-import Crypto.PublicKey.RSA
-import Crypto.Hash.SHA256
-import Crypto.Cipher.AES
-
-import cPickle as pickle
-import binascii
-import logging
-import os
-import struct
-import sys
-import simplejson
-import getpass
-
-import glider.formats
-import glider.util
-
-class PublicKey:
- def __init__(self):
- # Confusingly, these roles are the ones used for a private key to
- # remember what we're willing to do with it.
- self._roles = []
- def format(self):
- raise NotImplemented()
- def sign(self, data):
- # returns a list of method,signature tuples.
- raise NotImplemented()
- def checkSignature(self, method, data, signature):
- # returns True, False, or raises UnknownMethod.
- raise NotImplemented()
- def getKeyID(self):
- raise NotImplemented()
- def getRoles(self):
- return self._roles
- def addRole(self, role, path):
- assert role in glider.formats.ALL_ROLES
- self._roles.append((role, path))
- def clearRoles(self):
- del self._roles[:]
- def hasRole(self, role, path):
- for r, p in self._roles:
- if r == role and glider.formats.rolePathMatches(p, path):
- return True
- return False
-
-if hex(1L).upper() == "0X1L":
- def intToBinary(number):
- """Convert an int or long into a big-endian series of bytes.
- """
- # This "convert-to-hex, then use binascii" approach may look silly,
- # but it's over 10x faster than the Crypto.Util.number approach.
- h = hex(long(number))
- h = h[2:-1]
- if len(h)%2:
- h = "0"+h
- return binascii.a2b_hex(h)
-elif hex(1L).upper() == "0X1":
- def intToBinary(number):
- h = hex(long(number))
- h = h[2:]
- if len(h)%2:
- h = "0"+h
- return binascii.a2b_hex(h)
-else:
- import Crypto.Util.number
- intToBinary = Crypto.Util.number.long_to_bytes
- assert None
-
-def binaryToInt(binary):
- """Convert a big-endian series of bytes into a long.
- """
- return long(binascii.b2a_hex(binary), 16)
-
-def intToBase64(number):
- return glider.formats.formatBase64(intToBinary(number))
-
-def base64ToInt(number):
- return binaryToInt(glider.formats.parseBase64(number))
-
-def _pkcs1_padding(m, size):
- # I'd rather use OAEP+, but apparently PyCrypto barely supports
- # signature verification, and doesn't seem to support signature
- # verification with nondeterministic padding. "argh."
-
- s = [ "\x00\x01", "\xff"* (size-3-len(m)), "\x00", m ]
- r = "".join(s)
- return r
-
-def _xor(a,b):
- if a:
- return not b
- else:
- return b
-
-class RSAKey(PublicKey):
- """
- >>> k = RSAKey.generate(bits=512)
- >>> obj = k.format()
- >>> obj['_keytype']
- 'rsa'
- >>> base64ToInt(obj['e'])
- 65537L
- >>> k1 = RSAKey.fromJSon(obj)
- >>> k1.key.e == k.key.e
- True
- >>> k1.key.n == k.key.n
- True
- >>> k.getKeyID() == k1.getKeyID()
- True
- >>> s = { 'A B C' : "D", "E" : [ "F", "g", 99] }
- >>> method, sig = k.sign(obj=s)
- >>> k.checkSignature(method, sig, obj=s)
- True
- >>> s2 = [ s ]
- >>> k.checkSignature(method, sig, obj=s2)
- False
- """
- def __init__(self, key):
- PublicKey.__init__(self)
- self.key = key
- self.keyid = None
-
- @staticmethod
- def generate(bits=2048):
- key = Crypto.PublicKey.RSA.generate(bits=bits, randfunc=os.urandom)
- return RSAKey(key)
-
- @staticmethod
- def fromJSon(obj):
- # obj must match RSAKEY_SCHEMA
-
- glider.formats.RSAKEY_SCHEMA.checkMatch(obj)
- n = base64ToInt(obj['n'])
- e = base64ToInt(obj['e'])
- if obj.has_key('d'):
- d = base64ToInt(obj['d'])
- p = base64ToInt(obj['p'])
- q = base64ToInt(obj['q'])
- u = base64ToInt(obj['u'])
- key = Crypto.PublicKey.RSA.construct((n, e, d, p, q, u))
- else:
- key = Crypto.PublicKey.RSA.construct((n, e))
-
- result = RSAKey(key)
- if obj.has_key('roles'):
- for r, p in obj['roles']:
- result.addRole(r,p)
-
- return result
-
- def isPrivateKey(self):
- return hasattr(self.key, 'd')
-
- def format(self, private=False, includeRoles=False):
- n = intToBase64(self.key.n)
- e = intToBase64(self.key.e)
- result = { '_keytype' : 'rsa',
- 'e' : e,
- 'n' : n }
- if private:
- result['d'] = intToBase64(self.key.d)
- result['p'] = intToBase64(self.key.p)
- result['q'] = intToBase64(self.key.q)
- result['u'] = intToBase64(self.key.u)
- if includeRoles:
- result['roles'] = self.getRoles()
- return result
-
- def getKeyID(self):
- if self.keyid == None:
- d_obj = Crypto.Hash.SHA256.new()
- glider.formats.getDigest(self.format(), d_obj)
- self.keyid = glider.formats.formatHash(d_obj.digest())
- return self.keyid
-
- def _digest(self, obj, method=None):
- if method in (None, "sha256-pkcs1"):
- d_obj = Crypto.Hash.SHA256.new()
- glider.formats.getDigest(obj, d_obj)
- digest = d_obj.digest()
- return ("sha256-pkcs1", digest)
-
- raise UnknownMethod(method)
-
- def sign(self, obj=None, digest=None):
- assert _xor(obj == None, digest == None)
- if digest == None:
- method, digest = self._digest(obj)
- m = _pkcs1_padding(digest, (self.key.size()+1) // 8)
- sig = intToBase64(self.key.sign(m, "")[0])
- return (method, sig)
-
- def checkSignature(self, method, sig, obj=None, digest=None):
- assert _xor(obj == None, digest == None)
- if method != "sha256-pkcs1":
- raise UnknownMethod("method")
- if digest == None:
- method, digest = self._digest(obj, method)
- sig = base64ToInt(sig)
- m = _pkcs1_padding(digest, (self.key.size()+1) // 8)
- return bool(self.key.verify(m, (sig,)))
-
-SALTLEN=16
-
-def secretToKey(salt, secret):
- """Convert 'secret' to a 32-byte key, using a version of the algorithm
- from RFC2440. The salt must be SALTLEN+1 bytes long, and should
- be random, except for the last byte, which encodes how time-
- consuming the computation should be.
-
- (The goal is to make offline password-guessing attacks harder by
- increasing the time required to convert a password to a key, and to
- make precomputed password tables impossible to generate by )
- """
- assert len(salt) == SALTLEN+1
-
- # The algorithm is basically, 'call the last byte of the salt the
- # "difficulty", and all other bytes of the salt S. Now make
- # an infinite stream of S|secret|S|secret|..., and hash the
- # first N bytes of that, where N is determined by the difficulty.
- #
- # Obviously, this wants a hash algorithm that's tricky to
- # parallelize.
- #
- # Unlike RFC2440, we use a 16-byte salt. Because CPU times
- # have improved, we start at 16 times the previous minimum.
-
- difficulty = ord(salt[-1])
- count = (16L+(difficulty & 15)) << ((difficulty >> 4) + 10)
-
- # Make 'data' nice and long, so that we don't need to call update()
- # a zillion times.
- data = salt[:-1]+secret
- if len(data)<1024:
- data *= (1024 // len(data))+1
-
- d = Crypto.Hash.SHA256.new()
- iters, leftover = divmod(count, len(data))
- for _ in xrange(iters):
- d.update(data)
- #count -= len(data)
- if leftover:
- d.update(data[:leftover])
- #count -= leftover
- #assert count == 0
-
- return d.digest()
-
-def encryptSecret(secret, password, difficulty=0x80):
- """Encrypt the secret 'secret' using the password 'password',
- and return the encrypted result."""
- # The encrypted format is:
- # "GKEY1" -- 5 octets, fixed, denotes data format.
- # SALT -- 17 bytes, used to hash password
- # IV -- 16 bytes; salt for encryption
- # ENCRYPTED IN AES256-OFB, using a key=s2k(password, salt) and IV=IV:
- # SLEN -- 4 bytes; length of secret, big-endian.
- # SECRET -- len(secret) bytes
- # D -- 32 bytes; SHA256 hash of (salt|secret|salt).
- #
- # This format leaks the secret length, obviously.
- assert 0 <= difficulty < 256
- salt = os.urandom(SALTLEN)+chr(difficulty)
- key = secretToKey(salt, password)
-
- d_obj = Crypto.Hash.SHA256.new()
- d_obj.update(salt)
- d_obj.update(secret)
- d_obj.update(salt)
- d = d_obj.digest()
-
- iv = os.urandom(16)
- e = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_OFB, iv)
-
- # Stupidly, pycrypto doesn't accept that stream ciphers don't need to
- # take their input in blocks. So pad it, then ignore the padded output.
-
- padlen = 16-((len(secret)+len(d)+4) % 16)
- if padlen == 16: padlen = 0
- pad = '\x00' * padlen
-
- slen = struct.pack("!L",len(secret))
- encrypted = e.encrypt("%s%s%s%s" % (slen, secret, d, pad))
- if padlen:
- encrypted = encrypted[:-padlen]
- return "GKEY1%s%s%s"%(salt, iv, encrypted)
-
-def decryptSecret(encrypted, password):
- """Decrypt a value encrypted with encryptSecret. Raises UnknownFormat
- or FormatError if 'encrypted' was not generated with encryptSecret.
- Raises BadPassword if the password was not correct.
- """
- if encrypted[:5] != "GKEY1":
- raise glider.UnknownFormat()
- encrypted = encrypted[5:]
- if len(encrypted) < SALTLEN+1+16:
- raise glider.FormatException()
-
- salt = encrypted[:SALTLEN+1]
- iv = encrypted[SALTLEN+1:SALTLEN+1+16]
- encrypted = encrypted[SALTLEN+1+16:]
-
- key = secretToKey(salt, password)
-
- e = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_OFB, iv)
- padlen = 16-(len(encrypted) % 16)
- if padlen == 16: padlen = 0
- pad = '\x00' * padlen
-
- decrypted = e.decrypt("%s%s"%(encrypted,pad))
- slen = struct.unpack("!L", decrypted[:4])[0]
- secret = decrypted[4:4+slen]
- hash = decrypted[4+slen:4+slen+Crypto.Hash.SHA256.digest_size]
-
- d = Crypto.Hash.SHA256.new()
- d.update(salt)
- d.update(secret)
- d.update(salt)
-
- if d.digest() != hash:
- raise glider.BadPassword()
-
- return secret
-
-class KeyStore(glider.formats.KeyDB):
- def __init__(self, fname, encrypted=True):
- glider.formats.KeyDB.__init__(self)
-
- self._loaded = None
- self._fname = fname
- self._passwd = None
- self._encrypted = encrypted
-
- def getpass(self, reprompt=False):
- if self._passwd != None:
- return self._passwd
- while 1:
- pwd = getpass.getpass("Password: ", sys.stderr)
- if not reprompt:
- return pwd
-
- pwd2 = getpass.getpass("Confirm: ", sys.stderr)
- if pwd == pwd2:
- return pwd
- else:
- print "Mismatch; try again."
-
- def load(self, password=None):
- logging.info("Loading private keys from %r...", self._fname)
- if not os.path.exists(self._fname):
- logging.info("...no such file.")
- self._loaded = True
- return
-
- if password is None and self._encrypted:
- password = self.getpass()
-
- contents = open(self._fname, 'rb').read()
- if self._encrypted:
- contents = decryptSecret(contents, password)
-
- listOfKeys = simplejson.loads(contents)
- self._passwd = password # It worked.
- if not listOfKeys.has_key('keys'):
- listOfKeys['keys'] = []
- for obj in listOfKeys['keys']:
- key = RSAKey.fromJSon(obj)
- self.addKey(key)
- logging.info("Loaded key %s", key.getKeyID())
-
- self._loaded = True
-
- def setPassword(self, passwd):
- self._passwd = passwd
-
- def clearPassword(self):
- self._passwd = None
-
- def save(self, password=None):
- if not self._loaded and self._encrypted:
- self.load(password)
-
- if password is None:
- password = self.getpass(True)
-
- logging.info("Saving private keys into %r...", self._fname)
- listOfKeys = { 'keys' :
- [ key.format(private=True, includeRoles=True) for key in
- self._keys.values() ]
- }
- contents = simplejson.dumps(listOfKeys)
- if self._encrypted:
- contents = encryptSecret(contents, password)
- glider.util.replaceFile(self._fname, contents)
- self._passwd = password # It worked.
- logging.info("Done.")
-
-