From f72659738db95f801467dd753df364da345553dc Mon Sep 17 00:00:00 2001 From: "atul.jha" Date: Sat, 23 May 2020 18:39:18 +0200 Subject: [PATCH] implemented kdf. slightly broken. pointer passing issue!! --- trial1/layer1.c | 107 ++++++++++++++++++++++++++++++----------------- trial1/layer1.h | 10 ++--- trial1/layer2.c | 93 +++++++++++++++++++++++++++++++++++----- trial1/layer2.h | 28 ++++++++----- trial1/main.c | 3 +- trial1/out/main2 | Bin 13480 -> 18000 bytes 6 files changed, 176 insertions(+), 65 deletions(-) diff --git a/trial1/layer1.c b/trial1/layer1.c index bc484b0..5ddba0d 100644 --- a/trial1/layer1.c +++ b/trial1/layer1.c @@ -4,10 +4,10 @@ //ROM functions #define UDSFILE "./out/RANDFILE" -#define UDSsize 8 //bytes -#define FW_file "layer1.c" +#define UDSsize 8 //bytes +#define FW_file "layer2.c" //use RANDFILE if testing FW_M for consistency #define FW_size 1000 -////need to find a way to determine file size using BIO tools +////need to find a way to determine FW file size using BIO tools int createUDS() @@ -31,10 +31,10 @@ int readUDS(uint8_t* UDS_M) int i; //uint8_t UDSbuf[UDSsize] = {0}; - //uint8_t UDS_M[SHA256_dig_t] = {0}; + //uint8_t UDS_M[SHA256_DGST_SIZE] = {0}; uint8_t* UDSbuf = calloc(1,sizeof(uint8_t)*UDSsize); - //uint8_t* UDS_M = calloc(1,sizeof(uint8_t)*SHA256_dig_t); + //uint8_t* UDS_M = calloc(1,sizeof(uint8_t)*SHA256_DGST_SIZE); out = BIO_new_fp(stdout, BIO_NOCLOSE); @@ -62,11 +62,11 @@ int readUDS(uint8_t* UDS_M) //Print block. delete later for(i = 0; i < UDSsize; i++) - BIO_printf(out,"%x",UDSbuf[i]); + BIO_printf(out,"%x,",UDSbuf[i]); BIO_printf(out, "\n"); BIO_printf(out,"UDS digest : "); - for(i = 0; i < SHA256_dig_t; i++) + for(i = 0; i < SHA256_DGST_SIZE; i++) BIO_printf(out,"%x",UDS_M[i]); BIO_printf(out, "\n"); @@ -78,42 +78,39 @@ int readUDS(uint8_t* UDS_M) } -int readFWID(uint8_t * FW_M) +int readFWID(uint8_t* FW_M) { - //1. Read layer1.c into memory - //2. Calcualte hash into arg - //uint8_t * source; - //FW_size shoudl not be static. - //Use indefinite array or determine FW_size - uint8_t* source = calloc(1,sizeof(uint8_t)*(FW_size)); +// //1. Read layer1.c into memory +// //2. Calcualte hash into arg + BIO *fp, *out; - int buf_size = 0; int i; +// //uint8_t * source; +// //FW_size shoudl not be static. +// //Use indefinite array or determine FW_size + uint8_t* source = calloc(1,sizeof(uint8_t)*FW_size); out = BIO_new_fp(stdout, BIO_NOCLOSE); + fp = BIO_new_file(FW_file, "r"); - if(!fp) - perror("Opening FW to read failed\n"); + perror("Opening firmware bin to read failed\n"); + + if (BIO_read(fp,source,FW_size) < 0) //Suspecting a ENDian issue in reading. data is half byte reversed + perror("BIO read failed\n"); - if(BIO_eof(fp)) - perror("File empty\n"); - - while(!BIO_eof(fp) && (buf_size < FW_size)) - { - BIO_read(fp, source[buf_size], 1); - buf_size++; //buf_size includes EOF - //break; - } - - if(SHA256(source, buf_size -1, FW_M) == NULL) + //Compute hash of FW + if(SHA256(source, FW_size, FW_M) == NULL) perror("FW measurement failed\n"); + //Print block. delete later + // for(i = 0; i < 100; i++) + // BIO_printf(out,"%x,",source[i]); + // BIO_printf(out, "\n"); - //print block BIO_printf(out,"FW digest : "); - for(i = 0; i < SHA256_dig_t; i++) + for(i = 0; i < SHA256_DGST_SIZE; i++) BIO_printf(out,"%x",FW_M[i]); BIO_printf(out, "\n"); @@ -124,6 +121,8 @@ int readFWID(uint8_t * FW_M) } + + int calcCDID(uint8_t * UDS_M, uint8_t * FW_M, uint8_t * CDID) { //0. internally call readUDS and readFWID? abstraction of UDS against layer2 @@ -149,18 +148,18 @@ int calcCDID(uint8_t * UDS_M, uint8_t * FW_M, uint8_t * CDID) //print block - BIO_printf(out,"UDID_M : "); - for(int i = 0; i < SHA256_dig_t; i++) + BIO_printf(out,"UDID_M : "); + for(int i = 0; i < SHA256_DGST_SIZE; i++) BIO_printf(out,"%x",UDS_M[i]); BIO_printf(out, "\n"); BIO_printf(out,"FWID_M : "); - for(int i = 0; i < SHA256_dig_t; i++) + for(int i = 0; i < SHA256_DGST_SIZE; i++) BIO_printf(out,"%x",FW_M[i]); BIO_printf(out, "\n"); BIO_printf(out,"CDI : "); - for(int i = 0; i < SHA256_dig_t; i++) + for(int i = 0; i < SHA256_DGST_SIZE; i++) BIO_printf(out,"%x",CDID[i]); BIO_printf(out, "\n"); BIO_free(out); @@ -184,29 +183,59 @@ int _calcCDID(uint8_t * _CDID) BIO_printf(out, "\n");BIO_printf(out, "\n");BIO_printf(out, "\n"); //step 1 : Derive Device ID - uint8_t* UDS_ID = calloc(1,sizeof(uint8_t)*SHA256_dig_t); + uint8_t* UDS_ID = calloc(1,sizeof(uint8_t)*SHA256_DGST_SIZE); readUDS(UDS_ID); //step 2 : Derive Firmware ID - uint8_t* FW_ID = calloc(1,sizeof(uint8_t)*SHA256_dig_t); + uint8_t* FW_ID = calloc(1,sizeof(uint8_t)*SHA256_DGST_SIZE); readFWID(FW_ID); //step3 : call calcCDID calcCDID(UDS_ID,FW_ID,_CDID); + + // BIO_printf(out,"_UDID : "); + // for(int i = 0; i < SHA256_DGST_SIZE; i++) + // BIO_printf(out,"%x",UDS_ID[i]); + // BIO_printf(out, "\n"); + + // BIO_printf(out,"_FWID : "); + // for(int i = 0; i < SHA256_DGST_SIZE; i++) + // BIO_printf(out,"%x",FW_ID[i]); + // BIO_printf(out, "\n"); + + + // BIO_printf(out,"_CDI : "); + // for(int i = 0; i < SHA256_DGST_SIZE; i++) + // BIO_printf(out,"%x",_CDID[i]); + // BIO_printf(out, "\n"); + + // SHA256_CTX *ctx; + // if(!SHA256_Init(ctx)) + // perror("SHA init failed\n"); + + // if(!SHA256_Update(ctx, UDS_ID, UDSsize)) + // perror("SHA update failed\n"); + // if(!SHA256_Update(ctx, FW_ID, FW_size)) + // perror("SHA update2 failed\n"); + + // if(!SHA256_Final(_CDID, ctx)) + // perror("SHA close failed\n"); + + BIO_printf(out,"_UDID : "); - for(int i = 0; i < SHA256_dig_t; i++) + for(int i = 0; i < SHA256_DGST_SIZE; i++) BIO_printf(out,"%x",UDS_ID[i]); BIO_printf(out, "\n"); BIO_printf(out,"_FWID : "); - for(int i = 0; i < SHA256_dig_t; i++) + for(int i = 0; i < SHA256_DGST_SIZE; i++) BIO_printf(out,"%x",FW_ID[i]); BIO_printf(out, "\n"); BIO_printf(out,"_CDI : "); - for(int i = 0; i < SHA256_dig_t; i++) + for(int i = 0; i < SHA256_DGST_SIZE; i++) BIO_printf(out,"%x",_CDID[i]); BIO_printf(out, "\n"); BIO_free(out); diff --git a/trial1/layer1.h b/trial1/layer1.h index e7f87c7..e068ec1 100644 --- a/trial1/layer1.h +++ b/trial1/layer1.h @@ -1,19 +1,19 @@ #include #include + #include #include #include #include -#include -#include -#include -#define SHA256_dig_t 32 //bytes + +#define SHA256_DGST_SIZE 32 //bytes #define ECC_curve +int createUDS(); //create rand file. to be replaced with real fuse pointer + int readUDS(uint8_t* UDSdigest); -int createUDS(); int readFWID(uint8_t * FW_M); int calcCDID(uint8_t * UDS_M, uint8_t * FW_M, uint8_t * CDID); int _calcCDID(uint8_t * CDID); //wrapper function broken, do not use diff --git a/trial1/layer2.c b/trial1/layer2.c index aac4f33..1cd88f5 100644 --- a/trial1/layer2.c +++ b/trial1/layer2.c @@ -1,43 +1,116 @@ #include "layer2.h" +//RIOT core - -void startProtocol() +void startCDIProtocol() { //step 1 : Derive Device ID - uint8_t* UDS_ID = calloc(1,sizeof(uint8_t)*SHA256_dig_t); + uint8_t* UDS_ID = calloc(1,sizeof(uint8_t)*SHA256_DGST_SIZE); readUDS(UDS_ID); - // for(int i = 0; i < SHA256_dig_t; i++) + // for(int i = 0; i < SHA256_DGST_SIZE; i++) // printf("%x",UDS_ID[i]); // printf("\n"); //step 2 : Derive Firmware ID - uint8_t* FW_ID = calloc(1,sizeof(uint8_t)*SHA256_dig_t); + uint8_t* FW_ID = calloc(1,sizeof(uint8_t)*SHA256_DGST_SIZE); readFWID(FW_ID); - // for(int i = 0; i < SHA256_dig_t; i++) + // for(int i = 0; i < SHA256_DGST_SIZE; i++) // printf("%x",FW_ID[i]); // printf("\n"); //setp 3 : Derive Composite Device ID - uint8_t* CD_ID = calloc(1,sizeof(uint8_t)*SHA256_dig_t); + uint8_t* CD_ID = calloc(1,sizeof(uint8_t)*SHA256_DGST_SIZE); calcCDID(UDS_ID,FW_ID,CD_ID); free(UDS_ID); free(FW_ID); - // uint8_t* _CD_ID = calloc(1,sizeof(uint8_t)*SHA256_dig_t); + //uint8_t* _CD_ID = calloc(1,sizeof(uint8_t)*SHA256_DGST_SIZE); // _calcCDID(_CD_ID); - + for(int i = 0; i < SHA256_DGST_SIZE; i++) + printf("%x",CD_ID[i]); + printf( "\n"); + uint8_t* KEY_OUT = calloc(1,sizeof(uint8_t)*KDF_KEY_SIZE); + size_t KEY_LEN = KDF_KEY_SIZE; //need to pass pointer to the out key size, not the value + + + if(deriveKDF(KEY_OUT, &KEY_LEN, CD_ID, SHA256_DGST_SIZE, PASSPHRASE, lenofstr(PASSPHRASE))) + printf("KDF call success\n");; + + + //return value iz not correct. + //first 6 bytes are random, inconsistent, followed by two 0s then the next bytes are correct + //very suspicious behaviour + //is this similar to _cacl_CDID fun issue? + //should i use memset memcp isntead of passing pointers? + //learn pointers more thoroughly + + for(int i = 0; i <= KDF_KEY_SIZE; i++) + printf("%x,",KEY_OUT[i]); + printf( ":KEY_OUT\n"); //End block free(CD_ID); - // free(_CD_ID); + //free(_CD_ID); } + +int deriveKDF(uint8_t * out, size_t * out_len, uint8_t * secret, int secret_len, unsigned char * passphrase, int pass_len) +{ + //create comtext + //ctx set params + //passphrase + //secret + //alg //not taken as input. fixed to sha256 + //salt //meh, hardcode salt too + //out + + //hkdf derive key + + for(int i = 0; i < SHA256_DGST_SIZE; i++) + printf("%x",secret[i]); + printf( " : secret\n"); + + + //sample kdf prog + EVP_PKEY_CTX * pctx; + //uint8_t * OUT = calloc(1,sizeof(uint8_t)*KDF_KEY_SIZE); + //size_t keylen = KDF_KEY_SIZE; + + uint8_t salt[32] = {0x31,0xe2,0x3e,0xcc,0x28,0xc5,0x7b,0xbb,0x38,0x7d,0xe6,0x66,0xbb, + 0xbe,0x67,0x0a,0xf8,0xf3,0x92,0x0e,0xba,0x68,0xd1,0x56,0xea,0x34,0x3f,0xbc,0x4f, + 0xf1,0xd9,0x1e}; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); //..new_id() allocates pub key alg to ctx + + if(EVP_PKEY_derive_init(pctx) <= 0) + perror("pkey init failed:"); + // if (EVP_PKEY_CTX_hkdf_mode(pctx,EVP_PKEY_HKDEF_MODE_EXPAND_ONLY ) <= 0) + //perror("set message mode failed:"); + if (EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256()) <= 0) + perror("set message digest failed:"); + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, sizeof(salt)) <= 0) + perror("set salt failed:"); + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) + perror("set secret failed:"); + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, passphrase, pass_len) <= 0) + perror("set label failed:"); + if (EVP_PKEY_derive(pctx, out, out_len) <= 0) + perror("pkey derivation failed:"); + + for(int i = 0; i <= KDF_KEY_SIZE; i++) + printf("%x,",out[i]); + printf( ": OUT\n"); + + EVP_PKEY_CTX_free(pctx); + free(out); + + return 1; +} \ No newline at end of file diff --git a/trial1/layer2.h b/trial1/layer2.h index 93c0541..296d8a8 100644 --- a/trial1/layer2.h +++ b/trial1/layer2.h @@ -1,16 +1,24 @@ #include "layer1.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include -#define SHA256_dig_t 32 //bytes +#define SHA256_DGST_SIZE 32 //bytes +#define PASSPHRASE "Identity" +#define KDF_KEY_SIZE 32 +#define KDF_ALG EVP_sha256() +#define EC_KEY_SIZE 32 -void startProtocol(); +#define lenofstr(a) (sizeof(a)-1) -int deriveKDF(); -int deriveECC_Key(); \ No newline at end of file + +void startCDIProtocol(); + +int deriveKDF(uint8_t * out, size_t * out_len, uint8_t * secret, int secret_len, unsigned char * passphrase, int pass_len); +int deriveECC_Key(); +int deriveDEVICE_Key(); +int deriveALIAS_key(); +int genDEVICE_cert(); +int genALIAS_cert(); diff --git a/trial1/main.c b/trial1/main.c index ffc43da..a6a67df 100644 --- a/trial1/main.c +++ b/trial1/main.c @@ -3,7 +3,8 @@ int main() { printf("Hello World\n\n\n"); - startProtocol(); + startCDIProtocol(); + //deriveKDF(); printf("\n\nSeccessful exit\n"); return 0; } diff --git a/trial1/out/main2 b/trial1/out/main2 index 19c3daac8739609fbcdc3e1f33d1237b8775c001..bf61f812623bc30db25b043664152548c6d9dcbf 100755 GIT binary patch literal 18000 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 literal 13480 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 BasL1S