From 7c80515b342eeb099d3b6d159723b6014562d777 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Sun, 4 Nov 2018 17:54:40 +0100 Subject: [PATCH] Fix U2F authentication by upgrading libraries. --- client/src/lib/secondfactor/U2FValidator.ts | 32 +--- client/src/lib/secondfactor/index.ts | 2 - client/src/lib/u2f-register/u2f-register.ts | 19 +- client/types/u2f-api-polyfill.d.ts | 167 ------------------ package-lock.json | 11 +- package.json | 2 +- .../secondfactor/u2f/sign_request/get.spec.ts | 33 ++-- .../secondfactor/u2f/sign_request/get.ts | 20 +-- 8 files changed, 28 insertions(+), 258 deletions(-) delete mode 100644 client/types/u2f-api-polyfill.d.ts diff --git a/client/src/lib/secondfactor/U2FValidator.ts b/client/src/lib/secondfactor/U2FValidator.ts index fdaf639b..a2e3cd3f 100644 --- a/client/src/lib/secondfactor/U2FValidator.ts +++ b/client/src/lib/secondfactor/U2FValidator.ts @@ -1,5 +1,5 @@ import U2f = require("u2f"); -import U2fApi = require("u2f-api-polyfill"); +import U2fApi from "u2f-api"; import BluebirdPromise = require("bluebird"); import { SignMessage } from "../../../../shared/SignMessage"; import Endpoints = require("../../../../shared/api"); @@ -31,46 +31,20 @@ function finishU2fAuthentication(responseData: U2fApi.SignResponse, }); } -function u2fApiSign(appId: string, challenge: string, - registeredKey: U2fApi.RegisteredKey, timeout: number) - : BluebirdPromise { - - return new BluebirdPromise(function (resolve, reject) { - (window).u2f.sign(appId, challenge, [registeredKey], - function (signResponse: U2fApi.SignResponse | U2fApi.U2FError) { - if ((signResponse).errorCode != 0) { - reject(new Error((signResponse as U2fApi.U2FError).errorMessage)); - return; - } - resolve(signResponse as U2fApi.SignResponse); - }, timeout); - }); -} - function startU2fAuthentication($: JQueryStatic, notifier: INotifier) : BluebirdPromise { return GetPromised($, Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, {}, undefined, "json") - .then(function (signResponse: SignMessage) { + .then(function (signRequest: U2f.Request) { notifier.info(UserMessages.PLEASE_TOUCH_TOKEN); - - const registeredKey: U2fApi.RegisteredKey = { - keyHandle: signResponse.keyHandle, - version: "U2F_V2", - appId: signResponse.request.appId, - transports: [] - }; - - return u2fApiSign(signResponse.request.appId, - signResponse.request.challenge, registeredKey, 60); + return U2fApi.sign(signRequest, 60); }) .then(function (signResponse: U2fApi.SignResponse) { return finishU2fAuthentication(signResponse, $); }); } - export function validate($: JQueryStatic, notifier: INotifier) { return startU2fAuthentication($, notifier) .catch(function (err: Error) { diff --git a/client/src/lib/secondfactor/index.ts b/client/src/lib/secondfactor/index.ts index a345807a..c145db8f 100644 --- a/client/src/lib/secondfactor/index.ts +++ b/client/src/lib/secondfactor/index.ts @@ -1,5 +1,3 @@ -import jslogger = require("js-logger"); -import U2fApi = require("u2f-api-polyfill"); import TOTPValidator = require("./TOTPValidator"); import U2FValidator = require("./U2FValidator"); import ClientConstants = require("./constants"); diff --git a/client/src/lib/u2f-register/u2f-register.ts b/client/src/lib/u2f-register/u2f-register.ts index cc15258e..3c09fdbb 100644 --- a/client/src/lib/u2f-register/u2f-register.ts +++ b/client/src/lib/u2f-register/u2f-register.ts @@ -1,8 +1,7 @@ import BluebirdPromise = require("bluebird"); import U2f = require("u2f"); -import U2fApi = require("u2f-api-polyfill"); -import jslogger = require("js-logger"); +import * as U2fApi from "u2f-api"; import { Notifier } from "../Notifier"; import GetPromised from "../GetPromised"; import Endpoints = require("../../../../shared/api"); @@ -29,25 +28,11 @@ export default function (window: Window, $: JQueryStatic) { }); } - function register(appId: string, registerRequest: U2fApi.RegisterRequest, - timeout: number): BluebirdPromise { - return new BluebirdPromise((resolve, reject) => { - (window as any).u2f.register(appId, [registerRequest], [], - (res: U2fApi.RegisterResponse | U2fApi.U2FError) => { - if ((res).errorCode != 0) { - reject(new Error((res).errorMessage)); - return; - } - resolve(res); - }, timeout); - }); - } - function requestRegistration(): BluebirdPromise { return GetPromised($, Endpoints.SECOND_FACTOR_U2F_REGISTER_REQUEST_GET, {}, undefined, "json") .then((registrationRequest: U2f.Request) => { - return register(registrationRequest.appId, registrationRequest, 60); + return U2fApi.register(registrationRequest, [], 60); }) .then((res) => checkRegistration(res)); } diff --git a/client/types/u2f-api-polyfill.d.ts b/client/types/u2f-api-polyfill.d.ts deleted file mode 100644 index 67ddf4a7..00000000 --- a/client/types/u2f-api-polyfill.d.ts +++ /dev/null @@ -1,167 +0,0 @@ -// Base 64 using `-` and `_`, without trailing `=`. -// See: -// - https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-javascript-api-v1.2-ps-20170411.html#key-words -// - https://tools.ietf.org/html/rfc4648#section-5 -type WebSafeBase64 = string; - -// Blob with fields -type BinaryEncoded = string; - -type URL = String; -type WebOrigin = URL; -type TrustedFacetsURL = URL; - -// U2F Types - -type Challenge = WebSafeBase64; -type Typ = "navigator.id.getAssertion" | "navigator.id.finishEnrollment" - -// TODO: Which fields are optional? -type ClientData = { - typ: Typ, - challenge: Challenge, - origin: WebOrigin - cid_pubkey: "unused" -} - -type RegistrationData = { - keyHandle: string; - publicKey: string; -} - -type SignatureData = { - // TODO -} - -// TODO -type KeyHandle = string; - - -// Polyfill-specific types for `u2f-api-polyfill.d.ts` that are not defined in -// `u2f-api-polyfill` itself. - -type AppID = TrustedFacetsURL; // A URL -type EncodedClientData = WebSafeBase64; // TODO -type EncodedRegistrationData = BinaryEncoded; -type EncodedSignatureData = BinaryEncoded; // TODO -type ErrorMessage = string; -type EncodedKeyHandle = WebSafeBase64; -type PolyfillVersion = "U2F_V2"; // TODO: are other values supported? -type RequestID = number; -type Seconds = number; - -// Types from `u2f-api-polyfill`. - -export const EXTENSION_ID: string; - -export enum MessageType { - 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" -} - -export enum ErrorCode { - OK = 0, - OTHER_ERROR = 1, - BAD_REQUEST = 2, - CONFIGURATION_UNSUPPORTED = 3, - DEVICE_INELIGIBLE = 4, - TIMEOUT = 5 -} - -type U2FError = { - errorCode: ErrorCode, - errorMessage?: ErrorMessage -} - -// TODO: What are the values? -export enum Transport { - BLUETOOTH_RADIO, - BLUETOOTH_LOW_ENERGY, - USB, - NFC -} - -type SignResponse = { - keyHandle: EncodedKeyHandle, - signatureData: EncodedSignatureData, - clientData: EncodedClientData -} - -type RegisterRequest = { - version: PolyfillVersion, - challenge: Challenge -} - -type RegisterResponse = { - version: PolyfillVersion, - challenge: Challenge, - EncodedregistrationData: EncodedRegistrationData, - clientData: EncodedClientData -} - -type RegisteredKey = { - version: PolyfillVersion, - keyHandle: EncodedKeyHandle, - transports: Transport[], - appId?: AppID -} - -type GetJsApiVersionResponse = { - js_api_version: number -} - -// TODO: WrappedChromeRuntimePort_? -export function getMessagePort( - callback: (m: MessagePort) => void -): void; - -// TODO: function formatSignRequest_ is not marked as private? -// TODO: function formatRegisterRequest_ is not marked as private? - -// Default extension response timeout in seconds. -export const EXTENSION_TIMEOUT_SEC: Seconds; - -// 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. -export function sign( - appId: AppID | undefined, - challenge: Challenge | undefined, - registeredKeys: RegisteredKey[], - callback: (response: (U2FError | SignResponse)) => void, - timeout?: Seconds -): void; - -// Dispatches an array of sign requests to available U2F tokens. -export const sendSignRequest: typeof sign; - -// 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. -export function register( - appId: AppID | undefined, - registerRequests: RegisterRequest[], - registeredKeys: RegisteredKey[], - callback: (response: (U2FError | RegisterResponse)) => void, - timeout?: Seconds -): void; - -// Dispatches register requests to available U2F tokens. An array of sign -// requests identifies already registered tokens. -export const sendRegisterRequest: typeof register; - -// 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. -export function getApiVersion( - callback: (response: (U2FError | GetJsApiVersionResponse)) => void, - timeout?: Seconds -): void; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9a03f086..a38f989c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6276,7 +6276,8 @@ }, "fill-range": { "version": "2.2.3", - "resolved": "", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", "dev": true, "requires": { "is-number": "^2.1.0", @@ -10851,10 +10852,10 @@ "resolved": "https://registry.npmjs.org/u2f/-/u2f-0.1.3.tgz", "integrity": "sha512-/IaxeBqjo5o3D7plPkxdApbCpgGoI2bmTomS1kq5OjVflaE9UBJ0WfqoXqZryZKfFYBjQC7Tn1hA57WtRgh/Sg==" }, - "u2f-api-polyfill": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/u2f-api-polyfill/-/u2f-api-polyfill-0.4.4.tgz", - "integrity": "sha512-qg3LBBHzN46zNE+ySChra8i9PecrWk83DmEkxxMJ9wAy8wV3FGJi6gtV32L+pCIP+kTaxhIvxQe2k76OMuHe9Q==" + "u2f-api": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/u2f-api/-/u2f-api-1.0.7.tgz", + "integrity": "sha512-aey5tGk4hfw+EMKveDt0IQbM/5VCcACUBRpKU4iU42J6aD9xnmUH6aXFTVWkgfXsNKotbaNW0Tq4L1FKArI4bQ==" }, "uc.micro": { "version": "1.0.5", diff --git a/package.json b/package.json index 07e4655f..f188402b 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "redis": "^2.8.0", "speakeasy": "^2.0.0", "u2f": "^0.1.2", - "u2f-api-polyfill": "^0.4.4", + "u2f-api": "^1.0.7", "winston": "^2.3.1", "yamljs": "^0.3.0" }, diff --git a/server/src/lib/routes/secondfactor/u2f/sign_request/get.spec.ts b/server/src/lib/routes/secondfactor/u2f/sign_request/get.spec.ts index db2fb0c3..dd52b27e 100644 --- a/server/src/lib/routes/secondfactor/u2f/sign_request/get.spec.ts +++ b/server/src/lib/routes/secondfactor/u2f/sign_request/get.spec.ts @@ -4,9 +4,7 @@ import BluebirdPromise = require("bluebird"); import assert = require("assert"); import U2FSignRequestGet = require("./get"); import ExpressMock = require("../../../../stubs/express.spec"); -import { UserDataStoreStub } from "../../../../storage/UserDataStoreStub.spec"; -import U2FMock = require("../../../../stubs/u2f.spec"); -import U2f = require("u2f"); +import { Request } from "u2f"; import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../ServerVariablesMockBuilder.spec"; import { ServerVariables } from "../../../../ServerVariables"; @@ -40,10 +38,6 @@ describe("routes/secondfactor/u2f/sign_request/get", function () { mocks = s.mocks; vars = s.variables; - const options = { - inMemoryOnly: true - }; - res = ExpressMock.ResponseMock(); res.send = sinon.spy(); res.json = sinon.spy(); @@ -51,24 +45,23 @@ describe("routes/secondfactor/u2f/sign_request/get", function () { }); it("should send back the sign request and save it in the session", function () { - const expectedRequest: U2f.RegistrationResult = { - keyHandle: "keyHandle", - publicKey: "publicKey", - certificate: "Certificate", - successful: true + const expectedRequest: Request = { + version: "U2F_V2", + appId: 'app', + challenge: 'challenge!' }; mocks.u2f.requestStub.returns(expectedRequest); - mocks.userDataStore.retrieveU2FRegistrationStub.returns(BluebirdPromise.resolve({ - registration: { - publicKey: "PUBKEY", - keyHandle: "KeyHandle" - } - })); + mocks.userDataStore.retrieveU2FRegistrationStub + .returns(BluebirdPromise.resolve({ + registration: { + keyHandle: "KeyHandle" + } + })); return U2FSignRequestGet.default(vars)(req as any, res as any) - .then(function () { + .then(() => { assert.deepEqual(expectedRequest, req.session.auth.sign_request); - assert.deepEqual(expectedRequest, res.json.getCall(0).args[0].request); + assert.deepEqual(expectedRequest, res.json.getCall(0).args[0]); }); }); }); diff --git a/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts b/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts index 640ca255..9c19e468 100644 --- a/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts +++ b/server/src/lib/routes/secondfactor/u2f/sign_request/get.ts @@ -1,14 +1,9 @@ -import objectPath = require("object-path"); -import U2f = require("u2f"); import u2f_common = require("../../../secondfactor/u2f/U2FCommon"); import BluebirdPromise = require("bluebird"); import express = require("express"); -import { UserDataStore } from "../../../../storage/UserDataStore"; import { U2FRegistrationDocument } from "../../../../storage/U2FRegistrationDocument"; -import { Winston } from "../../../../../../types/Dependencies"; import exceptions = require("../../../../Exceptions"); -import { SignMessage } from "../../../../../../../shared/SignMessage"; import ErrorReplies = require("../../../../ErrorReplies"); import { AuthenticationSessionHandler } from "../../../../AuthenticationSessionHandler"; import UserMessages = require("../../../../../../../shared/UserMessages"); @@ -27,7 +22,7 @@ export default function (vars: ServerVariables) { .then(function () { return vars.userDataStore.retrieveU2FRegistration(authSession.userid, appId); }) - .then(function (doc: U2FRegistrationDocument): BluebirdPromise { + .then(function (doc: U2FRegistrationDocument): BluebirdPromise { if (!doc) return BluebirdPromise.reject(new exceptions.AccessDeniedError("No U2F registration found")); @@ -36,17 +31,8 @@ export default function (vars: ServerVariables) { vars.logger.debug(req, "AppId = %s, keyHandle = %s", appId, JSON.stringify(doc.registration.keyHandle)); const request = vars.u2f.request(appId, doc.registration.keyHandle); - const authenticationMessage: SignMessage = { - request: request, - keyHandle: doc.registration.keyHandle - }; - return BluebirdPromise.resolve(authenticationMessage); - }) - .then(function (authenticationMessage: SignMessage) { - vars.logger.info(req, "Store authentication request and reply"); - vars.logger.debug(req, "AuthenticationRequest = %s", authenticationMessage); - authSession.sign_request = authenticationMessage.request; - res.json(authenticationMessage); + res.json(request); + authSession.sign_request = request; return BluebirdPromise.resolve(); }) .catch(ErrorReplies.replyWithError200(req, res, vars.logger,