passing all tests
This commit is contained in:
parent
202f76d313
commit
42fe31af17
@ -1455,7 +1455,7 @@ int8_t ctap_load_key(uint8_t index, uint8_t * key)
|
|||||||
return ERR_NO_KEY_SPACE;
|
return ERR_NO_KEY_SPACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (STATE.key_lens[index] == 0)
|
if (STATE.key_lens[index] == 0xffff)
|
||||||
{
|
{
|
||||||
return ERR_KEY_SPACE_EMPTY;
|
return ERR_KEY_SPACE_EMPTY;
|
||||||
}
|
}
|
||||||
@ -1481,6 +1481,12 @@ void ctap_reset()
|
|||||||
authenticator_write_state(&STATE, 0);
|
authenticator_write_state(&STATE, 0);
|
||||||
authenticator_write_state(&STATE, 1);
|
authenticator_write_state(&STATE, 1);
|
||||||
|
|
||||||
|
if (ctap_generate_rng(PIN_TOKEN, PIN_TOKEN_SIZE) != 1)
|
||||||
|
{
|
||||||
|
printf2(TAG_ERR,"Error, rng failed\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
ctap_reset_state();
|
ctap_reset_state();
|
||||||
memset(PIN_CODE_HASH,0,sizeof(PIN_CODE_HASH));
|
memset(PIN_CODE_HASH,0,sizeof(PIN_CODE_HASH));
|
||||||
crypto_ecc256_make_key_pair(KEY_AGREEMENT_PUB, KEY_AGREEMENT_PRIV);
|
crypto_ecc256_make_key_pair(KEY_AGREEMENT_PUB, KEY_AGREEMENT_PRIV);
|
||||||
|
@ -41,6 +41,7 @@ typedef enum
|
|||||||
WalletPin = 0x12,
|
WalletPin = 0x12,
|
||||||
WalletReset= 0x13,
|
WalletReset= 0x13,
|
||||||
WalletVersion= 0x14,
|
WalletVersion= 0x14,
|
||||||
|
WalletRng = 0x15,
|
||||||
} WalletOperation;
|
} WalletOperation;
|
||||||
|
|
||||||
|
|
||||||
@ -198,7 +199,14 @@ int16_t bridge_u2f_to_wallet(uint8_t * _chal, uint8_t * _appid, uint8_t klen, ui
|
|||||||
|
|
||||||
printf1(TAG_WALLET, "u2f2wallet [%d]: ",reqlen); dump_hex1(TAG_WALLET, msg_buf,reqlen);
|
printf1(TAG_WALLET, "u2f2wallet [%d]: ",reqlen); dump_hex1(TAG_WALLET, msg_buf,reqlen);
|
||||||
|
|
||||||
|
if (req->operation != WalletRng)
|
||||||
|
{
|
||||||
count = ctap_atomic_count(0);
|
count = ctap_atomic_count(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
u2f_response_writeback(&up,1);
|
u2f_response_writeback(&up,1);
|
||||||
u2f_response_writeback((uint8_t *)&count,4);
|
u2f_response_writeback((uint8_t *)&count,4);
|
||||||
u2f_response_writeback((uint8_t *)&ret,1);
|
u2f_response_writeback((uint8_t *)&ret,1);
|
||||||
@ -274,7 +282,7 @@ int16_t bridge_u2f_to_wallet(uint8_t * _chal, uint8_t * _appid, uint8_t klen, ui
|
|||||||
keysize = ctap_key_len(0);
|
keysize = ctap_key_len(0);
|
||||||
|
|
||||||
crypto_load_external_key(key, keysize);
|
crypto_load_external_key(key, keysize);
|
||||||
crypto_ecdsa_sign(args[0], lens[0], sig, MBEDTLS_ECP_DP_SECP256R1);
|
crypto_ecdsa_sign(args[0], lens[0], sig, MBEDTLS_ECP_DP_SECP256K1);
|
||||||
|
|
||||||
u2f_response_writeback(sig,64);
|
u2f_response_writeback(sig,64);
|
||||||
|
|
||||||
@ -314,6 +322,8 @@ int16_t bridge_u2f_to_wallet(uint8_t * _chal, uint8_t * _appid, uint8_t klen, ui
|
|||||||
memmove(chksum, args[0] + lens[0] - 4, 4);
|
memmove(chksum, args[0] + lens[0] - 4, 4);
|
||||||
lens[0] -= 4;
|
lens[0] -= 4;
|
||||||
|
|
||||||
|
printf("chksum: "); dump_hex1(TAG_WALLET, chksum, 4);
|
||||||
|
|
||||||
// perform integrity check
|
// perform integrity check
|
||||||
printf1(TAG_WALLET,"shasum on [%d]: ",lens[0]); dump_hex1(TAG_WALLET, args[0], lens[0]);
|
printf1(TAG_WALLET,"shasum on [%d]: ",lens[0]); dump_hex1(TAG_WALLET, args[0], lens[0]);
|
||||||
crypto_sha256_init();
|
crypto_sha256_init();
|
||||||
@ -323,6 +333,8 @@ int16_t bridge_u2f_to_wallet(uint8_t * _chal, uint8_t * _appid, uint8_t klen, ui
|
|||||||
crypto_sha256_update(shasum, 32);
|
crypto_sha256_update(shasum, 32);
|
||||||
crypto_sha256_final(shasum);
|
crypto_sha256_final(shasum);
|
||||||
|
|
||||||
|
printf1(TAG_WALLET,"shasum: "); dump_hex1(TAG_WALLET, shasum, 32);
|
||||||
|
|
||||||
if (memcmp(shasum, chksum, 4) != 0)
|
if (memcmp(shasum, chksum, 4) != 0)
|
||||||
{
|
{
|
||||||
ret = CTAP2_ERR_CREDENTIAL_NOT_VALID;
|
ret = CTAP2_ERR_CREDENTIAL_NOT_VALID;
|
||||||
@ -390,8 +402,39 @@ int16_t bridge_u2f_to_wallet(uint8_t * _chal, uint8_t * _appid, uint8_t klen, ui
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case WalletVersion:
|
case WalletVersion:
|
||||||
u2f_response_writeback(WALLET_VERSION, sizeof(WALLET_VERSION));
|
u2f_response_writeback(WALLET_VERSION, sizeof(WALLET_VERSION)-1);
|
||||||
break;
|
break;
|
||||||
|
case WalletRng:
|
||||||
|
printf1(TAG_WALLET,"WalletRng\n");
|
||||||
|
if ( ctap_device_locked() )
|
||||||
|
{
|
||||||
|
printf1(TAG_ERR,"device locked\n");
|
||||||
|
ret = CTAP2_ERR_NOT_ALLOWED;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
if ( ctap_is_pin_set() )
|
||||||
|
{
|
||||||
|
if ( ! check_pinhash(req->pinAuth, msg_buf, reqlen))
|
||||||
|
{
|
||||||
|
printf1(TAG_WALLET,"pinAuth is NOT valid\n");
|
||||||
|
dump_hex(msg_buf,reqlen);
|
||||||
|
ret = CTAP2_ERR_PIN_AUTH_INVALID;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = ctap_generate_rng(sig, 72);
|
||||||
|
if (ret != 1)
|
||||||
|
{
|
||||||
|
printf1(TAG_WALLET,"Rng failed\n");
|
||||||
|
ret = CTAP2_ERR_PROCESSING;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
u2f_response_writeback((uint8_t *)sig,72);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
printf2(TAG_ERR,"Invalid wallet command: %x\n",req->operation);
|
printf2(TAG_ERR,"Invalid wallet command: %x\n",req->operation);
|
||||||
ret = CTAP1_ERR_INVALID_COMMAND;
|
ret = CTAP1_ERR_INVALID_COMMAND;
|
||||||
|
474
web/js/wallet.js
474
web/js/wallet.js
@ -3,6 +3,41 @@ DEVELOPMENT = 1;
|
|||||||
var to_b58 = function(B){var A="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";var d=[],s="",i,j,c,n;for(i in B){j=0,c=B[i];s+=c||s.length^i?"":1;while(j in d||c){n=d[j];n=n?n*256+c:c;c=n/58|0;d[j]=n%58;j++}}while(j--)s+=A[d[j]];return s};
|
var to_b58 = function(B){var A="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";var d=[],s="",i,j,c,n;for(i in B){j=0,c=B[i];s+=c||s.length^i?"":1;while(j in d||c){n=d[j];n=n?n*256+c:c;c=n/58|0;d[j]=n%58;j++}}while(j--)s+=A[d[j]];return s};
|
||||||
var from_b58 = function(S){var A="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";var d=[],b=[],i,j,c,n;for(i in S){j=0,c=A.indexOf(S[i]);if(c<0)throw new Error('Invald b58 character');c||b.length^i?i:b.push(0);while(j in d||c){n=d[j];n=n?n*58+c:c;c=n>>8;d[j]=n%256;j++}}while(j--)b.push(d[j]);return new Uint8Array(b)};
|
var from_b58 = function(S){var A="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";var d=[],b=[],i,j,c,n;for(i in S){j=0,c=A.indexOf(S[i]);if(c<0)throw new Error('Invald b58 character');c||b.length^i?i:b.push(0);while(j in d||c){n=d[j];n=n?n*58+c:c;c=n>>8;d[j]=n%256;j++}}while(j--)b.push(d[j]);return new Uint8Array(b)};
|
||||||
|
|
||||||
|
// Calculate the Shannon entropy of a string in bits per symbol.
|
||||||
|
// https://gist.github.com/jabney/5018b4adc9b2bf488696
|
||||||
|
(function(shannon) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Create a dictionary of character frequencies and iterate over it.
|
||||||
|
function process(s, evaluator) {
|
||||||
|
var h = Object.create(null), k;
|
||||||
|
s.split('').forEach(function(c) {
|
||||||
|
h[c] && h[c]++ || (h[c] = 1); });
|
||||||
|
if (evaluator) for (k in h) evaluator(k, h[k]);
|
||||||
|
return h;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Measure the entropy of a string in bits per symbol.
|
||||||
|
shannon.entropy = function(s) {
|
||||||
|
var sum = 0,len = s.length;
|
||||||
|
process(s, function(k, f) {
|
||||||
|
var p = f/len;
|
||||||
|
sum -= p * Math.log(p) / Math.log(2);
|
||||||
|
});
|
||||||
|
return sum;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Measure the entropy of a string in total bits.
|
||||||
|
shannon.bits = function(s) {
|
||||||
|
return shannon.entropy(s) * s.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Log the entropy of a string to the console.
|
||||||
|
shannon.log = function(s) {
|
||||||
|
console.log('Entropy of "' + s + '" in bits per symbol:', shannon.entropy(s));
|
||||||
|
};
|
||||||
|
})(window.shannon = window.shannon || Object.create(null));
|
||||||
|
|
||||||
function hex(byteArray, join) {
|
function hex(byteArray, join) {
|
||||||
if (join === undefined) join = ' ';
|
if (join === undefined) join = ' ';
|
||||||
return Array.from(byteArray, function(byte) {
|
return Array.from(byteArray, function(byte) {
|
||||||
@ -197,6 +232,7 @@ var CMD = {
|
|||||||
pin: 0x12,
|
pin: 0x12,
|
||||||
reset: 0x13,
|
reset: 0x13,
|
||||||
version: 0x14,
|
version: 0x14,
|
||||||
|
rng: 0x15,
|
||||||
};
|
};
|
||||||
|
|
||||||
var PIN = {
|
var PIN = {
|
||||||
@ -233,6 +269,7 @@ function parse_device_response(arr)
|
|||||||
data = null;
|
data = null;
|
||||||
if (arr[5] == 0) {
|
if (arr[5] == 0) {
|
||||||
data = arr.slice(6,arr.length);
|
data = arr.slice(6,arr.length);
|
||||||
|
|
||||||
}
|
}
|
||||||
return {count: count, status: error2string(arr[5]), data: data};
|
return {count: count, status: error2string(arr[5]), data: data};
|
||||||
}
|
}
|
||||||
@ -417,7 +454,11 @@ function signRequestFormat(sigAlg,pinToken,challenge,keyid) {
|
|||||||
var args = [challenge];
|
var args = [challenge];
|
||||||
if (keyid) args.push(keyid)
|
if (keyid) args.push(keyid)
|
||||||
|
|
||||||
var pinAuth = computePinAuth(pinToken,cmd,p1,p2,args);
|
var pinAuth;
|
||||||
|
|
||||||
|
if (pinToken) pinAuth = computePinAuth(pinToken,cmd,p1,p2,args);
|
||||||
|
else pinAuth = new Uint8Array(16);
|
||||||
|
|
||||||
var req = formatRequest(cmd,p1,p2,pinAuth,args);
|
var req = formatRequest(cmd,p1,p2,pinAuth,args);
|
||||||
|
|
||||||
return req;
|
return req;
|
||||||
@ -432,7 +473,11 @@ function registerRequestFormat(wifkey, pinToken) {
|
|||||||
var keyarr = from_b58(wifkey);
|
var keyarr = from_b58(wifkey);
|
||||||
var args = [keyarr];
|
var args = [keyarr];
|
||||||
|
|
||||||
var pinAuth = computePinAuth(pinToken,cmd,p1,p2,args);
|
var pinAuth;
|
||||||
|
|
||||||
|
if (pinToken) pinAuth = computePinAuth(pinToken,cmd,p1,p2,args);
|
||||||
|
else pinAuth = new Uint8Array(16);
|
||||||
|
|
||||||
var req = formatRequest(cmd,p1,p2,pinAuth,args);
|
var req = formatRequest(cmd,p1,p2,pinAuth,args);
|
||||||
|
|
||||||
return req;
|
return req;
|
||||||
@ -466,6 +511,7 @@ var get_shared_secret_ = function(func) {
|
|||||||
var self = this;
|
var self = this;
|
||||||
send_msg(req, function(resp){
|
send_msg(req, function(resp){
|
||||||
|
|
||||||
|
if (resp.status == 'CTAP1_SUCCESS') {
|
||||||
var i;
|
var i;
|
||||||
|
|
||||||
var devicePubkeyHex = '04'+hex(resp.data,'');
|
var devicePubkeyHex = '04'+hex(resp.data,'');
|
||||||
@ -482,7 +528,9 @@ var get_shared_secret_ = function(func) {
|
|||||||
hash.update(shared);
|
hash.update(shared);
|
||||||
shared = hash.array();
|
shared = hash.array();
|
||||||
|
|
||||||
if (func) func(shared);
|
resp.data = shared;
|
||||||
|
}
|
||||||
|
if (func) func(resp);
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -514,9 +562,16 @@ var authenticate_ = function(pin, func){
|
|||||||
send_msg(req, function(resp){
|
send_msg(req, function(resp){
|
||||||
var aesCbc = new aesjs.ModeOfOperation.cbc(self.shared_secret, iv);
|
var aesCbc = new aesjs.ModeOfOperation.cbc(self.shared_secret, iv);
|
||||||
var pinTokenEnc = resp.data;
|
var pinTokenEnc = resp.data;
|
||||||
|
if (resp.status == 'CTAP1_SUCCESS') {
|
||||||
var pinToken = aesCbc.decrypt(pinTokenEnc);
|
var pinToken = aesCbc.decrypt(pinTokenEnc);
|
||||||
self.pinToken = pinToken;
|
self.pinToken = pinToken;
|
||||||
if (func) func({pinToken: pinToken, status: resp.status});
|
if (func) func({pinToken: pinToken, status: resp.status});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
self.init(function(){
|
||||||
|
if (func) func(resp);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -551,7 +606,7 @@ function pin2bytes(pin){
|
|||||||
return pinBytesPadded;
|
return pinBytesPadded;
|
||||||
}
|
}
|
||||||
|
|
||||||
var set_pin_ = function(pin, func, failAuth){
|
var set_pin_ = function(pin, failAuth, func){
|
||||||
var subcmd = PIN.setPin;
|
var subcmd = PIN.setPin;
|
||||||
|
|
||||||
var pinBytesPadded = pin2bytes(pin);
|
var pinBytesPadded = pin2bytes(pin);
|
||||||
@ -565,6 +620,11 @@ var set_pin_ = function(pin, func, failAuth){
|
|||||||
|
|
||||||
var pinAuth = computePinAuthRaw(this.shared_secret, pinEnc);
|
var pinAuth = computePinAuthRaw(this.shared_secret, pinEnc);
|
||||||
|
|
||||||
|
if (func == undefined && typeof failAuth == 'function'){
|
||||||
|
func = failAuth;
|
||||||
|
failAuth = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (failAuth){
|
if (failAuth){
|
||||||
pinAuth.fill(0xAA);
|
pinAuth.fill(0xAA);
|
||||||
pinEnc.fill(0xAA);
|
pinEnc.fill(0xAA);
|
||||||
@ -582,17 +642,18 @@ var set_pin_ = function(pin, func, failAuth){
|
|||||||
|
|
||||||
var is_pin_set_ = function(func)
|
var is_pin_set_ = function(func)
|
||||||
{
|
{
|
||||||
this.set_pin('12345', function(resp){
|
this.set_pin('12345', true, function(resp){
|
||||||
if (resp.status == "CTAP2_ERR_NOT_ALLOWED") {
|
if (resp.status == "CTAP2_ERR_NOT_ALLOWED") {
|
||||||
func(true);
|
func({data:true, status: 'CTAP1_SUCCESS'});
|
||||||
}
|
}
|
||||||
else if (resp.status == "CTAP2_ERR_PIN_AUTH_INVALID"){
|
else if (resp.status == "CTAP2_ERR_PIN_AUTH_INVALID"){
|
||||||
func(false);
|
func({data:false, status: 'CTAP1_SUCCESS'});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new Error("Device returned expected status: " + stat);
|
func({data: undefined, status: resp.status});
|
||||||
|
//throw new Error("Device returned expected status: " + stat);
|
||||||
}
|
}
|
||||||
}, true);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var change_pin_ = function(curpin, newpin, func, failAuth){
|
var change_pin_ = function(curpin, newpin, func, failAuth){
|
||||||
@ -629,7 +690,7 @@ var change_pin_ = function(curpin, newpin, func, failAuth){
|
|||||||
var req = pinRequestFormat(subcmd, pinAuth, ourPubkeyBytes, newPinEnc, curPinHashEnc);
|
var req = pinRequestFormat(subcmd, pinAuth, ourPubkeyBytes, newPinEnc, curPinHashEnc);
|
||||||
|
|
||||||
send_msg(req, function(resp){
|
send_msg(req, function(resp){
|
||||||
if (func) func(resp.status);
|
if (func) func(resp);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -639,7 +700,8 @@ var get_retries_ = function(func){
|
|||||||
var req = pinRequestFormat(subcmd);
|
var req = pinRequestFormat(subcmd);
|
||||||
|
|
||||||
send_msg(req, function(resp){
|
send_msg(req, function(resp){
|
||||||
if (func) func(resp.data[0]);
|
resp.data = resp.data[0];
|
||||||
|
if (func) func(resp);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,9 +712,21 @@ var sign_ = function(obj, func){
|
|||||||
|
|
||||||
var alg = obj.alg || 3;
|
var alg = obj.alg || 3;
|
||||||
|
|
||||||
var req = signRequestFormat(alg,this.pinToken,obj.challenge,obj.keyid);
|
var pinToken = this.pinToken || undefined;
|
||||||
|
|
||||||
|
var req = signRequestFormat(alg,pinToken,obj.challenge,obj.keyid);
|
||||||
|
|
||||||
send_msg(req, function(resp){
|
send_msg(req, function(resp){
|
||||||
|
if (resp.status == 'CTAP1_SUCCESS') {
|
||||||
|
var r = resp.data.slice(0,32);
|
||||||
|
var s = resp.data.slice(32,64);
|
||||||
|
r = array2hex(r);
|
||||||
|
s = array2hex(s);
|
||||||
|
resp.sig = {};
|
||||||
|
resp.sig.r = r;
|
||||||
|
resp.sig.s = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (func) func(resp);
|
if (func) func(resp);
|
||||||
});
|
});
|
||||||
@ -697,6 +771,25 @@ var reset_ = function(func){
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Read 72 random bytes from hardware RNG on device
|
||||||
|
var get_rng_ = function(func){
|
||||||
|
|
||||||
|
var pinAuth = undefined;
|
||||||
|
|
||||||
|
if (this.pinToken) {
|
||||||
|
pinAuth = computePinAuth(this.pinToken, CMD.rng, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
var req = formatRequest(CMD.rng,0,0, pinAuth);
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
send_msg(req, function(resp){
|
||||||
|
if (func)func(resp);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function wrap_promise(func)
|
function wrap_promise(func)
|
||||||
{
|
{
|
||||||
var self = this;
|
var self = this;
|
||||||
@ -704,20 +797,26 @@ function wrap_promise(func)
|
|||||||
var args = arguments;
|
var args = arguments;
|
||||||
return new Promise(function(resolve,reject){
|
return new Promise(function(resolve,reject){
|
||||||
var i;
|
var i;
|
||||||
|
var oldfunc = null;
|
||||||
for (i = 0; i < args.length; i++)
|
for (i = 0; i < args.length; i++)
|
||||||
{
|
{
|
||||||
if (typeof args[i] == 'function')
|
if (typeof args[i] == 'function')
|
||||||
{
|
{
|
||||||
var oldfunc = args[i];
|
oldfunc = args[i];
|
||||||
args[i] = function(){
|
args[i] = function(){
|
||||||
oldfunc.apply(self,arguments);
|
oldfunc.apply(self,arguments);
|
||||||
resolve.apply(self,arguments);
|
resolve.apply(self,arguments);
|
||||||
//oldfunc.call(arguments);
|
|
||||||
//resolve.call(arguments);
|
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oldfunc === null)
|
||||||
|
{
|
||||||
|
args = Array.prototype.slice.call(args);
|
||||||
|
args.push(function(){
|
||||||
|
resolve.apply(self,arguments);
|
||||||
|
});
|
||||||
|
}
|
||||||
func.apply(self,args);
|
func.apply(self,args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -733,9 +832,11 @@ function WalletDevice() {
|
|||||||
this.init = function(func){
|
this.init = function(func){
|
||||||
self.get_version(function(ver){
|
self.get_version(function(ver){
|
||||||
self.version = ver;
|
self.version = ver;
|
||||||
self.get_shared_secret(function(shared){
|
self.get_shared_secret(function(resp){
|
||||||
self.shared_secret = shared;
|
if (resp.status == "CTAP1_SUCCESS") self.shared_secret = resp.data;
|
||||||
if (func) func();
|
else {
|
||||||
|
}
|
||||||
|
if (func) func(resp);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -768,18 +869,31 @@ function WalletDevice() {
|
|||||||
|
|
||||||
this.reset = reset_;
|
this.reset = reset_;
|
||||||
|
|
||||||
//this.init = wrap_promise(this.init);
|
this.get_rng = get_rng_;
|
||||||
//this.get_version = wrap_promise(this.get_version);
|
|
||||||
//this.get_shared_secret = wrap_promise(this.get_shared_secret );
|
|
||||||
//this.authenticate = wrap_promise(this.authenticate );
|
|
||||||
//this.sign = wrap_promise(this.sign );
|
|
||||||
//this.set_pin = wrap_promise(this.set_pin );
|
|
||||||
//this.is_pin_set = wrap_promise(this.is_pin_set );
|
|
||||||
//this.change_pin = wrap_promise(this.change_pin );
|
|
||||||
//this.get_retries = wrap_promise(this.get_retries );
|
|
||||||
//this.register = wrap_promise(this.register );
|
|
||||||
//this.reset = wrap_promise(this.reset );
|
|
||||||
|
|
||||||
|
this.init = wrap_promise.call(this, this.init);
|
||||||
|
this.get_version = wrap_promise.call(this, this.get_version);
|
||||||
|
this.get_shared_secret = wrap_promise.call(this, this.get_shared_secret );
|
||||||
|
this.authenticate = wrap_promise.call(this,this.authenticate );
|
||||||
|
this.sign = wrap_promise.call(this, this.sign );
|
||||||
|
this.set_pin = wrap_promise.call(this,this.set_pin );
|
||||||
|
this.is_pin_set = wrap_promise.call(this, this.is_pin_set );
|
||||||
|
this.change_pin = wrap_promise.call(this, this.change_pin );
|
||||||
|
this.get_retries = wrap_promise.call(this, this.get_retries );
|
||||||
|
this.register = wrap_promise.call(this, this.register );
|
||||||
|
this.reset = wrap_promise.call(this,this.reset );
|
||||||
|
this.get_rng = wrap_promise.call(this,this.get_rng);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function TEST(bool, test){
|
||||||
|
if (bool) {
|
||||||
|
if (test ) console.log("PASS: " + test);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("FAIL: " + test);
|
||||||
|
throw new Error("FAIL: " + test);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run_tests() {
|
async function run_tests() {
|
||||||
@ -788,92 +902,232 @@ async function run_tests() {
|
|||||||
var pin = "Conor's pin 👽 ";
|
var pin = "Conor's pin 👽 ";
|
||||||
var pin2 = "sogyhdxoh3qwli😀";
|
var pin2 = "sogyhdxoh3qwli😀";
|
||||||
|
|
||||||
|
function string2challenge(chal) {
|
||||||
function device_start_over(next)
|
|
||||||
{
|
|
||||||
dev.init(function(resp){
|
|
||||||
|
|
||||||
console.log('connected. version: ', dev.version);
|
|
||||||
|
|
||||||
dev.is_pin_set(function(bool){
|
|
||||||
if (bool) {
|
|
||||||
dev.authenticate(pin, function(resp){
|
|
||||||
//reset_device(next);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//reset_device(next);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset_device(func)
|
|
||||||
{
|
|
||||||
dev.reset(function(resp){
|
|
||||||
console.log("reset: ",resp);
|
|
||||||
if (func) func();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_pin(next)
|
|
||||||
{
|
|
||||||
dev.set_pin(pin, function(resp){
|
|
||||||
if (resp.status == "CTAP1_SUCCESS"){
|
|
||||||
console.log('Set pin to ' + pin);
|
|
||||||
dev.set_pin(pin, function(resp){
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log("Fail set_pin");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
device_start_over();
|
|
||||||
|
|
||||||
function t2 ()
|
|
||||||
{
|
|
||||||
var ec = new EC('p256');
|
|
||||||
var key = ec.genKeyPair();
|
|
||||||
|
|
||||||
var priv = key.getPrivate('hex');
|
|
||||||
|
|
||||||
// convert to wif
|
|
||||||
priv = key2wif(priv);
|
|
||||||
|
|
||||||
var chal = 'ogfhriodghdro;igh';
|
|
||||||
|
|
||||||
var hash = sha256.create();
|
var hash = sha256.create();
|
||||||
hash.update(chal);
|
hash.update(chal);
|
||||||
chal = hash.array();
|
chal = hash.array();
|
||||||
|
return chal;
|
||||||
|
|
||||||
dev.register(priv, function(resp){
|
|
||||||
console.log('register response', resp);
|
|
||||||
dev.sign({challenge: chal}, function(resp){
|
|
||||||
|
|
||||||
var r = resp.data.slice(0,32);
|
|
||||||
var s = resp.data.slice(32,64);
|
|
||||||
|
|
||||||
r = array2hex(r);
|
|
||||||
s = array2hex(s);
|
|
||||||
|
|
||||||
var sig = {r: r, s: s};
|
|
||||||
|
|
||||||
console.log('sign response', resp);
|
|
||||||
|
|
||||||
var ver = key.verify(chal, sig);
|
|
||||||
|
|
||||||
console.log("verify: ",ver);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function device_start_over()
|
||||||
|
{
|
||||||
|
|
||||||
|
var p = await dev.init();
|
||||||
|
if (p.status == 'CTAP2_ERR_NOT_ALLOWED') { // its already locked
|
||||||
|
p = await dev.reset();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS", 'Device reset');
|
||||||
|
p = await dev.init();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS", 'Device initialize');
|
||||||
|
} else {
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS", 'Device initialize');
|
||||||
|
|
||||||
|
//console.log(dev);
|
||||||
|
|
||||||
|
TEST(dev.version == "WALLET_V1.0", 'Device reports right version');
|
||||||
|
|
||||||
|
p = await dev.is_pin_set();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS", 'Check if pin is set');
|
||||||
|
|
||||||
|
if (!p.data) {
|
||||||
|
} else {
|
||||||
|
|
||||||
|
p = await dev.authenticate(pin);
|
||||||
|
|
||||||
|
if (p.status == "CTAP2_ERR_PIN_INVALID" ) {
|
||||||
|
p = await dev.authenticate(pin2); // try second pin
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS", 'Authenticated');
|
||||||
|
}
|
||||||
|
|
||||||
|
p = await dev.reset();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS", 'Device reset');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function test_pin()
|
||||||
|
{
|
||||||
|
var p = await dev.is_pin_set();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS" && !p.data, 'Pin is not set');
|
||||||
|
|
||||||
|
p = await dev.set_pin(pin);
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS", 'A pin was set');
|
||||||
|
|
||||||
|
p = await dev.is_pin_set();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS" && p.data, 'Pin set is detected');
|
||||||
|
|
||||||
|
p = await dev.set_pin(pin);
|
||||||
|
TEST(p.status == "CTAP2_ERR_NOT_ALLOWED", 'Trying to set a pin again will fail');
|
||||||
|
|
||||||
|
p = await dev.change_pin(pin, pin2);
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS", 'Going through change pin process is successful');
|
||||||
|
|
||||||
|
p = await dev.authenticate(pin);
|
||||||
|
TEST(p.status == "CTAP2_ERR_PIN_INVALID", 'Authenticating to previous/wrong pin is denied');
|
||||||
|
|
||||||
|
p = await dev.get_retries();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS" && p.data > 2, 'Have at least 2 tries left ('+p.data+')');
|
||||||
|
var tries = p.data;
|
||||||
|
|
||||||
|
p = await dev.authenticate(pin);
|
||||||
|
TEST(p.status == "CTAP2_ERR_PIN_INVALID");
|
||||||
|
|
||||||
|
p = await dev.get_retries();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS" && (p.data > 1) && (p.data < tries),
|
||||||
|
'Have less attempts left after another failed attempt (' + p.data+')');
|
||||||
|
|
||||||
|
p = await dev.authenticate(pin2);
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS", 'Authenticating with correct pin success');
|
||||||
|
|
||||||
|
p = await dev.get_retries();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS" && p.data > tries, 'Retries reset ('+ p.data+')');
|
||||||
|
|
||||||
|
p = await dev.change_pin(pin2, pin);
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS", 'Change pin back');
|
||||||
|
|
||||||
|
// Reset device for next set of tests
|
||||||
|
p = await dev.authenticate(pin);
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS");
|
||||||
|
|
||||||
|
p = await dev.reset();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function test_crypto(){
|
||||||
|
var ec = new EC('secp256k1');
|
||||||
|
var key = ec.genKeyPair();
|
||||||
|
var priv = key.getPrivate('hex');
|
||||||
|
|
||||||
|
var wif = key2wif(priv); // convert to wif
|
||||||
|
|
||||||
|
// Corrupt 1 byte
|
||||||
|
var b = (wif[32] == 'A') ? 'B' : 'A';
|
||||||
|
var badwif = wif.substring(0, 32) + b + wif.substring(32+1);
|
||||||
|
|
||||||
|
|
||||||
|
var p = await dev.set_pin(pin);
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS");
|
||||||
|
|
||||||
|
var chal = string2challenge('abc');
|
||||||
|
p = await dev.sign({challenge: chal});
|
||||||
|
TEST(p.status == 'CTAP2_ERR_PIN_AUTH_INVALID', 'No signature without authenticating first');
|
||||||
|
|
||||||
|
p = await dev.register(wif);
|
||||||
|
TEST(p.status == 'CTAP2_ERR_PIN_AUTH_INVALID', 'No key register without authenticating first');
|
||||||
|
|
||||||
|
p = await dev.get_rng();
|
||||||
|
TEST(p.status == "CTAP2_ERR_PIN_AUTH_INVALID", 'No rng without authenticating first');
|
||||||
|
|
||||||
|
p = await dev.authenticate(pin);
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS");
|
||||||
|
|
||||||
|
p = await dev.sign({challenge: chal});
|
||||||
|
TEST(p.status == 'CTAP2_ERR_NO_CREDENTIALS', 'No signature without key');
|
||||||
|
|
||||||
|
p = await dev.register(badwif);
|
||||||
|
TEST(p.status == 'CTAP2_ERR_CREDENTIAL_NOT_VALID', 'Wallet does not accept corrupted key');
|
||||||
|
|
||||||
|
p = await dev.register(wif);
|
||||||
|
TEST(p.status == 'CTAP1_SUCCESS', 'Wallet accepts good WIF key');
|
||||||
|
|
||||||
|
p = await dev.register(wif);
|
||||||
|
TEST(p.status == 'CTAP2_ERR_KEY_STORE_FULL', 'Wallet does not accept another key');
|
||||||
|
|
||||||
|
p = await dev.sign({challenge: chal});
|
||||||
|
TEST(p.status == 'CTAP1_SUCCESS', 'Wallet returns signature');
|
||||||
|
|
||||||
|
var ver = key.verify(chal, p.sig);
|
||||||
|
TEST(ver, 'Signature is valid');
|
||||||
|
|
||||||
|
var count = p.count;
|
||||||
|
p = await dev.sign({challenge: chal});
|
||||||
|
ver = key.verify(chal, p.sig);
|
||||||
|
TEST(p.status == 'CTAP1_SUCCESS' && p.count > count && ver, 'Count increments for each signature');
|
||||||
|
|
||||||
|
// Test lockout
|
||||||
|
console.log("Exceeding all pin attempts...");
|
||||||
|
p = await dev.get_retries();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS");
|
||||||
|
var tries = p.data;
|
||||||
|
|
||||||
|
while (tries > 0) {
|
||||||
|
p = await dev.authenticate('1234'); // wrong pin
|
||||||
|
TEST(p.status == "CTAP2_ERR_PIN_INVALID");
|
||||||
|
|
||||||
|
p = await dev.get_retries();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS");
|
||||||
|
tries = p.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = await dev.get_retries();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS");
|
||||||
|
tries = p.data;
|
||||||
|
TEST(tries == 0, 'Device has 0 tries left (lockout)');
|
||||||
|
|
||||||
|
p = await dev.register(wif);
|
||||||
|
TEST(p.status == 'CTAP2_ERR_PIN_AUTH_INVALID', 'Register is denied');
|
||||||
|
|
||||||
|
p = await dev.sign({challenge: chal});
|
||||||
|
TEST(p.status == 'CTAP2_ERR_PIN_AUTH_INVALID', 'Sign is denied');
|
||||||
|
|
||||||
|
p = await dev.set_pin(pin);
|
||||||
|
TEST(p.status == "CTAP2_ERR_NOT_ALLOWED", 'set_pin is locked out');
|
||||||
|
|
||||||
|
p = await dev.set_pin(pin,pin2);
|
||||||
|
TEST(p.status == "CTAP2_ERR_NOT_ALLOWED", 'change_pin is locked out');
|
||||||
|
|
||||||
|
p = await dev.get_rng();
|
||||||
|
TEST(p.status == "CTAP2_ERR_NOT_ALLOWED", 'get_rng is locked out');
|
||||||
|
|
||||||
|
p = await dev.init();
|
||||||
|
TEST(p.status == "CTAP2_ERR_NOT_ALLOWED", 'init (getKeyAgreement) is locked out');
|
||||||
|
|
||||||
|
p = await dev.reset();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS");
|
||||||
|
|
||||||
|
p = await dev.get_retries();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS");
|
||||||
|
tries = p.data;
|
||||||
|
|
||||||
|
p = await dev.is_pin_set();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS");
|
||||||
|
var is_pin_set = p.data;
|
||||||
|
|
||||||
|
p = await dev.sign({challenge: chal});
|
||||||
|
TEST(p.status == 'CTAP2_ERR_NO_CREDENTIALS');
|
||||||
|
|
||||||
|
TEST(tries > 2 && is_pin_set == false, 'Device is no longer locked after reset and pin and key are gone');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function test_rng(){
|
||||||
|
|
||||||
|
var pool = '';
|
||||||
|
|
||||||
|
var p = await dev.get_rng();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS", 'Rng responds');
|
||||||
|
|
||||||
|
pool += array2hex(p.data);
|
||||||
|
|
||||||
|
console.log("Gathering many RNG bytes..");
|
||||||
|
|
||||||
|
while (pool.length < 1024 * 10) {
|
||||||
|
var p = await dev.get_rng();
|
||||||
|
TEST(p.status == "CTAP1_SUCCESS");
|
||||||
|
pool += array2hex(p.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
var entropy = shannon.entropy(pool) * 2;
|
||||||
|
TEST(entropy > 7.99, 'Rng has good entropy: ' + entropy);
|
||||||
|
}
|
||||||
|
|
||||||
|
await device_start_over();
|
||||||
|
await test_pin();
|
||||||
|
await test_crypto();
|
||||||
|
await test_rng();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user