add wallet channel
This commit is contained in:
parent
fbbf430152
commit
7df04964cc
File diff suppressed because one or more lines are too long
@ -74,7 +74,7 @@
|
|||||||
<property object="USART1" propertyId="ABPeripheral.included" value="true"/>
|
<property object="USART1" propertyId="ABPeripheral.included" value="true"/>
|
||||||
<property object="USART1" propertyId="usart.mode.usartmode" value="Synchronous Mode (SPI / I2S)"/>
|
<property object="USART1" propertyId="usart.mode.usartmode" value="Synchronous Mode (SPI / I2S)"/>
|
||||||
<property object="USART1" propertyId="usart.outputsettings.clockselect" value="Disabled"/>
|
<property object="USART1" propertyId="usart.outputsettings.clockselect" value="Disabled"/>
|
||||||
<property object="USART1" propertyId="usart.synchronoussettings.baudrate" value="140000"/>
|
<property object="USART1" propertyId="usart.synchronoussettings.baudrate" value="130000"/>
|
||||||
</mode>
|
</mode>
|
||||||
<modeTransition>
|
<modeTransition>
|
||||||
<property object="RESET → DefaultMode" propertyId="modeTransition.source" value="RESET"/>
|
<property object="RESET → DefaultMode" propertyId="modeTransition.source" value="RESET"/>
|
||||||
|
@ -334,7 +334,7 @@ extern void USART1_enter_DefaultMode_from_RESET(void) {
|
|||||||
USART_InitSync_TypeDef initsync = USART_INITSYNC_DEFAULT;
|
USART_InitSync_TypeDef initsync = USART_INITSYNC_DEFAULT;
|
||||||
|
|
||||||
initsync.enable = usartDisable;
|
initsync.enable = usartDisable;
|
||||||
initsync.baudrate = 140000;
|
initsync.baudrate = 130000;
|
||||||
initsync.databits = usartDatabits8;
|
initsync.databits = usartDatabits8;
|
||||||
initsync.master = 1;
|
initsync.master = 1;
|
||||||
initsync.msbf = 1;
|
initsync.msbf = 1;
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
#define USING_DEV_BOARD
|
#define USING_DEV_BOARD
|
||||||
|
|
||||||
|
#define BRIDGE_TO_WALLET
|
||||||
|
|
||||||
void printing_init();
|
void printing_init();
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ struct logtag tagtable[] = {
|
|||||||
{TAG_GREEN,"[1;32mDEBUG[0m"},
|
{TAG_GREEN,"[1;32mDEBUG[0m"},
|
||||||
{TAG_RED,"[1;31mDEBUG[0m"},
|
{TAG_RED,"[1;31mDEBUG[0m"},
|
||||||
{TAG_TIME,"[1;33mTIME[0m"},
|
{TAG_TIME,"[1;33mTIME[0m"},
|
||||||
|
{TAG_WALLET,"[1;34mWALLET[0m"},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ typedef enum
|
|||||||
TAG_TIME= (1 << 11),
|
TAG_TIME= (1 << 11),
|
||||||
TAG_HID = (1 << 12),
|
TAG_HID = (1 << 12),
|
||||||
TAG_USB = (1 << 13),
|
TAG_USB = (1 << 13),
|
||||||
|
TAG_WALLET = (1 << 14),
|
||||||
|
|
||||||
TAG_FILENO = (1<<31)
|
TAG_FILENO = (1<<31)
|
||||||
} LOG_TAG;
|
} LOG_TAG;
|
||||||
|
@ -27,6 +27,7 @@ int main(int argc, char * argv[])
|
|||||||
// TAG_GEN|
|
// TAG_GEN|
|
||||||
/*TAG_MC |*/
|
/*TAG_MC |*/
|
||||||
/*TAG_GA |*/
|
/*TAG_GA |*/
|
||||||
|
TAG_WALLET |
|
||||||
/*TAG_CP |*/
|
/*TAG_CP |*/
|
||||||
// TAG_CTAP|
|
// TAG_CTAP|
|
||||||
// TAG_HID|
|
// TAG_HID|
|
||||||
|
45
fido2/u2f.c
45
fido2/u2f.c
@ -4,12 +4,13 @@
|
|||||||
#include "crypto.h"
|
#include "crypto.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
|
#include "app.h"
|
||||||
|
|
||||||
// void u2f_response_writeback(uint8_t * buf, uint8_t len);
|
// void u2f_response_writeback(uint8_t * buf, uint8_t len);
|
||||||
static int16_t u2f_register(struct u2f_register_request * req);
|
static int16_t u2f_register(struct u2f_register_request * req);
|
||||||
static int16_t u2f_version();
|
static int16_t u2f_version();
|
||||||
static int16_t u2f_authenticate(struct u2f_authenticate_request * req, uint8_t control);
|
static int16_t u2f_authenticate(struct u2f_authenticate_request * req, uint8_t control);
|
||||||
static int8_t u2f_response_writeback(const uint8_t * buf, uint16_t len);
|
int8_t u2f_response_writeback(const uint8_t * buf, uint16_t len);
|
||||||
|
|
||||||
static CTAP_RESPONSE * _u2f_resp = NULL;
|
static CTAP_RESPONSE * _u2f_resp = NULL;
|
||||||
|
|
||||||
@ -28,7 +29,43 @@ void u2f_request(struct u2f_request_apdu* req, CTAP_RESPONSE * resp)
|
|||||||
rcode = U2F_SW_CLASS_NOT_SUPPORTED;
|
rcode = U2F_SW_CLASS_NOT_SUPPORTED;
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
#ifdef BRIDGE_TO_WALLET
|
||||||
|
struct u2f_authenticate_request * auth = (struct u2f_register_request *) req->payload;
|
||||||
|
if (req->ins == U2F_AUTHENTICATE)
|
||||||
|
{
|
||||||
|
if (req->p1 == U2F_AUTHENTICATE_CHECK)
|
||||||
|
{
|
||||||
|
// if (u2f_appid_eq(&req->kh, req->app) == 0)
|
||||||
|
// {
|
||||||
|
// rcode = U2F_SW_CONDITIONS_NOT_SATISFIED;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
rcode = U2F_SW_WRONG_DATA;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rcode = bridge_u2f_to_wallet(auth->chal, auth->app, auth->khl, &auth->kh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (req->ins == U2F_VERSION)
|
||||||
|
{
|
||||||
|
printf1(TAG_U2F, "U2F_VERSION\n");
|
||||||
|
if (len)
|
||||||
|
{
|
||||||
|
rcode = U2F_SW_WRONG_LENGTH;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rcode = u2f_version();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rcode = U2F_SW_INS_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
#else
|
||||||
switch(req->ins)
|
switch(req->ins)
|
||||||
{
|
{
|
||||||
case U2F_REGISTER:
|
case U2F_REGISTER:
|
||||||
@ -73,7 +110,7 @@ void u2f_request(struct u2f_request_apdu* req, CTAP_RESPONSE * resp)
|
|||||||
rcode = U2F_SW_INS_NOT_SUPPORTED;
|
rcode = U2F_SW_INS_NOT_SUPPORTED;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
end:
|
end:
|
||||||
if (rcode != U2F_SW_NO_ERROR)
|
if (rcode != U2F_SW_NO_ERROR)
|
||||||
@ -91,7 +128,7 @@ end:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int8_t u2f_response_writeback(const uint8_t * buf, uint16_t len)
|
int8_t u2f_response_writeback(const uint8_t * buf, uint16_t len)
|
||||||
{
|
{
|
||||||
if ((_u2f_resp->length + len) > _u2f_resp->data_size)
|
if ((_u2f_resp->length + len) > _u2f_resp->data_size)
|
||||||
{
|
{
|
||||||
|
@ -93,6 +93,9 @@ struct u2f_authenticate_request
|
|||||||
void u2f_request(struct u2f_request_apdu* req, CTAP_RESPONSE * resp);
|
void u2f_request(struct u2f_request_apdu* req, CTAP_RESPONSE * resp);
|
||||||
|
|
||||||
|
|
||||||
|
int8_t u2f_response_writeback(const uint8_t * buf, uint16_t len);
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
/* Platform specific functions that must be implemented by user */
|
/* Platform specific functions that must be implemented by user */
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
|
63
fido2/wallet.c
Normal file
63
fido2/wallet.c
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* wallet.c
|
||||||
|
*
|
||||||
|
* Created on: Jul 7, 2018
|
||||||
|
* Author: conor
|
||||||
|
*/
|
||||||
|
#include "wallet.h"
|
||||||
|
#include "ctap.h"
|
||||||
|
#include "u2f.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
WalletSign = 0x10,
|
||||||
|
WalletRegister = 0x11,
|
||||||
|
WalletPin = 0x12,
|
||||||
|
} WalletOperation;
|
||||||
|
|
||||||
|
|
||||||
|
int16_t bridge_u2f_to_wallet(uint8_t * chal, uint8_t * appid, uint8_t klen, uint8_t * keyh)
|
||||||
|
{
|
||||||
|
static uint8_t msg_buf[WALLET_MAX_BUFFER];
|
||||||
|
int reqlen = klen;
|
||||||
|
|
||||||
|
uint32_t count;
|
||||||
|
uint8_t up = 1;
|
||||||
|
uint8_t sig[72];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
wallet_request * req = (wallet_request *) msg_buf;
|
||||||
|
|
||||||
|
// memmove(msg_buf, chal, 32);
|
||||||
|
memmove(msg_buf, keyh, klen);
|
||||||
|
|
||||||
|
count = ctap_atomic_count(0);
|
||||||
|
|
||||||
|
switch(req->operation)
|
||||||
|
{
|
||||||
|
case WalletSign:
|
||||||
|
printf1(TAG_WALLET,"WalletSign\n");
|
||||||
|
break;
|
||||||
|
case WalletRegister:
|
||||||
|
printf1(TAG_WALLET,"WalletRegister\n");
|
||||||
|
break;
|
||||||
|
case WalletPin:
|
||||||
|
printf1(TAG_WALLET,"WalletPin\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf2(TAG_ERR,"Invalid wallet command: %x\n",req->operation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// printf1(TAG_WALLET, "chal: "); dump_hex1(TAG_WALLET, chal,32);
|
||||||
|
// printf1(TAG_WALLET, "appid: "); dump_hex1(TAG_WALLET, appid,32);
|
||||||
|
// printf1(TAG_WALLET, "keyh: "); dump_hex1(TAG_WALLET, keyh,klen);
|
||||||
|
// printf1(TAG_WALLET, "u2f2wallet: "); dump_hex1(TAG_WALLET, msg_buf,reqlen);
|
||||||
|
|
||||||
|
u2f_response_writeback(&up,1);
|
||||||
|
u2f_response_writeback((uint8_t *)&count,4);
|
||||||
|
u2f_response_writeback(sig,72);
|
||||||
|
|
||||||
|
return U2F_SW_NO_ERROR;
|
||||||
|
}
|
65
fido2/wallet.h
Normal file
65
fido2/wallet.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* wallet.h
|
||||||
|
*
|
||||||
|
* Created on: Jul 7, 2018
|
||||||
|
* Author: conor
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WALLET_H_
|
||||||
|
#define WALLET_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define WALLET_MAX_BUFFER (32 + 255)
|
||||||
|
|
||||||
|
// Sign request
|
||||||
|
// op: 0x10
|
||||||
|
// authType: 0x00 //sign?
|
||||||
|
// reserved: 0x00 // mbedtls signature alg identifier
|
||||||
|
// challenge-length: 1-255
|
||||||
|
// challenge: data[1-255]
|
||||||
|
// keyID-length: 1-255
|
||||||
|
// keyID: data[1-255]
|
||||||
|
|
||||||
|
// Resp: normal U2F auth response
|
||||||
|
|
||||||
|
// Register request
|
||||||
|
// op: 0x11
|
||||||
|
// formatType: 0x00 //sign? [0x00: WIF, 0x01: raw]
|
||||||
|
// keyType: 0x03 // mbedtls signature alg identifier
|
||||||
|
// key-length: 1-255
|
||||||
|
// key: data[1-255]
|
||||||
|
|
||||||
|
|
||||||
|
// Resp: modded U2F auth response
|
||||||
|
|
||||||
|
// PIN request
|
||||||
|
// op: 0x12
|
||||||
|
// subcmd: 0x00 // Same as CTAP pin subcommands
|
||||||
|
// reserved: 0x03 // mbedtls signature alg identifier
|
||||||
|
// publickey: data[64]
|
||||||
|
// OR
|
||||||
|
// pinAuth data[64]
|
||||||
|
// OR
|
||||||
|
// pinHashEnc data[64]
|
||||||
|
// OR
|
||||||
|
// newPinEnc data[64]
|
||||||
|
|
||||||
|
// key: data[1-255]
|
||||||
|
// keyID-length: 1-255
|
||||||
|
// keyID: data[1-255]
|
||||||
|
|
||||||
|
// Resp: modded U2F auth response
|
||||||
|
// Returns public key OR pinAuth
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t operation;
|
||||||
|
uint8_t authType;
|
||||||
|
uint8_t keyType;
|
||||||
|
} wallet_request;
|
||||||
|
|
||||||
|
int16_t bridge_u2f_to_wallet(uint8_t * chal, uint8_t * appid, uint8_t klen, uint8_t * keyh);
|
||||||
|
|
||||||
|
#endif /* WALLET_H_ */
|
28
web/.key
Normal file
28
web/.key
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDxXjGjdpW8N2/3
|
||||||
|
s1pYCe1LgVEJlnv0sz/iUaVsd2Z6hv5khaJPVc/LBl5uR5IzotLVw/fp9CU4r7wL
|
||||||
|
xkwezpH+zyo7VjlulRxUZ2zaclYyiKuE1LpdYmuLetzkDI/e2CF26eCQv3vbqxPy
|
||||||
|
dS9wplbgplBax6s8IlfjEhpvIxb4JW1+g7U+zyYGTYVakJYzvzTTl3IFqtO34BBE
|
||||||
|
vSJFDtBuZViwfEctFsinjMHXBo6Nb7OoCQq0ih0gZJwKcojYTbpbm2YhNR2i8mHy
|
||||||
|
6jpsv3bJTygJHIEDGILgu5T6uNY5nwMC4Js/w4YnI+kO4kmwAEEFzxZXa666UxZV
|
||||||
|
VbkWMUezAgMBAAECggEBANr5yuzzC9uLLAC8ba1LrEYBF0Usil6xNLcdvYePN6DX
|
||||||
|
0fnepyd0waT+rTM9qW1BPigDt2pAPniULnU8PRkB7cAPsM/OZSJnEyGcB0yTyJHm
|
||||||
|
Hj3PRRitzsXm/HnEz82rpYidnEeWAUeEiP9Bw31e25jKKkKBvV88BSIT7XmAizkp
|
||||||
|
8H/ya+qm3R8Kxsr/t19UbkrBJ0V9OIM6GXsCZHul2EsRoY7d4om90IvKCHW5MIuT
|
||||||
|
MPkIR2msntgNIhYh+mxDrcZ2qq/AZO/GGf3GpxiDN4imuNvgd6HVg0qwxIpblhjK
|
||||||
|
qX1AeiLQ+ljlz3ktY4TVpt2vgPToekKbme6neCFD3FkCgYEA+4E5UegMTcLce6jR
|
||||||
|
6JMxA5PeING7zRLLKwNsgZvwdpEXh5OBMxWu674tg+OTWG3fPNaIdz+PN+DJFtoV
|
||||||
|
/dDNkOG5TCwBQJPipN2Y5bSmjbBDI4jb/rDLeUvFFF/Hp81bMDYe+o1DNomzkC+A
|
||||||
|
5/uGeNXET90D5NpgRU8Jk0gKlNcCgYEA9a6WmBHWZctXNqD9Sx9pw5i5RQyy2s/D
|
||||||
|
2PmZ01VcE6uMZGghHw4gJIzpD1bY2nP9g8yD5v/VD0bz+GTBp3eKlw/9E1aYfMwN
|
||||||
|
gUdP2sqgnYI7gareI/DTONBVQEmDcRTCH6fewgnwg9wuCwoqgxGAoi9IX4vBWFJt
|
||||||
|
YNlOYQErLIUCgYAVtJlV0Ej/jQmqQm+bOtjIDkLlYjRrBmwyUiFTLjoagXseYESO
|
||||||
|
PBjUj50t/L4Cq7jQb1NntzyM/gFcz4WGWjbjgheT01hoUlsFD3ramDSnlca1kmIq
|
||||||
|
IOful/NyRrHccYSlLIaP6REb69ZrYy4k1zhLxWcj3VcwsQgN8zxIUbdYEQKBgQCL
|
||||||
|
APUDpVQA1EPMDNpDHsrgeBCbGMw5MURGBzMZdzpZhr4wMRpMT9mv1Goo26JmNypA
|
||||||
|
3/3hPO53blWrPJa1AdXQEqPFxUERmwIpGwf7apnlhEHW4647944KnxUdAnr0CCKt
|
||||||
|
dnV6o9UJRhJm/KGA9u4o1UfFh3UlW723BjxqdhbPYQKBgF4KQtcw/Y2kF2BIerD6
|
||||||
|
LyO/tbS90VMQiZYZjGmuLG0K351RqW/l5U9RaEK2hpb10Qu7x/inmSM7sBY7NWE8
|
||||||
|
YOJLH/ArubTCLwiFzkQp24NFZt19xj2qlAunydhgus+q+r5mCp18QyjhTdMYVFqk
|
||||||
|
3biBiaWbI3HoS3qyCf7ezWvw
|
||||||
|
-----END PRIVATE KEY-----
|
87
web/index.html
Normal file
87
web/index.html
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>U2F Bridge Demo</h1>
|
||||||
|
</body>
|
||||||
|
<script src="u2f-api.js"></script>
|
||||||
|
<script>
|
||||||
|
// Convert from normal to web-safe, strip trailing "="s
|
||||||
|
function webSafe64(base64) {
|
||||||
|
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from web-safe to normal, add trailing "="s
|
||||||
|
function normal64(base64) {
|
||||||
|
return base64.replace(/\-/g, '+').replace(/_/g, '/') + '=='.substring(0, (3*base64.length)%4);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bin2string(array){
|
||||||
|
var result = "";
|
||||||
|
for(var i = 0; i < array.length; ++i){
|
||||||
|
result+= (String.fromCharCode(array[i]));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chal = webSafe64(btoa('A'));
|
||||||
|
|
||||||
|
var req = new Uint8Array(255);
|
||||||
|
for(var i =0; i<255; i += 1)
|
||||||
|
{
|
||||||
|
req[i] = 0x00;
|
||||||
|
}
|
||||||
|
req[0] = 0x10;
|
||||||
|
|
||||||
|
var keyHandle = webSafe64(btoa(bin2string(req)));
|
||||||
|
|
||||||
|
var port = ':4443'
|
||||||
|
var appid = 'https://localhost'+port;
|
||||||
|
|
||||||
|
var key = {
|
||||||
|
version: 'U2F_V2',
|
||||||
|
keyHandle: keyHandle,
|
||||||
|
transports: [],
|
||||||
|
appId: appid
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Dispatches an array of sign requests to available U2F tokens.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {string=} challenge
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
|
||||||
|
u2f.getPortSingleton_(function(port) {
|
||||||
|
var reqId = ++u2f.reqCounter_;
|
||||||
|
u2f.callbackMap_[reqId] = callback;
|
||||||
|
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
|
||||||
|
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
|
||||||
|
var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
|
||||||
|
port.postMessage(req);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
i = 0;
|
||||||
|
function test_channel()
|
||||||
|
{
|
||||||
|
var d = new Date();
|
||||||
|
t1 = d.getTime();
|
||||||
|
window.u2f.sign(appid,chal,[key], function(res){
|
||||||
|
|
||||||
|
var d2 = new Date();
|
||||||
|
t2 = d2.getTime();
|
||||||
|
console.log('response:', res);
|
||||||
|
console.log('time:', t2-t1);
|
||||||
|
i += 1;
|
||||||
|
if (i<10)
|
||||||
|
test_channel();
|
||||||
|
|
||||||
|
|
||||||
|
},5);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_channel();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</html>
|
18
web/localhost.crt
Normal file
18
web/localhost.crt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC5TCCAc2gAwIBAgIJAPAHlj0PtV3iMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
||||||
|
BAMMCWxvY2FsaG9zdDAeFw0xODA3MDgwMTUzMTlaFw0xODA4MDcwMTUzMTlaMBQx
|
||||||
|
EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||||
|
ggEBAM9uac6J8FOOaVFtQIWmjZKQbUs1k2SBHHYASE5hOqCw9sBjlGix2VC/yuk7
|
||||||
|
1Nn+N+QM59CkZxR33v2kEK1PTA5Ock/nOHljmnEQrDsKumpp6Jh/L3lB0uwHbTra
|
||||||
|
RTl7giJ4CRPdMc7EYZnK+TecKMaq5O7X8RG55UJoHZFxVIkc2LoTupW13NfAs09G
|
||||||
|
JdDSfMvLVy2ZqELlKQ4qR3kLE7RpDss7WKN0bqG3NebI5liG2cDdObP4yd4gBpKk
|
||||||
|
Dg8nwB4hrDaJaf4MzvrTefnd06Fkx2Fl/H0rZx7HOFMHEX+08fXcby7LMO3VbwEx
|
||||||
|
ChOlDvHiBUHJDLow67JzNq5lv+8CAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo
|
||||||
|
b3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B
|
||||||
|
AQsFAAOCAQEAa5rPgtqb8PJ/ZEE62T6kKaSZeT+ebFqdRvv24WYulbLbZGwryfhq
|
||||||
|
utzS2peNBq57LlwvI+bOFmPUdIJjHBX2EM9NV3B9nJliM22ri6HuiXFpNcD2ehJV
|
||||||
|
W2wMCNXZRyHySgM+7JIz/0TYEpJi3gq9P+IHtz4aXBZ5T2M+HsCBdnBci1n9EqTF
|
||||||
|
9rmt/2RPQxNDIWeAGMlq19cQmmGwVfjxtKIPBGJd7RxkgT3itSjVxb1wyQTlDsZc
|
||||||
|
0dJaMNGJDtGgpRtOlpC6Xv0SZ66GfsFwa8x5d/mty4m1kKYPu+77A6g9lBhjo8r6
|
||||||
|
5MFFjFoq0XZuW5bBLbxQUC6qR8+mRI7gNg==
|
||||||
|
-----END CERTIFICATE-----
|
28
web/localhost.key
Normal file
28
web/localhost.key
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDPbmnOifBTjmlR
|
||||||
|
bUCFpo2SkG1LNZNkgRx2AEhOYTqgsPbAY5RosdlQv8rpO9TZ/jfkDOfQpGcUd979
|
||||||
|
pBCtT0wOTnJP5zh5Y5pxEKw7CrpqaeiYfy95QdLsB2062kU5e4IieAkT3THOxGGZ
|
||||||
|
yvk3nCjGquTu1/ERueVCaB2RcVSJHNi6E7qVtdzXwLNPRiXQ0nzLy1ctmahC5SkO
|
||||||
|
Kkd5CxO0aQ7LO1ijdG6htzXmyOZYhtnA3Tmz+MneIAaSpA4PJ8AeIaw2iWn+DM76
|
||||||
|
03n53dOhZMdhZfx9K2cexzhTBxF/tPH13G8uyzDt1W8BMQoTpQ7x4gVByQy6MOuy
|
||||||
|
czauZb/vAgMBAAECggEBAJpxv14E3meYNVpnWg8o/2gNGRNShLfkWeiSNUQGqLxU
|
||||||
|
hqSggDOjmAbDhrU50zWsTrkB0uhKQGXBCssXiUNBM4PDbOGJa0cWnjtpCpO7XSk2
|
||||||
|
SfShXhuOoxkSPoX/VmOCTlaTwU6E9zzYg0MbGYwKPWIOg/5B0kA8hTJ4iutr0mFz
|
||||||
|
Pc8xu4CkEnyNirDpkCuFvC5sVe9xaLgN6AvhvmKteDYxA/deJhpTx21IwOVyoCt1
|
||||||
|
axhfqUf8RnQJAShxuGyM0k9f1eErvFipU9DOYlyBWI1F44esfT8EoC2Muo/P8fWH
|
||||||
|
qgz7UU4SMHgGRj78nq2d+Xh4J+5o+UCLxWFmikzgA+ECgYEA6Jp3xVS868PDkh2U
|
||||||
|
q14sRo3x+m8QPqFjRV7fYnYKFkikQLZ8NwVXCPAaUeK4+clw1CDawaE/J0exp88z
|
||||||
|
r7ETKiQWQFhVBXPI4xHVu5GvZ5Cl3Bl5nog6nXA8XFRf2dwQmQWYsx3RLFl/WR1G
|
||||||
|
v4rqth/Al5fZep0MI8oJ2SesCYMCgYEA5EvDxiZ4XoNmVUROUSSdpsObWUhp7wxE
|
||||||
|
VsJGXu8sahWzlfF669nJh9oykU0X+VP7pqgzUG8IC2L/urFK4l/QzyklOgslkbLM
|
||||||
|
2Si2SwkTzqQHa03iDvMa43t6+Q575PMFXWJQCoCO6pvlx/cUrDrj0Zl7jul+4Z6I
|
||||||
|
qTM+5IHIICUCgYEAhORz128hfMXc/6cc4IeuXiUNAhrgOhg0wlaA5B0yiCa6iSp5
|
||||||
|
b2oCnt2m+++/5R5c8yVONmBs14FTk9+C1TcjDumNOPf+o13SgUEYTtDeWRpAWK7J
|
||||||
|
WHTWoEqWgd3G4Y6kgPi8lGFNt7vuUTizyg2RKbqcaz/bhf0iCXSOCd5roQcCgYEA
|
||||||
|
l5uA/Zwan5lfYgykfdp1H4QTLG2ce/WHhJ4roWvv8NtMkaz11p6g6SkMH9Y0r40I
|
||||||
|
N5rHyQiTLQyni1Gly0OaJZjLcgpo/qLgyu3wcVAr6TShAK+OCiODncIo3jCyMk75
|
||||||
|
JxTc1rydLEwvgmYhQmcxV8Y3eaaybh6b3tF7WMCfKEkCgYAqySNWMDbmAkPyCFAV
|
||||||
|
1EsbYB9Hh8zLJEDfQCwAbCH4BLzRwFx5SpvEQsNML/kf0i/iBPfPoC4cvnMTVzk7
|
||||||
|
1LY247FCFrtEV5TxObWQJPN45SjOiQeqhpHzBcCu+kh1fz99+VcqZRbmrGV4E4NY
|
||||||
|
reVBSr7HxTaQzJqfg0Ss/y5J0A==
|
||||||
|
-----END PRIVATE KEY-----
|
202
web/self-signed-tls.sh
Normal file
202
web/self-signed-tls.sh
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Directories
|
||||||
|
cur=`pwd`
|
||||||
|
tmp=`mktemp -d`
|
||||||
|
scriptName=`basename $0`
|
||||||
|
|
||||||
|
# Certificate Variables
|
||||||
|
OUTPATH="./"
|
||||||
|
VERBOSE=0
|
||||||
|
DURATION=3650 # 10 years
|
||||||
|
|
||||||
|
safeExit() {
|
||||||
|
if [ -d $tmp ]; then
|
||||||
|
if [ $VERBOSE -eq 1 ]; then
|
||||||
|
echo "Removing temporary directory '${tmp}'"
|
||||||
|
fi
|
||||||
|
rm -rf $tmp
|
||||||
|
fi
|
||||||
|
|
||||||
|
trap - INT TERM EXIT
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
# Help Screen
|
||||||
|
help() {
|
||||||
|
echo -n "${scriptName} [OPTIONS] -c=US --state=California
|
||||||
|
|
||||||
|
Generate self-signed TLS certificate using OpenSSL
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-c|--country Country Name (2 letter code)
|
||||||
|
-s|--state State or Province Name (full name)
|
||||||
|
-l|--locality Locality Name (eg, city)
|
||||||
|
-o|--organization Organization Name (eg, company)
|
||||||
|
-u|--unit Organizational Unit Name (eg, section)
|
||||||
|
-n|--common-name Common Name (e.g. server FQDN or YOUR name)
|
||||||
|
-e|--email Email Address
|
||||||
|
-p|--path Path to output generated keys
|
||||||
|
-d|--duration Validity duration of the certificate (in days)
|
||||||
|
-h|--help Display this help and exit
|
||||||
|
-v|--verbose Verbose output
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test output path is valid
|
||||||
|
testPath() {
|
||||||
|
if [ ! -d $OUTPATH ]; then
|
||||||
|
echo "The specified directory \"${OUTPATH}\" does not exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process Arguments
|
||||||
|
while [ "$1" != "" ]; do
|
||||||
|
PARAM=`echo $1 | awk -F= '{print $1}'`
|
||||||
|
VALUE=`echo $1 | awk -F= '{print $2}'`
|
||||||
|
case $PARAM in
|
||||||
|
-h|--help) help; safeExit ;;
|
||||||
|
-c|--country) C=$VALUE ;;
|
||||||
|
-s|--state) ST=$VALUE ;;
|
||||||
|
-l|--locality) L=$VALUE ;;
|
||||||
|
-o|--organization) O=$VALUE ;;
|
||||||
|
-u|--unit) OU=$VALUE ;;
|
||||||
|
-n|--common-name) CN=$VALUE ;;
|
||||||
|
-e|--email) emailAddress=$VALUE ;;
|
||||||
|
-p|--path) OUTPATH=$VALUE; testPath ;;
|
||||||
|
-d|--duration) DURATION=$VALUE ;;
|
||||||
|
-v|--verbose) VERBOSE=1 ;;
|
||||||
|
*) echo "ERROR: unknown parameter \"$PARAM\""; help; exit 1 ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# Prompt for variables that were not provided in arguments
|
||||||
|
checkVariables() {
|
||||||
|
# Country
|
||||||
|
if [ -z $C ]; then
|
||||||
|
echo -n "Country Name (2 letter code) [AU]:"
|
||||||
|
read C
|
||||||
|
fi
|
||||||
|
|
||||||
|
# State
|
||||||
|
if [ -z $ST ]; then
|
||||||
|
echo -n "State or Province Name (full name) [Some-State]:"
|
||||||
|
read ST
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Locality
|
||||||
|
if [ -z $L ]; then
|
||||||
|
echo -n "Locality Name (eg, city) []:"
|
||||||
|
read L
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Organization
|
||||||
|
if [ -z $O ]; then
|
||||||
|
echo -n "Organization Name (eg, company) [Internet Widgits Pty Ltd]:"
|
||||||
|
read O
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Organizational Unit
|
||||||
|
if [ -z $OU ]; then
|
||||||
|
echo -n "Organizational Unit Name (eg, section) []:"
|
||||||
|
read OU
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Common Name
|
||||||
|
if [ -z $CN ]; then
|
||||||
|
echo -n "Common Name (e.g. server FQDN or YOUR name) []:"
|
||||||
|
read CN
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Common Name
|
||||||
|
if [ -z $emailAddress ]; then
|
||||||
|
echo -n "Email Address []:"
|
||||||
|
read emailAddress
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show variable values
|
||||||
|
showVals() {
|
||||||
|
echo "Country: ${C}";
|
||||||
|
echo "State: ${ST}";
|
||||||
|
echo "Locality: ${L}";
|
||||||
|
echo "Organization: ${O}";
|
||||||
|
echo "Organization Unit: ${OU}";
|
||||||
|
echo "Common Name: ${CN}";
|
||||||
|
echo "Email: ${emailAddress}";
|
||||||
|
echo "Output Path: ${OUTPATH}";
|
||||||
|
echo "Certificate Duration (Days): ${DURATION}";
|
||||||
|
echo "Verbose: ${VERBOSE}";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Init
|
||||||
|
init() {
|
||||||
|
cd $tmp
|
||||||
|
pwd
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
cleanup() {
|
||||||
|
echo "Cleaning up"
|
||||||
|
cd $cur
|
||||||
|
rm -rf $tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
buildCsrCnf() {
|
||||||
|
cat << EOF > ${tmp}/tmp.csr.cnf
|
||||||
|
[req]
|
||||||
|
default_bits = 2048
|
||||||
|
prompt = no
|
||||||
|
default_md = sha256
|
||||||
|
distinguished_name = dn
|
||||||
|
|
||||||
|
[dn]
|
||||||
|
C=${C}
|
||||||
|
ST=${ST}
|
||||||
|
L=${L}
|
||||||
|
O=${O}
|
||||||
|
OU=${OU}
|
||||||
|
CN=${CN}
|
||||||
|
emailAddress=${emailAddress}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
buildExtCnf() {
|
||||||
|
cat << EOF > ${tmp}/v3.ext
|
||||||
|
authorityKeyIdentifier=keyid,issuer
|
||||||
|
basicConstraints=CA:FALSE
|
||||||
|
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = ${CN}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build TLS Certificate
|
||||||
|
build() {
|
||||||
|
# Santizie domain name for file name
|
||||||
|
FILENAME=${CN/\*\./}
|
||||||
|
# Generate CA key & crt
|
||||||
|
openssl genrsa -out ${tmp}/tmp.key 2048
|
||||||
|
openssl req -x509 -new -nodes -key ${tmp}/tmp.key -sha256 -days ${DURATION} -out ${OUTPATH}${FILENAME}_CA.pem -subj "/C=${C}/ST=${ST}/L=${L}/O=${O}/OU=${OU}/CN=${CN}/emailAddress=${emailAddress}"
|
||||||
|
|
||||||
|
# CSR Configuration
|
||||||
|
buildCsrCnf
|
||||||
|
|
||||||
|
# Create v3.ext configuration file
|
||||||
|
buildExtCnf
|
||||||
|
|
||||||
|
# Server key
|
||||||
|
openssl req -new -sha256 -nodes -out ${OUTPATH}${FILENAME}.csr -newkey rsa:2048 -keyout ${OUTPATH}${FILENAME}.key -config <( cat ${tmp}/tmp.csr.cnf )
|
||||||
|
|
||||||
|
# Server certificate
|
||||||
|
openssl x509 -req -in ${OUTPATH}${FILENAME}.csr -CA ${OUTPATH}${FILENAME}_CA.pem -CAkey ${tmp}/tmp.key -CAcreateserial -out ${OUTPATH}${FILENAME}.crt -days ${DURATION} -sha256 -extfile ${tmp}/v3.ext
|
||||||
|
}
|
||||||
|
|
||||||
|
checkVariables
|
||||||
|
build
|
||||||
|
# showVals
|
||||||
|
safeExit
|
13
web/simple-https-server.py
Normal file
13
web/simple-https-server.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# https://blog.anvileight.com/posts/simple-python-http-server/#python-3-x
|
||||||
|
|
||||||
|
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||||
|
import ssl
|
||||||
|
|
||||||
|
|
||||||
|
httpd = HTTPServer(('localhost', 4443), SimpleHTTPRequestHandler)
|
||||||
|
|
||||||
|
httpd.socket = ssl.wrap_socket (httpd.socket,
|
||||||
|
keyfile="localhost.key",
|
||||||
|
certfile='localhost.crt', server_side=True)
|
||||||
|
|
||||||
|
httpd.serve_forever()
|
760
web/u2f-api.js
Normal file
760
web/u2f-api.js
Normal file
@ -0,0 +1,760 @@
|
|||||||
|
//Copyright 2014-2015 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
//Use of this source code is governed by a BSD-style
|
||||||
|
//license that can be found in the LICENSE file or at
|
||||||
|
//https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview The U2F api.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Namespace for the U2F api.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
var u2f = u2f || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIDO U2F Javascript API Version
|
||||||
|
* @number
|
||||||
|
*/
|
||||||
|
var js_api_version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The U2F extension id
|
||||||
|
* @const {string}
|
||||||
|
*/
|
||||||
|
// The Chrome packaged app extension ID.
|
||||||
|
// Uncomment this if you want to deploy a server instance that uses
|
||||||
|
// the package Chrome app and does not require installing the U2F Chrome extension.
|
||||||
|
u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
|
||||||
|
// The U2F Chrome extension ID.
|
||||||
|
// Uncomment this if you want to deploy a server instance that uses
|
||||||
|
// the U2F Chrome extension to authenticate.
|
||||||
|
// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message types for messsages to/from the extension
|
||||||
|
* @const
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
u2f.MessageTypes = {
|
||||||
|
'U2F_REGISTER_REQUEST': 'u2f_register_request',
|
||||||
|
'U2F_REGISTER_RESPONSE': 'u2f_register_response',
|
||||||
|
'U2F_SIGN_REQUEST': 'u2f_sign_request',
|
||||||
|
'U2F_SIGN_RESPONSE': 'u2f_sign_response',
|
||||||
|
'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
|
||||||
|
'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response status codes
|
||||||
|
* @const
|
||||||
|
* @enum {number}
|
||||||
|
*/
|
||||||
|
u2f.ErrorCodes = {
|
||||||
|
'OK': 0,
|
||||||
|
'OTHER_ERROR': 1,
|
||||||
|
'BAD_REQUEST': 2,
|
||||||
|
'CONFIGURATION_UNSUPPORTED': 3,
|
||||||
|
'DEVICE_INELIGIBLE': 4,
|
||||||
|
'TIMEOUT': 5
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message for registration requests
|
||||||
|
* @typedef {{
|
||||||
|
* type: u2f.MessageTypes,
|
||||||
|
* appId: ?string,
|
||||||
|
* timeoutSeconds: ?number,
|
||||||
|
* requestId: ?number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.U2fRequest;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message for registration responses
|
||||||
|
* @typedef {{
|
||||||
|
* type: u2f.MessageTypes,
|
||||||
|
* responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
|
||||||
|
* requestId: ?number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.U2fResponse;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An error object for responses
|
||||||
|
* @typedef {{
|
||||||
|
* errorCode: u2f.ErrorCodes,
|
||||||
|
* errorMessage: ?string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.Error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a single sign request.
|
||||||
|
* @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
|
||||||
|
*/
|
||||||
|
u2f.Transport;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a single sign request.
|
||||||
|
* @typedef {Array<u2f.Transport>}
|
||||||
|
*/
|
||||||
|
u2f.Transports;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a single sign request.
|
||||||
|
* @typedef {{
|
||||||
|
* version: string,
|
||||||
|
* challenge: string,
|
||||||
|
* keyHandle: string,
|
||||||
|
* appId: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.SignRequest;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a sign response.
|
||||||
|
* @typedef {{
|
||||||
|
* keyHandle: string,
|
||||||
|
* signatureData: string,
|
||||||
|
* clientData: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.SignResponse;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a registration request.
|
||||||
|
* @typedef {{
|
||||||
|
* version: string,
|
||||||
|
* challenge: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.RegisterRequest;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a registration response.
|
||||||
|
* @typedef {{
|
||||||
|
* version: string,
|
||||||
|
* keyHandle: string,
|
||||||
|
* transports: Transports,
|
||||||
|
* appId: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.RegisterResponse;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a registered key.
|
||||||
|
* @typedef {{
|
||||||
|
* version: string,
|
||||||
|
* keyHandle: string,
|
||||||
|
* transports: ?Transports,
|
||||||
|
* appId: ?string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.RegisteredKey;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a get API register response.
|
||||||
|
* @typedef {{
|
||||||
|
* js_api_version: number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.GetJsApiVersionResponse;
|
||||||
|
|
||||||
|
|
||||||
|
//Low level MessagePort API support
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up a MessagePort to the U2F extension using the
|
||||||
|
* available mechanisms.
|
||||||
|
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
|
||||||
|
*/
|
||||||
|
u2f.getMessagePort = function(callback) {
|
||||||
|
console.log("getMessagePort");
|
||||||
|
if (typeof chrome != 'undefined' && chrome.runtime) {
|
||||||
|
// The actual message here does not matter, but we need to get a reply
|
||||||
|
// for the callback to run. Thus, send an empty signature request
|
||||||
|
// in order to get a failure response.
|
||||||
|
var msg = {
|
||||||
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||||
|
signRequests: []
|
||||||
|
};
|
||||||
|
chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
|
||||||
|
if (!chrome.runtime.lastError) {
|
||||||
|
// We are on a whitelisted origin and can talk directly
|
||||||
|
// with the extension.
|
||||||
|
console.log("talk with ext");
|
||||||
|
u2f.getChromeRuntimePort_(callback);
|
||||||
|
} else {
|
||||||
|
// chrome.runtime was available, but we couldn't message
|
||||||
|
// the extension directly, use iframe
|
||||||
|
console.log("talk with ext from iframe");
|
||||||
|
u2f.getIframePort_(callback);
|
||||||
|
|
||||||
|
console.log("setup");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (u2f.isAndroidChrome_()) {
|
||||||
|
u2f.getAuthenticatorPort_(callback);
|
||||||
|
} else if (u2f.isIosChrome_()) {
|
||||||
|
u2f.getIosPort_(callback);
|
||||||
|
} else {
|
||||||
|
// chrome.runtime was not available at all, which is normal
|
||||||
|
// when this origin doesn't have access to any extensions.
|
||||||
|
u2f.getIframePort_(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect chrome running on android based on the browser's useragent.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.isAndroidChrome_ = function() {
|
||||||
|
var userAgent = navigator.userAgent;
|
||||||
|
return userAgent.indexOf('Chrome') != -1 &&
|
||||||
|
userAgent.indexOf('Android') != -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect chrome running on iOS based on the browser's platform.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.isIosChrome_ = function() {
|
||||||
|
return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects directly to the extension via chrome.runtime.connect.
|
||||||
|
* @param {function(u2f.WrappedChromeRuntimePort_)} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getChromeRuntimePort_ = function(callback) {
|
||||||
|
var port = chrome.runtime.connect(u2f.EXTENSION_ID,
|
||||||
|
{'includeTlsChannelId': true});
|
||||||
|
setTimeout(function() {
|
||||||
|
callback(new u2f.WrappedChromeRuntimePort_(port));
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a 'port' abstraction to the Authenticator app.
|
||||||
|
* @param {function(u2f.WrappedAuthenticatorPort_)} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getAuthenticatorPort_ = function(callback) {
|
||||||
|
setTimeout(function() {
|
||||||
|
callback(new u2f.WrappedAuthenticatorPort_());
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a 'port' abstraction to the iOS client app.
|
||||||
|
* @param {function(u2f.WrappedIosPort_)} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getIosPort_ = function(callback) {
|
||||||
|
setTimeout(function() {
|
||||||
|
callback(new u2f.WrappedIosPort_());
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper for chrome.runtime.Port that is compatible with MessagePort.
|
||||||
|
* @param {Port} port
|
||||||
|
* @constructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.WrappedChromeRuntimePort_ = function(port) {
|
||||||
|
this.port_ = port;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format and return a sign request compliant with the JS API version supported by the extension.
|
||||||
|
* @param {Array<u2f.SignRequest>} signRequests
|
||||||
|
* @param {number} timeoutSeconds
|
||||||
|
* @param {number} reqId
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
u2f.formatSignRequest_ =
|
||||||
|
function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
|
||||||
|
if (js_api_version === undefined || js_api_version < 1.1) {
|
||||||
|
// Adapt request to the 1.0 JS API
|
||||||
|
var signRequests = [];
|
||||||
|
for (var i = 0; i < registeredKeys.length; i++) {
|
||||||
|
signRequests[i] = {
|
||||||
|
version: registeredKeys[i].version,
|
||||||
|
challenge: challenge,
|
||||||
|
keyHandle: registeredKeys[i].keyHandle,
|
||||||
|
appId: appId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||||
|
signRequests: signRequests,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// JS 1.1 API
|
||||||
|
return {
|
||||||
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||||
|
appId: appId,
|
||||||
|
challenge: challenge,
|
||||||
|
registeredKeys: registeredKeys,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format and return a register request compliant with the JS API version supported by the extension..
|
||||||
|
* @param {Array<u2f.SignRequest>} signRequests
|
||||||
|
* @param {Array<u2f.RegisterRequest>} signRequests
|
||||||
|
* @param {number} timeoutSeconds
|
||||||
|
* @param {number} reqId
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
u2f.formatRegisterRequest_ =
|
||||||
|
function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
|
||||||
|
if (js_api_version === undefined || js_api_version < 1.1) {
|
||||||
|
// Adapt request to the 1.0 JS API
|
||||||
|
for (var i = 0; i < registerRequests.length; i++) {
|
||||||
|
registerRequests[i].appId = appId;
|
||||||
|
}
|
||||||
|
var signRequests = [];
|
||||||
|
for (var i = 0; i < registeredKeys.length; i++) {
|
||||||
|
signRequests[i] = {
|
||||||
|
version: registeredKeys[i].version,
|
||||||
|
challenge: registerRequests[0],
|
||||||
|
keyHandle: registeredKeys[i].keyHandle,
|
||||||
|
appId: appId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
|
||||||
|
signRequests: signRequests,
|
||||||
|
registerRequests: registerRequests,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// JS 1.1 API
|
||||||
|
return {
|
||||||
|
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
|
||||||
|
appId: appId,
|
||||||
|
registerRequests: registerRequests,
|
||||||
|
registeredKeys: registeredKeys,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a message on the underlying channel.
|
||||||
|
* @param {Object} message
|
||||||
|
*/
|
||||||
|
u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
|
||||||
|
this.port_.postMessage(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the HTML 5 addEventListener interface. Works only for the
|
||||||
|
* onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function({data: Object})} handler
|
||||||
|
*/
|
||||||
|
u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
|
||||||
|
function(eventName, handler) {
|
||||||
|
var name = eventName.toLowerCase();
|
||||||
|
if (name == 'message' || name == 'onmessage') {
|
||||||
|
this.port_.onMessage.addListener(function(message) {
|
||||||
|
// Emulate a minimal MessageEvent object
|
||||||
|
handler({'data': message});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('WrappedChromeRuntimePort only supports onMessage');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap the Authenticator app with a MessagePort interface.
|
||||||
|
* @constructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_ = function() {
|
||||||
|
this.requestId_ = -1;
|
||||||
|
this.requestObject_ = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch the Authenticator intent.
|
||||||
|
* @param {Object} message
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
|
||||||
|
var intentUrl =
|
||||||
|
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
|
||||||
|
';S.request=' + encodeURIComponent(JSON.stringify(message)) +
|
||||||
|
';end';
|
||||||
|
console.log(intentUrl);
|
||||||
|
document.location = intentUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells what type of port this is.
|
||||||
|
* @return {String} port type
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() {
|
||||||
|
return "WrappedAuthenticatorPort_";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the HTML 5 addEventListener interface.
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function({data: Object})} handler
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) {
|
||||||
|
var name = eventName.toLowerCase();
|
||||||
|
if (name == 'message') {
|
||||||
|
var self = this;
|
||||||
|
/* Register a callback to that executes when
|
||||||
|
* chrome injects the response. */
|
||||||
|
window.addEventListener(
|
||||||
|
'message', self.onRequestUpdate_.bind(self, handler), false);
|
||||||
|
} else {
|
||||||
|
console.error('WrappedAuthenticatorPort only supports message');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked when a response is received from the Authenticator.
|
||||||
|
* @param function({data: Object}) callback
|
||||||
|
* @param {Object} message message Object
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
|
||||||
|
function(callback, message) {
|
||||||
|
var messageObject = JSON.parse(message.data);
|
||||||
|
var intentUrl = messageObject['intentURL'];
|
||||||
|
|
||||||
|
var errorCode = messageObject['errorCode'];
|
||||||
|
var responseObject = null;
|
||||||
|
if (messageObject.hasOwnProperty('data')) {
|
||||||
|
responseObject = /** @type {Object} */ (
|
||||||
|
JSON.parse(messageObject['data']));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback({'data': responseObject});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base URL for intents to Authenticator.
|
||||||
|
* @const
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
|
||||||
|
'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap the iOS client app with a MessagePort interface.
|
||||||
|
* @constructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.WrappedIosPort_ = function() {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch the iOS client app request
|
||||||
|
* @param {Object} message
|
||||||
|
*/
|
||||||
|
u2f.WrappedIosPort_.prototype.postMessage = function(message) {
|
||||||
|
var str = JSON.stringify(message);
|
||||||
|
var url = "u2f://auth?" + encodeURI(str);
|
||||||
|
location.replace(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells what type of port this is.
|
||||||
|
* @return {String} port type
|
||||||
|
*/
|
||||||
|
u2f.WrappedIosPort_.prototype.getPortType = function() {
|
||||||
|
return "WrappedIosPort_";
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the HTML 5 addEventListener interface.
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function({data: Object})} handler
|
||||||
|
*/
|
||||||
|
u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) {
|
||||||
|
var name = eventName.toLowerCase();
|
||||||
|
if (name !== 'message') {
|
||||||
|
console.error('WrappedIosPort only supports message');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up an embedded trampoline iframe, sourced from the extension.
|
||||||
|
* @param {function(MessagePort)} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getIframePort_ = function(callback) {
|
||||||
|
// Create the iframe
|
||||||
|
var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
|
||||||
|
var iframe = document.createElement('iframe');
|
||||||
|
iframe.src = iframeOrigin + '/u2f-comms.html';
|
||||||
|
iframe.setAttribute('style', 'display:none');
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
|
||||||
|
var channel = new MessageChannel();
|
||||||
|
var ready = function(message) {
|
||||||
|
if (message.data == 'ready') {
|
||||||
|
channel.port1.removeEventListener('message', ready);
|
||||||
|
callback(channel.port1);
|
||||||
|
} else {
|
||||||
|
console.error('First event on iframe port was not "ready"');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
channel.port1.addEventListener('message', ready);
|
||||||
|
channel.port1.start();
|
||||||
|
|
||||||
|
iframe.addEventListener('load', function() {
|
||||||
|
|
||||||
|
// Deliver the port to the iframe and initialize
|
||||||
|
//
|
||||||
|
setTimeout(function(){
|
||||||
|
iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
|
||||||
|
|
||||||
|
},1000);
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//High-level JS API
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default extension response timeout in seconds.
|
||||||
|
* @const
|
||||||
|
*/
|
||||||
|
u2f.EXTENSION_TIMEOUT_SEC = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A singleton instance for a MessagePort to the extension.
|
||||||
|
* @type {MessagePort|u2f.WrappedChromeRuntimePort_}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.port_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks waiting for a port
|
||||||
|
* @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.waitingForPort_ = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A counter for requestIds.
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.reqCounter_ = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map from requestIds to client callbacks
|
||||||
|
* @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
|
||||||
|
* |function((u2f.Error|u2f.SignResponse)))>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.callbackMap_ = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates or retrieves the MessagePort singleton to use.
|
||||||
|
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getPortSingleton_ = function(callback) {
|
||||||
|
if (u2f.port_) {
|
||||||
|
callback(u2f.port_);
|
||||||
|
} else {
|
||||||
|
if (u2f.waitingForPort_.length == 0) {
|
||||||
|
u2f.getMessagePort(function(port) {
|
||||||
|
u2f.port_ = port;
|
||||||
|
u2f.port_.addEventListener('message',
|
||||||
|
/** @type {function(Event)} */ (u2f.responseHandler_));
|
||||||
|
|
||||||
|
// Careful, here be async callbacks. Maybe.
|
||||||
|
while (u2f.waitingForPort_.length)
|
||||||
|
u2f.waitingForPort_.shift()(u2f.port_);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
u2f.waitingForPort_.push(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles response messages from the extension.
|
||||||
|
* @param {MessageEvent.<u2f.Response>} message
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.responseHandler_ = function(message) {
|
||||||
|
var response = message.data;
|
||||||
|
var reqId = response['requestId'];
|
||||||
|
if (!reqId || !u2f.callbackMap_[reqId]) {
|
||||||
|
console.error('Unknown or missing requestId in response.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var cb = u2f.callbackMap_[reqId];
|
||||||
|
delete u2f.callbackMap_[reqId];
|
||||||
|
cb(response['responseData']);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an array of sign requests to available U2F tokens.
|
||||||
|
* If the JS API version supported by the extension is unknown, it first sends a
|
||||||
|
* message to the extension to find out the supported API version and then it sends
|
||||||
|
* the sign request.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {string=} challenge
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
|
||||||
|
if (js_api_version === undefined) {
|
||||||
|
// Send a message to get the extension to JS API version, then send the actual sign request.
|
||||||
|
u2f.getApiVersion(
|
||||||
|
function (response) {
|
||||||
|
js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
|
||||||
|
console.log("Extension JS API Version: ", js_api_version);
|
||||||
|
u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// We know the JS API version. Send the actual sign request in the supported API version.
|
||||||
|
u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an array of sign requests to available U2F tokens.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {string=} challenge
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
|
||||||
|
u2f.getPortSingleton_(function(port) {
|
||||||
|
var reqId = ++u2f.reqCounter_;
|
||||||
|
u2f.callbackMap_[reqId] = callback;
|
||||||
|
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
|
||||||
|
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
|
||||||
|
var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
|
||||||
|
port.postMessage(req);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches register requests to available U2F tokens. An array of sign
|
||||||
|
* requests identifies already registered tokens.
|
||||||
|
* If the JS API version supported by the extension is unknown, it first sends a
|
||||||
|
* message to the extension to find out the supported API version and then it sends
|
||||||
|
* the register request.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {Array<u2f.RegisterRequest>} registerRequests
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
|
||||||
|
if (js_api_version === undefined) {
|
||||||
|
// Send a message to get the extension to JS API version, then send the actual register request.
|
||||||
|
u2f.getApiVersion(
|
||||||
|
function (response) {
|
||||||
|
js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version'];
|
||||||
|
console.log("Extension JS API Version: ", js_api_version);
|
||||||
|
u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
|
||||||
|
callback, opt_timeoutSeconds);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// We know the JS API version. Send the actual register request in the supported API version.
|
||||||
|
u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
|
||||||
|
callback, opt_timeoutSeconds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches register requests to available U2F tokens. An array of sign
|
||||||
|
* requests identifies already registered tokens.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {Array<u2f.RegisterRequest>} registerRequests
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
|
||||||
|
u2f.getPortSingleton_(function(port) {
|
||||||
|
var reqId = ++u2f.reqCounter_;
|
||||||
|
u2f.callbackMap_[reqId] = callback;
|
||||||
|
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
|
||||||
|
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
|
||||||
|
var req = u2f.formatRegisterRequest_(
|
||||||
|
appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
|
||||||
|
port.postMessage(req);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a message to the extension to find out the supported
|
||||||
|
* JS API version.
|
||||||
|
* If the user is on a mobile phone and is thus using Google Authenticator instead
|
||||||
|
* of the Chrome extension, don't send the request and simply return 0.
|
||||||
|
* @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.getApiVersion = function(callback, opt_timeoutSeconds) {
|
||||||
|
u2f.getPortSingleton_(function(port) {
|
||||||
|
// If we are using Android Google Authenticator or iOS client app,
|
||||||
|
// do not fire an intent to ask which JS API version to use.
|
||||||
|
if (port.getPortType) {
|
||||||
|
var apiVersion;
|
||||||
|
switch (port.getPortType()) {
|
||||||
|
case 'WrappedIosPort_':
|
||||||
|
case 'WrappedAuthenticatorPort_':
|
||||||
|
apiVersion = 1.1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
apiVersion = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
callback({ 'js_api_version': apiVersion });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var reqId = ++u2f.reqCounter_;
|
||||||
|
u2f.callbackMap_[reqId] = callback;
|
||||||
|
var req = {
|
||||||
|
type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
|
||||||
|
timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
|
||||||
|
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
port.postMessage(req);
|
||||||
|
});
|
||||||
|
};
|
748
web/u2f-api2.js
Normal file
748
web/u2f-api2.js
Normal file
@ -0,0 +1,748 @@
|
|||||||
|
//Copyright 2014-2015 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
//Use of this source code is governed by a BSD-style
|
||||||
|
//license that can be found in the LICENSE file or at
|
||||||
|
//https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview The U2F api.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Namespace for the U2F api.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
var u2f = u2f || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIDO U2F Javascript API Version
|
||||||
|
* @number
|
||||||
|
*/
|
||||||
|
var js_api_version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The U2F extension id
|
||||||
|
* @const {string}
|
||||||
|
*/
|
||||||
|
// The Chrome packaged app extension ID.
|
||||||
|
// Uncomment this if you want to deploy a server instance that uses
|
||||||
|
// the package Chrome app and does not require installing the U2F Chrome extension.
|
||||||
|
u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
|
||||||
|
// The U2F Chrome extension ID.
|
||||||
|
// Uncomment this if you want to deploy a server instance that uses
|
||||||
|
// the U2F Chrome extension to authenticate.
|
||||||
|
// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message types for messsages to/from the extension
|
||||||
|
* @const
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
u2f.MessageTypes = {
|
||||||
|
'U2F_REGISTER_REQUEST': 'u2f_register_request',
|
||||||
|
'U2F_REGISTER_RESPONSE': 'u2f_register_response',
|
||||||
|
'U2F_SIGN_REQUEST': 'u2f_sign_request',
|
||||||
|
'U2F_SIGN_RESPONSE': 'u2f_sign_response',
|
||||||
|
'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
|
||||||
|
'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response status codes
|
||||||
|
* @const
|
||||||
|
* @enum {number}
|
||||||
|
*/
|
||||||
|
u2f.ErrorCodes = {
|
||||||
|
'OK': 0,
|
||||||
|
'OTHER_ERROR': 1,
|
||||||
|
'BAD_REQUEST': 2,
|
||||||
|
'CONFIGURATION_UNSUPPORTED': 3,
|
||||||
|
'DEVICE_INELIGIBLE': 4,
|
||||||
|
'TIMEOUT': 5
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message for registration requests
|
||||||
|
* @typedef {{
|
||||||
|
* type: u2f.MessageTypes,
|
||||||
|
* appId: ?string,
|
||||||
|
* timeoutSeconds: ?number,
|
||||||
|
* requestId: ?number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.U2fRequest;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message for registration responses
|
||||||
|
* @typedef {{
|
||||||
|
* type: u2f.MessageTypes,
|
||||||
|
* responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
|
||||||
|
* requestId: ?number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.U2fResponse;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An error object for responses
|
||||||
|
* @typedef {{
|
||||||
|
* errorCode: u2f.ErrorCodes,
|
||||||
|
* errorMessage: ?string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.Error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a single sign request.
|
||||||
|
* @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
|
||||||
|
*/
|
||||||
|
u2f.Transport;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a single sign request.
|
||||||
|
* @typedef {Array<u2f.Transport>}
|
||||||
|
*/
|
||||||
|
u2f.Transports;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a single sign request.
|
||||||
|
* @typedef {{
|
||||||
|
* version: string,
|
||||||
|
* challenge: string,
|
||||||
|
* keyHandle: string,
|
||||||
|
* appId: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.SignRequest;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a sign response.
|
||||||
|
* @typedef {{
|
||||||
|
* keyHandle: string,
|
||||||
|
* signatureData: string,
|
||||||
|
* clientData: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.SignResponse;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a registration request.
|
||||||
|
* @typedef {{
|
||||||
|
* version: string,
|
||||||
|
* challenge: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.RegisterRequest;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a registration response.
|
||||||
|
* @typedef {{
|
||||||
|
* version: string,
|
||||||
|
* keyHandle: string,
|
||||||
|
* transports: Transports,
|
||||||
|
* appId: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.RegisterResponse;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a registered key.
|
||||||
|
* @typedef {{
|
||||||
|
* version: string,
|
||||||
|
* keyHandle: string,
|
||||||
|
* transports: ?Transports,
|
||||||
|
* appId: ?string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.RegisteredKey;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data object for a get API register response.
|
||||||
|
* @typedef {{
|
||||||
|
* js_api_version: number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
u2f.GetJsApiVersionResponse;
|
||||||
|
|
||||||
|
|
||||||
|
//Low level MessagePort API support
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up a MessagePort to the U2F extension using the
|
||||||
|
* available mechanisms.
|
||||||
|
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
|
||||||
|
*/
|
||||||
|
u2f.getMessagePort = function(callback) {
|
||||||
|
if (typeof chrome != 'undefined' && chrome.runtime) {
|
||||||
|
// The actual message here does not matter, but we need to get a reply
|
||||||
|
// for the callback to run. Thus, send an empty signature request
|
||||||
|
// in order to get a failure response.
|
||||||
|
var msg = {
|
||||||
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||||
|
signRequests: []
|
||||||
|
};
|
||||||
|
chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
|
||||||
|
if (!chrome.runtime.lastError) {
|
||||||
|
// We are on a whitelisted origin and can talk directly
|
||||||
|
// with the extension.
|
||||||
|
u2f.getChromeRuntimePort_(callback);
|
||||||
|
} else {
|
||||||
|
// chrome.runtime was available, but we couldn't message
|
||||||
|
// the extension directly, use iframe
|
||||||
|
u2f.getIframePort_(callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (u2f.isAndroidChrome_()) {
|
||||||
|
u2f.getAuthenticatorPort_(callback);
|
||||||
|
} else if (u2f.isIosChrome_()) {
|
||||||
|
u2f.getIosPort_(callback);
|
||||||
|
} else {
|
||||||
|
// chrome.runtime was not available at all, which is normal
|
||||||
|
// when this origin doesn't have access to any extensions.
|
||||||
|
u2f.getIframePort_(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect chrome running on android based on the browser's useragent.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.isAndroidChrome_ = function() {
|
||||||
|
var userAgent = navigator.userAgent;
|
||||||
|
return userAgent.indexOf('Chrome') != -1 &&
|
||||||
|
userAgent.indexOf('Android') != -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect chrome running on iOS based on the browser's platform.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.isIosChrome_ = function() {
|
||||||
|
return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects directly to the extension via chrome.runtime.connect.
|
||||||
|
* @param {function(u2f.WrappedChromeRuntimePort_)} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getChromeRuntimePort_ = function(callback) {
|
||||||
|
var port = chrome.runtime.connect(u2f.EXTENSION_ID,
|
||||||
|
{'includeTlsChannelId': true});
|
||||||
|
setTimeout(function() {
|
||||||
|
callback(new u2f.WrappedChromeRuntimePort_(port));
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a 'port' abstraction to the Authenticator app.
|
||||||
|
* @param {function(u2f.WrappedAuthenticatorPort_)} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getAuthenticatorPort_ = function(callback) {
|
||||||
|
setTimeout(function() {
|
||||||
|
callback(new u2f.WrappedAuthenticatorPort_());
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a 'port' abstraction to the iOS client app.
|
||||||
|
* @param {function(u2f.WrappedIosPort_)} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getIosPort_ = function(callback) {
|
||||||
|
setTimeout(function() {
|
||||||
|
callback(new u2f.WrappedIosPort_());
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper for chrome.runtime.Port that is compatible with MessagePort.
|
||||||
|
* @param {Port} port
|
||||||
|
* @constructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.WrappedChromeRuntimePort_ = function(port) {
|
||||||
|
this.port_ = port;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format and return a sign request compliant with the JS API version supported by the extension.
|
||||||
|
* @param {Array<u2f.SignRequest>} signRequests
|
||||||
|
* @param {number} timeoutSeconds
|
||||||
|
* @param {number} reqId
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
u2f.formatSignRequest_ =
|
||||||
|
function(appId, challenge, registeredKeys, timeoutSeconds, reqId) {
|
||||||
|
if (js_api_version === undefined || js_api_version < 1.1) {
|
||||||
|
// Adapt request to the 1.0 JS API
|
||||||
|
var signRequests = [];
|
||||||
|
for (var i = 0; i < registeredKeys.length; i++) {
|
||||||
|
signRequests[i] = {
|
||||||
|
version: registeredKeys[i].version,
|
||||||
|
challenge: challenge,
|
||||||
|
keyHandle: registeredKeys[i].keyHandle,
|
||||||
|
appId: appId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||||
|
signRequests: signRequests,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// JS 1.1 API
|
||||||
|
return {
|
||||||
|
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
|
||||||
|
appId: appId,
|
||||||
|
challenge: challenge,
|
||||||
|
registeredKeys: registeredKeys,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format and return a register request compliant with the JS API version supported by the extension..
|
||||||
|
* @param {Array<u2f.SignRequest>} signRequests
|
||||||
|
* @param {Array<u2f.RegisterRequest>} signRequests
|
||||||
|
* @param {number} timeoutSeconds
|
||||||
|
* @param {number} reqId
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
u2f.formatRegisterRequest_ =
|
||||||
|
function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
|
||||||
|
if (js_api_version === undefined || js_api_version < 1.1) {
|
||||||
|
// Adapt request to the 1.0 JS API
|
||||||
|
for (var i = 0; i < registerRequests.length; i++) {
|
||||||
|
registerRequests[i].appId = appId;
|
||||||
|
}
|
||||||
|
var signRequests = [];
|
||||||
|
for (var i = 0; i < registeredKeys.length; i++) {
|
||||||
|
signRequests[i] = {
|
||||||
|
version: registeredKeys[i].version,
|
||||||
|
challenge: registerRequests[0],
|
||||||
|
keyHandle: registeredKeys[i].keyHandle,
|
||||||
|
appId: appId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
|
||||||
|
signRequests: signRequests,
|
||||||
|
registerRequests: registerRequests,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// JS 1.1 API
|
||||||
|
return {
|
||||||
|
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
|
||||||
|
appId: appId,
|
||||||
|
registerRequests: registerRequests,
|
||||||
|
registeredKeys: registeredKeys,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a message on the underlying channel.
|
||||||
|
* @param {Object} message
|
||||||
|
*/
|
||||||
|
u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
|
||||||
|
this.port_.postMessage(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the HTML 5 addEventListener interface. Works only for the
|
||||||
|
* onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function({data: Object})} handler
|
||||||
|
*/
|
||||||
|
u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
|
||||||
|
function(eventName, handler) {
|
||||||
|
var name = eventName.toLowerCase();
|
||||||
|
if (name == 'message' || name == 'onmessage') {
|
||||||
|
this.port_.onMessage.addListener(function(message) {
|
||||||
|
// Emulate a minimal MessageEvent object
|
||||||
|
handler({'data': message});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('WrappedChromeRuntimePort only supports onMessage');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap the Authenticator app with a MessagePort interface.
|
||||||
|
* @constructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_ = function() {
|
||||||
|
this.requestId_ = -1;
|
||||||
|
this.requestObject_ = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch the Authenticator intent.
|
||||||
|
* @param {Object} message
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
|
||||||
|
var intentUrl =
|
||||||
|
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
|
||||||
|
';S.request=' + encodeURIComponent(JSON.stringify(message)) +
|
||||||
|
';end';
|
||||||
|
document.location = intentUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells what type of port this is.
|
||||||
|
* @return {String} port type
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() {
|
||||||
|
return "WrappedAuthenticatorPort_";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the HTML 5 addEventListener interface.
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function({data: Object})} handler
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) {
|
||||||
|
var name = eventName.toLowerCase();
|
||||||
|
if (name == 'message') {
|
||||||
|
var self = this;
|
||||||
|
/* Register a callback to that executes when
|
||||||
|
* chrome injects the response. */
|
||||||
|
window.addEventListener(
|
||||||
|
'message', self.onRequestUpdate_.bind(self, handler), false);
|
||||||
|
} else {
|
||||||
|
console.error('WrappedAuthenticatorPort only supports message');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked when a response is received from the Authenticator.
|
||||||
|
* @param function({data: Object}) callback
|
||||||
|
* @param {Object} message message Object
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
|
||||||
|
function(callback, message) {
|
||||||
|
var messageObject = JSON.parse(message.data);
|
||||||
|
var intentUrl = messageObject['intentURL'];
|
||||||
|
|
||||||
|
var errorCode = messageObject['errorCode'];
|
||||||
|
var responseObject = null;
|
||||||
|
if (messageObject.hasOwnProperty('data')) {
|
||||||
|
responseObject = /** @type {Object} */ (
|
||||||
|
JSON.parse(messageObject['data']));
|
||||||
|
}
|
||||||
|
|
||||||
|
callback({'data': responseObject});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base URL for intents to Authenticator.
|
||||||
|
* @const
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
|
||||||
|
'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap the iOS client app with a MessagePort interface.
|
||||||
|
* @constructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.WrappedIosPort_ = function() {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch the iOS client app request
|
||||||
|
* @param {Object} message
|
||||||
|
*/
|
||||||
|
u2f.WrappedIosPort_.prototype.postMessage = function(message) {
|
||||||
|
var str = JSON.stringify(message);
|
||||||
|
var url = "u2f://auth?" + encodeURI(str);
|
||||||
|
location.replace(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells what type of port this is.
|
||||||
|
* @return {String} port type
|
||||||
|
*/
|
||||||
|
u2f.WrappedIosPort_.prototype.getPortType = function() {
|
||||||
|
return "WrappedIosPort_";
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulates the HTML 5 addEventListener interface.
|
||||||
|
* @param {string} eventName
|
||||||
|
* @param {function({data: Object})} handler
|
||||||
|
*/
|
||||||
|
u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) {
|
||||||
|
var name = eventName.toLowerCase();
|
||||||
|
if (name !== 'message') {
|
||||||
|
console.error('WrappedIosPort only supports message');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up an embedded trampoline iframe, sourced from the extension.
|
||||||
|
* @param {function(MessagePort)} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getIframePort_ = function(callback) {
|
||||||
|
// Create the iframe
|
||||||
|
var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
|
||||||
|
var iframe = document.createElement('iframe');
|
||||||
|
iframe.src = iframeOrigin + '/u2f-comms.html';
|
||||||
|
iframe.setAttribute('style', 'display:none');
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
|
||||||
|
var channel = new MessageChannel();
|
||||||
|
var ready = function(message) {
|
||||||
|
if (message.data == 'ready') {
|
||||||
|
channel.port1.removeEventListener('message', ready);
|
||||||
|
callback(channel.port1);
|
||||||
|
} else {
|
||||||
|
console.error('First event on iframe port was not "ready"');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
channel.port1.addEventListener('message', ready);
|
||||||
|
channel.port1.start();
|
||||||
|
|
||||||
|
iframe.addEventListener('load', function() {
|
||||||
|
// Deliver the port to the iframe and initialize
|
||||||
|
iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//High-level JS API
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default extension response timeout in seconds.
|
||||||
|
* @const
|
||||||
|
*/
|
||||||
|
u2f.EXTENSION_TIMEOUT_SEC = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A singleton instance for a MessagePort to the extension.
|
||||||
|
* @type {MessagePort|u2f.WrappedChromeRuntimePort_}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.port_ = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks waiting for a port
|
||||||
|
* @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.waitingForPort_ = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A counter for requestIds.
|
||||||
|
* @type {number}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.reqCounter_ = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map from requestIds to client callbacks
|
||||||
|
* @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
|
||||||
|
* |function((u2f.Error|u2f.SignResponse)))>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.callbackMap_ = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates or retrieves the MessagePort singleton to use.
|
||||||
|
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.getPortSingleton_ = function(callback) {
|
||||||
|
if (u2f.port_) {
|
||||||
|
callback(u2f.port_);
|
||||||
|
} else {
|
||||||
|
if (u2f.waitingForPort_.length == 0) {
|
||||||
|
u2f.getMessagePort(function(port) {
|
||||||
|
u2f.port_ = port;
|
||||||
|
u2f.port_.addEventListener('message',
|
||||||
|
/** @type {function(Event)} */ (u2f.responseHandler_));
|
||||||
|
|
||||||
|
// Careful, here be async callbacks. Maybe.
|
||||||
|
while (u2f.waitingForPort_.length)
|
||||||
|
u2f.waitingForPort_.shift()(u2f.port_);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
u2f.waitingForPort_.push(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles response messages from the extension.
|
||||||
|
* @param {MessageEvent.<u2f.Response>} message
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
u2f.responseHandler_ = function(message) {
|
||||||
|
var response = message.data;
|
||||||
|
var reqId = response['requestId'];
|
||||||
|
if (!reqId || !u2f.callbackMap_[reqId]) {
|
||||||
|
console.error('Unknown or missing requestId in response.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var cb = u2f.callbackMap_[reqId];
|
||||||
|
delete u2f.callbackMap_[reqId];
|
||||||
|
cb(response['responseData']);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an array of sign requests to available U2F tokens.
|
||||||
|
* If the JS API version supported by the extension is unknown, it first sends a
|
||||||
|
* message to the extension to find out the supported API version and then it sends
|
||||||
|
* the sign request.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {string=} challenge
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
|
||||||
|
if (js_api_version === undefined) {
|
||||||
|
// Send a message to get the extension to JS API version, then send the actual sign request.
|
||||||
|
u2f.getApiVersion(
|
||||||
|
function (response) {
|
||||||
|
js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
|
||||||
|
console.log("Extension JS API Version: ", js_api_version);
|
||||||
|
u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// We know the JS API version. Send the actual sign request in the supported API version.
|
||||||
|
u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an array of sign requests to available U2F tokens.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {string=} challenge
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.SignResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
|
||||||
|
u2f.getPortSingleton_(function(port) {
|
||||||
|
var reqId = ++u2f.reqCounter_;
|
||||||
|
u2f.callbackMap_[reqId] = callback;
|
||||||
|
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
|
||||||
|
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
|
||||||
|
var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
|
||||||
|
port.postMessage(req);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches register requests to available U2F tokens. An array of sign
|
||||||
|
* requests identifies already registered tokens.
|
||||||
|
* If the JS API version supported by the extension is unknown, it first sends a
|
||||||
|
* message to the extension to find out the supported API version and then it sends
|
||||||
|
* the register request.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {Array<u2f.RegisterRequest>} registerRequests
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
|
||||||
|
if (js_api_version === undefined) {
|
||||||
|
// Send a message to get the extension to JS API version, then send the actual register request.
|
||||||
|
u2f.getApiVersion(
|
||||||
|
function (response) {
|
||||||
|
js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version'];
|
||||||
|
console.log("Extension JS API Version: ", js_api_version);
|
||||||
|
u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
|
||||||
|
callback, opt_timeoutSeconds);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// We know the JS API version. Send the actual register request in the supported API version.
|
||||||
|
u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
|
||||||
|
callback, opt_timeoutSeconds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches register requests to available U2F tokens. An array of sign
|
||||||
|
* requests identifies already registered tokens.
|
||||||
|
* @param {string=} appId
|
||||||
|
* @param {Array<u2f.RegisterRequest>} registerRequests
|
||||||
|
* @param {Array<u2f.RegisteredKey>} registeredKeys
|
||||||
|
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
|
||||||
|
u2f.getPortSingleton_(function(port) {
|
||||||
|
var reqId = ++u2f.reqCounter_;
|
||||||
|
u2f.callbackMap_[reqId] = callback;
|
||||||
|
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
|
||||||
|
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
|
||||||
|
var req = u2f.formatRegisterRequest_(
|
||||||
|
appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
|
||||||
|
port.postMessage(req);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a message to the extension to find out the supported
|
||||||
|
* JS API version.
|
||||||
|
* If the user is on a mobile phone and is thus using Google Authenticator instead
|
||||||
|
* of the Chrome extension, don't send the request and simply return 0.
|
||||||
|
* @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
|
||||||
|
* @param {number=} opt_timeoutSeconds
|
||||||
|
*/
|
||||||
|
u2f.getApiVersion = function(callback, opt_timeoutSeconds) {
|
||||||
|
u2f.getPortSingleton_(function(port) {
|
||||||
|
// If we are using Android Google Authenticator or iOS client app,
|
||||||
|
// do not fire an intent to ask which JS API version to use.
|
||||||
|
if (port.getPortType) {
|
||||||
|
var apiVersion;
|
||||||
|
switch (port.getPortType()) {
|
||||||
|
case 'WrappedIosPort_':
|
||||||
|
case 'WrappedAuthenticatorPort_':
|
||||||
|
apiVersion = 1.1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
apiVersion = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
callback({ 'js_api_version': apiVersion });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var reqId = ++u2f.reqCounter_;
|
||||||
|
u2f.callbackMap_[reqId] = callback;
|
||||||
|
var req = {
|
||||||
|
type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
|
||||||
|
timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
|
||||||
|
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
|
||||||
|
requestId: reqId
|
||||||
|
};
|
||||||
|
port.postMessage(req);
|
||||||
|
});
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user