add wallet channel

This commit is contained in:
Conor Patrick 2018-07-07 22:43:06 -04:00
parent fbbf430152
commit 7df04964cc
20 changed files with 2067 additions and 8 deletions

File diff suppressed because one or more lines are too long

View File

@ -74,7 +74,7 @@
<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.outputsettings.clockselect" value="Disabled"/>
<property object="USART1" propertyId="usart.synchronoussettings.baudrate" value="140000"/>
<property object="USART1" propertyId="usart.synchronoussettings.baudrate" value="130000"/>
</mode>
<modeTransition>
<property object="RESET &#x2192; DefaultMode" propertyId="modeTransition.source" value="RESET"/>

View File

@ -334,7 +334,7 @@ extern void USART1_enter_DefaultMode_from_RESET(void) {
USART_InitSync_TypeDef initsync = USART_INITSYNC_DEFAULT;
initsync.enable = usartDisable;
initsync.baudrate = 140000;
initsync.baudrate = 130000;
initsync.databits = usartDatabits8;
initsync.master = 1;
initsync.msbf = 1;

View File

@ -12,6 +12,8 @@
#define USING_DEV_BOARD
#define BRIDGE_TO_WALLET
void printing_init();

View File

@ -31,6 +31,7 @@ struct logtag tagtable[] = {
{TAG_GREEN,"DEBUG"},
{TAG_RED,"DEBUG"},
{TAG_TIME,"TIME"},
{TAG_WALLET,"WALLET"},
};

View File

@ -25,6 +25,7 @@ typedef enum
TAG_TIME= (1 << 11),
TAG_HID = (1 << 12),
TAG_USB = (1 << 13),
TAG_WALLET = (1 << 14),
TAG_FILENO = (1<<31)
} LOG_TAG;

View File

@ -27,6 +27,7 @@ int main(int argc, char * argv[])
// TAG_GEN|
/*TAG_MC |*/
/*TAG_GA |*/
TAG_WALLET |
/*TAG_CP |*/
// TAG_CTAP|
// TAG_HID|

View File

@ -4,12 +4,13 @@
#include "crypto.h"
#include "log.h"
#include "device.h"
#include "app.h"
// void u2f_response_writeback(uint8_t * buf, uint8_t len);
static int16_t u2f_register(struct u2f_register_request * req);
static int16_t u2f_version();
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;
@ -28,7 +29,43 @@ void u2f_request(struct u2f_request_apdu* req, CTAP_RESPONSE * resp)
rcode = U2F_SW_CLASS_NOT_SUPPORTED;
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)
{
case U2F_REGISTER:
@ -73,7 +110,7 @@ void u2f_request(struct u2f_request_apdu* req, CTAP_RESPONSE * resp)
rcode = U2F_SW_INS_NOT_SUPPORTED;
break;
}
#endif
end:
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)
{

View File

@ -93,6 +93,9 @@ struct u2f_authenticate_request
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 */
//////////////////////////////////////////////////////////////////

63
fido2/wallet.c Normal file
View 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
View 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
View 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-----

1
web/.srl Normal file
View File

@ -0,0 +1 @@
D7564E117010E076

87
web/index.html Normal file
View 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
View 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
View 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
View 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

View 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
View 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
View 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);
});
};