summaryrefslogtreecommitdiff
path: root/chip/g/dcrypto/rsa.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/g/dcrypto/rsa.c')
-rw-r--r--chip/g/dcrypto/rsa.c506
1 files changed, 506 insertions, 0 deletions
diff --git a/chip/g/dcrypto/rsa.c b/chip/g/dcrypto/rsa.c
new file mode 100644
index 0000000000..d9774a2101
--- /dev/null
+++ b/chip/g/dcrypto/rsa.c
@@ -0,0 +1,506 @@
+/* Copyright 2015 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "dcrypto.h"
+#include "internal.h"
+
+#include "trng.h"
+
+#include <assert.h>
+
+static void MGF1_xor(uint8_t *dst, uint32_t dst_len,
+ const uint8_t *seed, uint32_t seed_len,
+ enum hashing_mode hashing)
+{
+ struct HASH_CTX ctx;
+ struct {
+ uint8_t b3;
+ uint8_t b2;
+ uint8_t b1;
+ uint8_t b0;
+ } cnt;
+ const uint8_t *digest;
+ const size_t hash_size = (hashing == HASH_SHA1) ? SHA1_DIGEST_BYTES
+ : SHA256_DIGEST_BYTES;
+
+ cnt.b0 = cnt.b1 = cnt.b2 = cnt.b3 = 0;
+ while (dst_len) {
+ int i;
+
+ if (hashing == HASH_SHA1)
+ DCRYPTO_SHA1_init(&ctx, 0);
+ else
+ DCRYPTO_SHA256_init(&ctx, 0);
+
+ DCRYPTO_HASH_update(&ctx, seed, seed_len);
+ DCRYPTO_HASH_update(&ctx, (uint8_t *) &cnt, sizeof(cnt));
+ digest = DCRYPTO_HASH_final(&ctx);
+ for (i = 0; i < dst_len && i < hash_size; ++i)
+ *dst++ ^= *digest++;
+ dst_len -= i;
+ if (!++cnt.b0)
+ ++cnt.b1;
+ }
+}
+
+/*
+ * struct OAEP { // MSB to LSB.
+ * uint8_t zero;
+ * uint8_t seed[HASH_SIZE];
+ * uint8_t phash[HASH_SIZE];
+ * uint8_t PS[]; // Variable length (optional) zero-pad.
+ * uint8_t one; // 0x01, message demarcator.
+ * uint8_t msg[]; // Input message.
+ * };
+ */
+/* encrypt */
+static int oaep_pad(uint8_t *output, uint32_t output_len,
+ const uint8_t *msg, uint32_t msg_len,
+ enum hashing_mode hashing, const char *label)
+{
+ int i;
+ const size_t hash_size = (hashing == HASH_SHA1) ? SHA1_DIGEST_BYTES
+ : SHA256_DIGEST_BYTES;
+ uint8_t *const seed = output + 1;
+ uint8_t *const phash = seed + hash_size;
+ uint8_t *const PS = phash + hash_size;
+ const uint32_t max_msg_len = output_len - 2 - 2 * hash_size;
+ const uint32_t ps_len = max_msg_len - msg_len;
+ uint8_t *const one = PS + ps_len;
+ struct HASH_CTX ctx;
+
+ if (output_len < 2 + 2 * hash_size)
+ return 0; /* Key size too small for chosen hash. */
+ if (msg_len > output_len - 2 - 2 * hash_size)
+ return 0; /* Input message too large for key size. */
+
+ dcrypto_memset(output, 0, output_len);
+ for (i = 0; i < hash_size;) {
+ uint32_t r = rand();
+
+ seed[i++] = r >> 0;
+ seed[i++] = r >> 8;
+ seed[i++] = r >> 16;
+ seed[i++] = r >> 24;
+ }
+
+ if (hashing == HASH_SHA1)
+ DCRYPTO_SHA1_init(&ctx, 0);
+ else
+ DCRYPTO_SHA256_init(&ctx, 0);
+
+ DCRYPTO_HASH_update(&ctx, label, label ? strlen(label) : 0);
+ memcpy(phash, DCRYPTO_HASH_final(&ctx), hash_size);
+ *one = 1;
+ memcpy(one + 1, msg, msg_len);
+ MGF1_xor(phash, hash_size + 1 + max_msg_len,
+ seed, hash_size, hashing);
+ MGF1_xor(seed, hash_size, phash, hash_size + 1 + max_msg_len,
+ hashing);
+ return 1;
+}
+
+/* decrypt */
+/* TODO(ngm): constant time. */
+static int check_oaep_pad(uint8_t *out, uint32_t *out_len,
+ uint8_t *padded, uint32_t padded_len,
+ enum hashing_mode hashing, const char *label)
+{
+ const size_t hash_size = (hashing == HASH_SHA1) ? SHA1_DIGEST_BYTES
+ : SHA256_DIGEST_BYTES;
+ uint8_t *seed = padded + 1;
+ uint8_t *phash = seed + hash_size;
+ uint8_t *PS = phash + hash_size;
+ const uint32_t max_msg_len = padded_len - 2 - 2 * hash_size;
+ struct HASH_CTX ctx;
+ int one_index = -1;
+ int bad;
+ int i;
+
+ if (padded_len < 2 + 2 * hash_size)
+ return 0; /* Invalid input size. */
+
+ /* Recover seed. */
+ MGF1_xor(seed, hash_size, phash, hash_size + 1 + max_msg_len, hashing);
+ /* Recover db. */
+ MGF1_xor(phash, hash_size + 1 + max_msg_len, seed, hash_size, hashing);
+
+ if (hashing == HASH_SHA1)
+ DCRYPTO_SHA1_init(&ctx, 0);
+ else
+ DCRYPTO_SHA256_init(&ctx, 0);
+ DCRYPTO_HASH_update(&ctx, label, label ? strlen(label) : 0);
+
+ bad = memcmp(phash, DCRYPTO_HASH_final(&ctx), hash_size);
+ bad |= padded[0];
+
+ for (i = PS - padded; i < padded_len; i++) {
+ if (padded[i] == 1) {
+ one_index = i;
+ break;
+ } else if (padded[i] != 0) {
+ bad = 1;
+ break;
+ }
+ }
+
+ if (one_index < 0 || bad)
+ return 0;
+ one_index++;
+ if (*out_len < padded_len - one_index)
+ return 0;
+ memcpy(out, padded + one_index, padded_len - one_index);
+ *out_len = padded_len - one_index;
+ return 1;
+}
+
+/* Constants from RFC 3447. */
+#define RSA_PKCS1_PADDING_SIZE 11
+
+/* encrypt */
+static int pkcs1_type2_pad(uint8_t *padded, uint32_t padded_len,
+ const uint8_t *in, uint32_t in_len)
+{
+ uint32_t PS_len;
+
+ if (padded_len < RSA_PKCS1_PADDING_SIZE)
+ return 0;
+ if (in_len > padded_len - RSA_PKCS1_PADDING_SIZE)
+ return 0;
+ PS_len = padded_len - 3 - in_len;
+
+ *(padded++) = 0;
+ *(padded++) = 2;
+ while (PS_len) {
+ int i;
+ uint32_t r = rand();
+
+ for (i = 0; i < 4 && PS_len; i++) {
+ uint8_t b = ((uint8_t *) &r)[i];
+
+ if (b) {
+ *padded++ = b;
+ PS_len--;
+ }
+ }
+ }
+ *(padded++) = 0;
+ memcpy(padded, in, in_len);
+ return 1;
+}
+
+/* decrypt */
+/* TODO(ngm): constant time */
+static int check_pkcs1_type2_pad(uint8_t *out, uint32_t *out_len,
+ const uint8_t *padded, uint32_t padded_len)
+{
+ int i;
+
+ if (padded_len < RSA_PKCS1_PADDING_SIZE)
+ return 0;
+ if (padded[0] != 0 || padded[1] != 2)
+ return 0;
+ for (i = 2; i < padded_len; i++) {
+ if (padded[i] == 0)
+ break;
+ }
+
+ if (i == padded_len)
+ return 0;
+ i++;
+ if (i < RSA_PKCS1_PADDING_SIZE)
+ return 0;
+ if (*out_len < padded_len - i)
+ return 0;
+ memcpy(out, &padded[i], padded_len - i);
+ *out_len = padded_len - i;
+ return 1;
+}
+
+static const uint8_t SHA1_DER[] = {
+ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
+ 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14
+};
+static const uint8_t SHA256_DER[] = {
+ 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+ 0x00, 0x04, 0x20
+};
+
+/* sign */
+static int pkcs1_type1_pad(uint8_t *padded, uint32_t padded_len,
+ const uint8_t *in, uint32_t in_len,
+ enum hashing_mode hashing)
+{
+ const uint8_t *der = (hashing == HASH_SHA1) ? &SHA1_DER[0]
+ : &SHA256_DER[0];
+ const uint32_t der_size = (hashing == HASH_SHA1) ? sizeof(SHA1_DER)
+ : sizeof(SHA256_DER);
+ const uint32_t hash_size = (hashing == HASH_SHA1) ? SHA1_DIGEST_BYTES
+ : SHA256_DIGEST_BYTES;
+ uint32_t ps_len;
+
+ if (padded_len < RSA_PKCS1_PADDING_SIZE + der_size)
+ return 0;
+ if (in_len != hash_size)
+ return 0;
+ if (in_len > padded_len - RSA_PKCS1_PADDING_SIZE - der_size)
+ return 0;
+ ps_len = padded_len - 3 - der_size - in_len;
+
+ *(padded++) = 0;
+ *(padded++) = 1;
+ dcrypto_memset(padded, 0xFF, ps_len);
+ padded += ps_len;
+ *(padded++) = 0;
+ memcpy(padded, der, der_size);
+ padded += der_size;
+ memcpy(padded, in, in_len);
+ return 1;
+}
+
+/* verify */
+/* TODO(ngm): constant time */
+static int check_pkcs1_type1_pad(const uint8_t *msg, uint32_t msg_len,
+ const uint8_t *padded, uint32_t padded_len,
+ enum hashing_mode hashing)
+{
+ int i;
+ const uint8_t *der = (hashing == HASH_SHA1) ? &SHA1_DER[0]
+ : &SHA256_DER[0];
+ const uint32_t der_size = (hashing == HASH_SHA1) ? sizeof(SHA1_DER)
+ : sizeof(SHA256_DER);
+ const uint32_t hash_size = (hashing == HASH_SHA1) ? SHA1_DIGEST_BYTES
+ : SHA256_DIGEST_BYTES;
+ uint32_t ps_len;
+
+ if (msg_len != hash_size)
+ return 0;
+ if (padded_len < RSA_PKCS1_PADDING_SIZE + der_size + hash_size)
+ return 0;
+ ps_len = padded_len - 3 - der_size - hash_size;
+
+ if (padded[0] != 0 || padded[1] != 1)
+ return 0;
+ for (i = 2; i < ps_len + 2; i++) {
+ if (padded[i] != 0xFF)
+ return 0;
+ }
+
+ if (padded[i++] != 0)
+ return 0;
+ if (memcmp(&padded[i], der, der_size) != 0)
+ return 0;
+ i += der_size;
+ return memcmp(msg, &padded[i], hash_size) == 0;
+}
+
+static void reverse(uint8_t *start, size_t len)
+{
+ int i;
+ uint8_t *end = start + len;
+
+ for (i = 0; i < len / 2; ++i) {
+ uint8_t tmp = *start;
+
+ *start++ = *--end;
+ *end = tmp;
+ }
+}
+
+static int check_modulus_params(const struct BIGNUM *N, uint32_t *out_len)
+{
+ if (bn_size(N) > RSA_MAX_BYTES)
+ return 0; /* Unsupported key size. */
+ if (!bn_check_topbit(N)) /* Check that top bit is set. */
+ return 0;
+ if (out_len && *out_len < bn_size(N))
+ return 0; /* Output buffer too small. */
+ return 1;
+}
+
+int DCRYPTO_rsa_encrypt(struct RSA *rsa, uint8_t *out, uint32_t *out_len,
+ const uint8_t *in, const uint32_t in_len,
+ enum padding_mode padding, enum hashing_mode hashing,
+ const char *label)
+{
+ uint32_t padded_buf[RSA_MAX_WORDS];
+ uint32_t e_buf[BN_BYTES / sizeof(uint32_t)];
+
+ struct BIGNUM padded;
+ struct BIGNUM e;
+ struct BIGNUM encrypted;
+
+ if (!check_modulus_params(&rsa->N, out_len))
+ return 0;
+
+ bn_init(&padded, padded_buf, bn_size(&rsa->N));
+ bn_init(&encrypted, out, bn_size(&rsa->N));
+ bn_init(&e, e_buf, sizeof(e_buf));
+ *e.d = rsa->e;
+
+ switch (padding) {
+ case PADDING_MODE_OAEP:
+ if (!oaep_pad((uint8_t *) padded.d, bn_size(&padded),
+ (const uint8_t *) in, in_len, hashing, label))
+ return 0;
+ break;
+ case PADDING_MODE_PKCS1:
+ if (!pkcs1_type2_pad((uint8_t *) padded.d, bn_size(&padded),
+ (const uint8_t *) in, in_len))
+ return 0;
+ break;
+ default:
+ return 0; /* Unsupported padding mode. */
+ }
+
+ /* Reverse from big-endian to little-endian notation. */
+ reverse((uint8_t *) padded.d, bn_size(&padded));
+ bn_mont_modexp(&encrypted, &padded, &e, &rsa->N);
+ /* Back to big-endian notation. */
+ reverse((uint8_t *) encrypted.d, bn_size(&encrypted));
+ *out_len = bn_size(&encrypted);
+
+ dcrypto_memset(padded_buf, 0, sizeof(padded_buf));
+ dcrypto_memset(e_buf, 0, sizeof(e_buf));
+ return 1;
+}
+
+int DCRYPTO_rsa_decrypt(struct RSA *rsa, uint8_t *out, uint32_t *out_len,
+ const uint8_t *in, const uint32_t in_len,
+ enum padding_mode padding, enum hashing_mode hashing,
+ const char *label)
+{
+ uint32_t encrypted_buf[RSA_MAX_WORDS];
+ uint32_t padded_buf[RSA_MAX_WORDS];
+
+ struct BIGNUM encrypted;
+ struct BIGNUM padded;
+ int ret = 1;
+
+ if (!check_modulus_params(&rsa->N, out_len))
+ return 0;
+ if (in_len != bn_size(&rsa->N))
+ return 0; /* Invalid input length. */
+
+ /* TODO(ngm): this copy can be eliminated if input may be modified. */
+ bn_init(&encrypted, encrypted_buf, in_len);
+ memcpy(encrypted_buf, in, in_len);
+ bn_init(&padded, padded_buf, in_len);
+
+ /* Reverse from big-endian to little-endian notation. */
+ reverse((uint8_t *) encrypted.d, encrypted.dmax * BN_BYTES);
+ bn_mont_modexp(&padded, &encrypted, &rsa->d, &rsa->N);
+ /* Back to big-endian notation. */
+ reverse((uint8_t *) padded.d, padded.dmax * BN_BYTES);
+
+ switch (padding) {
+ case PADDING_MODE_OAEP:
+ if (!check_oaep_pad(out, out_len, (uint8_t *) padded.d,
+ bn_size(&padded), hashing, label))
+ ret = 0;
+ break;
+ case PADDING_MODE_PKCS1:
+ if (!check_pkcs1_type2_pad(
+ out, out_len, (const uint8_t *) padded.d,
+ bn_size(&padded)))
+ ret = 0;
+ break;
+ default:
+ /* Unsupported padding mode. */
+ ret = 0;
+ break;
+ }
+
+ dcrypto_memset(encrypted_buf, 0, sizeof(encrypted_buf));
+ dcrypto_memset(padded_buf, 0, sizeof(padded_buf));
+ return ret;
+}
+
+int DCRYPTO_rsa_sign(struct RSA *rsa, uint8_t *out, uint32_t *out_len,
+ const uint8_t *in, const uint32_t in_len,
+ enum padding_mode padding, enum hashing_mode hashing)
+{
+ uint32_t padded_buf[RSA_MAX_WORDS];
+
+ struct BIGNUM padded;
+ struct BIGNUM signature;
+
+ if (!check_modulus_params(&rsa->N, out_len))
+ return 0;
+
+ bn_init(&padded, padded_buf, bn_size(&rsa->N));
+ bn_init(&signature, out, bn_size(&rsa->N));
+
+ /* TODO(ngm): add support for PSS. */
+ switch (padding) {
+ case PADDING_MODE_PKCS1:
+ if (!pkcs1_type1_pad((uint8_t *) padded.d, bn_size(&padded),
+ (const uint8_t *) in, in_len, hashing))
+ return 0;
+ break;
+ default:
+ return 0;
+ }
+
+ /* Reverse from big-endian to little-endian notation. */
+ reverse((uint8_t *) padded.d, bn_size(&padded));
+ bn_mont_modexp(&signature, &padded, &rsa->d, &rsa->N);
+ /* Back to big-endian notation. */
+ reverse((uint8_t *) signature.d, bn_size(&signature));
+ *out_len = bn_size(&rsa->N);
+
+ dcrypto_memset(padded_buf, 0, sizeof(padded_buf));
+ return 1;
+}
+
+int DCRYPTO_rsa_verify(struct RSA *rsa, const uint8_t *digest,
+ uint32_t digest_len, const uint8_t *sig,
+ const uint32_t sig_len, enum padding_mode padding,
+ enum hashing_mode hashing)
+{
+ uint32_t padded_buf[RSA_MAX_WORDS];
+ uint32_t signature_buf[RSA_MAX_WORDS];
+ uint32_t e_buf[BN_BYTES / sizeof(uint32_t)];
+
+ struct BIGNUM padded;
+ struct BIGNUM signature;
+ struct BIGNUM e;
+ int ret = 1;
+
+ if (!check_modulus_params(&rsa->N, NULL))
+ return 0;
+ if (sig_len != bn_size(&rsa->N))
+ return 0; /* Invalid input length. */
+
+ bn_init(&signature, signature_buf, bn_size(&rsa->N));
+ memcpy(signature_buf, sig, bn_size(&rsa->N));
+ bn_init(&padded, padded_buf, bn_size(&rsa->N));
+ bn_init(&e, e_buf, sizeof(e_buf));
+ *e.d = rsa->e;
+
+ /* Reverse from big-endian to little-endian notation. */
+ reverse((uint8_t *) signature.d, signature.dmax * BN_BYTES);
+ bn_mont_modexp(&padded, &signature, &e, &rsa->N);
+ /* Back to big-endian notation. */
+ reverse((uint8_t *) padded.d, padded.dmax * BN_BYTES);
+
+ switch (padding) {
+ case PADDING_MODE_PKCS1:
+ if (!check_pkcs1_type1_pad(
+ digest, digest_len, (uint8_t *) padded.d,
+ bn_size(&padded), hashing))
+ ret = 0;
+ break;
+ default:
+ /* Unsupported padding mode. */
+ ret = 0;
+ break;
+ }
+
+ dcrypto_memset(padded_buf, 0, sizeof(padded_buf));
+ dcrypto_memset(signature_buf, 0, sizeof(signature_buf));
+ return ret;
+}