Skip to content
Permalink
Browse files

[backend/crypto] simplify crypto API and remove unused bits

  • Loading branch information...
geertj committed Jan 12, 2014
1 parent 0b387ee commit 11b1c98ed6864a5c8df4782f105b8c93df8c7804
Showing with 279 additions and 765 deletions.
  1. +29 −0 assets/pem/dhparams.pem
  2. +7 −5 bluepass/backend.py
  3. +92 −215 bluepass/crypto.py
  4. +18 −15 bluepass/ext/_sslex.c
  5. +1 −343 bluepass/ext/openssl.c
  6. +26 −36 bluepass/model.py
  7. +8 −13 bluepass/passwords.py
  8. +3 −5 bluepass/socketapi.py
  9. +14 −22 bluepass/ssl.py
  10. +21 −53 bluepass/syncapi.py
  11. +1 −0 requirements.txt
  12. +2 −2 setup.py
  13. +0 −56 tools/oakley.py
  14. +57 −0 tools/rfc3526.py
@@ -0,0 +1,29 @@
PKCS#3 DH Parameters: (2048 bit)
prime:
00:ff:ff:ff:ff:ff:ff:ff:ff:c9:0f:da:a2:21:68:
c2:34:c4:c6:62:8b:80:dc:1c:d1:29:02:4e:08:8a:
67:cc:74:02:0b:be:a6:3b:13:9b:22:51:4a:08:79:
8e:34:04:dd:ef:95:19:b3:cd:3a:43:1b:30:2b:0a:
6d:f2:5f:14:37:4f:e1:35:6d:6d:51:c2:45:e4:85:
b5:76:62:5e:7e:c6:f4:4c:42:e9:a6:37:ed:6b:0b:
ff:5c:b6:f4:06:b7:ed:ee:38:6b:fb:5a:89:9f:a5:
ae:9f:24:11:7c:4b:1f:e6:49:28:66:51:ec:e4:5b:
3d:c2:00:7c:b8:a1:63:bf:05:98:da:48:36:1c:55:
d3:9a:69:16:3f:a8:fd:24:cf:5f:83:65:5d:23:dc:
a3:ad:96:1c:62:f3:56:20:85:52:bb:9e:d5:29:07:
70:96:96:6d:67:0c:35:4e:4a:bc:98:04:f1:74:6c:
08:ca:18:21:7c:32:90:5e:46:2e:36:ce:3b:e3:9e:
77:2c:18:0e:86:03:9b:27:83:a2:ec:07:a2:8f:b5:
c5:5d:f0:6f:4c:52:c9:de:2b:cb:f6:95:58:17:18:
39:95:49:7c:ea:95:6a:e5:15:d2:26:18:98:fa:05:
10:15:72:8e:5a:8a:ac:aa:68:ff:ff:ff:ff:ff:ff:
ff:ff
generator: 2 (0x2)

-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmONATd
75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr+1qJn6Wu
nyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXTmmkWP6j9JM9fg2VdI9yjrZYcYvNWIIVSu57V
KQdwlpZtZww1Tkq8mATxdGwIyhghfDKQXkYuNs474553LBgOhgObJ4Oi7Aeij7XFXfBvTFLJ3ivL
9pVYFxg5lUl86pVq5RXSJhiY+gUQFXKOWoqsqmj//////////wIBAg==
-----END DH PARAMETERS-----
@@ -10,19 +10,19 @@

import os
import json
import six

import gruvi

from bluepass import platform, util, logging
from bluepass.factory import singleton
from bluepass.component import Component
from bluepass.crypto import CryptoProvider
from bluepass.database import Database
from bluepass.model import Model
from bluepass.passwords import PasswordGenerator
from bluepass.locator import Locator, ZeroconfLocationSource
from bluepass.socketapi import SocketAPIServer
from bluepass.syncapi import SyncAPIServer, SyncAPIPublisher, init_syncapi_ssl
from bluepass.syncapi import SyncAPIServer, SyncAPIPublisher
from bluepass.syncer import Syncer


