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