1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Test suite for mpint.c and ecc.c.

This is a reasonably comprehensive test that exercises basically all
the functions I rewrote at the end of last year, and it's how I found
a lot of the bugs in them that I fixed earlier today.

It's written in Python, using the unittest framework, which is
convenient because that way I can cross-check Python's own large
integers against PuTTY's.

While I'm here, I've also added a few tests of higher-level crypto
primitives such as Ed25519, AES and HMAC, when I could find official
test vectors for them. I hope to add to that collection at some point,
and also add unit tests of some of the other primitives like ECDH and
RSA KEX.

The test suite is run automatically by my top-level build script, so
that I won't be able to accidentally ship anything which regresses it.
When it's run at build time, the testcrypt binary is built using both
Address and Leak Sanitiser, so anything they don't like will also
cause a test failure.
This commit is contained in:
Simon Tatham 2019-01-03 16:04:58 +00:00
parent 22a79fe733
commit e1627db3e5
2 changed files with 847 additions and 0 deletions

View File

@ -143,6 +143,16 @@ in putty do ./mkunxarc.sh '$(Autoconfver)' '$(Uxarcsuffix)' $(Docmakever)
in putty do perl mkfiles.pl
in putty/doc do make $(Docmakever) putty.hlp putty.chm -j$(nproc)
delegate -
# Run the test suite, under self-delegation so that we don't leave any
# cruft lying around. This involves doing a build of the Unix tools
# (which is a useful double-check anyway to pick up any build failures)
in putty do ./mkauto.sh
in putty do ./configure CC=clang CFLAGS="-fsanitize=address -fsanitize=leak"
in putty do make -j$(nproc)
in putty do python test/cryptsuite.py
enddelegate
# Munge the installer script locally so that it reports the version
# we're really building.
in putty/windows do perl -i~ -pe 'BEGIN{$$a=shift@ARGV;}s/^(AppVerName=).*$$/$$1$$a/' '$(Puttytextver)' putty.iss

837
test/cryptsuite.py Executable file
View File

