diff --git a/cipher/ecc-curves.c b/cipher/ecc-curves.c --- a/cipher/ecc-curves.c +++ b/cipher/ecc-curves.c @@ -48,7 +48,7 @@ { "Curve25519", "1.3.101.110" }, /* rfc8410 */ { "Curve25519", "X25519" }, /* rfc8410 */ - /* { "Ed448", "1.3.101.113" }, /\* rfc8410 *\/ */ + { "Ed448", "1.3.101.113" }, /* rfc8410 */ { "X448", "1.3.101.111" }, /* rfc8410 */ { "NIST P-192", "1.2.840.10045.3.1.1" }, /* X9.62 OID */ @@ -170,6 +170,23 @@ * the function _gcry_ecc_fill_in_curve. See bug #4712. */ }, + { + /* (x^2 + y^2 = 1 + dx^2y^2) */ + "Ed448", 448, 0, + MPI_EC_EDWARDS, ECC_DIALECT_SAFECURVE, + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "0x01", + "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6756", + "0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "7CCA23E9C44EDB49AED63690216CC2728DC58F552378C292AB5844F3", + "0x4F1970C66BED0DED221D15A622BF36DA9E146570470F1767EA6DE324" + "A3D3A46412AE1AF72AB66511433B80E18B00938E2626A82BC70CC05E", + "0x693F46716EB6BC248876203756C9C7624BEA73736CA3984087789C1E" + "05A0C2D73AD3FF1CE67C39C4FDBD132C4ED7C8AD9808795BF230FA14", + 4, + }, { /* (y^2 = x^3 + 156326*x^2 + x) */ "X448", 448, 0, @@ -1138,12 +1155,15 @@ if (ec->d && is_opaque_bytes) { unsigned int n = mpi_get_nbits (ec->d); + unsigned int len; + + len = (ec->nbits%8) == 0 ? (ec->nbits/8 + 1) : (ec->nbits+7)/8; - if ((n+7)/8 != (ec->nbits+7)/8) + if ((n+7)/8 != len) { if (DBG_CIPHER) log_debug ("scalar size (%d) != prime size (%d)", - (n+7)/8, (ec->nbits+7)/8); + (n+7)/8, len); errc = GPG_ERR_INV_OBJ; } diff --git a/cipher/ecc-eddsa.c b/cipher/ecc-eddsa.c --- a/cipher/ecc-eddsa.c +++ b/cipher/ecc-eddsa.c @@ -66,11 +66,12 @@ malloced buffer with the encoded point is stored at R_BUFFER; the length of this buffer is stored at R_BUFLEN. */ static gpg_err_code_t -eddsa_encodempi (gcry_mpi_t mpi, unsigned int minlen, +eddsa_encodempi (gcry_mpi_t mpi, unsigned int nbits, unsigned char **r_buffer, unsigned int *r_buflen) { unsigned char *rawmpi; unsigned int rawmpilen; + unsigned int minlen = (nbits%8) == 0 ? (nbits/8 + 1): (nbits+7)/8; rawmpi = _gcry_mpi_get_buffer (mpi, minlen, &rawmpilen, NULL); if (!rawmpi) @@ -82,19 +83,20 @@ } -/* Encode (X,Y) using the EdDSA scheme. MINLEN is the required length - in bytes for the result. If WITH_PREFIX is set the returned buffer - is prefixed with a 0x40 byte. On success 0 is returned and a - malloced buffer with the encoded point is stored at R_BUFFER; the +/* Encode (X,Y) using the EdDSA scheme. NBITS is the number of bits + of the field of the curve. If WITH_PREFIX is set the returned + buffer is prefixed with a 0x40 byte. On success 0 is returned and + a malloced buffer with the encoded point is stored at R_BUFFER; the length of this buffer is stored at R_BUFLEN. */ static gpg_err_code_t -eddsa_encode_x_y (gcry_mpi_t x, gcry_mpi_t y, unsigned int minlen, +eddsa_encode_x_y (gcry_mpi_t x, gcry_mpi_t y, unsigned int nbits, int with_prefix, unsigned char **r_buffer, unsigned int *r_buflen) { unsigned char *rawmpi; unsigned int rawmpilen; int off = with_prefix? 1:0; + unsigned int minlen = (nbits%8) == 0 ? (nbits/8 + 1): (nbits+7)/8; rawmpi = _gcry_mpi_get_buffer_extra (y, minlen, off?-1:0, &rawmpilen, NULL); if (!rawmpi) @@ -133,7 +135,7 @@ rc = GPG_ERR_INTERNAL; } else - rc = eddsa_encode_x_y (x, y, (ec->nbits+7)/8, with_prefix, r_buffer, r_buflen); + rc = eddsa_encode_x_y (x, y, ec->nbits, with_prefix, r_buffer, r_buflen); if (!x_in) mpi_free (x); @@ -180,7 +182,7 @@ return rc; } - rc = eddsa_encode_x_y (x, y, (nbits+7)/8, 0, &enc, &enclen); + rc = eddsa_encode_x_y (x, y, nbits, 0, &enc, &enclen); mpi_free (x); mpi_free (y); if (rc) @@ -202,6 +204,76 @@ } +static gpg_err_code_t +ecc_ed448_recover_x (gcry_mpi_t x, gcry_mpi_t y, int x_0, mpi_ec_t ec) +{ + gpg_err_code_t rc = 0; + gcry_mpi_t u, v, u3, v3, t; + static gcry_mpi_t p34; /* Hard coded (P-3)/4 */ + + if (mpi_cmp (y, ec->p) >= 0) + rc = GPG_ERR_INV_OBJ; + + if (!p34) + p34 = scanval ("3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + + u = mpi_new (0); + v = mpi_new (0); + u3 = mpi_new (0); + v3 = mpi_new (0); + t = mpi_new (0); + + /* Compute u and v */ + /* u = y^2 */ + mpi_mulm (u, y, y, ec->p); + /* v = b*y^2 */ + mpi_mulm (v, ec->b, u, ec->p); + /* u = y^2-1 */ + mpi_sub_ui (u, u, 1); + /* v = b*y^2-1 */ + mpi_sub_ui (v, v, 1); + + /* Compute sqrt(u/v) */ + /* u3 = u^3 */ + mpi_powm (u3, u, mpi_const (MPI_C_THREE), ec->p); + mpi_powm (v3, v, mpi_const (MPI_C_THREE), ec->p); + /* t = u^4 * u * v3 = u^5 * v^3 */ + mpi_powm (t, u, mpi_const (MPI_C_FOUR), ec->p); + mpi_mulm (t, t, u, ec->p); + mpi_mulm (t, t, v3, ec->p); + /* t = t^((p-3)/4) = (u^5 * v^3)^((p-3)/4) */ + mpi_powm (t, t, p34, ec->p); + /* x = t * u^3 * v = (u^3 * v) * (u^5 * v^3)^((p-3)/4) */ + mpi_mulm (t, t, u3, ec->p); + mpi_mulm (x, t, v, ec->p); + + /* t = v * x^2 */ + mpi_mulm (t, x, x, ec->p); + mpi_mulm (t, t, v, ec->p); + + if (mpi_cmp (t, u) != 0) + rc = GPG_ERR_INV_OBJ; + else + { + if (!mpi_cmp_ui (x, 0) && x_0) + rc = GPG_ERR_INV_OBJ; + + /* Choose the desired square root according to parity */ + if (mpi_test_bit (x, 0) != !!x_0) + mpi_sub (x, ec->p, x); + } + + mpi_free (t); + mpi_free (u3); + mpi_free (v3); + mpi_free (v); + mpi_free (u); + + return rc; +} + + /* Recover X from Y and SIGN (which actually is a parity bit). */ gpg_err_code_t _gcry_ecc_eddsa_recover_x (gcry_mpi_t x, gcry_mpi_t y, int sign, mpi_ec_t ec) @@ -210,8 +282,16 @@ gcry_mpi_t u, v, v3, t; static gcry_mpi_t p58, seven; + /* + * This routine is actually curve specific. Now, only supports + * Ed25519 and Ed448. + */ + if (ec->dialect != ECC_DIALECT_ED25519) - return GPG_ERR_NOT_IMPLEMENTED; + /* For now, it's only Ed448. */ + return ecc_ed448_recover_x (x, y, sign, ec); + + /* It's Ed25519. */ if (!p58) p58 = scanval ("0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" @@ -299,20 +379,23 @@ if (mpi_is_opaque (pk)) { const unsigned char *buf; + unsigned int len; + + len = (ctx->nbits%8) == 0 ? (ctx->nbits/8 + 1): (ctx->nbits+7)/8; buf = mpi_get_opaque (pk, &rawmpilen); if (!buf) return GPG_ERR_INV_OBJ; rawmpilen = (rawmpilen + 7)/8; - if (!(rawmpilen == (ctx->nbits+7)/8 - || rawmpilen == (ctx->nbits+7)/8 + 1 - || rawmpilen == (ctx->nbits+7)/8 * 2 + 1)) + if (!(rawmpilen == len + || rawmpilen == len + 1 + || rawmpilen == len * 2 + 1)) return GPG_ERR_INV_OBJ; /* Handle compression prefixes. The size of the buffer will be odd in this case. */ - if (rawmpilen > 1 && (rawmpilen%2)) + if (rawmpilen > 1 && (rawmpilen == len + 1 || rawmpilen == len * 2 + 1)) { /* First check whether the public key has been given in standard uncompressed format (SEC1). No need to recover @@ -335,7 +418,7 @@ if (r_encpk) { - rc = eddsa_encode_x_y (x, y, (ctx->nbits+7)/8, 0, + rc = eddsa_encode_x_y (x, y, ctx->nbits, 0, r_encpk, r_encpklen); if (rc) { @@ -417,18 +500,25 @@ unsigned char *rawmpi = NULL; unsigned int rawmpilen; unsigned char *digest; - gcry_buffer_t hvec[2]; int hashalgo, b; *r_digest = NULL; - hashalgo = GCRY_MD_SHA512; - if (hashalgo != GCRY_MD_SHA512) - return GPG_ERR_DIGEST_ALGO; - b = (ec->nbits+7)/8; - if (b != 256/8) - return GPG_ERR_INTERNAL; /* We only support 256 bit. */ + + /* + * Choice of hashalgo is curve specific. + * For now, it's determine by the bit size of the field. + */ + if (ec->nbits == 255) + hashalgo = GCRY_MD_SHA512; + else if (ec->nbits == 448) + { + b++; + hashalgo = GCRY_MD_SHAKE256; + } + else + return GPG_ERR_NOT_IMPLEMENTED; /* Note that we clear DIGEST so we can use it as input to left pad the key with zeroes for hashing. */ @@ -436,8 +526,6 @@ if (!digest) return gpg_err_code_from_syserror (); - memset (hvec, 0, sizeof hvec); - rawmpi = _gcry_mpi_get_buffer (ec->d, 0, &rawmpilen, NULL); if (!rawmpi) { @@ -445,13 +533,38 @@ return gpg_err_code_from_syserror (); } - hvec[0].data = digest; - hvec[0].off = 0; - hvec[0].len = b > rawmpilen? b - rawmpilen : 0; - hvec[1].data = rawmpi; - hvec[1].off = 0; - hvec[1].len = rawmpilen; - rc = _gcry_md_hash_buffers (hashalgo, 0, digest, hvec, 2); + if (hashalgo == GCRY_MD_SHAKE256) + { + gcry_error_t err; + gcry_md_hd_t hd; + + err = _gcry_md_open (&hd, hashalgo, 0); + if (err) + rc = gcry_err_code (err); + else + { + _gcry_md_write (hd, rawmpi, rawmpilen); + _gcry_md_ctl (hd, GCRYCTL_FINALIZE, NULL, 0); + _gcry_md_extract (hd, GCRY_MD_SHAKE256, digest, 2*b); + _gcry_md_close (hd); + rc = 0; + } + } + else + { + gcry_buffer_t hvec[2]; + + memset (hvec, 0, sizeof hvec); + + hvec[0].data = digest; + hvec[0].off = 0; + hvec[0].len = b > rawmpilen? b - rawmpilen : 0; + hvec[1].data = rawmpi; + hvec[1].off = 0; + hvec[1].len = rawmpilen; + rc = _gcry_md_hash_buffers (hashalgo, 0, digest, hvec, 2); + } + xfree (rawmpi); if (rc) { @@ -460,9 +573,20 @@ } /* Compute the A value. */ - reverse_buffer (digest, 32); /* Only the first half of the hash. */ - digest[0] = (digest[0] & 0x7f) | 0x40; - digest[31] &= 0xf8; + reverse_buffer (digest, b); /* Only the first half of the hash. */ + + /* Field specific handling of clearing/setting bits. */ + if (ec->nbits == 255) + { + digest[0] = (digest[0] & 0x7f) | 0x40; + digest[31] &= 0xf8; + } + else + { + digest[0] = 0; + digest[1] |= 0x80; + digest[56] &= 0xfc; + } *r_digest = digest; return 0; @@ -483,51 +607,42 @@ gpg_err_code_t _gcry_ecc_eddsa_genkey (mpi_ec_t ec, int flags) { - gpg_err_code_t rc; - int b = 256/8; /* The only size we currently support. */ + int b; gcry_mpi_t a, x, y; mpi_point_struct Q; gcry_random_level_t random_level; char *dbuf; size_t dlen; - gcry_buffer_t hvec[1]; unsigned char *hash_d = NULL; point_init (&Q); - memset (hvec, 0, sizeof hvec); if ((flags & PUBKEY_FLAG_TRANSIENT_KEY)) random_level = GCRY_STRONG_RANDOM; else random_level = GCRY_VERY_STRONG_RANDOM; + b = (ec->nbits+7)/8; + + if (ec->nbits == 255) + ; + else if (ec->nbits == 448) + b++; + else + return GPG_ERR_NOT_IMPLEMENTED; + + dlen = b; + a = mpi_snew (0); x = mpi_new (0); y = mpi_new (0); /* Generate a secret. */ - hash_d = xtrymalloc_secure (2*b); - if (!hash_d) - { - rc = gpg_err_code_from_syserror (); - goto leave; - } - dlen = b; dbuf = _gcry_random_bytes_secure (dlen, random_level); - - /* Compute the A value. */ - hvec[0].data = dbuf; - hvec[0].len = dlen; - rc = _gcry_md_hash_buffers (GCRY_MD_SHA512, 0, hash_d, hvec, 1); - if (rc) - goto leave; ec->d = _gcry_mpi_set_opaque (NULL, dbuf, dlen*8); - dbuf = NULL; - reverse_buffer (hash_d, 32); /* Only the first half of the hash. */ - hash_d[0] = (hash_d[0] & 0x7f) | 0x40; - hash_d[31] &= 0xf8; - _gcry_mpi_set_buffer (a, hash_d, 32, 0); - xfree (hash_d); hash_d = NULL; + _gcry_ecc_eddsa_compute_h_d (&hash_d, ec); + _gcry_mpi_set_buffer (a, hash_d, b, 0); + xfree (hash_d); /* log_printmpi ("ecgen a", a); */ /* Compute Q. */ @@ -540,12 +655,10 @@ Q.y = NULL; Q.x = NULL; - leave: _gcry_mpi_release (a); _gcry_mpi_release (x); _gcry_mpi_release (y); - xfree (hash_d); - return rc; + return 0; } @@ -558,21 +671,23 @@ * * Despite that this function requires the specification of a hash * algorithm, we only support what has been specified by the paper. - * This may change in the future. Note that we don't check the used - * curve; the user is responsible to use Ed25519. + * This may change in the future. * * Return the signature struct (r,s) from the message hash. The caller * must have allocated R_R and S. */ + +/* String to be used with Ed448 */ +#define DOM4_0_NONE "SigEd448\0\0" +#define DOM4_0_NONE_LEN 10 + gpg_err_code_t _gcry_ecc_eddsa_sign (gcry_mpi_t input, mpi_ec_t ec, gcry_mpi_t r_r, gcry_mpi_t s, int hashalgo) { int rc; - int b; unsigned int tmp; unsigned char *digest = NULL; - gcry_buffer_t hvec[3]; const void *mbuf; size_t mlen; unsigned char *rawmpi = NULL; @@ -581,8 +696,16 @@ unsigned int encpklen; mpi_point_struct I; /* Intermediate value. */ gcry_mpi_t a, x, y, r; + int b; - memset (hvec, 0, sizeof hvec); + b = (ec->nbits+7)/8; + + if (ec->nbits == 255) + ; + else if (ec->nbits == 448) + b++; + else + return GPG_ERR_NOT_IMPLEMENTED; if (!mpi_is_opaque (input)) return GPG_ERR_INV_DATA; @@ -594,17 +717,10 @@ y = mpi_new (0); r = mpi_snew (0); - b = (ec->nbits+7)/8; - if (b != 256/8) - { - rc = GPG_ERR_INTERNAL; /* We only support 256 bit. */ - goto leave; - } - rc = _gcry_ecc_eddsa_compute_h_d (&digest, ec); if (rc) goto leave; - _gcry_mpi_set_buffer (a, digest, 32, 0); + _gcry_mpi_set_buffer (a, digest, b, 0); /* Compute the public key if it's not available (only secret part). */ if (ec->Q == NULL) @@ -627,18 +743,46 @@ if (DBG_CIPHER) log_printhex (" m", mbuf, mlen); - hvec[0].data = digest; - hvec[0].off = 32; - hvec[0].len = 32; - hvec[1].data = (char*)mbuf; - hvec[1].len = mlen; - rc = _gcry_md_hash_buffers (hashalgo, 0, digest, hvec, 2); + if (hashalgo == GCRY_MD_SHAKE256) + { + gcry_error_t err; + gcry_md_hd_t hd; + + err = _gcry_md_open (&hd, hashalgo, 0); + if (err) + rc = gcry_err_code (err); + else + { + _gcry_md_write (hd, DOM4_0_NONE, DOM4_0_NONE_LEN); + _gcry_md_write (hd, digest+b, b); + _gcry_md_write (hd, mbuf, mlen); + _gcry_md_ctl (hd, GCRYCTL_FINALIZE, NULL, 0); + _gcry_md_extract (hd, GCRY_MD_SHAKE256, digest, 2*b); + _gcry_md_close (hd); + rc = 0; + } + } + else + { + gcry_buffer_t hvec[3]; + + memset (hvec, 0, sizeof hvec); + + hvec[0].data = digest; + hvec[0].off = b; + hvec[0].len = b; + hvec[1].data = (char*)mbuf; + hvec[1].len = mlen; + rc = _gcry_md_hash_buffers (hashalgo, 0, digest, hvec, 2); + } + if (rc) goto leave; - reverse_buffer (digest, 64); + reverse_buffer (digest, 2*b); if (DBG_CIPHER) - log_printhex (" r", digest, 64); - _gcry_mpi_set_buffer (r, digest, 64, 0); + log_printhex (" r", digest, 2*b); + _gcry_mpi_set_buffer (r, digest, 2*b, 0); + mpi_mod (r, r, ec->n); _gcry_mpi_ec_mul_point (&I, r, ec->G, ec); if (DBG_CIPHER) log_printpnt (" r", &I, ec); @@ -650,17 +794,45 @@ if (DBG_CIPHER) log_printhex (" e_r", rawmpi, rawmpilen); - /* S = r + a * H(encodepoint(R) + encodepoint(pk) + m) mod n */ - hvec[0].data = rawmpi; /* (this is R) */ - hvec[0].off = 0; - hvec[0].len = rawmpilen; - hvec[1].data = encpk; - hvec[1].off = 0; - hvec[1].len = encpklen; - hvec[2].data = (char*)mbuf; - hvec[2].off = 0; - hvec[2].len = mlen; - rc = _gcry_md_hash_buffers (hashalgo, 0, digest, hvec, 3); + if (hashalgo == GCRY_MD_SHAKE256) + { + gcry_error_t err; + gcry_md_hd_t hd; + + err = _gcry_md_open (&hd, hashalgo, 0); + if (err) + rc = gcry_err_code (err); + else + { + _gcry_md_write (hd, DOM4_0_NONE, DOM4_0_NONE_LEN); + _gcry_md_write (hd, rawmpi, rawmpilen); + _gcry_md_write (hd, encpk, encpklen); + _gcry_md_write (hd, mbuf, mlen); + _gcry_md_ctl (hd, GCRYCTL_FINALIZE, NULL, 0); + _gcry_md_extract (hd, GCRY_MD_SHAKE256, digest, 2*b); + _gcry_md_close (hd); + rc = 0; + } + } + else + { + gcry_buffer_t hvec[3]; + + memset (hvec, 0, sizeof hvec); + + /* S = r + a * H(encodepoint(R) + encodepoint(pk) + m) mod n */ + hvec[0].data = rawmpi; /* (this is R) */ + hvec[0].off = 0; + hvec[0].len = rawmpilen; + hvec[1].data = encpk; + hvec[1].off = 0; + hvec[1].len = encpklen; + hvec[2].data = (char*)mbuf; + hvec[2].off = 0; + hvec[2].len = mlen; + rc = _gcry_md_hash_buffers (hashalgo, 0, digest, hvec, 3); + } + if (rc) goto leave; @@ -668,13 +840,13 @@ mpi_set_opaque (r_r, rawmpi, rawmpilen*8); rawmpi = NULL; - reverse_buffer (digest, 64); + reverse_buffer (digest, 2*b); if (DBG_CIPHER) - log_printhex (" H(R+)", digest, 64); - _gcry_mpi_set_buffer (s, digest, 64, 0); + log_printhex (" H(R+)", digest, 2*b); + _gcry_mpi_set_buffer (s, digest, 2*b, 0); mpi_mulm (s, s, a, ec->n); mpi_addm (s, s, r, ec->n); - rc = eddsa_encodempi (s, b, &rawmpi, &rawmpilen); + rc = eddsa_encodempi (s, ec->nbits, &rawmpi, &rawmpilen); if (rc) goto leave; if (DBG_CIPHER) @@ -713,15 +885,12 @@ unsigned char *tbuf = NULL; size_t mlen, rlen; unsigned int tlen; - unsigned char digest[64]; - gcry_buffer_t hvec[3]; + unsigned char digest[114]; gcry_mpi_t h, s; mpi_point_struct Ia, Ib; if (!mpi_is_opaque (input) || !mpi_is_opaque (r_in) || !mpi_is_opaque (s_in)) return GPG_ERR_INV_DATA; - if (hashalgo != GCRY_MD_SHA512) - return GPG_ERR_DIGEST_ALGO; point_init (&Ia); point_init (&Ib); @@ -729,11 +898,13 @@ s = mpi_new (0); b = (ec->nbits+7)/8; - if (b != 256/8) - { - rc = GPG_ERR_INTERNAL; /* We only support 256 bit. */ - goto leave; - } + + if (ec->nbits == 255) + ; + else if (ec->nbits == 448) + b++; + else + return GPG_ERR_NOT_IMPLEMENTED; /* Encode and check the public key. */ rc = _gcry_ecc_eddsa_encodepoint (ec->Q, ec, NULL, NULL, 0, @@ -768,23 +939,51 @@ goto leave; } - /* h = H(encodepoint(R) + encodepoint(pk) + m) */ - hvec[0].data = (char*)rbuf; - hvec[0].off = 0; - hvec[0].len = rlen; - hvec[1].data = encpk; - hvec[1].off = 0; - hvec[1].len = encpklen; - hvec[2].data = (char*)mbuf; - hvec[2].off = 0; - hvec[2].len = mlen; - rc = _gcry_md_hash_buffers (hashalgo, 0, digest, hvec, 3); + if (hashalgo == GCRY_MD_SHAKE256) + { + gcry_error_t err; + gcry_md_hd_t hd; + + err = _gcry_md_open (&hd, hashalgo, 0); + if (err) + rc = gcry_err_code (err); + else + { + _gcry_md_write (hd, DOM4_0_NONE, DOM4_0_NONE_LEN); + _gcry_md_write (hd, rbuf, rlen); + _gcry_md_write (hd, encpk, encpklen); + _gcry_md_write (hd, mbuf, mlen); + _gcry_md_ctl (hd, GCRYCTL_FINALIZE, NULL, 0); + _gcry_md_extract (hd, GCRY_MD_SHAKE256, digest, 2*b); + _gcry_md_close (hd); + rc = 0; + } + } + else + { + gcry_buffer_t hvec[3]; + + memset (hvec, 0, sizeof hvec); + + /* h = H(encodepoint(R) + encodepoint(pk) + m) */ + hvec[0].data = (char*)rbuf; + hvec[0].off = 0; + hvec[0].len = rlen; + hvec[1].data = encpk; + hvec[1].off = 0; + hvec[1].len = encpklen; + hvec[2].data = (char*)mbuf; + hvec[2].off = 0; + hvec[2].len = mlen; + rc = _gcry_md_hash_buffers (hashalgo, 0, digest, hvec, 3); + } + if (rc) goto leave; - reverse_buffer (digest, 64); + reverse_buffer (digest, 2*b); if (DBG_CIPHER) - log_printhex (" H(R+)", digest, 64); - _gcry_mpi_set_buffer (h, digest, 64, 0); + log_printhex (" H(R+)", digest, 2*b); + _gcry_mpi_set_buffer (h, digest, 2*b, 0); /* According to the paper the best way for verification is: encodepoint(sG - h·Q) = encodepoint(r) diff --git a/cipher/pubkey-util.c b/cipher/pubkey-util.c --- a/cipher/pubkey-util.c +++ b/cipher/pubkey-util.c @@ -227,6 +227,8 @@ { "sha3-384", GCRY_MD_SHA3_384 }, { "sha3-512", GCRY_MD_SHA3_512 }, { "sm3", GCRY_MD_SM3 }, + { "shake128", GCRY_MD_SHAKE128 }, + { "shake256", GCRY_MD_SHAKE256 }, { NULL, 0 } }; int algo; diff --git a/mpi/ec.c b/mpi/ec.c --- a/mpi/ec.c +++ b/mpi/ec.c @@ -1015,7 +1015,10 @@ { gcry_err_code_t rc; - if (ec && ec->dialect == ECC_DIALECT_ED25519) + if (ec + && (ec->dialect == ECC_DIALECT_ED25519 + || (ec->model == MPI_EC_EDWARDS + && ec->dialect == ECC_DIALECT_SAFECURVE))) rc = _gcry_ecc_eddsa_decodepoint (value, ec, result, NULL, NULL); else if (ec && ec->model == MPI_EC_MONTGOMERY) rc = _gcry_ecc_mont_decodepoint (value, ec, result); diff --git a/tests/Makefile.am b/tests/Makefile.am --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -23,7 +23,7 @@ t-mpi-bit t-mpi-point curves t-lock \ prime basic keygen pubkey hmac hashtest t-kdf keygrip \ fips186-dsa aeswrap pkcs1v2 random dsa-rfc6979 \ - t-ed25519 t-cv25519 t-x448 + t-ed25519 t-cv25519 t-x448 t-ed448 tests_bin_last = benchmark bench-slope @@ -58,7 +58,7 @@ EXTRA_DIST = README rsa-16k.key cavs_tests.sh cavs_driver.pl \ pkcs1v2-oaep.h pkcs1v2-pss.h pkcs1v2-v15c.h pkcs1v2-v15s.h \ - t-ed25519.inp stopwatch.h hashtest-256g.in \ + t-ed25519.inp t-ed448.inp stopwatch.h hashtest-256g.in \ sha3-224.h sha3-256.h sha3-384.h sha3-512.h \ blake2b.h blake2s.h \ basic-disable-all-hwf.in basic_all_hwfeature_combinations.sh diff --git a/tests/curves.c b/tests/curves.c --- a/tests/curves.c +++ b/tests/curves.c @@ -33,7 +33,7 @@ #include "t-common.h" /* Number of curves defined in ../cipger/ecc-curves.c */ -#define N_CURVES 26 +#define N_CURVES 27 /* A real world sample public key. */ static char const sample_key_1[] = diff --git a/tests/t-ed448.c b/tests/t-ed448.c new file mode 100644 --- /dev/null +++ b/tests/t-ed448.c @@ -0,0 +1,497 @@ +/* t-ed448.c - Check the Ed448 crypto + * Copyright (C) 2020 g10 Code GmbH + * + * This file is part of Libgcrypt. + * + * Libgcrypt is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * Libgcrypt is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see . + */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include + +#include "stopwatch.h" + +#define PGM "t-ed448" +#include "t-common.h" +#define N_TESTS 8 + +static int sign_with_pk; +static int no_verify; +static int custom_data_file; + + +static void +show_note (const char *format, ...) +{ + va_list arg_ptr; + + if (!verbose && getenv ("srcdir")) + fputs (" ", stderr); /* To align above "PASS: ". */ + else + fprintf (stderr, "%s: ", PGM); + va_start (arg_ptr, format); + vfprintf (stderr, format, arg_ptr); + if (*format && format[strlen(format)-1] != '\n') + putc ('\n', stderr); + va_end (arg_ptr); +} + + +static void +show_sexp (const char *prefix, gcry_sexp_t a) +{ + char *buf; + size_t size; + + fprintf (stderr, "%s: ", PGM); + if (prefix) + fputs (prefix, stderr); + size = gcry_sexp_sprint (a, GCRYSEXP_FMT_ADVANCED, NULL, 0); + buf = xmalloc (size); + + gcry_sexp_sprint (a, GCRYSEXP_FMT_ADVANCED, buf, size); + fprintf (stderr, "%.*s", (int)size, buf); + gcry_free (buf); +} + + +/* Prepend FNAME with the srcdir environment variable's value and + * return an allocated filename. */ +char * +prepend_srcdir (const char *fname) +{ + static const char *srcdir; + char *result; + + if (!srcdir && !(srcdir = getenv ("srcdir"))) + srcdir = "."; + + result = xmalloc (strlen (srcdir) + 1 + strlen (fname) + 1); + strcpy (result, srcdir); + strcat (result, "/"); + strcat (result, fname); + return result; +} + + +/* Read next line but skip over empty and comment lines. Caller must + xfree the result. */ +static char * +read_textline (FILE *fp, int *lineno) +{ + char line[4096]; + char *p; + + do + { + if (!fgets (line, sizeof line, fp)) + { + if (feof (fp)) + return NULL; + die ("error reading input line: %s\n", strerror (errno)); + } + ++*lineno; + p = strchr (line, '\n'); + if (!p) + die ("input line %d not terminated or too long\n", *lineno); + *p = 0; + for (p--;p > line && my_isascii (*p) && isspace (*p); p--) + *p = 0; + } + while (!*line || *line == '#'); + /* if (debug) */ + /* info ("read line: '%s'\n", line); */ + return xstrdup (line); +} + + +/* Copy the data after the tag to BUFFER. BUFFER will be allocated as + needed. */ +static void +copy_data (char **buffer, const char *line, int lineno) +{ + const char *s; + + xfree (*buffer); + *buffer = NULL; + + s = strchr (line, ':'); + if (!s) + { + fail ("syntax error at input line %d", lineno); + return; + } + for (s++; my_isascii (*s) && isspace (*s); s++) + ; + *buffer = xstrdup (s); +} + + +/* Convert STRING consisting of hex characters into its binary + representation and return it as an allocated buffer. The valid + length of the buffer is returned at R_LENGTH. The string is + delimited by end of string. The function returns NULL on + error. */ +static void * +hex2buffer (const char *string, size_t *r_length) +{ + const char *s; + unsigned char *buffer; + size_t length; + + buffer = xmalloc (strlen(string)/2+1); + length = 0; + for (s=string; *s; s +=2 ) + { + if (!hexdigitp (s) || !hexdigitp (s+1)) + return NULL; /* Invalid hex digits. */ + ((unsigned char*)buffer)[length++] = xtoi_2 (s); + } + *r_length = length; + return buffer; +} + + +static void +hexdowncase (char *string) +{ + char *p; + + for (p=string; *p; p++) + if (my_isascii (*p)) + *p = tolower (*p); +} + + +static void +one_test (int testno, const char *sk, const char *pk, + const char *msg, const char *sig) +{ + gpg_error_t err; + int i; + char *p; + void *buffer = NULL; + void *buffer2 = NULL; + size_t buflen, buflen2; + gcry_sexp_t s_tmp, s_tmp2; + gcry_sexp_t s_sk = NULL; + gcry_sexp_t s_pk = NULL; + gcry_sexp_t s_msg= NULL; + gcry_sexp_t s_sig= NULL; + unsigned char *sig_r = NULL; + unsigned char *sig_s = NULL; + char *sig_rs_string = NULL; + size_t sig_r_len, sig_s_len; + + if (verbose > 1) + info ("Running test %d\n", testno); + + if (!(buffer = hex2buffer (sk, &buflen))) + { + fail ("error building s-exp for test %d, %s: %s", + testno, "sk", "invalid hex string"); + goto leave; + } + if (!(buffer2 = hex2buffer (pk, &buflen2))) + { + fail ("error building s-exp for test %d, %s: %s", + testno, "pk", "invalid hex string"); + goto leave; + } + if (sign_with_pk) + err = gcry_sexp_build (&s_sk, NULL, + "(private-key" + " (ecc" + " (curve \"Ed448\")" + " (flags eddsa)" + " (q %b)" + " (d %b)))", + (int)buflen2, buffer2, + (int)buflen, buffer); + else + err = gcry_sexp_build (&s_sk, NULL, + "(private-key" + " (ecc" + " (curve \"Ed448\")" + " (flags eddsa)" + " (d %b)))", + (int)buflen, buffer); + if (err) + { + fail ("error building s-exp for test %d, %s: %s", + testno, "sk", gpg_strerror (err)); + goto leave; + } + + if ((err = gcry_sexp_build (&s_pk, NULL, + "(public-key" + " (ecc" + " (curve \"Ed448\")" + " (flags eddsa)" + " (q %b)))", (int)buflen2, buffer2))) + { + fail ("error building s-exp for test %d, %s: %s", + testno, "pk", gpg_strerror (err)); + goto leave; + } + + xfree (buffer); + if (!(buffer = hex2buffer (msg, &buflen))) + { + fail ("error building s-exp for test %d, %s: %s", + testno, "msg", "invalid hex string"); + goto leave; + } + if ((err = gcry_sexp_build (&s_msg, NULL, + "(data" + " (flags eddsa)" + " (hash-algo shake256)" + " (value %b))", (int)buflen, buffer))) + { + fail ("error building s-exp for test %d, %s: %s", + testno, "msg", gpg_strerror (err)); + goto leave; + } + + if ((err = gcry_pk_sign (&s_sig, s_msg, s_sk))) + fail ("gcry_pk_sign failed for test %d: %s", testno, gpg_strerror (err)); + if (debug) + show_sexp ("sig=", s_sig); + + s_tmp2 = NULL; + s_tmp = gcry_sexp_find_token (s_sig, "sig-val", 0); + if (s_tmp) + { + s_tmp2 = s_tmp; + s_tmp = gcry_sexp_find_token (s_tmp2, "eddsa", 0); + if (s_tmp) + { + gcry_sexp_release (s_tmp2); + s_tmp2 = s_tmp; + s_tmp = gcry_sexp_find_token (s_tmp2, "r", 0); + if (s_tmp) + { + sig_r = gcry_sexp_nth_buffer (s_tmp, 1, &sig_r_len); + gcry_sexp_release (s_tmp); + } + s_tmp = gcry_sexp_find_token (s_tmp2, "s", 0); + if (s_tmp) + { + sig_s = gcry_sexp_nth_buffer (s_tmp, 1, &sig_s_len); + gcry_sexp_release (s_tmp); + } + } + } + gcry_sexp_release (s_tmp2); s_tmp2 = NULL; + + if (!sig_r || !sig_s) + fail ("gcry_pk_sign failed for test %d: %s", testno, "r or s missing"); + else + { + sig_rs_string = xmalloc (2*(sig_r_len + sig_s_len)+1); + p = sig_rs_string; + *p = 0; + for (i=0; i < sig_r_len; i++, p += 2) + snprintf (p, 3, "%02x", sig_r[i]); + for (i=0; i < sig_s_len; i++, p += 2) + snprintf (p, 3, "%02x", sig_s[i]); + if (strcmp (sig_rs_string, sig)) + { + fail ("gcry_pk_sign failed for test %d: %s", + testno, "wrong value returned"); + info (" expected: '%s'", sig); + info (" got: '%s'", sig_rs_string); + } + } + + if (!no_verify) + if ((err = gcry_pk_verify (s_sig, s_msg, s_pk))) + fail ("gcry_pk_verify failed for test %d: %s", + testno, gpg_strerror (err)); + + + leave: + gcry_sexp_release (s_sig); + gcry_sexp_release (s_sk); + gcry_sexp_release (s_pk); + gcry_sexp_release (s_msg); + xfree (buffer); + xfree (buffer2); + xfree (sig_r); + xfree (sig_s); + xfree (sig_rs_string); +} + + +static void +check_ed448 (const char *fname) +{ + FILE *fp; + int lineno, ntests; + char *line; + int testno; + char *sk, *pk, *msg, *sig; + + info ("Checking Ed448.\n"); + + fp = fopen (fname, "r"); + if (!fp) + die ("error opening '%s': %s\n", fname, strerror (errno)); + + testno = 0; + sk = pk = msg = sig = NULL; + lineno = ntests = 0; + while ((line = read_textline (fp, &lineno))) + { + if (!strncmp (line, "TST:", 4)) + testno = atoi (line+4); + else if (!strncmp (line, "SK:", 3)) + copy_data (&sk, line, lineno); + else if (!strncmp (line, "PK:", 3)) + copy_data (&pk, line, lineno); + else if (!strncmp (line, "MSG:", 4)) + copy_data (&msg, line, lineno); + else if (!strncmp (line, "SIG:", 4)) + copy_data (&sig, line, lineno); + else + fail ("unknown tag at input line %d", lineno); + + xfree (line); + if (testno && sk && pk && msg && sig) + { + hexdowncase (sig); + one_test (testno, sk, pk, msg, sig); + ntests++; + if (!(ntests % 256)) + show_note ("%d of %d tests done\n", ntests, N_TESTS); + xfree (pk); pk = NULL; + xfree (sk); sk = NULL; + xfree (msg); msg = NULL; + xfree (sig); sig = NULL; + } + + } + xfree (pk); + xfree (sk); + xfree (msg); + xfree (sig); + + if (ntests != N_TESTS && !custom_data_file) + fail ("did %d tests but expected %d", ntests, N_TESTS); + else if ((ntests % 256)) + show_note ("%d tests done\n", ntests); + + fclose (fp); +} + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + char *fname = NULL; + + if (argc) + { argc--; argv++; } + + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--")) + { + argc--; argv++; + break; + } + else if (!strcmp (*argv, "--help")) + { + fputs ("usage: " PGM " [options]\n" + "Options:\n" + " --verbose print timings etc.\n" + " --debug flyswatter\n" + " --sign-with-pk also use the public key for signing\n" + " --no-verify skip the verify test\n" + " --data FNAME take test data from file FNAME\n", + stdout); + exit (0); + } + else if (!strcmp (*argv, "--verbose")) + { + verbose++; + argc--; argv++; + } + else if (!strcmp (*argv, "--debug")) + { + verbose += 2; + debug++; + argc--; argv++; + } + else if (!strcmp (*argv, "--sign-with-pk")) + { + sign_with_pk = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--no-verify")) + { + no_verify = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--data")) + { + argc--; argv++; + if (argc) + { + xfree (fname); + fname = xstrdup (*argv); + argc--; argv++; + } + } + else if (!strncmp (*argv, "--", 2)) + die ("unknown option '%s'", *argv); + + } + + if (!fname) + fname = prepend_srcdir ("t-ed448.inp"); + else + custom_data_file = 1; + + xgcry_control ((GCRYCTL_DISABLE_SECMEM, 0)); + if (!gcry_check_version (GCRYPT_VERSION)) + die ("version mismatch\n"); + if (debug) + xgcry_control ((GCRYCTL_SET_DEBUG_FLAGS, 1u , 0)); + xgcry_control ((GCRYCTL_ENABLE_QUICK_RANDOM, 0)); + xgcry_control ((GCRYCTL_INITIALIZATION_FINISHED, 0)); + + /* Ed448 isn't supported in fips mode */ + if (gcry_fips_mode_active()) + return 77; + + start_timer (); + check_ed448 (fname); + stop_timer (); + + xfree (fname); + + info ("All tests completed in %s. Errors: %d\n", + elapsed_time (1), error_count); + return !!error_count; +} diff --git a/tests/t-ed448.inp b/tests/t-ed448.inp new file mode 100644 --- /dev/null +++ b/tests/t-ed448.inp @@ -0,0 +1,53 @@ +# t-ed448.inp +# This is from RFC 8032, the section +# +# 7.4. Test Vectors for Ed448 +# + +TST: 1 +SK: 6c82a562cb808d10d632be89c8513ebf6c929f34ddfa8c9f63c9960ef6e348a3528c8a3fcc2f044e39a3fc5b94492f8f032e7549a20098f95b +PK: 5fd7449b59b461fd2ce787ec616ad46a1da1342485a70e1f8a0ea75d80e96778edf124769b46c7061bd6783df1e50f6cd1fa1abeafe8256180 +MSG: +SIG: 533a37f6bbe457251f023c0d88f976ae2dfb504a843e34d2074fd823d41a591f2b233f034f628281f2fd7a22ddd47d7828c59bd0a21bfd3980ff0d2028d4b18a9df63e006c5d1c2d345b925d8dc00b4104852db99ac5c7cdda8530a113a0f4dbb61149f05a7363268c71d95808ff2e652600 + +TST: 2 +SK: c4eab05d357007c632f3dbb48489924d552b08fe0c353a0d4a1f00acda2c463afbea67c5e8d2877c5e3bc397a659949ef8021e954e0a12274e +PK: 43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a9480 +MSG: 03 +SIG: 26b8f91727bd62897af15e41eb43c377efb9c610d48f2335cb0bd0087810f4352541b143c4b981b7e18f62de8ccdf633fc1bf037ab7cd779805e0dbcc0aae1cbcee1afb2e027df36bc04dcecbf154336c19f0af7e0a6472905e799f1953d2a0ff3348ab21aa4adafd1d234441cf807c03a00 + +TST: 3 +SK: cd23d24f714274e744343237b93290f511f6425f98e64459ff203e8985083ffdf60500553abc0e05cd02184bdb89c4ccd67e187951267eb328 +PK: dcea9e78f35a1bf3499a831b10b86c90aac01cd84b67a0109b55a36e9328b1e365fce161d71ce7131a543ea4cb5f7e9f1d8b00696447001400 +MSG: 0c3e544074ec63b0265e0c +SIG: 1f0a8888ce25e8d458a21130879b840a9089d999aaba039eaf3e3afa090a09d389dba82c4ff2ae8ac5cdfb7c55e94d5d961a29fe0109941e00b8dbdeea6d3b051068df7254c0cdc129cbe62db2dc957dbb47b51fd3f213fb8698f064774250a5028961c9bf8ffd973fe5d5c206492b140e00 + +TST: 4 +SK: 258cdd4ada32ed9c9ff54e63756ae582fb8fab2ac721f2c8e676a72768513d939f63dddb55609133f29adf86ec9929dccb52c1c5fd2ff7e21b +PK: 3ba16da0c6f2cc1f30187740756f5e798d6bc5fc015d7c63cc9510ee3fd44adc24d8e968b6e46e6f94d19b945361726bd75e149ef09817f580 +MSG: 64a65f3cdedcdd66811e2915 +SIG: 7eeeab7c4e50fb799b418ee5e3197ff6bf15d43a14c34389b59dd1a7b1b85b4ae90438aca634bea45e3a2695f1270f07fdcdf7c62b8efeaf00b45c2c96ba457eb1a8bf075a3db28e5c24f6b923ed4ad747c3c9e03c7079efb87cb110d3a99861e72003cbae6d6b8b827e4e6c143064ff3c00 + +TST: 5 +SK: 7ef4e84544236752fbb56b8f31a23a10e42814f5f55ca037cdcc11c64c9a3b2949c1bb60700314611732a6c2fea98eebc0266a11a93970100e +PK: b3da079b0aa493a5772029f0467baebee5a8112d9d3a22532361da294f7bb3815c5dc59e176b4d9f381ca0938e13c6c07b174be65dfa578e80 +MSG: 64a65f3cdedcdd66811e2915e7 +SIG: 6a12066f55331b6c22acd5d5bfc5d71228fbda80ae8dec26bdd306743c5027cb4890810c162c027468675ecf645a83176c0d7323a2ccde2d80efe5a1268e8aca1d6fbc194d3f77c44986eb4ab4177919ad8bec33eb47bbb5fc6e28196fd1caf56b4e7e0ba5519234d047155ac727a1053100 + +TST: 6 +SK: d65df341ad13e008567688baedda8e9dcdc17dc024974ea5b4227b6530e339bff21f99e68ca6968f3cca6dfe0fb9f4fab4fa135d5542ea3f01 +PK: df9705f58edbab802c7f8363cfe5560ab1c6132c20a9f1dd163483a26f8ac53a39d6808bf4a1dfbd261b099bb03b3fb50906cb28bd8a081f00 +MSG: bd0f6a3747cd561bdddf4640a332461a4a30a12a434cd0bf40d766d9c6d458e5512204a30c17d1f50b5079631f64eb3112182da3005835461113718d1a5ef944 +SIG: 554bc2480860b49eab8532d2a533b7d578ef473eeb58c98bb2d0e1ce488a98b18dfde9b9b90775e67f47d4a1c3482058efc9f40d2ca033a0801b63d45b3b722ef552bad3b4ccb667da350192b61c508cf7b6b5adadc2c8d9a446ef003fb05cba5f30e88e36ec2703b349ca229c2670833900 + +TST: 7 +SK: 2ec5fe3c17045abdb136a5e6a913e32ab75ae68b53d2fc149b77e504132d37569b7e766ba74a19bd6162343a21c8590aa9cebca9014c636df5 +PK: 79756f014dcfe2079f5dd9e718be4171e2ef2486a08f25186f6bff43a9936b9bfe12402b08ae65798a3d81e22e9ec80e7690862ef3d4ed3a00 +MSG: 15777532b0bdd0d1389f636c5f6b9ba734c90af572877e2d272dd078aa1e567cfa80e12928bb542330e8409f3174504107ecd5efac61ae7504dabe2a602ede89e5cca6257a7c77e27a702b3ae39fc769fc54f2395ae6a1178cab4738e543072fc1c177fe71e92e25bf03e4ecb72f47b64d0465aaea4c7fad372536c8ba516a6039c3c2a39f0e4d832be432dfa9a706a6e5c7e19f397964ca4258002f7c0541b590316dbc5622b6b2a6fe7a4abffd96105eca76ea7b98816af0748c10df048ce012d901015a51f189f3888145c03650aa23ce894c3bd889e030d565071c59f409a9981b51878fd6fc110624dcbcde0bf7a69ccce38fabdf86f3bef6044819de11 +SIG: c650ddbb0601c19ca11439e1640dd931f43c518ea5bea70d3dcde5f4191fe53f00cf966546b72bcc7d58be2b9badef28743954e3a44a23f880e8d4f1cfce2d7a61452d26da05896f0a50da66a239a8a188b6d825b3305ad77b73fbac0836ecc60987fd08527c1a8e80d5823e65cafe2a3d00 + +TST: 8 +SK: 872d093780f5d3730df7c212664b37b8a0f24f56810daa8382cd4fa3f77634ec44dc54f1c2ed9bea86fafb7632d8be199ea165f5ad55dd9ce8 +PK: a81b2e8a70a5ac94ffdbcc9badfc3feb0801f258578bb114ad44ece1ec0e799da08effb81c5d685c0c56f64eecaef8cdf11cc38737838cf400 +MSG: 6ddf802e1aae4986935f7f981ba3f0351d6273c0a0c22c9c0e8339168e675412a3debfaf435ed651558007db4384b650fcc07e3b586a27a4f7a00ac8a6fec2cd86ae4bf1570c41e6a40c931db27b2faa15a8cedd52cff7362c4e6e23daec0fbc3a79b6806e316efcc7b68119bf46bc76a26067a53f296dafdbdc11c77f7777e972660cf4b6a9b369a6665f02e0cc9b6edfad136b4fabe723d2813db3136cfde9b6d044322fee2947952e031b73ab5c603349b307bdc27bc6cb8b8bbd7bd323219b8033a581b59eadebb09b3c4f3d2277d4f0343624acc817804728b25ab797172b4c5c21a22f9c7839d64300232eb66e53f31c723fa37fe387c7d3e50bdf9813a30e5bb12cf4cd930c40cfb4e1fc622592a49588794494d56d24ea4b40c89fc0596cc9ebb961c8cb10adde976a5d602b1c3f85b9b9a001ed3c6a4d3b1437f52096cd1956d042a597d561a596ecd3d1735a8d570ea0ec27225a2c4aaff26306d1526c1af3ca6d9cf5a2c98f47e1c46db9a33234cfd4d81f2c98538a09ebe76998d0d8fd25997c7d255c6d66ece6fa56f11144950f027795e653008f4bd7ca2dee85d8e90f3dc315130ce2a00375a318c7c3d97be2c8ce5b6db41a6254ff264fa6155baee3b0773c0f497c573f19bb4f4240281f0b1f4f7be857a4e59d416c06b4c50fa09e1810ddc6b1467baeac5a3668d11b6ecaa901440016f389f80acc4db977025e7f5924388c7e340a732e554440e76570f8dd71b7d640b3450d1fd5f0410a18f9a3494f707c717b79b4bf75c98400b096b21653b5d217cf3565c9597456f70703497a078763829bc01bb1cbc8fa04eadc9a6e3f6699587a9e75c94e5bab0036e0b2e711392cff0047d0d6b05bd2a588bc109718954259f1d86678a579a3120f19cfb2963f177aeb70f2d4844826262e51b80271272068ef5b3856fa8535aa2a88b2d41f2a0e2fda7624c2850272ac4a2f561f8f2f7a318bfd5caf9696149e4ac824ad3460538fdc25421beec2cc6818162d06bbed0c40a387192349db67a118bada6cd5ab0140ee273204f628aad1c135f770279a651e24d8c14d75a6059d76b96a6fd857def5e0b354b27ab937a5815d16b5fae407ff18222c6d1ed263be68c95f32d908bd895cd76207ae726487567f9a67dad79abec316f683b17f2d02bf07e0ac8b5bc6162cf94697b3c27cd1fea49b27f23ba2901871962506520c392da8b6ad0d99f7013fbc06c2c17a569500c8a7696481c1cd33e9b14e40b82e79a5f5db82571ba97bae3ad3e0479515bb0e2b0f3bfcd1fd33034efc6245eddd7ee2086ddae2600d8ca73e214e8c2b0bdb2b047c6a464a562ed77b73d2d841c4b34973551257713b753632efba348169abc90a68f42611a40126d7cb21b58695568186f7e569d2ff0f9e745d0487dd2eb997cafc5abf9dd102e62ff66cba87 +SIG: e301345a41a39a4d72fff8df69c98075a0cc082b802fc9b2b6bc503f926b65bddf7f4c8f1cb49f6396afc8a70abe6d8aef0db478d4c6b2970076c6a0484fe76d76b3a97625d79f1ce240e7c576750d295528286f719b413de9ada3e8eb78ed573603ce30d8bb761785dc30dbc320869e1a00