int32_t test(int32_t M,int32_t N,int32_t datasize) { int ok = 1, i; uint8_t * secret = malloc(datasize); uint8_t *shares[255]; uint8_t *recomb = malloc(datasize); uint8_t sharenrs[255],newsharenrs[255];// = (uint8_t *)strdup("0124z89abehtr"); gfshare_ctx *G; gfshare_fill_rand = gfshare_bad_idea_but_fill_rand_using_random; for (i=0; i<N; i++) { sharenrs[i] = i+1; shares[i] = malloc(datasize); } /* Stage 1, make a secret */ for( i = 0; i < datasize; ++i ) secret[i] = (rand() & 0xff00) >> 8; /* Stage 2, split it N ways with a threshold of M */ G = gfshare_ctx_init_enc( sharenrs, N, M, datasize ); gfshare_ctx_enc_setsecret( G, secret ); for (i=0; i<N; i++) gfshare_ctx_enc_getshare( G, i, shares[i] ); gfshare_ctx_free( G ); /* Prep the decode shape */ G = gfshare_ctx_init_dec( sharenrs, N, datasize ); memset(newsharenrs,0,N); int32_t j,r; for (i=0; i<M; i++) { r = rand() % N; while ( (j= sharenrs[r]) == 0 || newsharenrs[r] != 0 ) r = rand() % N; newsharenrs[r] = j; sharenrs[r] = 0; } for (i=0; i<N; i++) { if ( newsharenrs[i] != 0 ) gfshare_ctx_dec_giveshare( G, i, shares[i] ); //newsharenrs[i] = sharenrs[i]; } /* Stage 3, attempt a recombination with shares 1 and 2 */ //sharenrs[2] = 0; gfshare_ctx_dec_newshares( G, newsharenrs ); gfshare_ctx_dec_extract( G, recomb ); for( i = 0; i < datasize; ++i ) if( secret[i] != recomb[i] ) ok = 0; printf("M.%-3d N.%-3d ok.%d datalen.%d\n",M,N,ok,datasize); free(recomb), free(secret); for (i=0; i<N; i++) free(shares[i]); return ok!=1; }
END_TEST // this test is intended to check that a correct account structure is // producted, we check the expected hash matches and that we have a two-way // flow. In other words, that we can derive the hash from the xored hash and // vice versa START_TEST(test_create_account_entry_consistency) { PPH_ERROR error; pph_context *context; uint8 threshold = 2; uint8 partial_bytes = 0; unsigned char password[] = "verysecure"; unsigned char username[] = "atleastitry"; // We don't know the salt yet, but we know the password value, upon creating // the account, we will replace those x's with the salt values. unsigned char salted_password[] = {'x','x','x','x','x','x','x','x','x', 'x','x','x','x','x','x','x','v','e','r', 'y','s','e','c','u','r','e','\0'}; uint8 password_digest[DIGEST_LENGTH]; unsigned int i; uint8 *digest_result; uint8 share_result[SHARE_LENGTH]; context = pph_init_context(threshold, partial_bytes); ck_assert_msg(context != NULL, "this was a good initialization, go tell someone"); // create the username. error = pph_create_account(context, username,strlen(username), password,strlen(password),1); ck_assert_msg(error == PPH_ERROR_OK, "We should've gotten PPH_ERROR_OK in the return value"); // we do this because we assume the username here is a normal string, but // under normal circumstances, we can't assume this. context->account_data->account.username[strlen(username)]='\0'; ck_assert_str_eq(username,context->account_data->account.username); // now lets check we can take the digest back from the share memcpy(salted_password,context->account_data->account.entries->salt, MAX_SALT_LENGTH); _calculate_digest(password_digest, salted_password, MAX_SALT_LENGTH + strlen(password)); digest_result=context->account_data->account.entries->polyhashed_value; gfshare_ctx_enc_getshare(context->share_context, 1, share_result); _xor_share_with_digest(digest_result, share_result, digest_result, DIGEST_LENGTH); // compare the resulting digests to prove they match. for(i=0;i<DIGEST_LENGTH;i++) { ck_assert(password_digest[i]==digest_result[i]); } // we will check for the existing account error handler now... error = pph_create_account(context, username, strlen(username), password, strlen(password),1); ck_assert_msg(error == PPH_ACCOUNT_EXISTS, "We should've gotten an error since this account repeats"); // finally, check it returns the proper error code if the vault is locked // still, we will simulate account locking by unsetting the flag. context->is_unlocked = false; // we will check for the locked context error now... error = pph_create_account(context, "someotherguy", strlen("someotherguy"), "came-here-asking-the-same-thing",strlen("came-here-asking-the-same-thing") ,1); ck_assert_msg(error == PPH_CONTEXT_IS_LOCKED, "We should've gotten an error now that the vault is locked"); error = pph_destroy_context(context); ck_assert_msg(error == PPH_ERROR_OK, "the free function didn't work properly"); }
PPH_ERROR pph_create_account(pph_context *ctx, const uint8 *username, const unsigned int username_length, const uint8 *password, const unsigned int password_length, uint8 shares){ pph_account_node *node,*next; unsigned int length; unsigned int i; pph_entry *entry_node,*last_entry; uint8 current_entry; uint8 share_data[SHARE_LENGTH]; uint8 resulting_hash[DIGEST_LENGTH]; uint8 salt_buffer[MAX_SALT_LENGTH]; // 1) SANITIZE INFORMATION // check password length if(password_length > MAX_PASSWORD_LENGTH-1){ return PPH_PASSWORD_IS_TOO_LONG; } // check username length if(username_length > MAX_USERNAME_LENGTH-1){ return PPH_USERNAME_IS_TOO_LONG; } // check share numbers, we don't check for 0 since that means thresholdless // accounts if(shares>MAX_NUMBER_OF_SHARES){ return PPH_WRONG_SHARE_COUNT; } // check correct context pointer if(ctx == NULL){ return PPH_BAD_PTR; } // check if we are able to get shares from the context vault if(ctx->is_unlocked != true || ctx->AES_key == NULL){ return PPH_CONTEXT_IS_LOCKED; } // This while loop will traverse our accounts and check if the username is // already taken. next = ctx->account_data; while(next!=NULL){ node=next; next=next->next; // only compare them if their lengths match if(username_length==node->account.username_length && !memcmp(node->account.username,username,username_length)){ return PPH_ACCOUNT_EXISTS; } } // 2) check for the type of account requested. // this will generate a share list for threshold accounts, we won't // fall inside this loop for thresholdless accounts since shares is 0. last_entry = NULL; for(i=0;i<shares;i++){ // 3) Allocate entries for each account // get a new share value gfshare_ctx_enc_getshare( ctx->share_context, ctx->next_entry, share_data); // get a salt for the password get_random_bytes(MAX_SALT_LENGTH, salt_buffer); // Try to get a new entry. entry_node=create_polyhashed_entry(password, password_length, salt_buffer, MAX_SALT_LENGTH, share_data, SHARE_LENGTH, ctx->partial_bytes); if(entry_node == NULL){ _destroy_entry_list(last_entry); return PPH_NO_MEM; } // update the share number for this entry, and update the next available // share in a round robin fashion entry_node->share_number = ctx->next_entry; ctx->next_entry++; if(ctx->next_entry==0 || ctx->next_entry>=MAX_NUMBER_OF_SHARES){ ctx->next_entry=1; } // add the node to the list entry_node->next = last_entry; last_entry=entry_node; } // This if will check for thresholdless accounts, and will build a single // entry for them. if(shares == 0){ // 3) allocate an entry for each account // get a salt for the password get_random_bytes(MAX_SALT_LENGTH, salt_buffer); // generate the entry entry_node = create_thresholdless_entry(password, password_length, salt_buffer, MAX_SALT_LENGTH, ctx->AES_key, DIGEST_LENGTH, ctx->partial_bytes); if(entry_node == NULL){ return PPH_NO_MEM; } // we now have one share entry under this list, so we increment this // parameter. shares++; } // 4) Allocate the information for the account // allocate the account information, check for memory issues and return. node=malloc(sizeof(*node)); if(node==NULL){ // we should destroy the list we created now to avoid memory leaks _destroy_entry_list(entry_node); return PPH_NO_MEM; } // fill with the user entry with the rest of the account information. memcpy(node->account.username,username,username_length); node->account.number_of_entries = shares; node->account.username_length = username_length; node->account.entries = entry_node; // 5) add the resulting account to the current context. // append it to the context list, with the rest of thee users node->next = ctx->account_data; ctx->account_data = node; // 6) return. // everything is set! return PPH_ERROR_OK; }
PPH_ERROR pph_check_login(pph_context *ctx, const char *username, unsigned int username_length, uint8 *password, unsigned int password_length){ // this will be used to iterate all the users pph_account_node *search; pph_account_node *target = NULL; // we will store the current share in this buffer for xor'ing uint8 share_data[SHARE_LENGTH]; // we will calculate a "proposed hash" in this buffer uint8 resulting_hash[DIGEST_LENGTH]; uint8 salted_password[MAX_SALT_LENGTH+MAX_PASSWORD_LENGTH]; uint8 xored_hash[SHARE_LENGTH]; // these are value holders to improve readability uint8 sharenumber; pph_entry *current_entry; unsigned int i; // this will hold an offset value for partial verification. unsigned int partial_bytes_offset; // openSSL managers. EVP_CIPHER_CTX de_ctx; int p_len,f_len; // 1) Sanitize data and return errors. // check for any improper pointers if(ctx == NULL || username == NULL || password == NULL){ return PPH_BAD_PTR; } // if the length is too long for either field, return proper error. if(username_length > MAX_USERNAME_LENGTH){ return PPH_USERNAME_IS_TOO_LONG; } // do the same for the password if(password_length > MAX_PASSWORD_LENGTH){ return PPH_PASSWORD_IS_TOO_LONG; } // check if the context is locked and we lack partial bytes to check. If we // do not have enough partial bytes (at least one), we cannot do partial // verification if(ctx->is_unlocked != true && ctx->partial_bytes == 0){ return PPH_CONTEXT_IS_LOCKED; } // check we have a thresholdless key if(ctx->AES_key == NULL && ctx->partial_bytes == 0){ return PPH_CONTEXT_IS_LOCKED; } // 2) Try to find the user in our context. // search for our user, we search the entries with the same username length // first, and then we check if the contents are the same. search = ctx->account_data; while(search!=NULL){ // we check lengths first and then compare what's in it. if(username_length == search->account.username_length && !memcmp(search->account.username,username,username_length)){ target = search; } search=search->next; } //i.e. we found no one if(target == NULL){ return PPH_ACCOUNT_IS_INVALID; } // if we reach here, we should have enough resources to provide a login // functionality to the user. // 3) Try to verify the proper password for him. // first, check what type of account is this // this probably happens if data is inconsistent, but let's avoid // segmentation faults. if(target->account.entries == NULL){ return PPH_ERROR_UNKNOWN; } // we get the first entry to check if this is a valid login, we could be // thorough and check for each, but it looks like an overkill current_entry = target->account.entries; sharenumber = current_entry->share_number; partial_bytes_offset = DIGEST_LENGTH - ctx->partial_bytes; // if the context is not unlocked, we can only provide partial verification if(ctx->is_unlocked != true){ // partial bytes check // calculate the proposed digest, this means, calculate the hash with // the information just provided about the user. memcpy(salted_password,current_entry->salt,current_entry->salt_length); memcpy(salted_password+current_entry->salt_length, password, current_entry->password_length); _calculate_digest(resulting_hash, salted_password, current_entry->salt_length + password_length); // only compare the bytes that are not obscured by either AES or the // share, we start from share_length-partial_bytes to share_length. if(memcmp(resulting_hash+partial_bytes_offset, target->account.entries->polyhashed_value+partial_bytes_offset, ctx->partial_bytes)){ return PPH_ACCOUNT_IS_INVALID; } return PPH_ERROR_OK; } // we are unlocked and hence we can provide full verification. else{ // first, check if the account is a threshold or thresholdless account. if(sharenumber == 0){ // if the sharenumber is 0 then we have a thresholdless account // now we should calculate the expected hash by deciphering the // information inside the context. EVP_CIPHER_CTX_init(&de_ctx); EVP_DecryptInit_ex(&de_ctx, EVP_aes_256_ctr(), NULL, ctx->AES_key, NULL); EVP_DecryptUpdate(&de_ctx, xored_hash, &p_len, current_entry->polyhashed_value, partial_bytes_offset); EVP_DecryptFinal_ex(&de_ctx, xored_hash+p_len, &f_len); EVP_CIPHER_CTX_cleanup(&de_ctx); // append the unencrypted bytes if we have partial bytes. for(i=p_len+f_len;i<DIGEST_LENGTH;i++){ xored_hash[i] = current_entry->polyhashed_value[i]; } // calculate the proposed digest with the parameters provided in // this function. memcpy(salted_password,current_entry->salt, current_entry->salt_length); memcpy(salted_password+current_entry->salt_length, password, password_length); _calculate_digest(resulting_hash, salted_password, current_entry->salt_length + password_length); // 3) compare both, and they should match. if(memcmp(resulting_hash, xored_hash, DIGEST_LENGTH)){ return PPH_ACCOUNT_IS_INVALID; } return PPH_ERROR_OK; }else{ // we have a non thresholdless account instead, since the sharenumber is // not 0 gfshare_ctx_enc_getshare(ctx->share_context, sharenumber, share_data); // calculate the proposed digest with the salt from the account and // the password in the argument. memcpy(salted_password,current_entry->salt, current_entry->salt_length); memcpy(salted_password+current_entry->salt_length, password, password_length); _calculate_digest(resulting_hash, salted_password, current_entry->salt_length + password_length); // xor the thing back to normal _xor_share_with_digest(xored_hash,current_entry->polyhashed_value, share_data, partial_bytes_offset); // add the partial bytes to the end of the digest. for(i=DIGEST_LENGTH-ctx->partial_bytes;i<DIGEST_LENGTH;i++){ xored_hash[i] = target->account.entries->polyhashed_value[i]; } // compare both. if(memcmp(resulting_hash, xored_hash, DIGEST_LENGTH)){ return PPH_ACCOUNT_IS_INVALID; } return PPH_ERROR_OK; // this means, the login does match } } // if we get to reach here, we where diverged from usual flow. return PPH_ERROR_UNKNOWN; }
int PHS(void *out, size_t outlen, const void *in, size_t inlen, const void* salt, size_t saltlen, int tcost, int mcost){ static pph_context *context = NULL; pph_entry *generated_entry; uint8 share[DIGEST_LENGTH]; // check we are given proper pointers if(out == NULL || in == NULL || salt == NULL){ return -1; } // we only support 32 byte digests at the moment. if(outlen != DIGEST_LENGTH){ return -1; } // check the input length if(inlen < 1 || inlen > MAX_PASSWORD_LENGTH){ return -1; } // check the salt length if(saltlen < 1 || saltlen > MAX_SALT_LENGTH){ return -1; } if(tcost < 1 || tcost > MAX_NUMBER_OF_SHARES){ return -1; } // remember, in our case, tcost maps directly to the threshold value, we also // decided to leave no partial bytes to have the whole hash protected by the // shares context = pph_init_context(tcost,0); // get a share to xor it with the password gfshare_ctx_enc_getshare(context->share_context, context->next_entry, share); context->next_entry++; if(context->next_entry > MAX_NUMBER_OF_SHARES){ context->next_entry = 0; } // generate an entry. generated_entry = create_polyhashed_entry( in, inlen, salt, saltlen, share, DIGEST_LENGTH, context->partial_bytes); // copy the resulting polyhash to the output memcpy(out, generated_entry->polyhashed_value, outlen); // free the generated entry free(generated_entry); pph_destroy_context(context); return 0; }