complete pin support

pull/11/merge
Conor Patrick 2018-07-10 19:16:41 -04:00
rodzic 50ee579d03
commit 156dc3163a
5 zmienionych plików z 316 dodań i 72 usunięć

6
.gitignore vendored
Wyświetl plik

@ -65,4 +65,8 @@ _build/
GNU\ *
Keil\ *
docs/
efm32/hw
efm32/hw
mbedtls/
sl_crypto/
tools/python-fido2/*

Wyświetl plik

@ -21,7 +21,7 @@ uint8_t PIN_TOKEN[PIN_TOKEN_SIZE];
uint8_t KEY_AGREEMENT_PUB[64];
static uint8_t KEY_AGREEMENT_PRIV[32];
static uint8_t PIN_CODE_SET = 0;
static uint8_t PIN_CODE[NEW_PIN_ENC_MAX_SIZE];
uint8_t PIN_CODE[NEW_PIN_ENC_MAX_SIZE];
static uint8_t PIN_CODE_HASH[32];
static uint8_t DEVICE_LOCKOUT = 0;

Wyświetl plik

@ -249,6 +249,7 @@ void ctap_init();
void ctap_reset_state();
uint8_t ctap_add_pin_if_verified(uint8_t * pinTokenEnc, uint8_t * platform_pubkey, uint8_t * pinHashEnc);
uint8_t ctap_update_pin_if_verified(uint8_t * pinEnc, int len, uint8_t * platform_pubkey, uint8_t * pinAuth, uint8_t * pinHashEnc);
void ctap_update_pin(uint8_t * pin, int len);
uint8_t ctap_decrement_pin_attempts();
@ -262,6 +263,7 @@ int8_t ctap_device_locked();
#define PIN_TOKEN_SIZE 16
extern uint8_t PIN_TOKEN[PIN_TOKEN_SIZE];
extern uint8_t KEY_AGREEMENT_PUB[64];
extern uint8_t PIN_CODE[NEW_PIN_ENC_MAX_SIZE];
#endif

Wyświetl plik

@ -44,11 +44,11 @@ void wallet_init()
printf1(TAG_WALLET,"Wallet is ready\n");
ctap_update_pin("1234", 4);
/*ctap_update_pin("1234", 4);*/
}
int8_t wallet_pin(uint8_t subcmd, uint8_t * pinAuth, uint8_t * arg1, uint8_t * arg2)
int8_t wallet_pin(uint8_t subcmd, uint8_t * pinAuth, uint8_t * arg1, uint8_t * arg2, uint8_t * arg3, int len)
{
uint8_t pinTokenEnc[PIN_TOKEN_SIZE];
int ret;
@ -64,15 +64,38 @@ int8_t wallet_pin(uint8_t subcmd, uint8_t * pinAuth, uint8_t * arg1, uint8_t * a
break;
case CP_cmdGetRetries:
printf1(TAG_WALLET,"cmdGetRetries\n");
return CTAP2_ERR_UNSUPPORTED_OPTION;
pinTokenEnc[0] = ctap_leftover_pin_attempts();
u2f_response_writeback(pinTokenEnc,1);
break;
case CP_cmdSetPin:
printf1(TAG_WALLET,"cmdSetPin\n");
return CTAP2_ERR_UNSUPPORTED_OPTION;
if (ctap_is_pin_set())
{
return CTAP2_ERR_NOT_ALLOWED;
}
//pinEnc // plat_pubkey
ret = ctap_update_pin_if_verified( arg2, len, arg1, pinAuth, NULL);
if (ret != 0)
return ret;
printf1(TAG_WALLET,"Success. Pin = %s\n",PIN_CODE);
break;
case CP_cmdChangePin:
printf1(TAG_WALLET,"cmdChangePin\n");
return CTAP2_ERR_UNSUPPORTED_OPTION;
if (! ctap_is_pin_set())
{
return CTAP2_ERR_PIN_NOT_SET;
}
//pinEnc // plat_pubkey // pinHashEnc
ret = ctap_update_pin_if_verified( arg2, len, arg1, pinAuth, arg3);
if (ret != 0)
return ret;
break;
case CP_cmdGetPinToken:
printf1(TAG_WALLET,"cmdGetPinToken\n");
@ -162,17 +185,28 @@ int16_t bridge_u2f_to_wallet(uint8_t * _chal, uint8_t * _appid, uint8_t klen, ui
}
printf1(TAG_WALLET,"challenge:"); dump_hex1(TAG_WALLET, args[0], lens[0]);
if (args[1] != NULL) printf1(TAG_WALLET,"keyid:"); dump_hex1(TAG_WALLET, args[1], lens[1]);
if (check_pinhash(req->pinAuth, msg_buf, reqlen))
if (args[1] != NULL && req->numArgs > 1)
{
printf1(TAG_WALLET,"pinAuth is valid\n");
printf1(TAG_WALLET,"keyid is specified\n");
printf1(TAG_WALLET,"keyid:"); dump_hex1(TAG_WALLET, args[1], lens[1]);
}
if (ctap_is_pin_set())
{
if (check_pinhash(req->pinAuth, msg_buf, reqlen))
{
printf1(TAG_WALLET,"pinAuth is valid\n");
}
else
{
printf1(TAG_WALLET,"pinAuth is NOT valid\n");
ret = CTAP2_ERR_PIN_AUTH_INVALID;
goto cleanup;
}
}
else
{
printf1(TAG_WALLET,"pinAuth is NOT valid\n");
ret = CTAP2_ERR_PIN_AUTH_INVALID;
goto cleanup;
printf1(TAG_WALLET,"Warning: no pin is set. Ignoring pinAuth\n");
}
break;
case WalletRegister:
@ -180,7 +214,7 @@ int16_t bridge_u2f_to_wallet(uint8_t * _chal, uint8_t * _appid, uint8_t klen, ui
break;
case WalletPin:
printf1(TAG_WALLET,"WalletPin\n");
ret = wallet_pin(req->p1, req->pinAuth, args[0], args[1]);
ret = wallet_pin(req->p1, req->pinAuth, args[0], args[1], args[2], lens[0]);
break;
default:
printf2(TAG_ERR,"Invalid wallet command: %x\n",req->operation);

Wyświetl plik

@ -1,3 +1,5 @@
DEVELOPMENT = 1;
function hex(byteArray, join) {
if (join === undefined) join = ' ';
return Array.from(byteArray, function(byte) {
@ -162,7 +164,7 @@ var PIN = {
getPinToken: 0x05,
};
// Create the XHR object.
// Create XHR object.
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
@ -222,7 +224,8 @@ function send_msg_http(data, func, timeout) {
xhr.send(req);
}
//run_tests();
// For real
function send_msg_u2f(data, func, timeout) {
// Use key handle and signature response as comm channel
var d = new Date();
@ -253,7 +256,6 @@ function send_msg_u2f(data, func, timeout) {
},timeout);
}
DEVELOPMENT = 1
var send_msg;
if (DEVELOPMENT) {
send_msg = send_msg_http;
@ -304,6 +306,11 @@ function formatRequest(cmd, p1, p2, pinAuth, args) {
return array;
}
// Computes sha256 HMAC
// @pinToken is key for HMAC
// @cmd,p1,p2 each are bytes input to HMAC
// @args array of Uint8Arrays input to HMAC
// @return first 16 bytes of HMAC
function computePinAuth(pinToken, cmd,p1,p2,args)
{
var hmac = sha256.hmac.create(pinToken);
@ -322,6 +329,12 @@ function computePinAuth(pinToken, cmd,p1,p2,args)
return hmac.array().slice(0,16)
}
function computePinAuthRaw(pinToken, data)
{
var hmac = sha256.hmac.create(pinToken);
hmac.update(data);
return hmac.array().slice(0,16)
}
// @sigAlg is a number 0-255
// @pinAuth token, see pinToken information. Uint8Array
@ -340,7 +353,7 @@ function signRequestFormat(sigAlg,pinToken,challenge,keyid) {
// keyID: keyid 0-233
// Note: total size must not exceed 255 bytes
var cmd = 0x10;
var cmd = CMD.sign;
var p1 = sigAlg;
var p2 = 0;
var args = [challenge];
@ -355,17 +368,19 @@ function signRequestFormat(sigAlg,pinToken,challenge,keyid) {
}
// @subCmd is one of the following in PIN {}
function pinRequestFormat(subcmd,pubkey,pinHashEnc) {
function pinRequestFormat(subcmd, pinAuth, pubkey, pinEnc, pinHashEnc) {
var cmd = 0x12;
var cmd = CMD.pin;
var p1 = subcmd;
var p2 = 0;
//var args = [challenge];
//if (keyid) args.push(keyid)
var pinAuth = pinHashEnc || new Uint8Array(16);
pinAuth = pinAuth || new Uint8Array(16);
var args = [];
if (pubkey) args.push(pubkey)
if (pubkey) args.push(pubkey);
if (pinEnc) args.push(pinEnc);
if (pinHashEnc) args.push(pinHashEnc);
//var pinAuth = computePinAuth(pinToken,cmd,p1,p2,args);
//console.log(hex(pinAuth));
@ -374,85 +389,274 @@ function pinRequestFormat(subcmd,pubkey,pinHashEnc) {
return req;
}
function run_tests()
{
var pin = '1234';
var ec256k1 = new EC('secp256k1');
var ecp256 = new EC('p256');
var sharedSecret = new Uint8Array([1,2,3,4])
var pinToken = new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16])
var get_shared_secret_ = function(func) {
// Get temporary pubkey from device to compute shared secret
var req = pinRequestFormat(PIN.getKeyAgreement);
var self = this;
send_msg(req, function(resp){
var i;
console.log('getKeyAgreement response:', resp);
var pubkey_hex = '04'+hex(resp.data,'');
console.log('pubkey:', pubkey_hex);
var devicePubkeyHex = '04'+hex(resp.data,'');
var devicePubkey = self.ecp256.keyFromPublic(devicePubkeyHex,'hex');
// Generate a new key pair for shared secret
var platform_keypair = self.ecp256.genKeyPair();
self.platform_keypair = platform_keypair;
var pubkey = ecp256.keyFromPublic(pubkey_hex,'hex');
var keypair = ecp256.genKeyPair();
var shared = keypair.derive(pubkey.getPublic()).toArray();
hash = sha256.create();
// shared secret
var shared = platform_keypair.derive(devicePubkey.getPublic()).toArray();
var hash = sha256.create();
hash.update(shared);
shared = hash.array();
var ourPubkey = keypair.getPublic(undefined, 'hex');
if (func) func(shared);
var ourPubkeyBytes = hex2array(ourPubkey.slice(2,ourPubkey.length));
});
};
var authenticate_ = function(pin, func){
if (! this.shared_secret){
throw new Error('Device is not connected.');
}
hash = sha256.create();
hash.update(toUTF8Array(pin));
pinHash = hash.array().slice(0,16);
console.log('pinHash:', hex(pinHash));
var iv = new Uint8Array(16);
iv.fill(0);
var aesCbc = new aesjs.ModeOfOperation.cbc(this.shared_secret, iv);
pinHashEnc = aesCbc.encrypt(pinHash);
console.log('pinenc:', hex(pinHashEnc));
var ourPubkey = this.platform_keypair.getPublic(undefined, 'hex');
var ourPubkeyBytes = hex2array(ourPubkey.slice(2,ourPubkey.length));
console.log('shared-secret:', hex(shared));
console.log('our pubkey:', keypair.getPublic(undefined, 'hex'));
var req = pinRequestFormat(PIN.getPinToken, pinHashEnc, ourPubkeyBytes);
hash = sha256.create();
hash.update(toUTF8Array(pin));
pinHash = hash.array().slice(0,16);
console.log('pinHash:', hex(pinHash));
console.log('pinTokenReq',req);
var iv = new Uint8Array(16);
iv.fill(0);
var self = this;
var aesCbc = new aesjs.ModeOfOperation.cbc(shared, iv);
pinHashEnc = aesCbc.encrypt(pinHash);
send_msg(req, function(resp){
console.log('getPinToken:', resp);
var aesCbc = new aesjs.ModeOfOperation.cbc(self.shared_secret, iv);
var pinTokenEnc = resp.data;
var pinToken = aesCbc.decrypt(pinTokenEnc);
self.pinToken = pinToken;
if (func) func(pinToken);
});
};
console.log('pinenc:', hex(pinHashEnc));
function pin2bytes(pin){
var pinBytes = toUTF8Array(pin);
var req = pinRequestFormat(PIN.getPinToken, ourPubkeyBytes, pinHashEnc);
var encLen = pinBytes.length + (16-(pinBytes.length % 16));
console.log('pinTokenReq',req);
if (encLen < 64){
encLen = 64;
}
send_msg(req, function(resp){
console.log('getPinToken:', resp);
var aesCbc = new aesjs.ModeOfOperation.cbc(shared, iv);
var pinTokenEnc = resp.data;
var pinToken = aesCbc.decrypt(pinTokenEnc);
//var pinToken = string2array('123456789abcdfe0');
if (pin.length < 4){
throw Error('FIDO2 pin must be at least 4 unicode characters.');
}
if (encLen > 255){
throw Error('FIDO2 pin may not exceed 255 bytes');
}
if (encLen > 80){
throw Error('Recommended to not use pins longer than 80 bytes due to 255 byte max message size.');
}
console.log('pintoken:', hex(pinToken));
var sigAlg = 3;
var challenge = string2array('1234567890 1234567890 1234567890');
var keyid = string2array('');
var pinBytesPadded = new Uint8Array(encLen);
pinBytesPadded.fill(0);
var req = signRequestFormat(sigAlg,pinToken,challenge,keyid);
var i;
for (i = 0; i < pinBytes.length; i++){
pinBytesPadded[i] = pinBytes[i];
}
//console.log('req:',req)
return pinBytesPadded;
}
send_msg(req, function(resp){
console.assert(resp.status == 'CTAP1_SUCCESS');
console.log('Walletsign',resp);
});
var set_pin_ = function(pin, func, failAuth){
var subcmd = PIN.setPin;
var pinBytesPadded = pin2bytes(pin);
var encLen = pinBytesPadded.length;
console.log('encrypted len: ',encLen);
var iv = new Uint8Array(16);
iv.fill(0);
var aesCbc = new aesjs.ModeOfOperation.cbc(this.shared_secret, iv);
pinEnc = aesCbc.encrypt(pinBytesPadded);
var pinAuth = computePinAuthRaw(this.shared_secret, pinEnc);
if (failAuth){
pinAuth.fill(0xAA);
pinEnc.fill(0xAA);
}
var ourPubkey = this.platform_keypair.getPublic(undefined, 'hex');
var ourPubkeyBytes = hex2array(ourPubkey.slice(2,ourPubkey.length));
var req = pinRequestFormat(subcmd, pinAuth, ourPubkeyBytes, pinEnc);
send_msg(req, function(resp){
if (func) func(resp.status);
});
}
var is_pin_set_ = function(func)
{
this.set_pin('12345', function(stat){
if (stat == "CTAP2_ERR_NOT_ALLOWED") {
func(true);
}
else if (stat == "CTAP2_ERR_PIN_AUTH_INVALID"){
func(false);
}
else {
throw new Error("Device returned expected status: " + stat);
}
}, true);
}
var change_pin_ = function(curpin, newpin, func, failAuth){
var subcmd = PIN.changePin;
pin2bytes(curpin); // validation only
var pinBytesPadded = pin2bytes(newpin);
var encLen = pinBytesPadded.length;
console.log('encrypted len: ',encLen);
var iv = new Uint8Array(16);
iv.fill(0);
var aesCbc = new aesjs.ModeOfOperation.cbc(this.shared_secret, iv);
newPinEnc = aesCbc.encrypt(pinBytesPadded);
var hash = sha256.create();
hash.update(toUTF8Array(curpin));
curPinHash = hash.array().slice(0,16);
aesCbc = new aesjs.ModeOfOperation.cbc(this.shared_secret, iv);
curPinHashEnc = aesCbc.encrypt(curPinHash);
var concat = new Uint8Array(newPinEnc.length + curPinHashEnc.length);
concat.set(newPinEnc);
concat.set(curPinHashEnc, newPinEnc.length);
var pinAuth = computePinAuthRaw(this.shared_secret, concat);
var ourPubkey = this.platform_keypair.getPublic(undefined, 'hex');
var ourPubkeyBytes = hex2array(ourPubkey.slice(2,ourPubkey.length));
var req = pinRequestFormat(subcmd, pinAuth, ourPubkeyBytes, newPinEnc, curPinHashEnc);
send_msg(req, function(resp){
if (func) func(resp.status);
});
}
var get_retries_ = function(func){
var subcmd = PIN.getRetries;
var req = pinRequestFormat(subcmd);
send_msg(req, function(resp){
if (func) func(resp.data[0]);
});
}
var sign_ = function(obj, func){
if (!obj.challenge)
throw new Error("Need something to sign");
var alg = obj.alg || 3;
var req = signRequestFormat(alg,this.pinToken,obj.challenge,obj.keyid);
send_msg(req, function(resp){
if (func) func(resp);
});
};
function WalletDevice() {
var self = this;
this.shared_secret = null;
this.ec256k1 = new EC('secp256k1');
this.ecp256 = new EC('p256');
this.init = function(func){
this.get_shared_secret(function(shared){
self.shared_secret = shared;
if (func) func();
});
}
this.get_shared_secret = get_shared_secret_;
// getPinToken using set pin
this.authenticate = authenticate_;
this.sign = sign_;
this.set_pin = set_pin_;
this.is_pin_set = is_pin_set_;
this.change_pin = change_pin_;
this.get_retries = get_retries_;
}
function run_tests() {
var dev = new WalletDevice();
var pin = "Conor's pin 👽 ";;
var pin2 = "Conor's pin2 😀";;
dev.init(function(){
console.log('connected.');
dev.is_pin_set(function(bool){
if (bool) {
console.log('Pin is set. Changing it again..');
dev.change_pin(pin,pin2,function(succ){
console.log('Pin set to ' + pin2,succ);
dev.get_retries(function(num){
console.log("Have "+num+" attempts to get pin right");
});
});
}
else {
console.log('Pin is NOT set. Setting it to "' + pin + '"');
dev.set_pin(pin, function(succ){
console.log(succ);
});
}
});
});
}