From e9c45800136f8c4325b573bbdafea2dbcce1e5c9 Mon Sep 17 00:00:00 2001 From: "atul.jha" Date: Mon, 3 Aug 2020 18:06:57 +0200 Subject: [PATCH] Deterministic ECC rom Seed working --- trial3/defines.h | 37 +++++++++++++- trial3/layer1.c | 130 +++++++++++++++++++++++++++++++++++++++++++---- trial3/layer1.h | 7 ++- trial3/layer2.c | 42 +++++++-------- trial3/layer2.h | 6 +-- trial3/links.txt | 11 ++-- trial3/out/main | Bin 19016 -> 19024 bytes 7 files changed, 192 insertions(+), 41 deletions(-) diff --git a/trial3/defines.h b/trial3/defines.h index 32ba4a9..0a53aaa 100644 --- a/trial3/defines.h +++ b/trial3/defines.h @@ -31,4 +31,39 @@ /* CONFIGURATIONS */ -#define USE_ECC 1 \ No newline at end of file +#define USE_ECC 1 +#define USE_HW_TRNG 0 + + + +#define SW_PRNG 0 +#define HW_TRNG 1 +#define DETERM 2 + + + + + +/* SPECIFIC AND SPECIAL VALUES */ +/* DO NOT CHANGE THIS BLOCK */ + +#define ACCUM_BUFF_OFFSET 2 +#define ENTROPY_LEN 32 + + +/* typedes */ + +typedef struct +{ + /* + Use this typedef to define settings and vlaues to be passed to deriveECCKeyPair() + To be used or exchanging data between ROM and FW + */ + + mbedtls_mpi secret; //Private key holder + mbedtls_ecp_point Public; //Public key holder + int ENT_MODE; //0 - SW_PRNG, 1 - HW_TRNG, 2 - DETERM, + int PKC_MODE; // isECC, 1 = ECC, 0= RSA + const uint8_t * seed; //To seed + const char * phrase; //Session string +} KeyDrv_context; diff --git a/trial3/layer1.c b/trial3/layer1.c index 5c979ed..5fd4c78 100644 --- a/trial3/layer1.c +++ b/trial3/layer1.c @@ -196,8 +196,85 @@ int _calcCDIKEY(uint8_t * CDIKEY) //gen keypair +/* To use HW TRNG /dev/random as the source of entropy add source to entropy contxt - +*/ -int deriveECCKeyPair(mbedtls_mpi * SK, mbedtls_ecp_point * PK) +int use_dev_random(void *data, unsigned char *output, + size_t len, size_t *olen ) +{ + FILE *file; + size_t ret, left = len; + unsigned char *p = output; + ((void) data); + + *olen = 0; + + file = fopen( "/dev/random", "rb" ); + if( file == NULL ) + return( MBEDTLS_ERR_ENTROPY_SOURCE_FAILED ); + + while( left > 0 ) + { + /* /dev/random can return much less than requested. If so, try again */ + ret = fread( p, 1, left, file ); + if( ret == 0 && ferror( file ) ) + { + fclose( file ); + return( MBEDTLS_ERR_ENTROPY_SOURCE_FAILED ); + } + + p += ret; + left -= ret; + sleep( 1 ); + } + fclose( file ); + *olen = len; + + return( 0 ); +} + + +int seedRNGSource(void *data, unsigned char *output, size_t len) +{ + //srand(); lib fun call //https://stackoverflow.com/questions/55927662/generate-every-time-same-rsa-key-with-c + //ctr_drbg //programs/test/benchmark.c:705 + //hmac_drbg + + //Fill entropy accum with CDI and pass to DRBG + + + mbedtls_entropy_context * p_ent = data; + printf("manual update entropy with CDI\n"); + printf("%d : len of buffer\n", (int)sizeof(p_ent -> accumulator.buffer) ); + + if(memcpy(output, p_ent -> accumulator.buffer + ACCUM_BUFF_OFFSET , ENTROPY_LEN) < 0) + return( MBEDTLS_ERR_ENTROPY_SOURCE_FAILED ); + + for(int i = 0; i < ENTROPY_LEN; i++) + printf("0x%hhx,",output[i]); + printf(" : CDIKEY\n"); + len = ENTROPY_LEN; + printf("%d\n", (int)len ); + + return 0; + + // ((void) data); + // printf("Direct return CDI to drbg\n"); + + // len = sizeof(CDI); + // memcpy(output, CDI, len); + // for(int i = 0; i < len; i++) + // printf("0x%hhx,",output[i]); + // printf(" : CDIKEY\n"); + // printf("%d\n", (int)len); + // return 0; + +} + + + + +int deriveECCKeyPair(KeyDrv_context * KD_ctx) { printf("inside deriveECCKeyPair layer1\n\n"); @@ -229,15 +306,48 @@ int deriveECCKeyPair(mbedtls_mpi * SK, mbedtls_ecp_point * PK) mbedtls_ctr_drbg_context drbgCtx; mbedtls_ctr_drbg_init(&drbgCtx); - //Seed drbg with secret data now? - //move "private" string to n param - if(mbedtls_ctr_drbg_seed(&drbgCtx, mbedtls_entropy_func, &entropyCtx, - (const unsigned char *) "Private", sizeof("Private")) < 0) + + if (KD_ctx->ENT_MODE == HW_TRNG) //HW RNG { - perror("drbg seed failed\v"); - return RIOTFAILURE; + printf("using /dev/random.... this may take a moment\n"); + mbedtls_entropy_add_source( &entropyCtx, use_dev_random, + NULL, ENTROPY_LEN, MBEDTLS_ENTROPY_SOURCE_STRONG ); + + mbedtls_ctr_drbg_seed(&drbgCtx, mbedtls_entropy_func, + &entropyCtx, + (const unsigned char *) KD_ctx->phrase , + strlen(KD_ctx->phrase) + ); } + else if (KD_ctx->ENT_MODE == DETERM) // Deterministic derviation with seed + { + printf("Seeding entropy accumulator....\n"); + if(mbedtls_entropy_update_manual(&entropyCtx, KD_ctx->seed, ENTROPY_LEN) < 0) + { + perror("Accumulator seed failed\n"); + return RIOTFAILURE; + } + + if(mbedtls_ctr_drbg_seed(&drbgCtx, seedRNGSource, &entropyCtx, + (const unsigned char *) KD_ctx->phrase, sizeof(&KD_ctx->phrase)) < 0) + { + perror("drbg seed failed\v"); + return RIOTFAILURE; + } + } + + else //regular key derivation + { + printf("Accumulating entropy ...\n"); + mbedtls_entropy_update_manual(&entropyCtx, KD_ctx->seed, ENTROPY_LEN); + mbedtls_ctr_drbg_seed(&drbgCtx, mbedtls_entropy_func, + &entropyCtx, + (const unsigned char *) KD_ctx->phrase , + strlen(KD_ctx->phrase) + ); + } + if(mbedtls_ecp_gen_keypair(&ecpGrp, &secret, &Public, mbedtls_ctr_drbg_random, &drbgCtx) <0) { @@ -270,8 +380,10 @@ int deriveECCKeyPair(mbedtls_mpi * SK, mbedtls_ecp_point * PK) printf("%s : PrivKey\n",privkeybuf); //copy keys to parent function - mbedtls_ecp_copy(PK, &Public); - mbedtls_mpi_copy(SK, &secret); + mbedtls_ecp_copy(&KD_ctx->Public, &Public); + mbedtls_mpi_copy(&KD_ctx->secret, &secret); /* Make SK NULL for Identitiy key generation */ + + //what now? how to obtain the keys in PEM/DER/bin format? diff --git a/trial3/layer1.h b/trial3/layer1.h index 597c0b0..fc2329a 100644 --- a/trial3/layer1.h +++ b/trial3/layer1.h @@ -1,6 +1,8 @@ #include #include #include +#include + #include #include @@ -30,7 +32,10 @@ int _calcCDID(uint8_t * CDID); int _calcCDIKEY(uint8_t * CDIKEY); -int deriveECCKeyPair(mbedtls_mpi * SK, mbedtls_ecp_point * PK); + +int seedRNGSource(void *data, unsigned char *output, size_t len); + +int deriveECCKeyPair(KeyDrv_context * KD_ctx); int deriveRSAKeyPair(void); diff --git a/trial3/layer2.c b/trial3/layer2.c index 16f0907..1a379d1 100644 --- a/trial3/layer2.c +++ b/trial3/layer2.c @@ -16,8 +16,10 @@ void ROMprotocol(void) printf(" : CDIKEY main\n"); printf("USE_ECC %d\n", USE_ECC); + + + deriveDeviceIDKeyPair(CDIKEY, USE_ECC); deriveDeviceIDKeyPair(CDIKEY, USE_ECC); - deriveDeviceIDKeyPair(CDIKEY, !USE_ECC); printf("pass 100\n"); @@ -25,23 +27,6 @@ printf("pass 100\n"); return; } -void seedRNGSource(uint8_t * CDIKEY) -{ - //srand(); lib fun call //https://stackoverflow.com/questions/55927662/generate-every-time-same-rsa-key-with-c - //ctr_drbg //programs/test/benchmark.c:705 - //hmac_drbg - - //seed rng with CDIKEY - //init - //drbg seed - - - - - return; -} - - void deriveDeviceIDKeyPair(uint8_t * CDIKEY, int isECC) { @@ -61,22 +46,31 @@ void deriveDeviceIDKeyPair(uint8_t * CDIKEY, int isECC) //return pubkey and privkey both to L2? - if(isECC) + KeyDrv_context DID_ctx; + DID_ctx.ENT_MODE = DETERM; //deterministec + DID_ctx.PKC_MODE = isECC; + DID_ctx.seed = CDIKEY; + DID_ctx.phrase = IDENTITY; + + + if(DID_ctx.PKC_MODE == isECC) { - mbedtls_mpi secret; - mbedtls_mpi_init(&secret); + //mbedtls_mpi secret; + mbedtls_mpi_init(&DID_ctx.secret); - mbedtls_ecp_point Public; - mbedtls_ecp_point_init(&Public); + //mbedtls_ecp_point Public; + mbedtls_ecp_point_init(&DID_ctx.Public); //return pubkey and privkey both to L2? - deriveECCKeyPair(&secret, &Public); + deriveECCKeyPair(&DID_ctx); //secret and Public contain raw key inormation of generated keys //Public also needs group and context to handle ECP //Converting raw info to PEM is not easible like this + //free(DID_ctx); + return; } diff --git a/trial3/layer2.h b/trial3/layer2.h index 9453b8c..5f59e65 100644 --- a/trial3/layer2.h +++ b/trial3/layer2.h @@ -16,15 +16,15 @@ #include -#include "defines.h" +//#include "defines.h" +#define IDENTITY "Identity" +#define ALIAS "ALIAS" void ROMprotocol(void); -void seedRNGSource(uint8_t * CDIKEY); - void deriveDeviceIDKeyPair(uint8_t * CDIKEY, int isECC); void deriveAliasKeyPair(uint8_t * ALIKEY, int isECC); diff --git a/trial3/links.txt b/trial3/links.txt index fad64b9..f4573f1 100644 --- a/trial3/links.txt +++ b/trial3/links.txt @@ -8,14 +8,19 @@ https://www.cryptopp.com/wiki/ +https://forums.mbed.com/t/ecc-raw-byte-stream-to-pem/4540/3 +https://github.com/ARMmbed/mbedtls/issues/2560 +ENTROPY - - +gen_key.c adding /dev/random to entropy - mbedtls_entropy_add_source( &entropy, dev_random_entropy_poll, NULL, DEV_RANDOM_THRESHOLD, MBEDTLS_ENTROPY_SOURCE_STRONG ) ) != 0 ) - \ No newline at end of file +https://forums.mbed.com/t/mbedtls-porting-into-new-environment-help-with-networking-and-entropy/4969 +https://os.mbed.com/docs/mbed-os/v6.2/porting/entropy-sources.html + +https://tls.mbed.org/discussions/crypto-and-ssl/deterministic-random-bit-generator-help \ No newline at end of file diff --git a/trial3/out/main b/trial3/out/main index 1f933d99acdd31428786616940cc6d0d64824b3f..553fea7fe04338079e52bf14b1fb2616a036e7e8 100755 GIT binary patch delta 7104 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 Xc!NAi9ITiuw+LTFsT?WN7f<+K&fwY4 delta 6929 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 Q