forked from google/u2f-ref-code
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gnubby-u2f.js
154 lines (146 loc) · 5.01 KB
/
gnubby-u2f.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Copyright 2014 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 Gnubby methods related to U2F support.
*/
'use strict';
// Commands and flags of the Gnubby applet
/** Enroll */
Gnubby.U2F_ENROLL = 0x01;
/** Request signature */
Gnubby.U2F_SIGN = 0x02;
/** Request protocol version */
Gnubby.U2F_VERSION = 0x03;
/** Request applet version */
Gnubby.APPLET_VERSION = 0x11; // First 3 bytes are applet version.
// APDU.P1 flags
/** Test of User Presence required */
Gnubby.P1_TUP_REQUIRED = 0x01;
/** Consume a Test of User Presence */
Gnubby.P1_TUP_CONSUME = 0x02;
/** Test signature only, no TUP. E.g. to check for existing enrollments. */
Gnubby.P1_TUP_TESTONLY = 0x04;
/** Attest with device key */
Gnubby.P1_INDIVIDUAL_KEY = 0x80;
// Version values
/** V1 of the applet. */
Gnubby.U2F_V1 = 'U2F_V1';
/** V2 of the applet. */
Gnubby.U2F_V2 = 'U2F_V2';
/** Perform enrollment
* @param {Array<number>|ArrayBuffer|Uint8Array} challenge Enrollment challenge
* @param {Array<number>|ArrayBuffer|Uint8Array} appIdHash Hashed application
* id
* @param {function(...)} cb Result callback
* @param {boolean=} opt_individualAttestation Request the individual
* attestation cert rather than the batch one.
*/
Gnubby.prototype.enroll = function(challenge, appIdHash, cb,
opt_individualAttestation) {
var p1 = Gnubby.P1_TUP_REQUIRED | Gnubby.P1_TUP_CONSUME;
if (opt_individualAttestation) {
p1 |= Gnubby.P1_INDIVIDUAL_KEY;
}
var apdu = new Uint8Array(
[0x00,
Gnubby.U2F_ENROLL,
p1,
0x00, 0x00, 0x00,
challenge.length + appIdHash.length]);
var u8 = new Uint8Array(apdu.length + challenge.length +
appIdHash.length + 2);
for (var i = 0; i < apdu.length; ++i) u8[i] = apdu[i];
for (var i = 0; i < challenge.length; ++i) u8[i + apdu.length] =
challenge[i];
for (var i = 0; i < appIdHash.length; ++i) {
u8[i + apdu.length + challenge.length] = appIdHash[i];
}
this.apduReply(u8.buffer, cb);
};
/** Request signature
* @param {Array<number>|ArrayBuffer|Uint8Array} challengeHash Hashed
* signature challenge
* @param {Array<number>|ArrayBuffer|Uint8Array} appIdHash Hashed application
* id
* @param {Array<number>|ArrayBuffer|Uint8Array} keyHandle Key handle to use
* @param {function(...)} cb Result callback
* @param {boolean=} opt_nowink Request signature without winking
* (e.g. during enroll)
*/
Gnubby.prototype.sign = function(challengeHash, appIdHash, keyHandle, cb,
opt_nowink) {
var self = this;
// The sign command's format is ever-so-slightly different between V1 and V2,
// so get this gnubby's version prior to sending it.
this.version(function(rc, opt_data) {
if (rc) {
cb(rc);
return;
}
var version = UTIL_BytesToString(new Uint8Array(opt_data || []));
var apduDataLen =
challengeHash.length + appIdHash.length + keyHandle.length;
if (version != Gnubby.U2F_V1) {
// The V2 sign command includes a length byte for the key handle.
apduDataLen++;
}
var apdu = new Uint8Array(
[0x00,
Gnubby.U2F_SIGN,
Gnubby.P1_TUP_REQUIRED | Gnubby.P1_TUP_CONSUME,
0x00, 0x00, 0x00,
apduDataLen]);
if (opt_nowink) {
// A signature request that does not want winking.
// These are used during enroll to figure out whether a gnubby was already
// enrolled.
// Tell applet to not actually produce a signature, even
// if already touched.
apdu[2] |= Gnubby.P1_TUP_TESTONLY;
}
var u8 = new Uint8Array(apdu.length + apduDataLen + 2);
for (var i = 0; i < apdu.length; ++i) u8[i] = apdu[i];
for (var i = 0; i < challengeHash.length; ++i) u8[i + apdu.length] =
challengeHash[i];
for (var i = 0; i < appIdHash.length; ++i) {
u8[i + apdu.length + challengeHash.length] = appIdHash[i];
}
var keyHandleOffset = apdu.length + challengeHash.length + appIdHash.length;
if (version != Gnubby.U2F_V1) {
u8[keyHandleOffset++] = keyHandle.length;
}
for (var i = 0; i < keyHandle.length; ++i) {
u8[i + keyHandleOffset] = keyHandle[i];
}
self.apduReply(u8.buffer, cb, opt_nowink);
});
};
/** Request version information
* @param {function(...)} cb Callback
*/
Gnubby.prototype.version = function(cb) {
if (!cb) cb = Gnubby.defaultCallback;
if (this.version_) {
cb(-GnubbyDevice.OK, this.version_);
return;
}
var self = this;
var apdu = new Uint8Array([0x00, Gnubby.U2F_VERSION, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00]);
this.apduReply(apdu.buffer, function(rc, data) {
if (rc == 0x6d00) {
// Command not implemented. Pretend this is v1.
var v1 = new Uint8Array(UTIL_StringToBytes(Gnubby.U2F_V1));
self.version_ = v1.buffer;
cb(-GnubbyDevice.OK, v1.buffer);
} else {
if (!rc) {
self.version_ = data;
}
cb(rc, data);
}
});
};