486 lines
21 KiB

import unittest
import os
import time
import shutil
import subprocess
from binascii import hexlify, unhexlify
from hashlib import sha1, sha256
from keys import SigningKey, VerifyingKey
from keys import BadSignatureError
import util
from util import sigencode_der, sigencode_strings
from util import sigdecode_der, sigdecode_strings
from curves import Curve, UnknownCurveError
from curves import NIST192p, NIST224p, NIST256p, NIST384p, NIST521p
import der
class SubprocessError(Exception):
pass
def run_openssl(cmd):
OPENSSL = "openssl"
p = subprocess.Popen([OPENSSL] + cmd.split(),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
stdout, ignored = p.communicate()
if p.returncode != 0:
raise SubprocessError("cmd '%s %s' failed: rc=%s, stdout/err was %s" %
(OPENSSL, cmd, p.returncode, stdout))
return stdout
BENCH = False
class ECDSA(unittest.TestCase):
def test_basic(self):
priv = SigningKey.generate()
pub = priv.get_verifying_key()
data = "blahblah"
sig = priv.sign(data)
self.failUnless(pub.verify(sig, data))
self.failUnlessRaises(BadSignatureError, pub.verify, sig, data+"bad")
pub2 = VerifyingKey.from_string(pub.to_string())
self.failUnless(pub2.verify(sig, data))
def test_bad_usage(self):
# sk=SigningKey() is wrong
self.failUnlessRaises(TypeError, SigningKey)
self.failUnlessRaises(TypeError, VerifyingKey)
def test_lengths(self):
default = NIST192p
priv = SigningKey.generate()
pub = priv.get_verifying_key()
self.failUnlessEqual(len(pub.to_string()), default.verifying_key_length)
sig = priv.sign("data")
self.failUnlessEqual(len(sig), default.signature_length)
if BENCH:
print
for curve in (NIST192p, NIST224p, NIST256p, NIST384p, NIST521p):
start = time.time()
priv = SigningKey.generate(curve=curve)
pub1 = priv.get_verifying_key()
keygen_time = time.time() - start
pub2 = VerifyingKey.from_string(pub1.to_string(), curve)
self.failUnlessEqual(pub1.to_string(), pub2.to_string())
self.failUnlessEqual(len(pub1.to_string()),
curve.verifying_key_length)
start = time.time()
sig = priv.sign("data")
sign_time = time.time() - start
self.failUnlessEqual(len(sig), curve.signature_length)
if BENCH:
start = time.time()
pub1.verify(sig, "data")
verify_time = time.time() - start
print "%s: siglen=%d, keygen=%0.3fs, sign=%0.3f, verify=%0.3f" \
% (curve.name, curve.signature_length,
keygen_time, sign_time, verify_time)
def test_serialize(self):
seed = "secret"
curve = NIST192p
secexp1 = util.randrange_from_seed__trytryagain(seed, curve.order)
secexp2 = util.randrange_from_seed__trytryagain(seed, curve.order)
self.failUnlessEqual(secexp1, secexp2)
priv1 = SigningKey.from_secret_exponent(secexp1, curve)
priv2 = SigningKey.from_secret_exponent(secexp2, curve)
self.failUnlessEqual(hexlify(priv1.to_string()),
hexlify(priv2.to_string()))
self.failUnlessEqual(priv1.to_pem(), priv2.to_pem())
pub1 = priv1.get_verifying_key()
pub2 = priv2.get_verifying_key()
data = "data"
sig1 = priv1.sign(data)
sig2 = priv2.sign(data)
self.failUnless(pub1.verify(sig1, data))
self.failUnless(pub2.verify(sig1, data))
self.failUnless(pub1.verify(sig2, data))
self.failUnless(pub2.verify(sig2, data))
self.failUnlessEqual(hexlify(pub1.to_string()),
hexlify(pub2.to_string()))
def test_nonrandom(self):
s = "all the entropy in the entire world, compressed into one line"
def not_much_entropy(numbytes):
return s[:numbytes]
# we control the entropy source, these two keys should be identical:
priv1 = SigningKey.generate(entropy=not_much_entropy)
priv2 = SigningKey.generate(entropy=not_much_entropy)
self.failUnlessEqual(hexlify(priv1.get_verifying_key().to_string()),
hexlify(priv2.get_verifying_key().to_string()))
# likewise, signatures should be identical. Obviously you'd never
# want to do this with keys you care about, because the secrecy of
# the private key depends upon using different random numbers for
# each signature
sig1 = priv1.sign("data", entropy=not_much_entropy)
sig2 = priv2.sign("data", entropy=not_much_entropy)
self.failUnlessEqual(hexlify(sig1), hexlify(sig2))
def failUnlessPrivkeysEqual(self, priv1, priv2):
self.failUnlessEqual(priv1.privkey.secret_multiplier,
priv2.privkey.secret_multiplier)
self.failUnlessEqual(priv1.privkey.public_key.generator,
priv2.privkey.public_key.generator)
def failIfPrivkeysEqual(self, priv1, priv2):
self.failIfEqual(priv1.privkey.secret_multiplier,
priv2.privkey.secret_multiplier)
def test_privkey_creation(self):
s = "all the entropy in the entire world, compressed into one line"
def not_much_entropy(numbytes):
return s[:numbytes]
priv1 = SigningKey.generate()
self.failUnlessEqual(priv1.baselen, NIST192p.baselen)
priv1 = SigningKey.generate(curve=NIST224p)
self.failUnlessEqual(priv1.baselen, NIST224p.baselen)
priv1 = SigningKey.generate(entropy=not_much_entropy)
self.failUnlessEqual(priv1.baselen, NIST192p.baselen)
priv2 = SigningKey.generate(entropy=not_much_entropy)
self.failUnlessEqual(priv2.baselen, NIST192p.baselen)
self.failUnlessPrivkeysEqual(priv1, priv2)
priv1 = SigningKey.from_secret_exponent(secexp=3)
self.failUnlessEqual(priv1.baselen, NIST192p.baselen)
priv2 = SigningKey.from_secret_exponent(secexp=3)
self.failUnlessPrivkeysEqual(priv1, priv2)
priv1 = SigningKey.from_secret_exponent(secexp=4, curve=NIST224p)
self.failUnlessEqual(priv1.baselen, NIST224p.baselen)
def test_privkey_strings(self):
priv1 = SigningKey.generate()
s1 = priv1.to_string()
self.failUnlessEqual(type(s1), str)
self.failUnlessEqual(len(s1), NIST192p.baselen)
priv2 = SigningKey.from_string(s1)
self.failUnlessPrivkeysEqual(priv1, priv2)
s1 = priv1.to_pem()
self.failUnlessEqual(type(s1), str)
self.failUnless(s1.startswith("-----BEGIN EC PRIVATE KEY-----"))
self.failUnless(s1.strip().endswith("-----END EC PRIVATE KEY-----"))
priv2 = SigningKey.from_pem(s1)
self.failUnlessPrivkeysEqual(priv1, priv2)
s1 = priv1.to_der()
self.failUnlessEqual(type(s1), str)
priv2 = SigningKey.from_der(s1)
self.failUnlessPrivkeysEqual(priv1, priv2)
priv1 = SigningKey.generate(curve=NIST256p)
s1 = priv1.to_pem()
self.failUnlessEqual(type(s1), str)
self.failUnless(s1.startswith("-----BEGIN EC PRIVATE KEY-----"))
self.failUnless(s1.strip().endswith("-----END EC PRIVATE KEY-----"))
priv2 = SigningKey.from_pem(s1)
self.failUnlessPrivkeysEqual(priv1, priv2)
s1 = priv1.to_der()
self.failUnlessEqual(type(s1), str)
priv2 = SigningKey.from_der(s1)
self.failUnlessPrivkeysEqual(priv1, priv2)
def failUnlessPubkeysEqual(self, pub1, pub2):
self.failUnlessEqual(pub1.pubkey.point, pub2.pubkey.point)
self.failUnlessEqual(pub1.pubkey.generator, pub2.pubkey.generator)
self.failUnlessEqual(pub1.curve, pub2.curve)
def test_pubkey_strings(self):
priv1 = SigningKey.generate()
pub1 = priv1.get_verifying_key()
s1 = pub1.to_string()
self.failUnlessEqual(type(s1), str)
self.failUnlessEqual(len(s1), NIST192p.verifying_key_length)
pub2 = VerifyingKey.from_string(s1)
self.failUnlessPubkeysEqual(pub1, pub2)
priv1 = SigningKey.generate(curve=NIST256p)
pub1 = priv1.get_verifying_key()
s1 = pub1.to_string()
self.failUnlessEqual(type(s1), str)
self.failUnlessEqual(len(s1), NIST256p.verifying_key_length)
pub2 = VerifyingKey.from_string(s1, curve=NIST256p)
self.failUnlessPubkeysEqual(pub1, pub2)
pub1_der = pub1.to_der()
self.failUnlessEqual(type(pub1_der), str)
pub2 = VerifyingKey.from_der(pub1_der)
self.failUnlessPubkeysEqual(pub1, pub2)
self.failUnlessRaises(der.UnexpectedDER,
VerifyingKey.from_der, pub1_der+"junk")
badpub = VerifyingKey.from_der(pub1_der)
class FakeGenerator:
def order(self): return 123456789
badcurve = Curve("unknown", None, FakeGenerator(), (1,2,3,4,5,6))
badpub.curve = badcurve
badder = badpub.to_der()
self.failUnlessRaises(UnknownCurveError, VerifyingKey.from_der, badder)
pem = pub1.to_pem()
self.failUnlessEqual(type(pem), str)
self.failUnless(pem.startswith("-----BEGIN PUBLIC KEY-----"), pem)
self.failUnless(pem.strip().endswith("-----END PUBLIC KEY-----"), pem)
pub2 = VerifyingKey.from_pem(pem)
self.failUnlessPubkeysEqual(pub1, pub2)
def test_signature_strings(self):
priv1 = SigningKey.generate()
pub1 = priv1.get_verifying_key()
data = "data"
sig = priv1.sign(data)
self.failUnlessEqual(type(sig), str)
self.failUnlessEqual(len(sig), NIST192p.signature_length)
self.failUnless(pub1.verify(sig, data))
sig = priv1.sign(data, sigencode=sigencode_strings)
self.failUnlessEqual(type(sig), tuple)
self.failUnlessEqual(len(sig), 2)
self.failUnlessEqual(type(sig[0]), str)
self.failUnlessEqual(type(sig[1]), str)
self.failUnlessEqual(len(sig[0]), NIST192p.baselen)
self.failUnlessEqual(len(sig[1]), NIST192p.baselen)
self.failUnless(pub1.verify(sig, data, sigdecode=sigdecode_strings))
sig_der = priv1.sign(data, sigencode=sigencode_der)
self.failUnlessEqual(type(sig_der), str)
self.failUnless(pub1.verify(sig_der, data, sigdecode=sigdecode_der))
def test_hashfunc(self):
sk = SigningKey.generate(curve=NIST256p, hashfunc=sha256)
data = "security level is 128 bits"
sig = sk.sign(data)
vk = VerifyingKey.from_string(sk.get_verifying_key().to_string(),
curve=NIST256p, hashfunc=sha256)
self.failUnless(vk.verify(sig, data))
sk2 = SigningKey.generate(curve=NIST256p)
sig2 = sk2.sign(data, hashfunc=sha256)
vk2 = VerifyingKey.from_string(sk2.get_verifying_key().to_string(),
curve=NIST256p, hashfunc=sha256)
self.failUnless(vk2.verify(sig2, data))
vk3 = VerifyingKey.from_string(sk.get_verifying_key().to_string(),
curve=NIST256p)
self.failUnless(vk3.verify(sig, data, hashfunc=sha256))
class OpenSSL(unittest.TestCase):
# test interoperability with OpenSSL tools. Note that openssl's ECDSA
# sign/verify arguments changed between 0.9.8 and 1.0.0: the early
# versions require "-ecdsa-with-SHA1", the later versions want just
# "-SHA1" (or to leave out that argument entirely, which means the
# signature will use some default digest algorithm, probably determined
# by the key, probably always SHA1).
#
# openssl ecparam -name secp224r1 -genkey -out privkey.pem
# openssl ec -in privkey.pem -text -noout # get the priv/pub keys
# openssl dgst -ecdsa-with-SHA1 -sign privkey.pem -out data.sig data.txt
# openssl asn1parse -in data.sig -inform DER
# data.sig is 64 bytes, probably 56b plus ASN1 overhead
# openssl dgst -ecdsa-with-SHA1 -prverify privkey.pem -signature data.sig data.txt ; echo $?
# openssl ec -in privkey.pem -pubout -out pubkey.pem
# openssl ec -in privkey.pem -pubout -outform DER -out pubkey.der
def get_openssl_messagedigest_arg(self):
v = run_openssl("version")
# e.g. "OpenSSL 1.0.0 29 Mar 2010", or "OpenSSL 1.0.0a 1 Jun 2010",
# or "OpenSSL 0.9.8o 01 Jun 2010"
vs = v.split()[1].split(".")
if vs >= ["1","0","0"]:
return "-SHA1"
else:
return "-ecdsa-with-SHA1"
# sk: 1:OpenSSL->python 2:python->OpenSSL
# vk: 3:OpenSSL->python 4:python->OpenSSL
# sig: 5:OpenSSL->python 6:python->OpenSSL
def test_from_openssl_nist192p(self):
return self.do_test_from_openssl(NIST192p, "prime192v1")
def test_from_openssl_nist224p(self):
return self.do_test_from_openssl(NIST224p, "secp224r1")
def test_from_openssl_nist384p(self):
return self.do_test_from_openssl(NIST384p, "secp384r1")
def test_from_openssl_nist521p(self):
return self.do_test_from_openssl(NIST521p, "secp521r1")
def do_test_from_openssl(self, curve, curvename):
# OpenSSL: create sk, vk, sign.
# Python: read vk(3), checksig(5), read sk(1), sign, check
mdarg = self.get_openssl_messagedigest_arg()
if os.path.isdir("t"):
shutil.rmtree("t")
os.mkdir("t")
run_openssl("ecparam -name %s -genkey -out t/privkey.pem" % curvename)
run_openssl("ec -in t/privkey.pem -pubout -out t/pubkey.pem")
data = "data"
open("t/data.txt","wb").write(data)
run_openssl("dgst %s -sign t/privkey.pem -out t/data.sig t/data.txt" % mdarg)
run_openssl("dgst %s -verify t/pubkey.pem -signature t/data.sig t/data.txt" % mdarg)
pubkey_pem = open("t/pubkey.pem").read()
vk = VerifyingKey.from_pem(pubkey_pem) # 3
sig_der = open("t/data.sig","rb").read()
self.failUnless(vk.verify(sig_der, data, # 5
hashfunc=sha1, sigdecode=sigdecode_der))
sk = SigningKey.from_pem(open("t/privkey.pem").read()) # 1
sig = sk.sign(data)
self.failUnless(vk.verify(sig, data))
def test_to_openssl_nist192p(self):
self.do_test_to_openssl(NIST192p, "prime192v1")
def test_to_openssl_nist224p(self):
self.do_test_to_openssl(NIST224p, "secp224r1")
def test_to_openssl_nist384p(self):
self.do_test_to_openssl(NIST384p, "secp384r1")
def test_to_openssl_nist521p(self):
self.do_test_to_openssl(NIST521p, "secp521r1")
def do_test_to_openssl(self, curve, curvename):
# Python: create sk, vk, sign.
# OpenSSL: read vk(4), checksig(6), read sk(2), sign, check
mdarg = self.get_openssl_messagedigest_arg()
if os.path.isdir("t"):
shutil.rmtree("t")
os.mkdir("t")
sk = SigningKey.generate(curve=curve)
vk = sk.get_verifying_key()
data = "data"
open("t/pubkey.der","wb").write(vk.to_der()) # 4
open("t/pubkey.pem","wb").write(vk.to_pem()) # 4
sig_der = sk.sign(data, hashfunc=sha1, sigencode=sigencode_der)
open("t/data.sig","wb").write(sig_der) # 6
open("t/data.txt","wb").write(data)
open("t/baddata.txt","wb").write(data+"corrupt")
self.failUnlessRaises(SubprocessError, run_openssl,
"dgst %s -verify t/pubkey.der -keyform DER -signature t/data.sig t/baddata.txt" % mdarg)
run_openssl("dgst %s -verify t/pubkey.der -keyform DER -signature t/data.sig t/data.txt" % mdarg)
open("t/privkey.pem","wb").write(sk.to_pem()) # 2
run_openssl("dgst %s -sign t/privkey.pem -out t/data.sig2 t/data.txt" % mdarg)
run_openssl("dgst %s -verify t/pubkey.pem -signature t/data.sig2 t/data.txt" % mdarg)
class DER(unittest.TestCase):
def test_oids(self):
oid_ecPublicKey = der.encode_oid(1, 2, 840, 10045, 2, 1)
self.failUnlessEqual(hexlify(oid_ecPublicKey), "06072a8648ce3d0201")
self.failUnlessEqual(hexlify(NIST224p.encoded_oid), "06052b81040021")
self.failUnlessEqual(hexlify(NIST256p.encoded_oid),
"06082a8648ce3d030107")
x = oid_ecPublicKey + "more"
x1, rest = der.remove_object(x)
self.failUnlessEqual(x1, (1, 2, 840, 10045, 2, 1))
self.failUnlessEqual(rest, "more")
def test_integer(self):
self.failUnlessEqual(der.encode_integer(0), "\x02\x01\x00")
self.failUnlessEqual(der.encode_integer(1), "\x02\x01\x01")
self.failUnlessEqual(der.encode_integer(127), "\x02\x01\x7f")
self.failUnlessEqual(der.encode_integer(128), "\x02\x02\x00\x80")
self.failUnlessEqual(der.encode_integer(256), "\x02\x02\x01\x00")
#self.failUnlessEqual(der.encode_integer(-1), "\x02\x01\xff")
def s(n): return der.remove_integer(der.encode_integer(n) + "junk")
self.failUnlessEqual(s(0), (0, "junk"))
self.failUnlessEqual(s(1), (1, "junk"))
self.failUnlessEqual(s(127), (127, "junk"))
self.failUnlessEqual(s(128), (128, "junk"))
self.failUnlessEqual(s(256), (256, "junk"))
self.failUnlessEqual(s(1234567890123456789012345678901234567890),
( 1234567890123456789012345678901234567890,"junk"))
def test_number(self):
self.failUnlessEqual(der.encode_number(0), "\x00")
self.failUnlessEqual(der.encode_number(127), "\x7f")
self.failUnlessEqual(der.encode_number(128), "\x81\x00")
self.failUnlessEqual(der.encode_number(3*128+7), "\x83\x07")
#self.failUnlessEqual(der.read_number("\x81\x9b"+"more"), (155, 2))
#self.failUnlessEqual(der.encode_number(155), "\x81\x9b")
for n in (0, 1, 2, 127, 128, 3*128+7, 840, 10045): #, 155):
x = der.encode_number(n) + "more"
n1, llen = der.read_number(x)
self.failUnlessEqual(n1, n)
self.failUnlessEqual(x[llen:], "more")
def test_length(self):
self.failUnlessEqual(der.encode_length(0), "\x00")
self.failUnlessEqual(der.encode_length(127), "\x7f")
self.failUnlessEqual(der.encode_length(128), "\x81\x80")
self.failUnlessEqual(der.encode_length(255), "\x81\xff")
self.failUnlessEqual(der.encode_length(256), "\x82\x01\x00")
self.failUnlessEqual(der.encode_length(3*256+7), "\x82\x03\x07")
self.failUnlessEqual(der.read_length("\x81\x9b"+"more"), (155, 2))
self.failUnlessEqual(der.encode_length(155), "\x81\x9b")
for n in (0, 1, 2, 127, 128, 255, 256, 3*256+7, 155):
x = der.encode_length(n) + "more"
n1, llen = der.read_length(x)
self.failUnlessEqual(n1, n)
self.failUnlessEqual(x[llen:], "more")
def test_sequence(self):
x = der.encode_sequence("ABC", "DEF") + "GHI"
self.failUnlessEqual(x, "\x30\x06ABCDEFGHI")
x1, rest = der.remove_sequence(x)
self.failUnlessEqual(x1, "ABCDEF")
self.failUnlessEqual(rest, "GHI")
def test_constructed(self):
x = der.encode_constructed(0, NIST224p.encoded_oid)
self.failUnlessEqual(hexlify(x), "a007" + "06052b81040021")
x = der.encode_constructed(1, unhexlify("0102030a0b0c"))
self.failUnlessEqual(hexlify(x), "a106" + "0102030a0b0c")
class Util(unittest.TestCase):
def test_trytryagain(self):
tta = util.randrange_from_seed__trytryagain
for i in range(1000):
seed = "seed-%d" % i
for order in (2**8-2, 2**8-1, 2**8, 2**8+1, 2**8+2,
2**16-1, 2**16+1):
n = tta(seed, order)
self.failUnless(1 <= n < order, (1, n, order))
# this trytryagain *does* provide long-term stability
self.failUnlessEqual("%x"%(tta("seed", NIST224p.order)),
"6fa59d73bf0446ae8743cf748fc5ac11d5585a90356417e97155c3bc")
def test_randrange(self):
# util.randrange does not provide long-term stability: we might
# change the algorithm in the future.
for i in range(1000):
entropy = util.PRNG("seed-%d" % i)
for order in (2**8-2, 2**8-1, 2**8,
2**16-1, 2**16+1,
):
# that oddball 2**16+1 takes half our runtime
n = util.randrange(order, entropy=entropy)
self.failUnless(1 <= n < order, (1, n, order))
def OFF_test_prove_uniformity(self):
order = 2**8-2
counts = dict([(i, 0) for i in range(1, order)])
assert 0 not in counts
assert order not in counts
for i in range(1000000):
seed = "seed-%d" % i
n = util.randrange_from_seed__trytryagain(seed, order)
counts[n] += 1
# this technique should use the full range
self.failUnless(counts[order-1])
for i in range(1, order):
print "%3d: %s" % (i, "*"*(counts[i]//100))
def __main__():
unittest.main()
if __name__ == "__main__":
__main__()