@ -0,0 +1,837 @@
#!/usr/bin/env python
import unittest
import struct
import itertools
import contextlib
import hashlib
from binascii import unhexlify as unhex
try:
from math import gcd
except ImportError:
from fractions import gcd
from eccref import *
from testcrypt import *
def ssh_string(s):
return struct.pack(">L", len(s)) + s
def find_non_square_mod(p):
# Find a non-square mod p, using the Jacobi symbol
# calculation function from eccref.py.
return next(z for z in itertools.count(2) if jacobi(z, p) == -1)
def fibonacci_scattered(n=10):
# Generate a list of Fibonacci numbers with power-of-2 indices
# (F_1, F_2, F_4, ...), to be used as test inputs of varying
# sizes. Also put F_0 = 0 into the list as a bonus.
yield 0
a, b, c = 0, 1, 1
while True:
yield b
n -= 1
if n <= 0:
break
a, b, c = (a**2+b**2, b*(a+c), b**2+c**2)
def fibonacci(n=10):
# Generate the full Fibonacci sequence starting from F_0 = 0.
a, b = 0, 1
while True:
yield a
n -= 1
if n <= 0:
break
a, b = b, a+b
def mp_mask(mp):
# Return the value that mp would represent if all its bits
# were set. Useful for masking a true mathematical output
# value (e.g. from an operation that can over/underflow, like
# mp_sub or mp_anything_into) to check it's right within the
# ability of that particular mp_int to represent.
return ((1 << mp_max_bits(mp))-1)
def adjtuples(iterable, n):
# Return all the contiguous n-tuples of an iterable, including
# overlapping ones. E.g. if called on [0,1,2,3,4] with n=3 it
# would return (0,1,2), (1,2,3), (2,3,4) and then stop.
it = iter(iterable)
toret = [next(it) for _ in range(n-1)]
for element in it:
toret.append(element)
yield tuple(toret)
toret[:1] = []
@contextlib.contextmanager
def queued_random_data(nbytes, seed):
hashsize = 512 // 8
data = b''.join(
hashlib.sha512(unicode_to_bytes("preimage:{:d}:{}".format(i, seed)))
.digest() for i in range((nbytes + hashsize - 1) // hashsize))
data = data[:nbytes]
random_queue(data)
yield None
random_clear()
def hash_str(alg, message):
h = ssh_hash_new(alg)
ssh_hash_update(h, message)
return ssh_hash_final(h)
def mac_str(alg, key, message, cipher=None):
m = ssh2_mac_new(alg, cipher)
ssh2_mac_setkey(m, key)
ssh2_mac_start(m)
ssh2_mac_update(m, "dummy")
# Make sure ssh_mac_start erases previous state
ssh2_mac_start(m)
ssh2_mac_update(m, message)
return ssh2_mac_genresult(m)
class mpint(unittest.TestCase):
def testCreation(self):
self.assertEqual(int(mp_new(128)), 0)
self.assertEqual(int(mp_from_bytes_be(b'ABCDEFGHIJKLMNOP')),
0x4142434445464748494a4b4c4d4e4f50)
self.assertEqual(int(mp_from_bytes_le(b'ABCDEFGHIJKLMNOP')),
0x504f4e4d4c4b4a494847464544434241)
self.assertEqual(int(mp_from_integer(12345)), 12345)
decstr = '91596559417721901505460351493238411077414937428167'
self.assertEqual(int(mp_from_decimal_pl(decstr)), int(decstr, 10))
self.assertEqual(int(mp_from_decimal(decstr)), int(decstr, 10))
# For hex, test both upper and lower case digits
hexstr = 'ea7cb89f409ae845215822e37D32D0C63EC43E1381C2FF8094'
self.assertEqual(int(mp_from_hex_pl(hexstr)), int(hexstr, 16))
self.assertEqual(int(mp_from_hex(hexstr)), int(hexstr, 16))
p2 = mp_power_2(123)
self.assertEqual(int(p2), 1 << 123)
p2c = mp_copy(p2)
self.assertEqual(int(p2c), 1 << 123)
# Check mp_copy really makes a copy, not an alias (ok, that's
# testing the testcrypt system more than it's testing the
# underlying C functions)
mp_set_bit(p2c, 120, 1)
self.assertEqual(int(p2c), (1 << 123) + (1 << 120))
self.assertEqual(int(p2), 1 << 123)
def testBytesAndBits(self):
x = mp_new(128)
self.assertEqual(mp_get_byte(x, 2), 0)
mp_set_bit(x, 2*8+3, 1)
self.assertEqual(mp_get_byte(x, 2), 1<<3)
self.assertEqual(mp_get_bit(x, 2*8+3), 1)
mp_set_bit(x, 2*8+3, 0)
self.assertEqual(mp_get_byte(x, 2), 0)
self.assertEqual(mp_get_bit(x, 2*8+3), 0)
# Currently I expect 128 to be a multiple of any
# BIGNUM_INT_BITS value we might be running with, so these
# should be exact equality
self.assertEqual(mp_max_bytes(x), 128/8)
self.assertEqual(mp_max_bits(x), 128)
nb = lambda hexstr: mp_get_nbits(mp_from_hex(hexstr))
self.assertEqual(nb('00000000000000000000000000000000'), 0)
self.assertEqual(nb('00000000000000000000000000000001'), 1)
self.assertEqual(nb('00000000000000000000000000000002'), 2)
self.assertEqual(nb('00000000000000000000000000000003'), 2)
self.assertEqual(nb('00000000000000000000000000000004'), 3)
self.assertEqual(nb('000003ffffffffffffffffffffffffff'), 106)
self.assertEqual(nb('000003ffffffffff0000000000000000'), 106)
self.assertEqual(nb('80000000000000000000000000000000'), 128)
self.assertEqual(nb('ffffffffffffffffffffffffffffffff'), 128)
def testDecAndHex(self):
def checkHex(hexstr):
n = mp_from_hex(hexstr)
i = int(hexstr, 16)
self.assertEqual(mp_get_hex(n),
unicode_to_bytes("{:x}".format(i)))
self.assertEqual(mp_get_hex_uppercase(n),
unicode_to_bytes("{:X}".format(i)))
checkHex("0")
checkHex("f")
checkHex("00000000000000000000000000000000000000000000000000")
checkHex("d5aa1acd5a9a1f6b126ed416015390b8dc5fceee4c86afc8c2")
checkHex("ffffffffffffffffffffffffffffffffffffffffffffffffff")
def checkDec(hexstr):
n = mp_from_hex(hexstr)
i = int(hexstr, 16)
self.assertEqual(mp_get_decimal(n),
unicode_to_bytes("{:d}".format(i)))
checkDec("0")
checkDec("f")
checkDec("00000000000000000000000000000000000000000000000000")
checkDec("d5aa1acd5a9a1f6b126ed416015390b8dc5fceee4c86afc8c2")
checkDec("ffffffffffffffffffffffffffffffffffffffffffffffffff")
checkDec("f" * 512)
def testComparison(self):
inputs = [
"0", "1", "2", "10", "314159265358979", "FFFFFFFFFFFFFFFF",
# Test over-long versions of some of the same numbers we
# had short forms of above
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000001",
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000002",
"0000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFF",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
]
values = [(mp_from_hex(s), int(s, 16)) for s in inputs]
for am, ai in values:
for bm, bi in values:
self.assertEqual(mp_cmp_eq(am, bm) == 1, ai == bi)
self.assertEqual(mp_cmp_hs(am, bm) == 1, ai >= bi)
if (bi >> 64) == 0:
self.assertEqual(mp_eq_integer(am, bi) == 1, ai == bi)
self.assertEqual(mp_hs_integer(am, bi) == 1, ai >= bi)
# mp_min{,_into} is a reasonable thing to test here as well
self.assertEqual(int(mp_min(am, bm)), min(ai, bi))
am2 = mp_copy(am)
mp_min_into(am2, am, bm)
self.assertEqual(int(am2), min(ai, bi))
def testConditionals(self):
testnumbers = [(mp_copy(n),n) for n in fibonacci_scattered()]
for am, ai in testnumbers:
for bm, bi in testnumbers:
cm = mp_copy(am)
mp_select_into(cm, am, bm, 0)
self.assertEqual(int(cm), ai & mp_mask(am))
mp_select_into(cm, am, bm, 1)
self.assertEqual(int(cm), bi & mp_mask(am))
mp_cond_add_into(cm, am, bm, 0)
self.assertEqual(int(cm), ai & mp_mask(am))
mp_cond_add_into(cm, am, bm, 1)
self.assertEqual(int(cm), (ai+bi) & mp_mask(am))
mp_cond_sub_into(cm, am, bm, 0)
self.assertEqual(int(cm), ai & mp_mask(am))
mp_cond_sub_into(cm, am, bm, 1)
self.assertEqual(int(cm), (ai-bi) & mp_mask(am))
maxbits = max(mp_max_bits(am), mp_max_bits(bm))
cm = mp_new(maxbits)
dm = mp_new(maxbits)
mp_copy_into(cm, am)
mp_copy_into(dm, bm)
self.assertEqual(int(cm), ai)
self.assertEqual(int(dm), bi)
mp_cond_swap(cm, dm, 0)
self.assertEqual(int(cm), ai)
self.assertEqual(int(dm), bi)
mp_cond_swap(cm, dm, 1)
self.assertEqual(int(cm), bi)
self.assertEqual(int(dm), ai)
if bi != 0:
mp_cond_clear(cm, 0)
self.assertEqual(int(cm), bi)
mp_cond_clear(cm, 1)
self.assertEqual(int(cm), 0)
def testBasicArithmetic(self):
testnumbers = list(fibonacci_scattered(5))
testnumbers.extend([1 << (1 << i) for i in range(3,10)])
testnumbers.extend([(1 << (1 << i)) - 1 for i in range(3,10)])
testnumbers = [(mp_copy(n),n) for n in testnumbers]
for am, ai in testnumbers:
for bm, bi in testnumbers:
self.assertEqual(int(mp_add(am, bm)), ai + bi)
self.assertEqual(int(mp_mul(am, bm)), ai * bi)
# Cope with underflow in subtraction
diff = mp_sub(am, bm)
self.assertEqual(int(diff), (ai - bi) & mp_mask(diff))
for bits in range(0, 512, 64):
cm = mp_new(bits)
mp_add_into(cm, am, bm)
self.assertEqual(int(cm), (ai + bi) & mp_mask(cm))
mp_mul_into(cm, am, bm)
self.assertEqual(int(cm), (ai * bi) & mp_mask(cm))
mp_sub_into(cm, am, bm)
self.assertEqual(int(cm), (ai - bi) & mp_mask(cm))
# A test cherry-picked from the old bignum test script,
# involving two numbers whose product has a single 1 bit miles
# in the air and then all 0s until a bunch of cruft at the
# bottom, the aim being to test that carry propagation works
# all the way up.
ai, bi = 0xb4ff6ed2c633847562087ed9354c5c17be212ac83b59c10c316250f50b7889e5b058bf6bfafd12825225ba225ede0cba583ffbd0882de88c9e62677385a6dbdedaf81959a273eb7909ebde21ae5d12e2a584501a6756fe50ccb93b93f0d6ee721b6052a0d88431e62f410d608532868cdf3a6de26886559e94cc2677eea9bd797918b70e2717e95b45918bd1f86530cb9989e68b632c496becff848aa1956cd57ed46676a65ce6dd9783f230c8796909eef5583fcfe4acbf9c8b4ea33a08ec3fd417cf7175f434025d032567a00fc329aee154ca20f799b961fbab8f841cb7351f561a44aea45746ceaf56874dad99b63a7d7af2769d2f185e2d1c656cc6630b5aba98399fa57, 0xb50a77c03ac195225021dc18d930a352f27c0404742f961ca828c972737bad3ada74b1144657ab1d15fe1b8aefde8784ad61783f3c8d4584aa5f22a4eeca619f90563ae351b5da46770df182cf348d8e23b25fda07670c6609118e916a57ce4043608752c91515708327e36f5bb5ebd92cd4cfb39424167a679870202b23593aa524bac541a3ad322c38102a01e9659b06a4335c78d50739a51027954ac2bf03e500f975c2fa4d0ab5dd84cc9334f219d2ae933946583e384ed5dbf6498f214480ca66987b867df0f69d92e4e14071e4b8545212dd5e29ff0248ed751e168d78934da7930bcbe10e9a212128a68de5d749c61f5e424cf8cf6aa329674de0cf49c6f9b4c8b8cc3
am = mp_copy(ai)
bm = mp_copy(bi)
self.assertEqual(int(mp_mul(am, bm)), ai * bi)
@staticmethod
def nbits(n):
# Mimic mp_get_nbits for ordinary Python integers.
assert 0 <= n
smax = next(s for s in itertools.count() if (n >> (1 << s)) == 0)
toret = 0
for shift in reversed([1 << s for s in range(smax)]):
if n >> shift != 0:
n >>= shift
toret += shift
assert n <= 1
if n == 1:
toret += 1
return toret
def testDivision(self):
divisors = [1, 2, 3, 2**16+1, 2**32-1, 2**32+1, 2**128-159,
141421356237309504880168872420969807856967187537694807]
quotients = [0, 1, 2, 2**64-1, 2**64, 2**64+1, 17320508075688772935]
for d in divisors:
for q in quotients:
remainders = {0, 1, d-1, 2*d//3}
for r in sorted(remainders):
if r >= d:
continue # silly cases with tiny divisors
n = q*d + r
mq = mp_new(self.nbits(q))
mr = mp_new(self.nbits(r))
mp_divmod_into(n, d, mq, mr)
self.assertEqual(int(mq), q)
self.assertEqual(int(mr), r)
self.assertEqual(int(mp_div(n, d)), q)
self.assertEqual(int(mp_mod(n, d)), r)
def testInversion(self):
# Test mp_invert_mod_2to.
testnumbers = [(mp_copy(n),n) for n in fibonacci_scattered()
if n & 1]
for power2 in [1, 2, 3, 5, 13, 32, 64, 127, 128, 129]:
for am, ai in testnumbers:
bm = mp_invert_mod_2to(am, power2)
bi = int(bm)
self.assertEqual(((ai * bi) & ((1 << power2) - 1)), 1)
# mp_reduce_mod_2to is a much simpler function, but
# this is as good a place as any to test it.
rm = mp_copy(am)
mp_reduce_mod_2to(rm, power2)
self.assertEqual(int(rm), ai & ((1 << power2) - 1))
# Test mp_invert proper.
moduli = [2, 3, 2**16+1, 2**32-1, 2**32+1, 2**128-159,
141421356237309504880168872420969807856967187537694807]
for m in moduli:
# Prepare a MontyContext for the monty_invert test below
# (unless m is even, in which case we can't)
mc = monty_new(m) if m & 1 else None
to_invert = {1, 2, 3, 7, 19, m-1, 5*m//17}
for x in sorted(to_invert):
if gcd(x, m) != 1:
continue # filter out non-invertible cases
inv = int(mp_invert(x, m))
assert x * inv % m == 1
# Test monty_invert too, while we're here
if mc is not None:
self.assertEqual(
int(monty_invert(mc, monty_import(mc, x))),
int(monty_import(mc, inv)))
def testMonty(self):
moduli = [5, 19, 2**16+1, 2**31-1, 2**128-159, 2**255-19,
293828847201107461142630006802421204703,
113064788724832491560079164581712332614996441637880086878209969852674997069759]
for m in moduli:
mc = monty_new(m)
# Import some numbers
inputs = [(monty_import(mc, n), n)
for n in sorted({0, 1, 2, 3, 2*m//3, m-1})]
# Check modulus and identity
self.assertEqual(int(monty_modulus(mc)), m)
self.assertEqual(int(monty_identity(mc)), int(inputs[1][0]))
# Check that all those numbers export OK
for mn, n in inputs:
self.assertEqual(int(monty_export(mc, mn)), n)
for ma, a in inputs:
for mb, b in inputs:
xprod = int(monty_export(mc, monty_mul(mc, ma, mb)))
self.assertEqual(xprod, a*b % m)
xsum = int(monty_export(mc, monty_add(mc, ma, mb)))
self.assertEqual(xsum, (a+b) % m)
xdiff = int(monty_export(mc, monty_sub(mc, ma, mb)))
self.assertEqual(xdiff, (a-b) % m)
# Test the ordinary mp_mod{add,sub,mul} at the
# same time, even though those don't do any
# montying at all
xprod = int(mp_modmul(a, b, m))
self.assertEqual(xprod, a*b % m)
xsum = int(mp_modadd(a, b, m))
self.assertEqual(xsum, (a+b) % m)
xdiff = int(mp_modsub(a, b, m))
self.assertEqual(xdiff, (a-b) % m)
for ma, a in inputs:
# Compute a^0, a^1, a^1, a^2, a^3, a^5, ...
indices = list(fibonacci())
powers = [int(monty_export(mc, monty_pow(mc, ma, power)))
for power in indices]
# Check the first two make sense
self.assertEqual(powers[0], 1)
self.assertEqual(powers[1], a)
# Check the others using the Fibonacci identity:
# F_n + F_{n+1} = F_{n+2}, so a^{F_n} a^{F_{n+1}} = a^{F_{n+2}}
for p0, p1, p2 in adjtuples(powers, 3):
self.assertEqual(p2, p0 * p1 % m)
# Test the ordinary mp_modpow here as well, while
# we've got the machinery available
for index, power in zip(indices, powers):
self.assertEqual(int(mp_modpow(a, index, m)), power)
# A regression test for a bug I encountered during initial
# development of mpint.c, in which an incomplete reduction
# happened somewhere in an intermediate value.
b, e, m = 0x2B5B93812F253FF91F56B3B4DAD01CA2884B6A80719B0DA4E2159A230C6009EDA97C5C8FD4636B324F9594706EE3AD444831571BA5E17B1B2DFA92DEA8B7E, 0x25, 0xC8FCFD0FD7371F4FE8D0150EFC124E220581569587CCD8E50423FA8D41E0B2A0127E100E92501E5EE3228D12EA422A568C17E0AD2E5C5FCC2AE9159D2B7FB8CB
assert(int(mp_modpow(b, e, m)) == pow(b, e, m))
def testModsqrt(self):
moduli = [
5, 19, 2**16+1, 2**31-1, 2**128-159, 2**255-19,
293828847201107461142630006802421204703,
113064788724832491560079164581712332614996441637880086878209969852674997069759,
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6FFFFFFFF00000001]
for p in moduli:
# Count the factors of 2 in the group. (That is, we want
# p-1 to be an odd multiple of 2^{factors_of_2}.)
factors_of_2 = self.nbits((p-1) & (1-p)) - 1
assert (p & ((2 << factors_of_2)-1)) == ((1 << factors_of_2)+1)
z = find_non_square_mod(p)
sc = modsqrt_new(p, z)
def ptest(x):
root, success = mp_modsqrt(sc, x)
r = int(root)
self.assertTrue(success)
self.assertEqual((r * r - x) % p, 0)
def ntest(x):
root, success = mp_modsqrt(sc, x)
self.assertFalse(success)
# Make up some more or less random values mod p to square
v1 = pow(3, self.nbits(p), p)
v2 = pow(5, v1, p)
test_roots = [0, 1, 2, 3, 4, 3*p//4, v1, v2, v1+1, 12873*v1, v1*v2]
known_squares = {r*r % p for r in test_roots}
for s in known_squares:
ptest(s)
if s != 0:
ntest(z*s % p)
# Make sure we've tested a value that is in each of the
# subgroups of order (p-1)/2^k but not in the next one
# (with the exception of k=0, which just means 'have we
# tested a non-square?', which we have in the above loop).
#
# We do this by starting with a known non-square; then
# squaring it (factors_of_2) times will return values
# nested deeper and deeper in those subgroups.
vbase = z
for k in range(factors_of_2):
# Adjust vbase by an arbitrary odd power of
# z, so that it won't look too much like the previous
# value.
vbase = vbase * pow(z, (vbase + v1 + v2) | 1, p) % p
# Move vbase into the next smaller group by squaring
# it.
vbase = pow(vbase, 2, p)
ptest(vbase)
def testShifts(self):
x = ((1<<900) // 9949) | 1
for i in range(2049):
mp = mp_copy(x)
mp_lshift_fixed_into(mp, mp, i)
self.assertEqual(int(mp), (x << i) & mp_mask(mp))
mp_copy_into(mp, x)
mp_rshift_fixed_into(mp, mp, i)
self.assertEqual(int(mp), x >> i)
self.assertEqual(int(mp_rshift_fixed(x, i)), x >> i)
self.assertEqual(int(mp_rshift_safe(x, i)), x >> i)
def testRandom(self):
# Test random_bits to ensure it correctly masks the return
# value, and uses exactly as many random bytes as we expect it
# to.
for bits in range(512):
bytes_needed = (bits + 7) // 8
with queued_random_data(bytes_needed, "random_bits test"):
mp = mp_random_bits(bits)
self.assertTrue(int(mp) < (1 << bits))
self.assertEqual(random_queue_len(), 0)
# Test mp_random_in_range to ensure it returns things in the
# right range.
for rangesize in [2, 3, 19, 35]:
for lo in [0, 1, 0x10001, 1<<512]:
hi = lo + rangesize
bytes_needed = mp_max_bytes(hi) + 16
for trial in range(rangesize*3):
with queued_random_data(
bytes_needed,
"random_in_range {:d}".format(trial)):
v = int(mp_random_in_range(lo, hi))
self.assertTrue(lo <= v < hi)
class ecc(unittest.TestCase):
def testWeierstrassSimple(self):
# Simple tests using a Weierstrass curve I made up myself,
# which (unlike the ones used for serious crypto) is small
# enough that you can fit all the coordinates for a curve on
# to your retina in one go.
p = 3141592661
a, b = -3 % p, 12345
rc = WeierstrassCurve(p, a, b)
wc = ecc_weierstrass_curve(p, a, b, None)
def check_point(wp, rp):
self.assertTrue(ecc_weierstrass_point_valid(wp))
is_id = ecc_weierstrass_is_identity(wp)
x, y = ecc_weierstrass_get_affine(wp)
if rp.infinite:
self.assertEqual(is_id, 1)
else:
self.assertEqual(is_id, 0)
self.assertEqual(int(x), int(rp.x))
self.assertEqual(int(y), int(rp.y))
def make_point(x, y):
wp = ecc_weierstrass_point_new(wc, x, y)
rp = rc.point(x, y)
check_point(wp, rp)
return wp, rp
# Some sample points, including the identity and also a pair
# of mutual inverses.
wI, rI = ecc_weierstrass_point_new_identity(wc), rc.point()
wP, rP = make_point(102, 387427089)
wQ, rQ = make_point(1000, 546126574)
wmP, rmP = make_point(102, p - 387427089)
# Check the simple arithmetic functions.
check_point(ecc_weierstrass_add(wP, wQ), rP + rQ)
check_point(ecc_weierstrass_add(wQ, wP), rP + rQ)
check_point(ecc_weierstrass_double(wP), rP + rP)
check_point(ecc_weierstrass_double(wQ), rQ + rQ)
# Check all the special cases with add_general:
# Adding two finite unequal non-mutually-inverse points
check_point(ecc_weierstrass_add_general(wP, wQ), rP + rQ)
# Doubling a finite point
check_point(ecc_weierstrass_add_general(wP, wP), rP + rP)
check_point(ecc_weierstrass_add_general(wQ, wQ), rQ + rQ)
# Adding the identity to a point (both ways round)
check_point(ecc_weierstrass_add_general(wI, wP), rP)
check_point(ecc_weierstrass_add_general(wI, wQ), rQ)
check_point(ecc_weierstrass_add_general(wP, wI), rP)
check_point(ecc_weierstrass_add_general(wQ, wI), rQ)
# Doubling the identity
check_point(ecc_weierstrass_add_general(wI, wI), rI)
# Adding a point to its own inverse, giving the identity.
check_point(ecc_weierstrass_add_general(wmP, wP), rI)
check_point(ecc_weierstrass_add_general(wP, wmP), rI)
# Verify that point_valid fails if we pass it nonsense.
bogus = ecc_weierstrass_point_new(wc, int(rP.x), int(rP.y * 3))
self.assertFalse(ecc_weierstrass_point_valid(bogus))
# Re-instantiate the curve with the ability to take square
# roots, and check that we can reconstruct P and Q from their
# x coordinate and y parity only.
wc = ecc_weierstrass_curve(p, a, b, find_non_square_mod(p))
x, yp = int(rP.x), (int(rP.y) & 1)
check_point(ecc_weierstrass_point_new_from_x(wc, x, yp), rP)
check_point(ecc_weierstrass_point_new_from_x(wc, x, yp ^ 1), rmP)
x, yp = int(rQ.x), (int(rQ.y) & 1)
check_point(ecc_weierstrass_point_new_from_x(wc, x, yp), rQ)
def testMontgomerySimple(self):
p, a, b = 3141592661, 0xabc, 0xde
rc = MontgomeryCurve(p, a, b)
mc = ecc_montgomery_curve(p, a, b)
rP = rc.cpoint(0x1001)
rQ = rc.cpoint(0x20001)
rdiff = rP - rQ
rsum = rP + rQ
def make_mpoint(rp):
return ecc_montgomery_point_new(mc, int(rp.x))
mP = make_mpoint(rP)
mQ = make_mpoint(rQ)
mdiff = make_mpoint(rdiff)
msum = make_mpoint(rsum)
def check_point(mp, rp):
x = ecc_montgomery_get_affine(mp)
self.assertEqual(int(x), int(rp.x))
check_point(ecc_montgomery_diff_add(mP, mQ, mdiff), rsum)
check_point(ecc_montgomery_diff_add(mQ, mP, mdiff), rsum)
check_point(ecc_montgomery_diff_add(mP, mQ, msum), rdiff)
check_point(ecc_montgomery_diff_add(mQ, mP, msum), rdiff)
check_point(ecc_montgomery_double(mP), rP + rP)
check_point(ecc_montgomery_double(mQ), rQ + rQ)
def testEdwardsSimple(self):
p, d, a = 3141592661, 2688750488, 367934288
rc = TwistedEdwardsCurve(p, d, a)
ec = ecc_edwards_curve(p, d, a, None)
def check_point(ep, rp):
x, y = ecc_edwards_get_affine(ep)
self.assertEqual(int(x), int(rp.x))
self.assertEqual(int(y), int(rp.y))
def make_point(x, y):
ep = ecc_edwards_point_new(ec, x, y)
rp = rc.point(x, y)
check_point(ep, rp)
return ep, rp
# Some sample points, including the identity and also a pair
# of mutual inverses.
eI, rI = make_point(0, 1)
eP, rP = make_point(196270812, 1576162644)
eQ, rQ = make_point(1777630975, 2717453445)
emP, rmP = make_point(p - 196270812, 1576162644)
# Check that the ordinary add function handles all the special
# cases.
# Adding two finite unequal non-mutually-inverse points
check_point(ecc_edwards_add(eP, eQ), rP + rQ)
check_point(ecc_edwards_add(eQ, eP), rP + rQ)
# Doubling a finite point
check_point(ecc_edwards_add(eP, eP), rP + rP)
check_point(ecc_edwards_add(eQ, eQ), rQ + rQ)
# Adding the identity to a point (both ways round)
check_point(ecc_edwards_add(eI, eP), rP)
check_point(ecc_edwards_add(eI, eQ), rQ)
check_point(ecc_edwards_add(eP, eI), rP)
check_point(ecc_edwards_add(eQ, eI), rQ)
# Doubling the identity
check_point(ecc_edwards_add(eI, eI), rI)
# Adding a point to its own inverse, giving the identity.
check_point(ecc_edwards_add(emP, eP), rI)
check_point(ecc_edwards_add(eP, emP), rI)
# Re-instantiate the curve with the ability to take square
# roots, and check that we can reconstruct P and Q from their
# y coordinate and x parity only.
ec = ecc_edwards_curve(p, d, a, find_non_square_mod(p))
y, xp = int(rP.y), (int(rP.x) & 1)
check_point(ecc_edwards_point_new_from_y(ec, y, xp), rP)
check_point(ecc_edwards_point_new_from_y(ec, y, xp ^ 1), rmP)
y, xp = int(rQ.y), (int(rQ.x) & 1)
check_point(ecc_edwards_point_new_from_y(ec, y, xp), rQ)
# For testing point multiplication, let's switch to the full-sized
# standard curves, because I want to have tested those a bit too.
def testWeierstrassMultiply(self):
wc = ecc_weierstrass_curve(p256.p, int(p256.a), int(p256.b), None)
wG = ecc_weierstrass_point_new(wc, int(p256.G.x), int(p256.G.y))
self.assertTrue(ecc_weierstrass_point_valid(wG))
ints = set(i % p256.p for i in fibonacci_scattered(10))
ints.remove(0) # the zero multiple isn't expected to work
for i in sorted(ints):
wGi = ecc_weierstrass_multiply(wG, i)
x, y = ecc_weierstrass_get_affine(wGi)
rGi = p256.G * i
self.assertEqual(int(x), int(rGi.x))
self.assertEqual(int(y), int(rGi.y))
def testMontgomeryMultiply(self):
mc = ecc_montgomery_curve(
curve25519.p, int(curve25519.a), int(curve25519.b))
mG = ecc_montgomery_point_new(mc, int(curve25519.G.x))
ints = set(i % p256.p for i in fibonacci_scattered(10))
ints.remove(0) # the zero multiple isn't expected to work
for i in sorted(ints):
mGi = ecc_montgomery_multiply(mG, i)
x = ecc_montgomery_get_affine(mGi)
rGi = curve25519.G * i
self.assertEqual(int(x), int(rGi.x))
def testEdwardsMultiply(self):
ec = ecc_edwards_curve(ed25519.p, int(ed25519.d), int(ed25519.a), None)
eG = ecc_edwards_point_new(ec, int(ed25519.G.x), int(ed25519.G.y))
ints = set(i % ed25519.p for i in fibonacci_scattered(10))
ints.remove(0) # the zero multiple isn't expected to work
for i in sorted(ints):
eGi = ecc_edwards_multiply(eG, i)
x, y = ecc_edwards_get_affine(eGi)
rGi = ed25519.G * i
self.assertEqual(int(x), int(rGi.x))
self.assertEqual(int(y), int(rGi.y))
class standard_test_vectors(unittest.TestCase):
def testAES(self):
def vector(cipher, key, plaintext, ciphertext):
c = ssh2_cipher_new(cipher)
ssh2_cipher_setkey(c, key)
# The AES test vectors are implicitly in ECB mode, because
# they're testing the cipher primitive rather than any
# mode layered on top of it. We fake this by using PuTTY's
# CBC setting, and clearing the IV to all zeroes before
# each operation.
ssh2_cipher_setiv(c, b'\x00' * 16)
self.assertEqual(ssh2_cipher_encrypt(c, plaintext), ciphertext)
ssh2_cipher_setiv(c, b'\x00' * 16)
self.assertEqual(ssh2_cipher_decrypt(c, ciphertext), plaintext)
# The test vectors from FIPS 197 appendix C: the key bytes go
# 00 01 02 03 ... for as long as needed, and the plaintext
# bytes go 00 11 22 33 ... FF.
fullkey = struct.pack("B"*32, *range(32))
plaintext = struct.pack("B"*16, *[0x11*i for i in range(16)])
vector('aes128', fullkey[:16], plaintext,
unhex('69c4e0d86a7b0430d8cdb78070b4c55a'))
vector('aes192', fullkey[:24], plaintext,
unhex('dda97ca4864cdfe06eaf70a0ec0d7191'))
vector('aes256', fullkey[:32], plaintext,
unhex('8ea2b7ca516745bfeafc49904b496089'))
def testMD5(self):
MD5 = lambda s: hash_str('md5', s)
# The test vectors from RFC 1321 section A.5.
self.assertEqual(MD5(""), unhex('d41d8cd98f00b204e9800998ecf8427e'))
self.assertEqual(MD5("a"), unhex('0cc175b9c0f1b6a831c399e269772661'))
self.assertEqual(MD5("abc"), unhex('900150983cd24fb0d6963f7d28e17f72'))
self.assertEqual(MD5("message digest"),
unhex('f96b697d7cb7938d525a2f31aaf161d0'))
self.assertEqual(MD5("abcdefghijklmnopqrstuvwxyz"),
unhex('c3fcd3d76192e4007dfb496cca67e13b'))
self.assertEqual(MD5("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz0123456789"),
unhex('d174ab98d277d9f5a5611c2c9f419d9f'))
self.assertEqual(MD5("1234567890123456789012345678901234567890"
"1234567890123456789012345678901234567890"),
unhex('57edf4a22be3c955ac49da2e2107b67a'))
def testHmacMD5(self):
# The test vectors from the RFC 2104 Appendix.
self.assertEqual(mac_str('hmac_md5', unhex('0b'*16), "Hi There"),
unhex('9294727a3638bb1c13f48ef8158bfc9d'))
self.assertEqual(mac_str('hmac_md5', "Jefe",
"what do ya want for nothing?"),
unhex('750c783e6ab0b503eaa86e310a5db738'))
self.assertEqual(mac_str('hmac_md5', unhex('aa'*16), unhex('dd'*50)),
unhex('56be34521d144c88dbb8c733f0e8b3f6'))
def testEd25519(self):
def vector(privkey, pubkey, message, signature):
x, y = ecc_edwards_get_affine(eddsa_public(
mp_from_bytes_le(privkey), 'ed25519'))
self.assertEqual(int(y) | ((int(x) & 1) << 255),
int(mp_from_bytes_le(pubkey)))
pubblob = ssh_string(b"ssh-ed25519") + ssh_string(pubkey)
privblob = ssh_string(privkey)
sigblob = ssh_string(b"ssh-ed25519") + ssh_string(signature)
pubkey = ssh_key_new_pub('ed25519', pubblob)
self.assertTrue(ssh_key_verify(pubkey, sigblob, message))
privkey = ssh_key_new_priv('ed25519', pubblob, privblob)
# By testing that the signature is exactly the one expected in
# the test vector and not some equivalent one generated with a
# different nonce, we're verifying in particular that we do
# our deterministic nonce generation in the manner specified
# by Ed25519. Getting that wrong would lead to no obvious
# failure, but would surely turn out to be a bad idea sooner
# or later...
self.assertEqual(ssh_key_sign(privkey, message, 0), sigblob)
# A cherry-picked example from DJB's test vector data at
# https://ed25519.cr.yp.to/python/sign.input, which is too
# large to copy into here in full.
privkey = unhex(
'c89955e0f7741d905df0730b3dc2b0ce1a13134e44fef3d40d60c020ef19df77')
pubkey = unhex(
'fdb30673402faf1c8033714f3517e47cc0f91fe70cf3836d6c23636e3fd2287c')
message = unhex(
'507c94c8820d2a5793cbf3442b3d71936f35fe3afef316')
signature = unhex(
'7ef66e5e86f2360848e0014e94880ae2920ad8a3185a46b35d1e07dea8fa8ae4'
'f6b843ba174d99fa7986654a0891c12a794455669375bf92af4cc2770b579e0c')
vector(privkey, pubkey, message, signature)
# You can get this test program to run the full version of
# DJB's test vectors by modifying the source temporarily to
# set this variable to a pathname where you downloaded the
# file.
ed25519_test_vector_path = None
if ed25519_test_vector_path is not None:
with open(ed25519_test_vector_path) as f:
for line in iter(f.readline, ""):
words = line.split(":")
# DJB's test vector input format concatenates a
# spare copy of the public key to the end of the
# private key, and a spare copy of the message to
# the end of the signature. Strip those off.
privkey = unhex(words[0])[:32]
pubkey = unhex(words[1])
message = unhex(words[2])
signature = unhex(words[3])[:64]
vector(privkey, pubkey, message, signature)
if __name__ == "__main__":
try:
unittest.main()
finally:
# On exit, make sure we check the subprocess's return status,
# so that if Leak Sanitiser detected any memory leaks, the
# test will turn into a failure at the last minute.
childprocess.check_return_status()