diff --git a/Recipe b/Recipe index a28d7696..c332577c 100644 --- a/Recipe +++ b/Recipe @@ -252,7 +252,7 @@ NONSSH = telnet raw rlogin supdup ldisc pinger # SSH back end (putty, plink, pscp, psftp). ARITH = mpint ecc -SSHCRYPTO = ARITH sshmd5 sshsha sshsh256 sshsh512 sshsha3 +SSHCRYPTO = ARITH sshmd5 sshsha sshsh256 sshsh512 sshsha3 sshblake2 + sshrsa sshdss sshecc + sshdes sshblowf sshaes sshccp ssharcf + sshdh sshcrc sshcrcda sshauxcrypt diff --git a/ssh.h b/ssh.h index c4c1d3d9..56a2c82d 100644 --- a/ssh.h +++ b/ssh.h @@ -986,6 +986,7 @@ extern const ssh_hashalg ssh_sha3_256; extern const ssh_hashalg ssh_sha3_384; extern const ssh_hashalg ssh_sha3_512; extern const ssh_hashalg ssh_shake256_114bytes; +extern const ssh_hashalg ssh_blake2b; extern const ssh_kexes ssh_diffiehellman_group1; extern const ssh_kexes ssh_diffiehellman_group14; extern const ssh_kexes ssh_diffiehellman_gex; @@ -1015,6 +1016,10 @@ extern const ssh2_macalg ssh_hmac_sha256; extern const ssh2_macalg ssh2_poly1305; extern const ssh_compression_alg ssh_zlib; +/* Special constructor: BLAKE2b can be instantiated with any hash + * length up to 128 bytes */ +ssh_hash *blake2b_new_general(unsigned hashlen); + /* * On some systems, you have to detect hardware crypto acceleration by * asking the local OS API rather than OS-agnostically asking the CPU diff --git a/sshblake2.c b/sshblake2.c new file mode 100644 index 00000000..a4d42f21 --- /dev/null +++ b/sshblake2.c @@ -0,0 +1,223 @@ +/* + * BLAKE2 (RFC 7693) implementation for PuTTY. + * + * The BLAKE2 hash family includes BLAKE2s, in which the hash state is + * operated on as a collection of 32-bit integers, and BLAKE2b, based + * on 64-bit integers. At present this code implements BLAKE2b only. + */ + +#include +#include "ssh.h" + +static inline uint64_t ror(uint64_t x, unsigned rotation) +{ + unsigned lshift = 63 & -rotation, rshift = 63 & rotation; + return (x << lshift) | (x >> rshift); +} + +/* RFC 7963 section 2.1 */ +enum { R1 = 32, R2 = 24, R3 = 16, R4 = 63 }; + +/* RFC 7693 section 2.6 */ +static const uint64_t iv[] = { + 0x6a09e667f3bcc908, /* floor(2^64 * frac(sqrt(2))) */ + 0xbb67ae8584caa73b, /* floor(2^64 * frac(sqrt(3))) */ + 0x3c6ef372fe94f82b, /* floor(2^64 * frac(sqrt(5))) */ + 0xa54ff53a5f1d36f1, /* floor(2^64 * frac(sqrt(7))) */ + 0x510e527fade682d1, /* floor(2^64 * frac(sqrt(11))) */ + 0x9b05688c2b3e6c1f, /* floor(2^64 * frac(sqrt(13))) */ + 0x1f83d9abfb41bd6b, /* floor(2^64 * frac(sqrt(17))) */ + 0x5be0cd19137e2179, /* floor(2^64 * frac(sqrt(19))) */ +}; + +/* RFC 7693 section 2.7 */ +static const unsigned char sigma[][16] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, + /* This array recycles if you have more than 10 rounds. BLAKE2b + * has 12, so we repeat the first two rows again. */ + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, +}; + +static inline void g_half(uint64_t v[16], unsigned a, unsigned b, unsigned c, + unsigned d, uint64_t x, unsigned r1, unsigned r2) +{ + v[a] += v[b] + x; + v[d] ^= v[a]; + v[d] = ror(v[d], r1); + v[c] += v[d]; + v[b] ^= v[c]; + v[b] = ror(v[b], r2); +} + +static inline void g(uint64_t v[16], unsigned a, unsigned b, unsigned c, + unsigned d, uint64_t x, uint64_t y) +{ + g_half(v, a, b, c, d, x, R1, R2); + g_half(v, a, b, c, d, y, R3, R4); +} + +static inline void f(uint64_t h[8], uint64_t m[16], uint64_t offset_hi, + uint64_t offset_lo, unsigned final) +{ + uint64_t v[16]; + memcpy(v, h, 8 * sizeof(*v)); + memcpy(v + 8, iv, 8 * sizeof(*v)); + v[12] ^= offset_lo; + v[13] ^= offset_hi; + v[14] ^= -(uint64_t)final; + for (unsigned round = 0; round < 12; round++) { + const unsigned char *s = sigma[round]; + g(v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]]); + g(v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]]); + g(v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]]); + g(v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]]); + g(v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]]); + g(v, 1, 6, 11, 12, m[s[10]], m[s[11]]); + g(v, 2, 7, 8, 13, m[s[12]], m[s[13]]); + g(v, 3, 4, 9, 14, m[s[14]], m[s[15]]); + } + for (unsigned i = 0; i < 8; i++) + h[i] ^= v[i] ^ v[i+8]; + smemclr(v, sizeof(v)); +} + +static inline void f_outer(uint64_t h[8], uint8_t blk[128], uint64_t offset_hi, + uint64_t offset_lo, unsigned final) +{ + uint64_t m[16]; + for (unsigned i = 0; i < 16; i++) + m[i] = GET_64BIT_LSB_FIRST(blk + 8*i); + f(h, m, offset_hi, offset_lo, final); + smemclr(m, sizeof(m)); +} + +typedef struct blake2b { + uint64_t h[8]; + unsigned hashlen; + + uint8_t block[128]; + size_t used; + uint64_t lenhi, lenlo; + + BinarySink_IMPLEMENTATION; + ssh_hash hash; +} blake2b; + +static void blake2b_write(BinarySink *bs, const void *vp, size_t len); + +static ssh_hash *blake2b_new_inner(unsigned hashlen) +{ + assert(hashlen <= ssh_blake2b.hlen); + + blake2b *s = snew(blake2b); + s->hash.vt = &ssh_blake2b; + s->hashlen = hashlen; + BinarySink_INIT(s, blake2b_write); + BinarySink_DELEGATE_INIT(&s->hash, s); + return &s->hash; +} + +static ssh_hash *blake2b_new(const ssh_hashalg *alg) +{ + return blake2b_new_inner(alg->hlen); +} + +ssh_hash *blake2b_new_general(unsigned hashlen) +{ + ssh_hash *h = blake2b_new_inner(hashlen); + ssh_hash_reset(h); + return h; +} + +static void blake2b_reset(ssh_hash *hash) +{ + blake2b *s = container_of(hash, blake2b, hash); + + /* Initialise the hash to the standard IV */ + memcpy(s->h, iv, sizeof(s->h)); + + /* XOR in the parameters: secret key length (here always 0) in + * byte 1, and hash length in byte 0. */ + s->h[0] ^= 0x01010000 ^ s->hashlen; + + s->used = 0; + s->lenhi = s->lenlo = 0; +} + +static void blake2b_copyfrom(ssh_hash *hcopy, ssh_hash *horig) +{ + blake2b *copy = container_of(hcopy, blake2b, hash); + blake2b *orig = container_of(horig, blake2b, hash); + + memcpy(copy, orig, sizeof(*copy)); + BinarySink_COPIED(copy); + BinarySink_DELEGATE_INIT(©->hash, copy); +} + +static void blake2b_free(ssh_hash *hash) +{ + blake2b *s = container_of(hash, blake2b, hash); + + smemclr(s, sizeof(*s)); + sfree(s); +} + +static void blake2b_write(BinarySink *bs, const void *vp, size_t len) +{ + blake2b *s = BinarySink_DOWNCAST(bs, blake2b); + const uint8_t *p = vp; + + while (len > 0) { + if (s->used == sizeof(s->block)) { + f_outer(s->h, s->block, s->lenhi, s->lenlo, 0); + s->used = 0; + } + + size_t chunk = sizeof(s->block) - s->used; + if (chunk > len) + chunk = len; + + memcpy(s->block + s->used, p, chunk); + s->used += chunk; + p += chunk; + len -= chunk; + + s->lenlo += chunk; + s->lenhi += (s->lenlo < chunk); + } +} + +static void blake2b_digest(ssh_hash *hash, uint8_t *digest) +{ + blake2b *s = container_of(hash, blake2b, hash); + + memset(s->block + s->used, 0, sizeof(s->block) - s->used); + f_outer(s->h, s->block, s->lenhi, s->lenlo, 1); + + uint8_t hash_pre[128]; + for (unsigned i = 0; i < 8; i++) + PUT_64BIT_LSB_FIRST(hash_pre + 8*i, s->h[i]); + memcpy(digest, hash_pre, s->hashlen); + smemclr(hash_pre, sizeof(hash_pre)); +} + +const ssh_hashalg ssh_blake2b = { + .new = blake2b_new, + .reset = blake2b_reset, + .copyfrom = blake2b_copyfrom, + .digest = blake2b_digest, + .free = blake2b_free, + .hlen = 64, + .blocklen = 128, + HASHALG_NAMES_BARE("BLAKE2b-64"), +}; diff --git a/test/cryptsuite.py b/test/cryptsuite.py index e2039c42..b3d6e623 100755 --- a/test/cryptsuite.py +++ b/test/cryptsuite.py @@ -9,6 +9,7 @@ import contextlib import hashlib import binascii import base64 +import json try: from math import gcd except ImportError: @@ -1727,6 +1728,35 @@ culpa qui officia deserunt mollit anim id est laborum. self.assertFalse(ssh_key_verify(pubkey, badsig0, "hello, again")) self.assertFalse(ssh_key_verify(pubkey, badsigq, "hello, again")) + def testBLAKE2b(self): + # The standard test vectors for BLAKE2b (in the separate class + # below) don't satisfy me because they only test one hash + # size. These additional tests exercise BLAKE2b's configurable + # output length. The expected results are derived from the + # BLAKE2 reference implementation. + + def b2_with_len(data, length): + h = blake2b_new_general(length) + h.update(data) + return h.digest()[:length] + + self.assertEqualBin(b2_with_len(b'hello', 1), unhex("29")) + self.assertEqualBin(b2_with_len(b'hello', 2), unhex("accd")) + self.assertEqualBin(b2_with_len(b'hello', 3), unhex("980032")) + self.assertEqualBin(b2_with_len(b'hello', 5), unhex("9baecc38f2")) + self.assertEqualBin(b2_with_len(b'hello', 8), unhex( + "a7b6eda801e5347d")) + self.assertEqualBin(b2_with_len(b'hello', 13), unhex( + "6eedb122c6707328a66aa34a07")) + self.assertEqualBin(b2_with_len(b'hello', 21), unhex( + "c7f0f74a227116547b3d2788e927ee2a76c87d8797")) + self.assertEqualBin(b2_with_len(b'hello', 34), unhex( + "2f5fcdf2b870fa254051dd448193a1fb6e92be122efca539ba2aeac0bc6c77d0" + "dadc")) + self.assertEqualBin(b2_with_len(b'hello', 55), unhex( + "daafcf2bd6fccf976cbc234b71cd9f4f7d56fe0eb33a40018707089a215c44a8" + "4b272d0329ae6d85a0f8acc7e964dc2facb715ba472bb6")) + def testRSAVerify(self): def blobs(n, e, d, p, q, iqmp): pubblob = ssh_string(b"ssh-rsa") + ssh2_mpint(e) + ssh2_mpint(n) @@ -2404,6 +2434,43 @@ class standard_test_vectors(MyTestBase): self.assertEqualBin(hash_str('shake256_114bytes', ''), unhex("46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762fd75dc4ddd8c0f200cb05019d67b592f6fc821c49479ab48640292eacb3b7c4be141e96616fb13957692cc7edd0b45ae3dc07223c8e92937bef84bc0eab862853349ec75546f58fb7c2775c38462c5010d846")) self.assertEqualBin(hash_str('shake256_114bytes', unhex('a3')*200), unhex("cd8a920ed141aa0407a22d59288652e9d9f1a7ee0c1e7c1ca699424da84a904d2d700caae7396ece96604440577da4f3aa22aeb8857f961c4cd8e06f0ae6610b1048a7f64e1074cd629e85ad7566048efc4fb500b486a3309a8f26724c0ed628001a1099422468de726f1061d99eb9e93604")) + def testBLAKE2b(self): + # Test case from RFC 7693 appendix A. + self.assertEqualBin(hash_str('blake2b', b'abc'), unhex( + "ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1" + "7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923")) + + # A small number of test cases from the larger test vector + # set, testing multiple blocks and the empty input. + self.assertEqualBin(hash_str('blake2b', b''), unhex( + "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419" + "d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce")) + self.assertEqualBin(hash_str('blake2b', unhex('00')), unhex( + "2fa3f686df876995167e7c2e5d74c4c7b6e48f8068fe0e44208344d480f7904c" + "36963e44115fe3eb2a3ac8694c28bcb4f5a0f3276f2e79487d8219057a506e4b")) + self.assertEqualBin(hash_str('blake2b', bytes(range(255))), unhex( + "5b21c5fd8868367612474fa2e70e9cfa2201ffeee8fafab5797ad58fefa17c9b" + "5b107da4a3db6320baaf2c8617d5a51df914ae88da3867c2d41f0cc14fa67928")) + + # You can get this test program to run the full version of the + # test vectors by modifying the source temporarily to set this + # variable to a pathname where you downloaded the JSON file + # blake2-kat.json. + blake2_test_vectors_path = None + if blake2_test_vectors_path is not None: + with open(blake2_test_vectors_path) as fh: + vectors = json.load(fh) + for vector in vectors: + if vector['hash'] != 'blake2b': + continue + if len(vector['key']) != 0: + continue + + h = blake2b_new_general(len(vector['out']) // 2) + ssh_hash_update(h, unhex(vector['in'])) + digest = ssh_hash_digest(h) + self.assertEqualBin(digest, unhex(vector['out'])) + def testHmacSHA(self): # Test cases from RFC 6234 section 8.5. def vector(key, message, s1=None, s256=None): diff --git a/testcrypt.c b/testcrypt.c index f6d9e61d..0113fbfc 100644 --- a/testcrypt.c +++ b/testcrypt.c @@ -225,6 +225,7 @@ static const ssh_hashalg *get_hashalg(BinarySource *in) {"sha3_384", &ssh_sha3_384}, {"sha3_512", &ssh_sha3_512}, {"shake256_114bytes", &ssh_shake256_114bytes}, + {"blake2b", &ssh_blake2b}, }; ptrlen name = get_word(in); diff --git a/testcrypt.h b/testcrypt.h index a50c920c..e7624aa0 100644 --- a/testcrypt.h +++ b/testcrypt.h @@ -136,6 +136,8 @@ FUNC1(val_string, ssh_hash_digest, val_hash) FUNC1(val_string, ssh_hash_final, consumed_val_hash) FUNC2(void, ssh_hash_update, val_hash, val_string_ptrlen) +FUNC1(opt_val_hash, blake2b_new_general, uint) + /* * The ssh2_mac abstraction. Note the optional ssh_cipher parameter * to ssh2_mac_new. Also, again, I've invented an ssh2_mac_update so diff --git a/testsc.c b/testsc.c index a977084e..5beeee95 100644 --- a/testsc.c +++ b/testsc.c @@ -277,6 +277,7 @@ VOLATILE_WRAPPED_DEFN(static, size_t, looplimit, (size_t x)) X(Y, ssh_sha3_384) \ X(Y, ssh_sha3_512) \ X(Y, ssh_shake256_114bytes) \ + X(Y, ssh_blake2b) \ /* end of list */ #define HASH_TESTLIST(X, name) X(hash_ ## name)