diff options
-rw-r--r-- | Makefile.in | 3 | ||||
-rw-r--r-- | pss-mgf1.c | 73 | ||||
-rw-r--r-- | pss-mgf1.h | 58 | ||||
-rw-r--r-- | pss.c | 196 | ||||
-rw-r--r-- | pss.h | 65 | ||||
-rw-r--r-- | testsuite/.test-rules.make | 6 | ||||
-rw-r--r-- | testsuite/Makefile.in | 4 | ||||
-rw-r--r-- | testsuite/pss-mgf1-test.c | 36 | ||||
-rw-r--r-- | testsuite/pss-test.c | 118 |
9 files changed, 556 insertions, 3 deletions
diff --git a/Makefile.in b/Makefile.in index 135542f5..0ab77591 100644 --- a/Makefile.in +++ b/Makefile.in @@ -144,6 +144,7 @@ hogweed_SOURCES = sexp.c sexp-format.c \ pkcs1.c pkcs1-encrypt.c pkcs1-decrypt.c \ pkcs1-rsa-digest.c pkcs1-rsa-md5.c pkcs1-rsa-sha1.c \ pkcs1-rsa-sha256.c pkcs1-rsa-sha512.c \ + pss.c pss-mgf1.c \ rsa.c rsa-sign.c rsa-sign-tr.c rsa-verify.c \ rsa-pkcs1-sign.c rsa-pkcs1-sign-tr.c rsa-pkcs1-verify.c \ rsa-md5-sign.c rsa-md5-sign-tr.c rsa-md5-verify.c \ @@ -196,7 +197,7 @@ HEADERS = aes.h arcfour.h arctwo.h asn1.h blowfish.h \ memops.h memxor.h \ nettle-meta.h nettle-types.h \ pbkdf2.h \ - pgp.h pkcs1.h realloc.h ripemd160.h rsa.h \ + pgp.h pkcs1.h pss.h pss-mgf1.h realloc.h ripemd160.h rsa.h \ salsa20.h sexp.h \ serpent.h sha.h sha1.h sha2.h sha3.h twofish.h \ umac.h yarrow.h poly1305.h diff --git a/pss-mgf1.c b/pss-mgf1.c new file mode 100644 index 00000000..67df5570 --- /dev/null +++ b/pss-mgf1.c @@ -0,0 +1,73 @@ +/* pss-mgf1.c + + PKCS#1 mask generation function 1, used in RSA-PSS (RFC-3447). + + Copyright (C) 2017 Daiki Ueno + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include "pss-mgf1.h" + +#include <assert.h> +#include <string.h> + +#include "nettle-internal.h" +#include "macros.h" + +void +pss_mgf1(const void *seed, const struct nettle_hash *hash, + size_t length, uint8_t *mask) +{ + TMP_DECL(h, uint8_t, NETTLE_MAX_HASH_DIGEST_SIZE); + TMP_DECL(state, uint8_t, NETTLE_MAX_HASH_CONTEXT_SIZE); + size_t i; + uint8_t c[4]; + + TMP_ALLOC(h, hash->digest_size); + TMP_ALLOC(state, hash->context_size); + + for (i = 0;; + i++, mask += hash->digest_size, length -= hash->digest_size) + { + WRITE_UINT32(c, i); + + memcpy(state, seed, hash->context_size); + hash->update(state, 4, c); + + if (length <= hash->digest_size) + { + hash->digest(state, length, mask); + return; + } + hash->digest(state, hash->digest_size, mask); + } +} diff --git a/pss-mgf1.h b/pss-mgf1.h new file mode 100644 index 00000000..4a29c108 --- /dev/null +++ b/pss-mgf1.h @@ -0,0 +1,58 @@ +/* pss-mgf1.h + + PKCS#1 mask generation function 1, used in RSA-PSS (RFC-3447). + + Copyright (C) 2017 Daiki Ueno + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#ifndef NETTLE_PSS_MGF1_H_INCLUDED +#define NETTLE_PSS_MGF1_H_INCLUDED + +#include "nettle-meta.h" + +#include "sha1.h" +#include "sha2.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Namespace mangling */ +#define pss_mgf1 nettle_pss_mgf1 + +void +pss_mgf1(const void *seed, const struct nettle_hash *hash, + size_t length, uint8_t *mask); + +#ifdef __cplusplus +} +#endif + +#endif /* NETTLE_PSS_MGF1_H_INCLUDED */ @@ -0,0 +1,196 @@ +/* pss.c + + PKCS#1 RSA-PSS padding (RFC-3447). + + Copyright (C) 2017 Daiki Ueno + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include <assert.h> +#include <string.h> + +#include "pss.h" +#include "pss-mgf1.h" + +#include "bignum.h" +#include "gmp-glue.h" + +#include "memxor.h" +#include "nettle-internal.h" + +/* Masks to clear the leftmost N bits. */ +static const uint8_t pss_masks[8] = { + 0xFF, 0x7F, 0x3F, 0x1F, 0xF, 0x7, 0x3, 0x1 +}; + +/* Format the PKCS#1 PSS padding for given salt and digest, using + * pss_mgf1() as the mask generation function. + * + * The encoded messsage is stored in M, and the consistency can be + * checked with pss_verify_mgf1(), which takes the encoded message, + * the length of salt, and the digest. */ +int +pss_encode_mgf1(mpz_t m, size_t bits, + const struct nettle_hash *hash, + size_t salt_length, const uint8_t *salt, + const uint8_t *digest) +{ + TMP_GMP_DECL(em, uint8_t); + TMP_DECL(state, uint8_t, NETTLE_MAX_HASH_CONTEXT_SIZE); + uint8_t pad[8]; + size_t key_size = (bits + 7) / 8; + size_t j; + + TMP_GMP_ALLOC(em, key_size); + TMP_ALLOC(state, hash->context_size); + + if (key_size < hash->digest_size + salt_length + 2) + { + TMP_GMP_FREE(em); + return 0; + } + + /* Compute M'. */ + hash->init(state); + memset(pad, 0, 8); + hash->update(state, 8, pad); + hash->update(state, hash->digest_size, digest); + hash->update(state, salt_length, salt); + + /* Store H in EM, right after maskedDB. */ + hash->digest(state, hash->digest_size, em + key_size - hash->digest_size - 1); + + /* Compute dbMask. */ + hash->init(state); + hash->update(state, hash->digest_size, em + key_size - hash->digest_size - 1); + + pss_mgf1(state, hash, key_size - hash->digest_size - 1, em); + + /* Compute maskedDB and store it in front of H in EM. */ + for (j = 0; j < key_size - salt_length - hash->digest_size - 2; j++) + em[j] ^= 0; + em[j++] ^= 1; + memxor(em + j, salt, salt_length); + j += salt_length; + + /* Store the trailer field following H. */ + j += hash->digest_size; + *(em + j) = 0xbc; + + /* Clear the leftmost 8 * emLen - emBits of the leftmost octet in EM. */ + *em &= pss_masks[(8 * key_size - bits)]; + + nettle_mpz_set_str_256_u(m, key_size, em); + TMP_GMP_FREE(em); + return 1; +} + +/* Check the consistency of given PKCS#1 PSS encoded message, created + * with pss_encode_mgf1(). + * + * Returns 1 if the encoded message is consistent, 0 if it is + * inconsistent. */ +int +pss_verify_mgf1(mpz_t m, size_t bits, + const struct nettle_hash *hash, + size_t salt_length, + const uint8_t *digest) +{ + TMP_GMP_DECL(em, uint8_t); + TMP_DECL(h2, uint8_t, NETTLE_MAX_HASH_DIGEST_SIZE); + TMP_DECL(state, uint8_t, NETTLE_MAX_HASH_CONTEXT_SIZE); + uint8_t pad[8], *h, *db, *salt; + size_t key_size = (bits + 7) / 8; + size_t j; + int ret = 0; + + /* Allocate twice the key size to store the intermediate data DB + * following the EM value. */ + TMP_GMP_ALLOC(em, key_size * 2); + + TMP_ALLOC(h2, hash->digest_size); + TMP_ALLOC(state, hash->context_size); + + if (key_size < hash->digest_size + salt_length + 2) + goto cleanup; + + nettle_mpz_get_str_256(key_size, em, m); + + /* Check the trailer field. */ + if (em[key_size - 1] != 0xbc) + goto cleanup; + + /* Extract H. */ + h = em + (key_size - hash->digest_size - 1); + + /* Check if the leftmost 8 * emLen - emBits bits of the leftmost + * octet of EM are all equal to zero. */ + if ((*em & ~pss_masks[(8 * key_size - bits)]) != 0) + goto cleanup; + + /* Compute dbMask. */ + hash->init(state); + hash->update(state, hash->digest_size, h); + + db = em + key_size; + pss_mgf1(state, hash, key_size - hash->digest_size - 1, db); + + /* Compute DB. */ + memxor(db, em, key_size - hash->digest_size - 1); + + *db &= pss_masks[(8 * key_size - bits)]; + for (j = 0; j < key_size - salt_length - hash->digest_size - 2; j++) + if (db[j] != 0) + goto cleanup; + + /* Check the octet right after PS is 0x1. */ + if (db[j] != 0x1) + goto cleanup; + salt = db + j + 1; + + /* Compute H'. */ + memset(pad, 0, 8); + hash->init(state); + hash->update(state, 8, pad); + hash->update(state, hash->digest_size, digest); + hash->update(state, salt_length, salt); + hash->digest(state, hash->digest_size, h2); + + /* Check if H' = H. */ + if (memcmp(h2, h, hash->digest_size) != 0) + goto cleanup; + + ret = 1; + cleanup: + TMP_GMP_FREE(em); + return ret; +} @@ -0,0 +1,65 @@ +/* pss.h + + PKCS#1 RSA-PSS (RFC-3447). + + Copyright (C) 2017 Daiki Ueno + + This file is part of GNU Nettle. + + GNU Nettle is free software: you can redistribute it and/or + modify it under the terms of either: + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at your + option) any later version. + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your + option) any later version. + + or both in parallel, as here. + + GNU Nettle 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 + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see http://www.gnu.org/licenses/. +*/ + +#ifndef NETTLE_PSS_H_INCLUDED +#define NETTLE_PSS_H_INCLUDED + +#include "nettle-types.h" +#include "bignum.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Namespace mangling */ +#define pss_encode_mgf1 nettle_pss_encode_mgf1 +#define pss_verify_mgf1 nettle_pss_verify_mgf1 + +int +pss_encode_mgf1(mpz_t m, size_t bits, + const struct nettle_hash *hash, + size_t salt_length, const uint8_t *salt, + const uint8_t *digest); + +int +pss_verify_mgf1(mpz_t m, size_t bits, + const struct nettle_hash *hash, + size_t salt_length, + const uint8_t *digest); + +#ifdef __cplusplus +} +#endif + +#endif /* NETTLE_PSS_H_INCLUDED */ diff --git a/testsuite/.test-rules.make b/testsuite/.test-rules.make index b263e1fd..2cb7f7aa 100644 --- a/testsuite/.test-rules.make +++ b/testsuite/.test-rules.make @@ -157,6 +157,9 @@ yarrow-test$(EXEEXT): yarrow-test.$(OBJEXT) pbkdf2-test$(EXEEXT): pbkdf2-test.$(OBJEXT) $(LINK) pbkdf2-test.$(OBJEXT) $(TEST_OBJS) -o pbkdf2-test$(EXEEXT) +pss-mgf1-test$(EXEEXT): pss-mgf1-test.$(OBJEXT) + $(LINK) pss-mgf1-test.$(OBJEXT) $(TEST_OBJS) -o pss-mgf1-test$(EXEEXT) + sexp-test$(EXEEXT): sexp-test.$(OBJEXT) $(LINK) sexp-test.$(OBJEXT) $(TEST_OBJS) -o sexp-test$(EXEEXT) @@ -178,6 +181,9 @@ random-prime-test$(EXEEXT): random-prime-test.$(OBJEXT) pkcs1-test$(EXEEXT): pkcs1-test.$(OBJEXT) $(LINK) pkcs1-test.$(OBJEXT) $(TEST_OBJS) -o pkcs1-test$(EXEEXT) +pss-test$(EXEEXT): pss-test.$(OBJEXT) + $(LINK) pss-test.$(OBJEXT) $(TEST_OBJS) -o pss-test$(EXEEXT) + rsa-sign-tr-test$(EXEEXT): rsa-sign-tr-test.$(OBJEXT) $(LINK) rsa-sign-tr-test.$(OBJEXT) $(TEST_OBJS) -o rsa-sign-tr-test$(EXEEXT) diff --git a/testsuite/Makefile.in b/testsuite/Makefile.in index 689d4325..07058fca 100644 --- a/testsuite/Makefile.in +++ b/testsuite/Makefile.in @@ -30,12 +30,12 @@ TS_NETTLE_SOURCES = aes-test.c arcfour-test.c arctwo-test.c \ hmac-test.c umac-test.c \ meta-hash-test.c meta-cipher-test.c\ meta-aead-test.c meta-armor-test.c \ - buffer-test.c yarrow-test.c pbkdf2-test.c + buffer-test.c yarrow-test.c pbkdf2-test.c pss-mgf1-test.c TS_HOGWEED_SOURCES = sexp-test.c sexp-format-test.c \ rsa2sexp-test.c sexp2rsa-test.c \ bignum-test.c random-prime-test.c \ - pkcs1-test.c rsa-sign-tr-test.c \ + pkcs1-test.c pss-test.c rsa-sign-tr-test.c \ rsa-test.c rsa-encrypt-test.c rsa-keygen-test.c \ dsa-test.c dsa-keygen-test.c \ curve25519-dh-test.c \ diff --git a/testsuite/pss-mgf1-test.c b/testsuite/pss-mgf1-test.c new file mode 100644 index 00000000..640500b5 --- /dev/null +++ b/testsuite/pss-mgf1-test.c @@ -0,0 +1,36 @@ +#include "testutils.h" +#include "pss-mgf1.h" + +void +test_main(void) +{ + struct sha1_ctx sha1ctx; + struct sha256_ctx sha256ctx; + const struct tstring *seed, *expected; + uint8_t mask[120]; + + /* From ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1d2-vec.zip */ + seed = SHEX("df1a896f9d8bc816d97cd7a2c43bad54" + "6fbe8cfe"); + expected = SHEX("66e4672e836ad121ba244bed6576b867d9a447c28a6e66a5b87dee" + "7fbc7e65af5057f86fae8984d9ba7f969ad6fe02a4d75f7445fefd" + "d85b6d3a477c28d24ba1e3756f792dd1dce8ca94440ecb5279ecd3" + "183a311fc89739a96643136e8b0f465e87a4535cd4c59b10028d"); + sha1_init(&sha1ctx); + sha1_update(&sha1ctx, seed->length, seed->data); + pss_mgf1(&sha1ctx, &nettle_sha1, expected->length, mask); + ASSERT(MEMEQ (expected->length, mask, expected->data)); + + /* Test with our own data. */ + seed = SDATA("abc"); + expected = SHEX("cf2db1ac9867debdf8ce91f99f141e5544bf26ca36b3fd4f8e4035" + "eec42cab0d46c386ebccef82ba0bb0b095aaa5548b03cdff695187" + "1c6fb505af68af688332f885d324a47d2145a3d8392c37978d7dc9" + "84c95728950c4cf3de6becc59e60ea506951bd40e6de3863095064" + "3ab2edbb47dc66cb54beb2d1"); + + sha256_init(&sha256ctx); + sha256_update(&sha256ctx, seed->length, seed->data); + pss_mgf1(&sha256ctx, &nettle_sha256, expected->length, mask); + ASSERT(MEMEQ (expected->length, mask, expected->data)); +} diff --git a/testsuite/pss-test.c b/testsuite/pss-test.c new file mode 100644 index 00000000..81226554 --- /dev/null +++ b/testsuite/pss-test.c @@ -0,0 +1,118 @@ +#include "testutils.h" + +#include "pss.h" + +#if HAVE_VALGRIND_MEMCHECK_H +# include <valgrind/memcheck.h> + +static void +test_unmark_mpz(mpz_t m) +{ + VALGRIND_MAKE_MEM_DEFINED (m, sizeof(*m)); + VALGRIND_MAKE_MEM_DEFINED (&m->_mp_d, sizeof(mp_limb_t) * mpz_size(m)); +} + +static int +pss_encode_mgf1_for_test(mpz_t m, size_t bits, + const struct nettle_hash *hash, + size_t salt_length, const uint8_t *salt, + const uint8_t *digest) +{ + int res; + + /* Makes valgrind trigger on any branches depending on the input + data. */ + VALGRIND_MAKE_MEM_UNDEFINED (salt, salt_length); + VALGRIND_MAKE_MEM_UNDEFINED (digest, hash->digest_size); + + res = pss_encode_mgf1 (m, bits, hash, salt_length, salt, digest); + VALGRIND_MAKE_MEM_DEFINED (&res, sizeof(res)); + test_unmark_mpz (m); + return res; +} +#else +#define pss_encode_mgf1_for_test pss_encode_mgf1 +#endif + +void +test_main(void) +{ + struct tstring *salt; + struct tstring *digest; + mpz_t m; + mpz_t expected; + + /* From ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1d2-vec.zip */ + mpz_init(m); + mpz_init(expected); + + salt = SHEX("e3b5d5d002c1bce50c2b65ef88a188d83bce7e61"); + digest = SHEX("37b66ae0445843353d47ecb0b4fd14c110e62d6a"); + ASSERT(pss_encode_mgf1(m, 1024, &nettle_sha1, + salt->length, salt->data, digest->data)); + + mpz_set_str(expected, + "66e4672e836ad121ba244bed6576b867d9a447c28a6e66a5b87dee" + "7fbc7e65af5057f86fae8984d9ba7f969ad6fe02a4d75f7445fefd" + "d85b6d3a477c28d24ba1e3756f792dd1dce8ca94440ecb5279ecd3" + "183a311fc896da1cb39311af37ea4a75e24bdbfd5c1da0de7cecdf" + "1a896f9d8bc816d97cd7a2c43bad546fbe8cfebc", 16); + + ASSERT(mpz_cmp(m, expected) == 0); + + mpz_add_ui(m, m, 2); + ASSERT(!pss_verify_mgf1(m, 1024, &nettle_sha1, salt->length, digest->data)); + + mpz_sub_ui(m, m, 2); + ASSERT(pss_verify_mgf1(m, 1024, &nettle_sha1, salt->length, digest->data)); + + mpz_clear(m); + mpz_clear(expected); + + /* Test with our own data. */ + mpz_init(m); + mpz_init(expected); + + salt = SHEX("11223344556677889900"); + /* From sha256-test.c */ + digest = SHEX("ba7816bf8f01cfea 414140de5dae2223" + "b00361a396177a9c b410ff61f20015ad"); + + mpz_set_str(expected, + "76b9a52705c8382c5367732f993184eff340b6305c9f73e7e308c8" + "004fcc15cbbaab01e976bae4b774628595379a2d448a36b3ea6fa8" + "353b97eeea7bdac93b4b7807ac98cd4b3bebfb31f3718e1dd3625f" + "227fbb8696606498e7070e21c3cbbd7386ea20eb81ac7927e0c6d1" + "d7788826a63af767f301bcc05dd65b00da862cbc", 16); + + /* Try bad salt */ + salt->data[6] = 0x00; + ASSERT(pss_encode_mgf1(m, 1024, &nettle_sha256, + salt->length, salt->data, digest->data)); + ASSERT(mpz_cmp(m, expected) != 0); + + /* Try the good salt */ + salt->data[6] = 0x77; + ASSERT(pss_encode_mgf1(m, 1024, &nettle_sha256, + salt->length, salt->data, digest->data)); + ASSERT(mpz_cmp(m, expected) == 0); + + /* Try bad message */ + mpz_add_ui(m, m, 2); + ASSERT(!pss_verify_mgf1(m, 1024, &nettle_sha256, salt->length, digest->data)); + + /* Try the good message */ + mpz_sub_ui(m, m, 2); + ASSERT(pss_verify_mgf1(m, 1024, &nettle_sha256, salt->length, digest->data)); + + /* Try bad digest */ + digest->data[17] = 0x00; + ASSERT(!pss_verify_mgf1(m, 1024, &nettle_sha256, salt->length, digest->data)); + + /* Try the good digest */ + digest->data[17] = 0x03; + ASSERT(pss_verify_mgf1(m, 1024, &nettle_sha256, salt->length, digest->data)); + + mpz_clear(m); + mpz_clear(expected); +} |