/* * Copyright (C) 2018 Linaro Ltd * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #ifdef CONFIG_JWT_SIGN_RSA #include #include #include #include #endif #ifdef CONFIG_JWT_SIGN_ECDSA #include #include #include #include #include #endif /* * Base-64 encoding is typically done by lookup into a 64-byte static * array. As an experiment, lets look at both code size and time for * one that does the character encoding computationally. Like the * array version, this doesn't do bounds checking, and assumes the * passed value has been masked. * * On Cortex-M, this function is 34 bytes of code, which is only a * little more than half of the size of the lookup table. */ #if 1 static int base64_char(int value) { if (value < 26) { return value + 'A'; } else if (value < 52) { return value + 'a' - 26; } else if (value < 62) { return value + '0' - 52; } else if (value == 62) { return '-'; } else { return '_'; } } #else static const char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; static inline int base64_char(int value) { return b64_table[value]; } #endif /* * Add a single character to the jwt buffer. Detects overflow, and * always keeps the buffer null terminated. */ static void base64_outch(struct jwt_builder *st, char ch) { if (st->overflowed) { return; } if (st->len < 2) { st->overflowed = true; return; } *st->buf++ = ch; st->len--; *st->buf = 0; } /* * Flush any pending base64 character data out. If we have all three * bytes are present, this will generate 4 characters, otherwise it * may generate fewer. */ static void base64_flush(struct jwt_builder *st) { if (st->pending < 1) { return; } base64_outch(st, base64_char(st->wip[0] >> 2)); base64_outch(st, base64_char(((st->wip[0] & 0x03) << 4) | (st->wip[1] >> 4))); if (st->pending >= 2) { base64_outch(st, base64_char(((st->wip[1] & 0x0f) << 2) | (st->wip[2] >> 6))); } if (st->pending >= 3) { base64_outch(st, base64_char(st->wip[2] & 0x3f)); } st->pending = 0; memset(st->wip, 0, 3); } static void base64_addbyte(struct jwt_builder *st, uint8_t byte) { st->wip[st->pending++] = byte; if (st->pending == 3) { base64_flush(st); } } static int base64_append_bytes(const char *bytes, size_t len, void *data) { struct jwt_builder *st = data; while (len-- > 0) { base64_addbyte(st, *bytes++); } return 0; } struct jwt_payload { int32_t exp; int32_t iat; const char *aud; }; static struct json_obj_descr jwt_payload_desc[] = { JSON_OBJ_DESCR_PRIM(struct jwt_payload, aud, JSON_TOK_STRING), JSON_OBJ_DESCR_PRIM(struct jwt_payload, exp, JSON_TOK_NUMBER), JSON_OBJ_DESCR_PRIM(struct jwt_payload, iat, JSON_TOK_NUMBER), }; /* * Add the JWT header to the buffer. */ static int jwt_add_header(struct jwt_builder *builder) { /* * Pre-computed JWT header * Use https://www.base64encode.org/ for update */ const char jwt_header[] = #ifdef CONFIG_JWT_SIGN_RSA /* {"alg":"RS256","typ":"JWT"} */ "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"; #endif #ifdef CONFIG_JWT_SIGN_ECDSA /* {"alg":"ES256","typ":"JWT"} */ "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9"; #endif int jwt_header_len = ARRAY_SIZE(jwt_header); if (jwt_header_len > builder->len) { builder->overflowed = true; return -ENOSPC; } strcpy(builder->buf, jwt_header); builder->buf += jwt_header_len - 1; builder->len -= jwt_header_len - 1; return 0; } int jwt_add_payload(struct jwt_builder *builder, int32_t exp, int32_t iat, const char *aud) { struct jwt_payload payload = { .exp = exp, .iat = iat, .aud = aud, }; base64_outch(builder, '.'); int res = json_obj_encode(jwt_payload_desc, ARRAY_SIZE(jwt_payload_desc), &payload, base64_append_bytes, builder); base64_flush(builder); return res; } #ifdef CONFIG_JWT_SIGN_RSA static int csprng_wrapper(void *ctx, unsigned char *dest, size_t size) { ARG_UNUSED(ctx); return sys_csrand_get((void *)dest, size); } int jwt_sign(struct jwt_builder *builder, const char *der_key, size_t der_key_len) { int res; mbedtls_pk_context ctx; mbedtls_pk_init(&ctx); res = mbedtls_pk_parse_key(&ctx, der_key, der_key_len, NULL, 0, csprng_wrapper, NULL); if (res != 0) { return res; } uint8_t hash[32], sig[256]; size_t sig_len = sizeof(sig); /* * The '0' indicates to mbedtls to do a SHA256, instead of * 224. */ mbedtls_sha256(builder->base, builder->buf - builder->base, hash, 0); res = mbedtls_pk_sign(&ctx, MBEDTLS_MD_SHA256, hash, sizeof(hash), sig, sig_len, &sig_len, csprng_wrapper, NULL); if (res != 0) { return res; } base64_outch(builder, '.'); base64_append_bytes(sig, sig_len, builder); base64_flush(builder); return builder->overflowed ? -ENOMEM : 0; } #endif #ifdef CONFIG_JWT_SIGN_ECDSA static TCCtrPrng_t prng_state; static bool prng_init; static const char personalize[] = "zephyr:drivers/jwt/jwt.c"; static int setup_prng(void) { if (prng_init) { return 0; } prng_init = true; uint8_t entropy[TC_AES_KEY_SIZE + TC_AES_BLOCK_SIZE]; for (int i = 0; i < sizeof(entropy); i += sizeof(uint32_t)) { uint32_t rv = sys_rand32_get(); memcpy(entropy + i, &rv, sizeof(uint32_t)); } int res = tc_ctr_prng_init(&prng_state, (const uint8_t *) &entropy, sizeof(entropy), personalize, sizeof(personalize)); return res == TC_CRYPTO_SUCCESS ? 0 : -EINVAL; } int default_CSPRNG(uint8_t *dest, unsigned int size) { int res = tc_ctr_prng_generate(&prng_state, NULL, 0, dest, size); return res; } int jwt_sign(struct jwt_builder *builder, const char *der_key, size_t der_key_len) { struct tc_sha256_state_struct ctx; uint8_t hash[32], sig[64]; int res; tc_sha256_init(&ctx); tc_sha256_update(&ctx, builder->base, builder->buf - builder->base); tc_sha256_final(hash, &ctx); res = setup_prng(); if (res != 0) { return res; } uECC_set_rng(&default_CSPRNG); /* Note that tinycrypt only supports P-256. */ res = uECC_sign(der_key, hash, sizeof(hash), sig, &curve_secp256r1); if (res != TC_CRYPTO_SUCCESS) { return -EINVAL; } base64_outch(builder, '.'); base64_append_bytes(sig, sizeof(sig), builder); base64_flush(builder); return 0; } #endif int jwt_init_builder(struct jwt_builder *builder, char *buffer, size_t buffer_size) { builder->base = buffer; builder->buf = buffer; builder->len = buffer_size; builder->overflowed = false; builder->pending = 0; return jwt_add_header(builder); }