New: Update `webauthnsample` to use ES6
This commit is contained in:
Родитель
eb7db7daa4
Коммит
fbe28c87a3
16
app.js
16
app.js
|
@ -1,8 +1,8 @@
|
|||
var express = require("express");
|
||||
var app = express();
|
||||
var fido = require('./fido.js');
|
||||
var bodyParser = require('body-parser');
|
||||
var enforce = require('express-sslify');
|
||||
const express = require("express");
|
||||
const app = express();
|
||||
const fido = require('./fido.js');
|
||||
const bodyParser = require('body-parser');
|
||||
const enforce = require('express-sslify');
|
||||
|
||||
if (process.env.ENFORCE_SSL_HEROKU === "true") {
|
||||
app.use(enforce.HTTPS({ trustProtoHeader: true }));
|
||||
|
@ -16,7 +16,7 @@ app.use(bodyParser.urlencoded({ extended: true }));
|
|||
|
||||
app.get('/challenge', async (req, res) => {
|
||||
try {
|
||||
var challenge = await fido.getChallenge();
|
||||
const challenge = await fido.getChallenge();
|
||||
res.json({
|
||||
result: challenge
|
||||
});
|
||||
|
@ -29,7 +29,7 @@ app.get('/challenge', async (req, res) => {
|
|||
|
||||
app.put('/credentials', async (req, res) => {
|
||||
try {
|
||||
var credential = await fido.makeCredential(req.body);
|
||||
const credential = await fido.makeCredential(req.body);
|
||||
res.json({
|
||||
result: credential
|
||||
});
|
||||
|
@ -42,7 +42,7 @@ app.put('/credentials', async (req, res) => {
|
|||
|
||||
app.put('/assertion', async (req, res) => {
|
||||
try {
|
||||
var credential = await fido.verifyAssertion(req.body);
|
||||
const credential = await fido.verifyAssertion(req.body);
|
||||
res.json({
|
||||
result: credential
|
||||
});
|
||||
|
|
94
fido.js
94
fido.js
|
@ -1,18 +1,18 @@
|
|||
var base64url = require('base64url');
|
||||
var cbor = require('cbor');
|
||||
var uuid = require('uuid-parse');
|
||||
var jwkToPem = require('jwk-to-pem');
|
||||
var jwt = require('jsonwebtoken');
|
||||
var crypto = require('crypto');
|
||||
var url = require('url');
|
||||
const base64url = require('base64url');
|
||||
const cbor = require('cbor');
|
||||
const uuid = require('uuid-parse');
|
||||
const jwkToPem = require('jwk-to-pem');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const crypto = require('crypto');
|
||||
const url = require('url');
|
||||
|
||||
var storage = require('./storage.js');
|
||||
const storage = require('./storage.js');
|
||||
|
||||
var hostname = process.env.HOSTNAME || "localhost";
|
||||
var jwt_secret = process.env.JWT_SECRET || "defaultsecret";
|
||||
const hostname = process.env.HOSTNAME || "localhost";
|
||||
const jwt_secret = process.env.JWT_SECRET || "defaultsecret";
|
||||
|
||||
|
||||
var fido = {};
|
||||
const fido = {};
|
||||
|
||||
/**
|
||||
* Gets an opaque challenge for the client.
|
||||
|
@ -43,7 +43,7 @@ fido.makeCredential = async (attestation) => {
|
|||
|
||||
//Step 1-2: Let C be the parsed the client data claimed as collected during
|
||||
//the credential creation
|
||||
var C;
|
||||
let C;
|
||||
try {
|
||||
C = JSON.parse(attestation.clientDataJSON);
|
||||
} catch (e) {
|
||||
|
@ -53,17 +53,17 @@ fido.makeCredential = async (attestation) => {
|
|||
//Step 3-6: Verify client data
|
||||
validateClientData(C, "webauthn.create");
|
||||
//Step 7: Compute the hash of response.clientDataJSON using SHA-256.
|
||||
var clientDataHash = sha256(attestation.clientDataJSON);
|
||||
const clientDataHash = sha256(attestation.clientDataJSON);
|
||||
|
||||
//Step 8: Perform CBOR decoding on the attestationObject
|
||||
var attestationObject;
|
||||
let attestationObject;
|
||||
try {
|
||||
attestationObject = cbor.decodeFirstSync(Buffer.from(attestation.attestationObject, 'base64'));
|
||||
} catch (e) {
|
||||
throw new Error("attestationObject could not be decoded");
|
||||
}
|
||||
//Step 8.1: Parse authData data inside the attestationObject
|
||||
var authenticatorData = parseAuthenticatorData(attestationObject.authData);
|
||||
const authenticatorData = parseAuthenticatorData(attestationObject.authData);
|
||||
//Step 8.2: authenticatorData should contain attestedCredentialData
|
||||
if (!authenticatorData.attestedCredentialData)
|
||||
throw new Exception("Did not see AD flag in authenticatorData");
|
||||
|
@ -84,10 +84,10 @@ fido.makeCredential = async (attestation) => {
|
|||
throw new Error("User Verified bit was not set.");
|
||||
}
|
||||
|
||||
//Steps 12-19 are skipped because this is a sample app.
|
||||
//Steps 12-19 are skipped because this is a sample app.
|
||||
|
||||
//Store the credential
|
||||
var credential = await storage.Credentials.create({
|
||||
const credential = await storage.Credentials.create({
|
||||
id: authenticatorData.attestedCredentialData.credentialId.toString('base64'),
|
||||
publicKeyJwk: authenticatorData.attestedCredentialData.publicKeyJwk,
|
||||
signCount: authenticatorData.signCount
|
||||
|
@ -107,26 +107,26 @@ fido.verifyAssertion = async (assertion) => {
|
|||
|
||||
// Step 1 and 2 are skipped because this is a sample app
|
||||
|
||||
// Step 3: Using credential’s id attribute look up the corresponding
|
||||
// Step 3: Using credential’s id attribute look up the corresponding
|
||||
// credential public key.
|
||||
var credential = await storage.Credentials.findOne({
|
||||
let credential = await storage.Credentials.findOne({
|
||||
id: assertion.id
|
||||
});
|
||||
if (!credential) {
|
||||
throw new Error("Could not find credential with that ID");
|
||||
}
|
||||
var publicKey = credential.publicKeyJwk;
|
||||
const publicKey = credential.publicKeyJwk;
|
||||
if (!publicKey)
|
||||
throw new Error("Could not read stored credential public key");
|
||||
|
||||
// Step 4: Let cData, authData and sig denote the value of credential’s
|
||||
// Step 4: Let cData, authData and sig denote the value of credential’s
|
||||
// response's clientDataJSON, authenticatorData, and signature respectively
|
||||
var cData = assertion.clientDataJSON;
|
||||
var authData = Buffer.from(assertion.authenticatorData, 'base64');
|
||||
var sig = Buffer.from(assertion.signature, 'base64');
|
||||
const cData = assertion.clientDataJSON;
|
||||
const authData = Buffer.from(assertion.authenticatorData, 'base64');
|
||||
const sig = Buffer.from(assertion.signature, 'base64');
|
||||
|
||||
// Step 5 and 6: Let C be the decoded client data claimed by the signature.
|
||||
var C;
|
||||
let C;
|
||||
try {
|
||||
C = JSON.parse(cData);
|
||||
} catch (e) {
|
||||
|
@ -136,7 +136,7 @@ fido.verifyAssertion = async (assertion) => {
|
|||
validateClientData(C, "webauthn.get");
|
||||
|
||||
//Parse authenticator data used for the next few steps
|
||||
var authenticatorData = parseAuthenticatorData(authData);
|
||||
const authenticatorData = parseAuthenticatorData(authData);
|
||||
|
||||
//Step 11: Verify that the rpIdHash in authData is the SHA-256 hash of the
|
||||
//RP ID expected by the Relying Party.
|
||||
|
@ -154,7 +154,7 @@ fido.verifyAssertion = async (assertion) => {
|
|||
throw new Error("User Verified bit was not set.");
|
||||
}
|
||||
|
||||
//Step 14: Verify that the values of the client extension outputs in
|
||||
//Step 14: Verify that the values of the client extension outputs in
|
||||
//clientExtensionResults and the authenticator extension outputs in the
|
||||
//extensions in authData are as expected
|
||||
if (authenticatorData.extensionData) {
|
||||
|
@ -162,15 +162,14 @@ fido.verifyAssertion = async (assertion) => {
|
|||
throw new Error("Received unexpected extension data");
|
||||
}
|
||||
|
||||
//Step 15: Let hash be the result of computing a hash over the cData using
|
||||
//Step 15: Let hash be the result of computing a hash over the cData using
|
||||
//SHA-256.
|
||||
var hash = sha256(cData);
|
||||
const hash = sha256(cData);
|
||||
|
||||
//Step 16: Using the credential public key looked up in step 3, verify
|
||||
//Step 16: Using the credential public key looked up in step 3, verify
|
||||
//that sig is a valid signature over the binary concatenation of authData
|
||||
//and hash.
|
||||
var publicKey = credential.publicKeyJwk;
|
||||
var verify = (publicKey.kty === "RSA") ? crypto.createVerify('RSA-SHA256') : crypto.createVerify('sha256');
|
||||
const verify = (publicKey.kty === "RSA") ? crypto.createVerify('RSA-SHA256') : crypto.createVerify('sha256');
|
||||
verify.update(authData);
|
||||
verify.update(hash);
|
||||
if (!verify.verify(jwkToPem(publicKey), sig))
|
||||
|
@ -210,21 +209,21 @@ fido.verifyAssertion = async (assertion) => {
|
|||
* @property {string} credentialId
|
||||
* @property {number} credentialIdLength
|
||||
*/
|
||||
function parseAuthenticatorData(authData) {
|
||||
const parseAuthenticatorData = authData => {
|
||||
try {
|
||||
var authenticatorData = {};
|
||||
const authenticatorData = {};
|
||||
|
||||
authenticatorData.rpIdHash = authData.slice(0, 32);
|
||||
authenticatorData.flags = authData[32];
|
||||
authenticatorData.signCount = (authData[33] << 24) | (authData[34] << 16) | (authData[35] << 8) | (authData[36]);
|
||||
|
||||
if (authenticatorData.flags & 64) {
|
||||
var attestedCredentialData = {};
|
||||
const attestedCredentialData = {};
|
||||
attestedCredentialData.aaguid = uuid.unparse(authData.slice(37, 53)).toUpperCase();
|
||||
attestedCredentialData.credentialIdLength = (authData[53] << 8) | authData[54];
|
||||
attestedCredentialData.credentialId = authData.slice(55, 55 + attestedCredentialData.credentialIdLength);
|
||||
//Public key is the first CBOR element of the remaining buffer
|
||||
var publicKeyCoseBuffer = authData.slice(55 + attestedCredentialData.credentialIdLength, authData.length);
|
||||
const publicKeyCoseBuffer = authData.slice(55 + attestedCredentialData.credentialIdLength, authData.length);
|
||||
|
||||
//convert public key to JWK for storage
|
||||
attestedCredentialData.publicKeyJwk = coseToJwk(publicKeyCoseBuffer);
|
||||
|
@ -235,10 +234,10 @@ function parseAuthenticatorData(authData) {
|
|||
if (authenticatorData.flags & 128) {
|
||||
//has extension data
|
||||
|
||||
var extensionDataCbor;
|
||||
let extensionDataCbor;
|
||||
|
||||
if (authenticatorData.attestedCredentialData) {
|
||||
//if we have attesttestedCredentialData, then extension data is
|
||||
//if we have attesttestedCredentialData, then extension data is
|
||||
//the second element
|
||||
extensionDataCbor = cbor.decodeAllSync(authData.slice(55 + authenticatorData.attestedCredentialData.credentialIdLength, authData.length));
|
||||
extensionDataCbor = extensionDataCbor[1];
|
||||
|
@ -261,11 +260,11 @@ function parseAuthenticatorData(authData) {
|
|||
* @param {any} clientData JSON parsed client data object received from client
|
||||
* @param {string} type Operation type: webauthn.create or webauthn.get
|
||||
*/
|
||||
function validateClientData(clientData, type) {
|
||||
const validateClientData = (clientData, type) => {
|
||||
if (clientData.type !== type)
|
||||
throw new Error("collectedClientData type was expected to be " + type);
|
||||
|
||||
var origin;
|
||||
let origin;
|
||||
try {
|
||||
origin = url.parse(clientData.origin);
|
||||
} catch (e) {
|
||||
|
@ -278,8 +277,7 @@ function validateClientData(clientData, type) {
|
|||
if (hostname !== "localhost" && origin.protocol !== "https:")
|
||||
throw new Error("Invalid origin in collectedClientData. Expected HTTPS protocol.");
|
||||
|
||||
var decodedChallenge;
|
||||
|
||||
let decodedChallenge;
|
||||
try {
|
||||
decodedChallenge = jwt.verify(base64url.decode(clientData.challenge), jwt_secret);
|
||||
} catch (err) {
|
||||
|
@ -292,10 +290,10 @@ function validateClientData(clientData, type) {
|
|||
* @param {Buffer} cose Buffer containing COSE key data
|
||||
* @returns {any} JWK object
|
||||
*/
|
||||
function coseToJwk(cose) {
|
||||
const coseToJwk = cose => {
|
||||
try {
|
||||
var publicKeyJwk = {};
|
||||
var publicKeyCbor = cbor.decodeFirstSync(cose);
|
||||
let publicKeyJwk = {};
|
||||
const publicKeyCbor = cbor.decodeFirstSync(cose);
|
||||
|
||||
if (publicKeyCbor.get(3) == -7) {
|
||||
publicKeyJwk = {
|
||||
|
@ -322,11 +320,11 @@ function coseToJwk(cose) {
|
|||
|
||||
/**
|
||||
* Evaluates the sha256 hash of a buffer
|
||||
* @param {Buffer} data
|
||||
* @param {Buffer} data
|
||||
* @returns sha256 of the input data
|
||||
*/
|
||||
function sha256(data) {
|
||||
var hash = crypto.createHash('sha256');
|
||||
const sha256 = data => {
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(data);
|
||||
return hash.digest();
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
"description": "WebAuthn Sample App",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node app.js"
|
||||
},
|
||||
"author": "Ibrahim Damlaj <idamlaj@gmail.com> (https://github.com/MicrosoftEdge/webauthnsample)",
|
||||
"license": "MIT",
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
.btn {
|
||||
width: 240px;
|
||||
margin-right:10px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
margin-right:10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
|
@ -15,3 +15,8 @@
|
|||
.errorText {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.wordWrap {
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
|
@ -80,7 +80,7 @@
|
|||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<div class="alert alert-primary hidden" role="alert" id="status">
|
||||
<div class="alert alert-primary hidden wordWrap" role="alert" id="status">
|
||||
Successfully received assertion
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
var mongodb_url = process.env.MONGODB_URL || 'mongodb://localhost/fido';
|
||||
const mongodb_url = process.env.MONGODB_URL || 'mongodb://localhost/fido';
|
||||
mongoose.connect(mongodb_url);
|
||||
|
||||
var storage = {};
|
||||
const storage = {};
|
||||
|
||||
storage.Credentials = mongoose.model('Credential', new mongoose.Schema({
|
||||
id: {type: String, index: true},
|
||||
|
|
Загрузка…
Ссылка в новой задаче