@@ -64,12 +64,14 @@ def check_options(cls, options):

def run(self):
"""Initialize the backend and run its main loop."""
if not six.PY3:
import bluepass.ssl
bluepass.ssl.patch_ssl_wrap_socket()

self._log.debug('initializing backend components')

self._log.debug('initializing cryto provider')
crypto = singleton(CryptoProvider)
self._log.debug('initializing password generator')
pwgen = singleton(PasswordGenerator)
init_syncapi_ssl(self.options.data_dir)

self._log.debug('initializing database')
fname = os.path.join(self.options.data_dir, 'bluepass.db')
@@ -1,228 +1,105 @@
#
# This file is part of Bluepass. Bluepass is Copyright (c) 2012-2013
# This file is part of Bluepass. Bluepass is Copyright (c) 2012-2014
# Geert Jansen.
#
# Bluepass is free software available under the GNU General Public License,
# version 3. See the file LICENSE distributed with this file for the exact
# licensing terms.

import os
import hmac
import time
import math
import random
import hashlib
import logging
import uuid
import textwrap
import hmac as hmaclib
import binascii

from bluepass.ext import openssl
from bluepass.logging import *

CryptoError = openssl.Error

# Some useful commonly used DH parameters.
dhparams = \
{
'skip2048': textwrap.dedent("""\
MIIBCAKCAQEA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlLOCDaAadWoxTpj0BV
89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N286Z4VeSWc39uK50
T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/RgBYK+X0iP1YTknb
zSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdX
Q6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqVDNmWn6vQClCbAkbT
CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==
"""),
'ietf768': textwrap.dedent("""\
MGYCYQD//////////8kP2qIhaMI0xMZii4DcHNEpAk4IimfMdAILvqY7E5siUUoIeY40BN3vlRmz
zTpDGzArCm3yXxQ3T+E1bW1RwkXkhbV2Yl5+xvRMQummOjYg//////////8CAQI=
"""),
'ietf1024': textwrap.dedent("""\
MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+V
GbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8k
EXxLH+ZJKGZR7OZTgf//////////AgEC
""")
}


class CryptoProvider(object):
"""Crypto provider.
This class exposes the cryptographic primitives that are required by
Bluepass. Currently the only available engine is OpenSSL, but at some
point this could use a native platform crypto provider.
"""

_pbkdf2_speed = {}

def __init__(self, engine=None):
"""Create a new crypto provider."""
self.engine = engine or openssl
self._log = get_logger(self)

def rsa_genkey(self, bits):
"""Generate an RSA key pair of `bits' bits. The result is a 2-tuple
containing the private and public keys. The keys themselves as ASN.1
encoded bitstrings.
"""
return self.engine.rsa_genkey(bits)

def rsa_checkkey(self, privkey):
"""Check that `privkey' is a valid RSA private key."""
return self.engine.rsa_checkkey(privkey)

def rsa_size(self, pubkey):
"""Return the size in bits of an RSA public key."""
return self.engine.rsa_size(pubkey)

def rsa_encrypt(self, s, pubkey, padding='oaep'):
"""RSA Encrypt a string `s' with public key `pubkey'. This uses direct
encryption with OAEP padding.
"""
return self.engine.rsa_encrypt(s, pubkey, padding)

def rsa_decrypt(self, s, privkey, padding='oaep'):
"""RSA Decrypt a string `s' using the private key `privkey'."""
return self.engine.rsa_decrypt(s, privkey, padding)

def rsa_sign(self, s, privkey, padding='pss-sha256'):
"""Create a detached RSA signature of `s' using private key
`privkey'."""
return self.engine.rsa_sign(s, privkey, padding)

def rsa_verify(self, s, sig, pubkey, padding='pss-sha256'):
"""Verify a detached RSA signature `sig' over `s' using the public key
`pubkey'."""
return self.engine.rsa_verify(s, sig, pubkey, padding)

