diff --git a/.gitignore b/.gitignore index 016ed62..5578a4c 100644 --- a/.gitignore +++ b/.gitignore @@ -65,4 +65,8 @@ _build/ GNU\ * Keil\ * docs/ -efm32/hw \ No newline at end of file +efm32/hw + +mbedtls/ +sl_crypto/ +tools/python-fido2/* diff --git a/fido2/ctap.c b/fido2/ctap.c index 978580d..f48d06d 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -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; diff --git a/fido2/ctap.h b/fido2/ctap.h index d1a6d46..138002d 100644 --- a/fido2/ctap.h +++ b/fido2/ctap.h @@ -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 diff --git a/fido2/wallet.c b/fido2/wallet.c index 177958f..19678ca 100644 --- a/fido2/wallet.c +++ b/fido2/wallet.c @@ -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); diff --git a/web/js/wallet.js b/web/js/wallet.js index 5cccc46..e1d160e 100644 --- a/web/js/wallet.js +++ b/web/js/wallet.js @@ -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); + }); + } }); }); + }