summaryrefslogtreecommitdiff
path: root/lib/glider/keys.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/glider/keys.py')
-rw-r--r--lib/glider/keys.py232
1 files changed, 174 insertions, 58 deletions
diff --git a/lib/glider/keys.py b/lib/glider/keys.py
index e2b2736..fefff6c 100644
--- a/lib/glider/keys.py
+++ b/lib/glider/keys.py
@@ -4,25 +4,23 @@ import Crypto.PublicKey.RSA
import Crypto.Hash.SHA256
import Crypto.Cipher.AES
-import sexp.access
-import sexp.encode
-import sexp.parse
-
import cPickle as pickle
import binascii
+import logging
import os
import struct
+import sys
+import simplejson
+import getpass
-class CryptoError(Exception):
- pass
-
-class PubkeyFormatException(Exception):
- pass
-
-class UnknownMethod(Exception):
- pass
+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):
@@ -34,7 +32,17 @@ class PublicKey:
def getKeyID(self):
raise NotImplemented()
def getRoles(self):
- raise NotImplemented()
+ 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):
@@ -64,8 +72,13 @@ def binaryToInt(binary):
"""
return long(binascii.b2a_hex(binary), 16)
-def _pkcs1_padding(m, size):
+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."
@@ -83,25 +96,28 @@ def _xor(a,b):
class RSAKey(PublicKey):
"""
>>> k = RSAKey.generate(bits=512)
- >>> sexpr = k.format()
- >>> sexpr[:2]
- ('pubkey', [('type', 'rsa')])
- >>> k1 = RSAKey.fromSExpression(sexpr)
+ >>> 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 = ['tag1', ['foobar'], [['foop', 'bar']], 'baz']
- >>> method, sig = k.sign(sexpr=s)
- >>> k.checkSignature(method, sig, sexpr=s)
+ >>> 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, sexpr=s2)
+ >>> k.checkSignature(method, sig, obj=s2)
False
"""
def __init__(self, key):
+ PublicKey.__init__(self)
self.key = key
self.keyid = None
@@ -111,58 +127,79 @@ class RSAKey(PublicKey):
return RSAKey(key)
@staticmethod
- def fromSExpression(sexpr):
- # sexpr must match PUBKEY_SCHEMA
- typeattr = sexp.access.s_attr(sexpr[1], "type")
- if typeattr != "rsa":
- return None
- if len(sexpr[2]) != 2:
- raise PubkeyFormatException("RSA keys must have an e,n pair")
- e,n = sexpr[2]
- key = Crypto.PublicKey.RSA.construct((binaryToInt(n), binaryToInt(e)))
- return RSAKey(key)
-
- def format(self):
- n = intToBinary(self.key.n)
- e = intToBinary(self.key.e)
- return ("pubkey", [("type", "rsa")], (e, n))
+ 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:
- n = intToBinary(self.key.n)
- e = intToBinary(self.key.e)
- keyval = (e,n)
d_obj = Crypto.Hash.SHA256.new()
- sexp.encode.hash_canonical(keyval, d_obj)
- self.keyid = ("rsa", d_obj.digest())
+ glider.formats.getDigest(self.format(), d_obj)
+ self.keyid = glider.formats.formatHash(d_obj.digest())
return self.keyid
- def _digest(self, sexpr, method=None):
+ def _digest(self, obj, method=None):
if method in (None, "sha256-pkcs1"):
d_obj = Crypto.Hash.SHA256.new()
- sexp.encode.hash_canonical(sexpr, d_obj)
+ glider.formats.getDigest(obj, d_obj)
digest = d_obj.digest()
return ("sha256-pkcs1", digest)
raise UnknownMethod(method)
- def sign(self, sexpr=None, digest=None):
- assert _xor(sexpr == None, digest == None)
+ def sign(self, obj=None, digest=None):
+ assert _xor(obj == None, digest == None)
if digest == None:
- method, digest = self._digest(sexpr)
+ method, digest = self._digest(obj)
m = _pkcs1_padding(digest, (self.key.size()+1) // 8)
- sig = intToBinary(self.key.sign(m, "")[0])
+ sig = intToBase64(self.key.sign(m, "")[0])
return (method, sig)
- def checkSignature(self, method, sig, sexpr=None, digest=None):
- assert _xor(sexpr == None, digest == None)
+ 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(sexpr, method)
- sig = binaryToInt(sig)
+ method, digest = self._digest(obj, method)
+ sig = base64ToInt(sig)
m = _pkcs1_padding(digest, (self.key.size()+1) // 8)
- return self.key.verify(m, (sig,))
+ return bool(self.key.verify(m, (sig,)))
SALTLEN=16
@@ -244,15 +281,21 @@ def encryptSecret(secret, password, difficulty=0x80):
pad = '\x00' * padlen
slen = struct.pack("!L",len(secret))
- encrypted = e.encrypt("%s%s%s%s" % (slen, secret, d, pad))[:-padlen]
+ 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 UnknownFormat()
+ raise glider.UnknownFormat()
encrypted = encrypted[5:]
if len(encrypted) < SALTLEN+1+16:
- raise FormatError()
+ raise glider.FormatException()
salt = encrypted[:SALTLEN+1]
iv = encrypted[SALTLEN+1:SALTLEN+1+16]
@@ -276,8 +319,81 @@ def decryptSecret(encrypted, password):
d.update(salt)
if d.digest() != hash:
- print repr(decrypted)
- raise BadPassword()
+ 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.")
+
+