def dh_genparams(self, bits, generator):
"""Generate Diffie-Hellman parameters. The prime will be `bits'
bits in size and `generator' will be the generator."""
return self.engine.dh_genparams(bits)

def dh_checkparams(self, params):
"""Check Diffie-Hellman parameters."""
return self.engine.dh_checkparams(params)

def dh_size(self, params):
"""Return the size in bits of the DH parameters `params'."""
return self.engine.dh_size(params)

def dh_genkey(self, params):
"""Generate a Diffie-Hellman key pair. The return value is a tuple
(privkey, pubkey)."""
return self.engine.dh_genkey(params)

def dh_checkkey(self, params, pubkey):
"""Check a Diffie-Hellman public key."""
return self.engine.dh_checkkey(params, pubkey)

def dh_compute(self, params, privkey, pubkey):
"""Perform a Diffie-Hellman key exchange. The `privkey' parameter is
our private key, `pubkey' is our peer's public key."""
return self.engine.dh_compute(params, privkey, pubkey)

def aes_encrypt(self, s, key, iv, mode='cbc-pkcs7'):
"""AES encrypt a string `s' with key `key'."""
return self.engine.aes_encrypt(s, key, iv, mode)

def aes_decrypt(self, s, key, iv, mode='cbc-pkcs7'):
"""AES decrypt a string `s' with key `key'."""
return self.engine.aes_decrypt(s, key, iv, mode)

def pbkdf2(self, password, salt, count, length, prf='hmac-sha1'):
"""PBKDF2 key derivation function from PKCS#5."""
return self.engine.pbkdf2(password, salt, count, length, prf)

def _measure_pbkdf2_speed(self, prf='hmac-sha1'):
"""Measure the speed of PBKDF2 on this system."""
salt = password = '0123456789abcdef'
length = 1; count = 1000
self._log.debug('starting PBKDF2 speed measurement')
start = time.time()
while True:
startrun = time.time()
self.pbkdf2(password, salt, count, length, prf)
endrun = time.time()
if endrun - startrun > 0.4:
break
count = int(count * math.e)
end = time.time()
speed = int(count / (endrun - startrun))
self._log.debug('PBKDF2 speed is {} iterations / second', speed)
self._log.debug('PBKDF2 speed measurement took {:2f}', (end - start))
# Store the speed in the class so that it can be re-used by
# other instances.
self._pbkdf2_speed[prf] = speed

def pbkdf2_speed(self, prf='hmac-sha1'):
"""Return the speed in rounds/second for generating a key
with PBKDF2 of up to the hash length size of `prf`."""
if prf not in self._pbkdf2_speed:
self._measure_pbkdf2_speed(prf)
return self._pbkdf2_speed[prf]

def pbkdf2_prf_available(self, prf):
"""Test if a given PRF is available for PBKDF2."""
try:
dummy = self.pbkdf2('test', 'test', 1, 1, prf)
except CryptoError:
return False
return True

def random(self, count, alphabet=None, separator=None):
"""Create a random string.
The random string will be the concatenation of `count` elements
randomly chosen from `alphabet`. The alphabet parameter can be a
string, unicode string, a sequence of strings, or a sequence of unicode
strings. If no alphabet is provided, a default alphabet is used
containing all possible single byte values (0 through to 255).
The type of the return value is the same as the elements in the
alphabet (string or unicode).
"""
return self.engine.random(count, alphabet, separator)

def randint(self, bits):
"""Return a random integer with `bits' bits."""
nbytes = (bits + 7) // 8
mask = (1<<bits)-1
return int(binascii.hexlify(self.random(nbytes)), 16) & mask

def randuuid(self):
"""Return a type-4 random UUID."""
return str(uuid.uuid4())

def _get_hash(self, name):
"""INTERNAL: return a hash contructor from its name."""
if not hasattr(hashlib, name):
raise ValueError('no such hash function: %s' % name)
return getattr(hashlib, name)

