| /* sign-mbedtls.c |
| * |
| * Copyright (c) 2018-2019 Apple Computer, Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| * DNS SIG(0) signature generation for DNSSD SRP using mbedtls. |
| * |
| * Functions required for loading, saving, and generating public/private keypairs, extracting the public key |
| * into KEY RR data, and computing signatures. |
| * |
| * This is the implementation for mbedtls, e.g. on Thread Devices, Linux, and OpenWRT. |
| */ |
| |
| #include <stdio.h> |
| #ifdef THREAD_DEVKIT_ADK |
| #include <openthread/random_noncrypto.h> |
| #include "HAPPlatformRandomNumber.h" |
| #else |
| #include <arpa/inet.h> |
| #ifdef LINUX_GETENTROPY |
| #define _GNU_SOURCE |
| #include <linux/random.h> |
| #include <sys/syscall.h> |
| #else |
| #include <sys/random.h> |
| #endif // LINUX_GETENTROPY |
| #endif // THREAD_DEVKIT_ADK |
| #include <string.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| |
| #include "srp.h" |
| #include "dns-msg.h" |
| #include "srp-crypto.h" |
| #include "dns_sd.h" |
| |
| // For debugging |
| #ifdef DEBUG_SHA256 |
| int |
| srp_mbedtls_sha256_update_ret(const char *thing_name, |
| mbedtls_sha256_context *sha, uint8_t *data, size_t len) |
| { |
| int i; |
| fprintf(stderr, "%s %lu: ", thing_name, (unsigned long)len); |
| if (len > 400) { |
| len = 400; |
| } |
| |
| for (i = 0; i < len; i++) { |
| fprintf(stderr, "%02x", data[i]); |
| } |
| fputs("\n", stderr); |
| return mbedtls_sha256_update_ret(sha, data, len); |
| } |
| |
| int |
| srp_mbedtls_sha256_finish_ret(mbedtls_sha256_context *sha, uint8_t *hash) |
| { |
| int i; |
| int status = mbedtls_sha256_finish_ret(sha, hash); |
| fprintf(stderr, "hash: "); |
| for (i = 0; i < ECDSA_SHA256_HASH_SIZE; i++) { |
| fprintf(stderr, "%02x", hash[i]); |
| } |
| fputs("\n", stderr); |
| return status; |
| } |
| #endif |
| |
| // Key is stored in an opaque data structure, for mbedtls this is an mbedtls_pk_context. |
| // Function to read a public key from a KEY record |
| // Function to validate a signature given some data and a public key (not required on client) |
| |
| // Function to free a key |
| void |
| srp_keypair_free(srp_key_t *key) |
| { |
| mbedtls_pk_free(&key->key); |
| free(key); |
| } |
| |
| // Needed to seed the RNG with good entropy data. |
| static int |
| get_entropy(void *data, unsigned char *output, size_t len, size_t *outlen) |
| { |
| #ifdef THREAD_DEVKIT_ADK |
| HAPPlatformRandomNumberFill(output, len); |
| *outlen = len; |
| return 0; |
| #else |
| #ifdef LINUX_GETENTROPY |
| int result = syscall(SYS_getrandom, output, len, GRND_RANDOM); |
| #else |
| int result = getentropy(output, len); |
| #endif |
| (void)data; |
| |
| if (result != 0) { |
| ERROR("getentropy returned %s", strerror(errno)); |
| return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED; |
| } |
| *outlen = len; |
| #endif // THREAD_DEVKIT_ADK |
| return 0; |
| } |
| |
| // mbedtls on embedded devices seems to react poorly to multiple rng contexts, so we create just |
| // one and keep it around. It would be nice if this got fixed, but it's actually more efficient |
| // to have one context, so not something we need to fix. |
| typedef struct rng_state { |
| mbedtls_entropy_context entropy_context; |
| mbedtls_ctr_drbg_context rng_context; |
| char errbuf[64]; |
| } rng_state_t; |
| |
| static rng_state_t *rng_state; |
| |
| bool |
| rng_state_fetch(void) |
| { |
| int status; |
| |
| if (rng_state == NULL) { |
| rng_state = calloc(1, sizeof *rng_state); |
| if (rng_state == NULL) { |
| ERROR("srp_random16(): no memory for state."); |
| goto fail; |
| } |
| |
| mbedtls_entropy_init(&rng_state->entropy_context); |
| status = mbedtls_entropy_add_source(&rng_state->entropy_context, get_entropy, |
| NULL, 1, MBEDTLS_ENTROPY_SOURCE_STRONG); |
| if (status != 0) { |
| mbedtls_strerror(status, rng_state->errbuf, sizeof rng_state->errbuf); |
| ERROR("mbedtls_entropy_add_source failed: %s", rng_state->errbuf); |
| goto fail; |
| } |
| |
| mbedtls_ctr_drbg_init(&rng_state->rng_context); |
| status = mbedtls_ctr_drbg_seed(&rng_state->rng_context, |
| mbedtls_entropy_func, &rng_state->entropy_context, NULL, 0); |
| |
| if (status != 0) { |
| mbedtls_strerror(status, rng_state->errbuf, sizeof rng_state->errbuf); |
| ERROR("mbedtls_ctr_drbg_seed failed: %s", rng_state->errbuf); |
| fail: |
| free(rng_state); |
| rng_state = NULL; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static srp_key_t * |
| srp_key_setup(void) |
| { |
| srp_key_t *key = calloc(1, sizeof(*key)); |
| |
| if (key == NULL) { |
| return key; |
| } |
| |
| mbedtls_pk_init(&key->key); |
| if (rng_state_fetch()) { |
| return key; |
| } |
| mbedtls_pk_free(&key->key); |
| free(key); |
| return NULL; |
| } |
| |
| uint16_t |
| srp_random16() |
| { |
| int status; |
| uint16_t ret; |
| char errbuf[64]; |
| if (rng_state_fetch()) { |
| status = mbedtls_ctr_drbg_random(&rng_state->rng_context, (unsigned char *)&ret, sizeof ret); |
| if (status != 0) { |
| mbedtls_strerror(status, errbuf, sizeof errbuf); |
| ERROR("mbedtls_ctr_drbg_random failed: %s", errbuf); |
| return 0xffff; |
| } |
| return ret; |
| } |
| return 0xffff; |
| } |
| |
| uint32_t |
| srp_random32() |
| { |
| int status; |
| uint32_t ret; |
| char errbuf[64]; |
| if (rng_state_fetch()) { |
| status = mbedtls_ctr_drbg_random(&rng_state->rng_context, (unsigned char *)&ret, sizeof ret); |
| if (status != 0) { |
| mbedtls_strerror(status, errbuf, sizeof errbuf); |
| ERROR("mbedtls_ctr_drbg_random failed: %s", errbuf); |
| return 0xffffffff; |
| } |
| return ret; |
| } |
| return 0xffffffff; |
| } |
| |
| uint64_t |
| srp_random64() |
| { |
| int status; |
| uint64_t ret; |
| char errbuf[64]; |
| if (rng_state_fetch()) { |
| status = mbedtls_ctr_drbg_random(&rng_state->rng_context, (unsigned char *)&ret, sizeof ret); |
| if (status != 0) { |
| mbedtls_strerror(status, errbuf, sizeof errbuf); |
| ERROR("mbedtls_ctr_drbg_random failed: %s", errbuf); |
| return 0xffffffffffffffffull; |
| } |
| return ret; |
| } |
| return 0xffffffffffffffffull; |
| } |
| |
| bool |
| srp_randombytes(uint8_t *dest, size_t num) |
| { |
| int status; |
| char errbuf[64]; |
| if (rng_state_fetch()) { |
| status = mbedtls_ctr_drbg_random(&rng_state->rng_context, (unsigned char *)dest, num); |
| if (status != 0) { |
| mbedtls_strerror(status, errbuf, sizeof errbuf); |
| ERROR("mbedtls_ctr_drbg_random failed: %s", errbuf); |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| srp_key_t * |
| srp_load_key_from_buffer(const uint8_t *buffer, size_t length) |
| { |
| srp_key_t *key; |
| int status; |
| char errbuf[64]; |
| |
| key = srp_key_setup(); |
| if (key == NULL) { |
| return NULL; |
| } |
| |
| if ((status = mbedtls_pk_parse_key(&key->key, buffer, length, NULL, 0)) != 0) { |
| mbedtls_strerror(status, errbuf, sizeof errbuf); |
| ERROR("mbedtls_pk_parse_key failed: %s", errbuf); |
| } else if (!mbedtls_pk_can_do(&key->key, MBEDTLS_PK_ECDSA)) { |
| ERROR("Buffer does not contain a usable ECDSA key."); |
| } else { |
| return key; |
| } |
| srp_keypair_free(key); |
| return NULL; |
| } |
| |
| // Function to generate a key |
| srp_key_t * |
| srp_generate_key(void) |
| { |
| srp_key_t *key; |
| int status; |
| char errbuf[64]; |
| const mbedtls_pk_info_t *key_type; |
| |
| INFO("srp_key_setup"); |
| key = srp_key_setup(); |
| if (key == NULL) { |
| ERROR("srp_key_setup() failed."); |
| return NULL; |
| } |
| key_type = mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY); |
| if (key_type == NULL) { |
| INFO("mbedtls_pk_info_from_type failed"); |
| return NULL; |
| } |
| |
| INFO("mbedtls_pk_setup"); |
| if ((status = mbedtls_pk_setup(&key->key, key_type)) != 0) { |
| mbedtls_strerror(status, errbuf, sizeof errbuf); |
| ERROR("mbedtls_pk_setup failed: %s", errbuf); |
| } else { |
| INFO("mbedtls_pk_ecdsa_genkey"); |
| if ((status = mbedtls_ecdsa_genkey(mbedtls_pk_ec(key->key), MBEDTLS_ECP_DP_SECP256R1, |
| mbedtls_ctr_drbg_random, &rng_state->rng_context)) != 0) { |
| mbedtls_strerror(status, errbuf, sizeof errbuf); |
| ERROR("mbedtls_ecdsa_genkey failed: %s", errbuf); |
| } else { |
| return key; |
| } |
| } |
| srp_keypair_free(key); |
| return NULL; |
| } |
| |
| // Copy an srp_key_t into a buffer. Key is not necessarily aligned with the beginning of the |
| // buffer; the return value, if not NULL, is the beginning of the key. If NULL, the buffer wasn't |
| // big enough. |
| uint8_t * |
| srp_store_key_to_buffer(uint8_t *buffer, size_t *length, srp_key_t *key) |
| { |
| size_t len = mbedtls_pk_write_key_der(&key->key, buffer, *length); |
| uint8_t *ret; |
| char errbuf[64]; |
| if (len <= 0) { |
| mbedtls_strerror(len, errbuf, sizeof errbuf); |
| ERROR("mbedtls_pk_write_key_der failed: %s", errbuf); |
| return NULL; |
| } |
| ret = &buffer[*length - len]; |
| *length = len; |
| return ret; |
| } |
| |
| srp_key_t * |
| srp_get_key(const char *key_name, void *os_context) |
| { |
| uint8_t buf[256]; |
| uint16_t buf_length; |
| uint8_t *key_bytes; |
| size_t keydata_length; |
| int err; |
| srp_key_t *key; |
| |
| err = srp_load_key_data(os_context, key_name, buf, &buf_length, sizeof buf); |
| if (err == kDNSServiceErr_NoError) { |
| key = srp_load_key_from_buffer(buf, buf_length); |
| if (key == NULL) { |
| INFO("load key fail"); |
| return NULL; |
| } |
| // Otherwise we have a key. |
| } else if (err == kDNSServiceErr_NoSuchKey) { |
| key = srp_generate_key(); |
| if (key == NULL) { |
| INFO("gen key fail"); |
| return NULL; |
| } |
| keydata_length = sizeof buf; |
| if ((key_bytes = srp_store_key_to_buffer(buf, &keydata_length, key)) == NULL) { |
| INFO("store key fail"); |
| return NULL; |
| } |
| // Note that it's possible for key_bytes != buf. |
| err = srp_store_key_data(os_context, key_name, key_bytes, (uint16_t)keydata_length); |
| if (err != kDNSServiceErr_NoError) { |
| INFO("store key data fail"); |
| return NULL; |
| } |
| } else { |
| INFO("weird error %d", err); |
| return NULL; |
| } |
| return key; |
| } |
| |
| // Function to get the length of the public key |
| size_t |
| srp_pubkey_length(srp_key_t *key) |
| { |
| return ECDSA_KEY_SIZE; |
| } |
| |
| uint8_t |
| srp_key_algorithm(srp_key_t *key) |
| { |
| return dnssec_keytype_ecdsa; |
| } |
| |
| size_t |
| srp_signature_length(srp_key_t *key) |
| { |
| return ECDSA_KEY_SIZE; |
| } |
| |
| // Function to copy out the public key as binary data |
| size_t |
| srp_pubkey_copy(uint8_t *buf, size_t max, srp_key_t *key) |
| { |
| mbedtls_ecp_keypair *ecp = mbedtls_pk_ec(key->key); |
| char errbuf[64]; |
| int status; |
| |
| if (max < ECDSA_KEY_SIZE) { |
| return 0; |
| } |
| |
| // Currently ECP only. |
| if ((status = mbedtls_mpi_write_binary(&ecp->Q.X, buf, ECDSA_KEY_PART_SIZE)) != 0 || |
| (status = mbedtls_mpi_write_binary(&ecp->Q.Y, buf + ECDSA_KEY_PART_SIZE, ECDSA_KEY_PART_SIZE)) != 0) { |
| mbedtls_strerror(status, errbuf, sizeof errbuf); |
| ERROR("mbedtls_mpi_write_binary: %s", errbuf); |
| return 0; |
| } |
| |
| #ifdef MBEDTLS_PUBKEY_DUMP |
| int i; |
| fprintf(stderr, "pubkey %d: ", ECDSA_KEY_SIZE); |
| for (i = 0; i < ECDSA_KEY_SIZE; i++) { |
| fprintf(stderr, "%02x", buf[i]); |
| } |
| putc('\n', stderr); |
| #endif // MBEDTLS_PUBKEY_DUMP |
| return ECDSA_KEY_SIZE; |
| } |
| |
| // Function to generate a signature given some data and a private key |
| int |
| srp_sign(uint8_t *output, size_t max, uint8_t *message, size_t msglen, uint8_t *rr, size_t rdlen, srp_key_t *key) |
| { |
| int success = 1; |
| int status; |
| unsigned char hash[ECDSA_SHA256_HASH_SIZE]; |
| char errbuf[64]; |
| mbedtls_sha256_context *sha; |
| uint8_t shabuf[16 + sizeof(*sha)]; |
| uint32_t *sbp; |
| mbedtls_ecp_keypair *ecp = mbedtls_pk_ec(key->key); |
| mbedtls_mpi r, s; |
| |
| if (max < ECDSA_SHA256_SIG_SIZE) { |
| ERROR("srp_sign: not enough space in output buffer (%lu) for signature (%d).", |
| (unsigned long)max, ECDSA_SHA256_SIG_SIZE); |
| return 0; |
| } |
| |
| sbp = (uint32_t *)shabuf; |
| sha = (mbedtls_sha256_context *)sbp; |
| mbedtls_sha256_init(sha); |
| memset(hash, 0, sizeof hash); |
| mbedtls_mpi_init(&r); |
| mbedtls_mpi_init(&s); |
| |
| // Calculate the hash across first the SIG RR (minus the signature) and then the message |
| // up to but not including the SIG RR. |
| status = mbedtls_sha256_starts_ret(sha, 0); |
| if (status == 0) { |
| status = srp_mbedtls_sha256_update_ret("rr", sha, rr, rdlen); |
| } |
| if (status == 0) { |
| status = srp_mbedtls_sha256_update_ret("message", sha, message, msglen); |
| } |
| if (status == 0) { |
| status = srp_mbedtls_sha256_finish_ret(sha, hash); |
| } |
| if (status != 0) { |
| mbedtls_strerror(status, errbuf, sizeof errbuf); |
| ERROR("mbedtls_sha_256 hash failed: %s", errbuf); |
| success = 0; |
| goto cleanup; |
| } |
| |
| status = mbedtls_ecdsa_sign(&ecp->grp, &r, &s, &ecp->d, hash, sizeof hash, |
| mbedtls_ctr_drbg_random, &rng_state->rng_context); |
| if (status != 0) { |
| mbedtls_strerror(status, errbuf, sizeof errbuf); |
| ERROR("mbedtls_ecdsa_sign failed: %s", errbuf); |
| success = 0; |
| goto cleanup; |
| } |
| |
| if ((status = mbedtls_mpi_write_binary(&r, output, ECDSA_SHA256_SIG_PART_SIZE)) != 0 || |
| (status = mbedtls_mpi_write_binary(&s, output + ECDSA_SHA256_SIG_PART_SIZE, |
| ECDSA_SHA256_SIG_PART_SIZE)) != 0) { |
| mbedtls_strerror(status, errbuf, sizeof errbuf); |
| ERROR("mbedtls_ecdsa_sign failed: %s", errbuf); |
| success = 0; |
| goto cleanup; |
| } |
| cleanup: |
| mbedtls_mpi_free(&r); |
| mbedtls_mpi_free(&s); |
| return success; |
| } |
| |
| #ifndef THREAD_DEVKIT_ADK |
| int |
| srp_reset_key(const char *key_name, void *UNUSED os_context) |
| { |
| return srp_remove_key_file(os_context, key_name); |
| } |
| #endif |
| |
| // Local Variables: |
| // mode: C |
| // tab-width: 4 |
| // c-file-style: "bsd" |
| // c-basic-offset: 4 |
| // fill-column: 108 |
| // indent-tabs-mode: nil |
| // End: |