Example #1
0
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;
    
}