def hmac(self, key, message, hash='sha256'):
"""Return the HMAC of `message' under `key', using the hash function
`hash' (default: sha256)."""
md = self._get_hash(hash)
return hmac.new(key, message, md).digest()

def hkdf(self, password, salt, info, length, hash='sha256'):
"""HKDF key derivation function."""
md = self._get_hash(hash)
md_size = md().digest_size
if length > 255*md_size:
raise ValueError('can only generate keys up to 255*md_size bytes')
if not isinstance(password, bytes):
password = password.encode('ascii')
if salt is None:
salt = b'\x00' * md_size
elif not isinstance(salt, bytes):
salt = salt.encode('ascii')
if not isinstance(info, bytes):
info = info.encode('ascii')
prk = hmac.new(salt, password, md).digest()
blocks = [b'']
nblocks = (length + md_size - 1) // md_size
for i in range(nblocks):
blocks.append(hmac.new(prk, blocks[i] + info +
chr(i+1).encode('ascii'), md).digest())
return b''.join(blocks)[:length]
from bluepass import logging
from bluepass.ext.openssl import *

__all__ = []

_pbkdf2_speed = {}

def measure_pbkdf2_speed(prf='hmac-sha1'):
"""Measure the speed of PBKDF2 on this system."""
salt = password = '0123456789abcdef'
length = 1; count = 1000
log = logging.get_logger('measure_pbkdf2_speed()')
log.debug('starting PBKDF2 speed measurement')
start = time.time()
while True:
startrun = time.time()
pbkdf2(password, salt, count, length, prf)
endrun = time.time()
if endrun - startrun > 0.2:
break
count *= 2
end = time.time()
speed = int(count / (endrun - startrun))
log.debug('PBKDF2 speed is {} iterations / second', speed)
log.debug('PBKDF2 speed measurement took {:2f}', (end - start))
return speed

def pbkdf2_speed(prf='hmac-sha1'):
"""Return the speed in rounds/second for generating a key
with PBKDF2 of up to the hash length size of `prf`."""
if prf not in _pbkdf2_speed:
_pbkdf2_speed[prf] = measure_pbkdf2_speed(prf)
return _pbkdf2_speed[prf]


def random_bytes(count):
"""Return *count* random bytes."""
return os.urandom(count)

def random_int(below):
"""Return a random integer < *below*."""
return random.randrange(0, below)

def random_uuid():
"""Return a type-4 random UUID."""
return str(uuid.uuid4())

def random_token(bits):
"""Return a random string token with at least *bits* of entropy."""
size = (bits + 7) // 8
return binascii.hexlify(random_bytes(size)).decode('ascii')

def random_element(elements):
"""Return a random element from *elements*."""
return random.choice(elements)


def _get_hash(name):
if not hasattr(hashlib, name):
raise ValueError('no such hash function: %s' % name)
return getattr(hashlib, name)

def hmac(key, message, hash='sha256'):
"""Return the HMAC of *message* under *key*."""
md = _get_hash(hash)
return hmaclib.new(key, message, md).digest()


def hkdf(password, salt, info, length, hash='sha256'):
"""HKDF key derivation function."""
md = _get_hash(hash)
md_size = md().digest_size
if length > 255*md_size:
raise ValueError('can only generate keys up to 255*md_size bytes')
if not isinstance(password, bytes):
password = password.encode('ascii')
if salt is None:
salt = b'\x00' * md_size
elif not isinstance(salt, bytes):
salt = salt.encode('ascii')
if not isinstance(info, bytes):
info = info.encode('ascii')
prk = hmaclib.new(salt, password, md).digest()
blocks = [b'']
nblocks = (length + md_size - 1) // md_size
for i in range(nblocks):
blocks.append(hmaclib.new(prk, blocks[i] + info +
chr(i+1).encode('ascii'), md).digest())
return b''.join(blocks)[:length]
Oops, something went wrong.

0 comments on commit 11b1c98

Please sign in to comment.
You can’t perform that action at this time.