diff options
author | Matthew Miller <matthew@millerti.me> | 2020-05-25 11:11:48 -0700 |
---|---|---|
committer | Matthew Miller <matthew@millerti.me> | 2020-05-25 11:11:48 -0700 |
commit | ce486db665fd805fff59772dfc852ac2d4cb3a43 (patch) | |
tree | a727ea2d928d50cc576bd150d08cc4de679e22f5 | |
parent | 279723fc1cb10e653431a9d7dbe3592f760b3c8b (diff) |
Run Prettier over everything
42 files changed, 512 insertions, 525 deletions
diff --git a/example/README.md b/example/README.md index 599faa8..1154d77 100644 --- a/example/README.md +++ b/example/README.md @@ -8,7 +8,7 @@ A fully-functional reference implementation of [@webauthntine/server](../package ### SSL Certificate -Websites that wish to leverage WebAuthn *must* be served over HTTPS, **including during development!** +Websites that wish to leverage WebAuthn _must_ be served over HTTPS, **including during development!** Here's one technique for setting up SSL for a local dev instance: diff --git a/example/index.js b/example/index.js index 1f1c0b8..6cc1408 100644 --- a/example/index.js +++ b/example/index.js @@ -28,7 +28,7 @@ app.use(express.json()); * RP ID represents the "scope" of websites on which a authenticator should be usable. The Origin * represents the expected URL from which an attestation or assertion occurs. */ -const rpID = 'dev.yourdomain.com'; +const rpID = 'dev.dontneeda.pw'; const origin = `https://${rpID}`; /** * WebAuthn expects you to be able to uniquely identify the user that performs an attestation or @@ -113,13 +113,9 @@ app.get('/generate-attestation-options', (req, res) => { const challenge = 'totallyUniqueValueEveryAttestation'; inMemoryUserDeviceDB[loggedInUserId].currentChallenge = challenge; - res.send(generateAttestationOptions( - 'WebAuthntine Example', - rpID, - challenge, - loggedInUserId, - username, - )); + res.send( + generateAttestationOptions('WebAuthntine Example', rpID, challenge, loggedInUserId, username), + ); }); app.post('/verify-attestation', (req, res) => { @@ -131,11 +127,7 @@ app.post('/verify-attestation', (req, res) => { let verification; try { - verification = verifyAttestationResponse( - body, - expectedChallenge, - origin, - ); + verification = verifyAttestationResponse(body, expectedChallenge, origin); } catch (error) { console.error(error); return res.status(400).send({ error: error.message }); @@ -147,7 +139,7 @@ app.post('/verify-attestation', (req, res) => { const { base64PublicKey, base64CredentialID, counter } = authenticatorInfo; const existingDevice = user.devices.find( - (device) => device.base64CredentialID === base64CredentialID, + device => device.base64CredentialID === base64CredentialID, ); if (!existingDevice) { @@ -180,10 +172,12 @@ app.get('/generate-assertion-options', (req, res) => { const challenge = 'totallyUniqueValueEveryAssertion'; inMemoryUserDeviceDB[loggedInUserId].currentChallenge = challenge; - res.send(generateAssertionOptions( - challenge, - user.devices.map(data => data.base64CredentialID), - )); + res.send( + generateAssertionOptions( + challenge, + user.devices.map(data => data.base64CredentialID), + ), + ); }); app.post('/verify-assertion', (req, res) => { @@ -195,7 +189,7 @@ app.post('/verify-assertion', (req, res) => { let dbAuthenticator; // "Query the DB" here for an authenticator matching `base64CredentialID` - for(let dev of user.devices) { + for (let dev of user.devices) { if (dev.base64CredentialID === body.base64CredentialID) { dbAuthenticator = dev; break; @@ -204,12 +198,7 @@ app.post('/verify-assertion', (req, res) => { let verification; try { - verification = verifyAssertionResponse( - body, - expectedChallenge, - origin, - dbAuthenticator, - ); + verification = verifyAssertionResponse(body, expectedChallenge, origin, dbAuthenticator); } catch (error) { console.error(error); return res.status(400).send({ error: error.message }); @@ -225,16 +214,21 @@ app.post('/verify-assertion', (req, res) => { res.send({ verified }); }); -https.createServer({ - /** - * You'll need to provide a SSL cert and key here because - * WebAuthn can only be run from HTTPS:// URLs - * - * HINT: If you create a `dev` subdomain A-record that points to 127.0.0.1, - * you can manually generate an HTTPS certificate for it using Let's Encrypt certbot. - */ - key: fs.readFileSync('./dev.yourdomain.com.key'), - cert: fs.readFileSync('./dev.yourdomain.com.crt'), -}, app).listen(port, host, () => { - console.log(`🚀 Server ready at https://${host}:${port}`); -}); +https + .createServer( + { + /** + * You'll need to provide a SSL cert and key here because + * WebAuthn can only be run from HTTPS:// URLs + * + * HINT: If you create a `dev` subdomain A-record that points to 127.0.0.1, + * you can manually generate an HTTPS certificate for it using Let's Encrypt certbot. + */ + key: fs.readFileSync('./dev.yourdomain.com.key'), + cert: fs.readFileSync('./dev.yourdomain.com.crt'), + }, + app, + ) + .listen(port, host, () => { + console.log(`🚀 Server ready at https://${host}:${port}`); + }); diff --git a/example/public/index.html b/example/public/index.html index 4a42d55..e313ba7 100644 --- a/example/public/index.html +++ b/example/public/index.html @@ -1,20 +1,16 @@ <!DOCTYPE html> <html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <link rel="stylesheet" href="./styles.css" /> - <title>WebAuthntine Example Site</title> -</head> -<body> - <div class="container"> - <h1>WebAuthntine Example Site</h1> - <p> - 🚪 <a href="/register">Register</a> - </p> - <p> - 🔐 <a href="/login">Login</a> - </p> - </div> -</body> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="stylesheet" href="./styles.css" /> + <title>WebAuthntine Example Site</title> + </head> + <body> + <div class="container"> + <h1>WebAuthntine Example Site</h1> + <p>🚪 <a href="/register">Register</a></p> + <p>🔐 <a href="/login">Login</a></p> + </div> + </body> </html> diff --git a/example/public/login/index.html b/example/public/login/index.html index be4ea71..8652ede 100644 --- a/example/public/login/index.html +++ b/example/public/login/index.html @@ -1,62 +1,64 @@ <!DOCTYPE html> <html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <script src="https://unpkg.com/@webauthntine/browser@0.2.2/dist/webauthntine-browser.min.js"></script> - <link rel="stylesheet" href="../styles.css" /> - <title>WebAuthntine Example Site | Login</title> -</head> -<body> - <div class="container"> - <p> - <span>⬅️ <a href="/">Go Back</a></span> - </p> - <h1>🔐 Login</h1> - <h2>(a.k.a. "Assertion")</h2> - <button id="btnBegin">Begin Login</button> - <p id="success"></p> - <p id="error"></p> - </div> - <script> - const elemBegin = document.getElementById('btnBegin'); - const elemSuccess = document.getElementById('success'); - const elemError = document.getElementById('error'); + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <script src="https://unpkg.com/@webauthntine/browser@0.2.2/dist/webauthntine-browser.min.js"></script> + <link rel="stylesheet" href="../styles.css" /> + <title>WebAuthntine Example Site | Login</title> + </head> + <body> + <div class="container"> + <p> + <span>⬅️ <a href="/">Go Back</a></span> + </p> + <h1>🔐 Login</h1> + <h2>(a.k.a. "Assertion")</h2> + <button id="btnBegin">Begin Login</button> + <p id="success"></p> + <p id="error"></p> + </div> + <script> + const elemBegin = document.getElementById('btnBegin'); + const elemSuccess = document.getElementById('success'); + const elemError = document.getElementById('error'); - const { startAssertion } = WebAuthntineBrowser; + const { startAssertion } = WebAuthntineBrowser; - elemBegin.addEventListener('click', (async () => { - // Reset success/error messages - elemSuccess.innerHTML = ''; - elemError.innerHTML = ''; + elemBegin.addEventListener('click', async () => { + // Reset success/error messages + elemSuccess.innerHTML = ''; + elemError.innerHTML = ''; - const resp = await fetch('/generate-assertion-options'); + const resp = await fetch('/generate-assertion-options'); - let asseResp; - try { - const opts = await resp.json(); - asseResp = await startAssertion(opts); - } catch (error) { - elemError.innerText = error; - throw new Error(error); - } + let asseResp; + try { + const opts = await resp.json(); + asseResp = await startAssertion(opts); + } catch (error) { + elemError.innerText = error; + throw new Error(error); + } - const verificationResp = await fetch('/verify-assertion', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(asseResp), - }); + const verificationResp = await fetch('/verify-assertion', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(asseResp), + }); - const verificationJSON = await verificationResp.json(); + const verificationJSON = await verificationResp.json(); - if (verificationJSON && verificationJSON.verified) { - elemSuccess.innerHTML = 'Success! <a href="/register">Try to register again?</a> 🚪'; - } else { - elemError.innerHTML = `Oh no, something went wrong! Response: <pre>${JSON.stringify(verificationJSON)}</pre>`; - } - })); - </script> -</body> + if (verificationJSON && verificationJSON.verified) { + elemSuccess.innerHTML = 'Success! <a href="/register">Try to register again?</a> 🚪'; + } else { + elemError.innerHTML = `Oh no, something went wrong! Response: <pre>${JSON.stringify( + verificationJSON, + )}</pre>`; + } + }); + </script> + </body> </html> diff --git a/example/public/register/index.html b/example/public/register/index.html index 583f678..ea4403b 100644 --- a/example/public/register/index.html +++ b/example/public/register/index.html @@ -1,60 +1,62 @@ <!DOCTYPE html> <html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <script src="https://unpkg.com/@webauthntine/browser@0.2.2/dist/webauthntine-browser.min.js"></script> - <link rel="stylesheet" href="../styles.css" /> - <title>WebAuthntine Example Site | Register</title> -</head> -<body> - <div class="container"> - <p> - <span>⬅️ <a href="/">Go Back</a></span> - </p> - <h1>🚪 Register</h1> - <h2>(a.k.a. "Attestation")</h2> - <button id="btnBegin">Begin Registration</button> - <p id="success"></p> - <p id="error"></p> - </div> - <script> - const elemBegin = document.getElementById('btnBegin'); - const elemSuccess = document.getElementById('success'); - const elemError = document.getElementById('error'); + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <script src="https://unpkg.com/@webauthntine/browser@0.2.2/dist/webauthntine-browser.min.js"></script> + <link rel="stylesheet" href="../styles.css" /> + <title>WebAuthntine Example Site | Register</title> + </head> + <body> + <div class="container"> + <p> + <span>⬅️ <a href="/">Go Back</a></span> + </p> + <h1>🚪 Register</h1> + <h2>(a.k.a. "Attestation")</h2> + <button id="btnBegin">Begin Registration</button> + <p id="success"></p> + <p id="error"></p> + </div> + <script> + const elemBegin = document.getElementById('btnBegin'); + const elemSuccess = document.getElementById('success'); + const elemError = document.getElementById('error'); - const { startAttestation } = WebAuthntineBrowser; + const { startAttestation } = WebAuthntineBrowser; - elemBegin.addEventListener('click', (async () => { - // Reset success/error messages - elemSuccess.innerHTML = ''; - elemError.innerHTML = ''; + elemBegin.addEventListener('click', async () => { + // Reset success/error messages + elemSuccess.innerHTML = ''; + elemError.innerHTML = ''; - const resp = await fetch('/generate-attestation-options'); + const resp = await fetch('/generate-attestation-options'); - let attResp; - try { - attResp = await startAttestation(await resp.json()); - } catch (error) { - elemError.innerText = error; - } + let attResp; + try { + attResp = await startAttestation(await resp.json()); + } catch (error) { + elemError.innerText = error; + } - const verificationResp = await fetch('/verify-attestation', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(attResp), - }); + const verificationResp = await fetch('/verify-attestation', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(attResp), + }); - const verificationJSON = await verificationResp.json(); + const verificationJSON = await verificationResp.json(); - if (verificationJSON && verificationJSON.verified) { - elemSuccess.innerHTML = 'Success! <a href="/login">Now try to log in</a> 🔐'; - } else { - elemError.innerHTML = `Oh no, something went wrong! Response: <pre>${JSON.stringify(verificationJSON)}</pre>`; - } - })); - </script> -</body> + if (verificationJSON && verificationJSON.verified) { + elemSuccess.innerHTML = 'Success! <a href="/login">Now try to log in</a> 🔐'; + } else { + elemError.innerHTML = `Oh no, something went wrong! Response: <pre>${JSON.stringify( + verificationJSON, + )}</pre>`; + } + }); + </script> + </body> </html> diff --git a/example/public/styles.css b/example/public/styles.css index 3eeeb7b..f99b391 100644 --- a/example/public/styles.css +++ b/example/public/styles.css @@ -10,7 +10,7 @@ body { justify-content: center; align-items: center; height: 100vh; - background: #F7F7F7; + background: #f7f7f7; } h1 { @@ -28,11 +28,11 @@ button { } button:active { - background: #EEEEEE; + background: #eeeeee; } button:hover { - background: #EFEFEF; + background: #efefef; } .container { @@ -46,5 +46,5 @@ button:hover { } #success { - color: #11A000; + color: #11a000; } diff --git a/packages/browser/README.md b/packages/browser/README.md index b1da211..df22dbd 100644 --- a/packages/browser/README.md +++ b/packages/browser/README.md @@ -1,5 +1,7 @@ <!-- omit in toc --> + # @webauthntine/browser +  [](https://www.npmjs.com/package/@webauthntine/browser)  diff --git a/packages/browser/src/helpers/supportsWebauthn.test.ts b/packages/browser/src/helpers/supportsWebauthn.test.ts index f6b8a8e..6eb42c9 100644 --- a/packages/browser/src/helpers/supportsWebauthn.test.ts +++ b/packages/browser/src/helpers/supportsWebauthn.test.ts @@ -1,4 +1,4 @@ -import supportsWebauthn from './supportsWebauthn' +import supportsWebauthn from './supportsWebauthn'; beforeEach(() => { // @ts-ignore 2741 @@ -17,12 +17,12 @@ test('should return false when browser does not support WebAuthn', () => { test('should return false when window is undefined', () => { // Make window undefined as it is in node environments. // @ts-expect-error - const windowSpy = jest.spyOn(global, "window", "get"); + const windowSpy = jest.spyOn(global, 'window', 'get'); windowSpy.mockImplementation(() => undefined); - expect(window).toBe(undefined) + expect(window).toBe(undefined); expect(supportsWebauthn()).toBe(false); // Restore original window value. - windowSpy.mockRestore() -}) + windowSpy.mockRestore(); +}); diff --git a/packages/browser/src/helpers/supportsWebauthn.ts b/packages/browser/src/helpers/supportsWebauthn.ts index 605bb67..b572080 100644 --- a/packages/browser/src/helpers/supportsWebauthn.ts +++ b/packages/browser/src/helpers/supportsWebauthn.ts @@ -3,7 +3,6 @@ */ export default function supportsWebauthn(): boolean { return ( - window?.PublicKeyCredential !== undefined - && typeof window.PublicKeyCredential === 'function' + window?.PublicKeyCredential !== undefined && typeof window.PublicKeyCredential === 'function' ); } diff --git a/packages/browser/src/helpers/toBase64String.ts b/packages/browser/src/helpers/toBase64String.ts index 9c949be..3178695 100644 --- a/packages/browser/src/helpers/toBase64String.ts +++ b/packages/browser/src/helpers/toBase64String.ts @@ -2,7 +2,5 @@ import base64js from 'base64-js'; export default function toBase64String(buffer: ArrayBuffer): string { // TODO: Make sure converting buffer to Uint8Array() is correct - return base64js.fromByteArray(new Uint8Array(buffer)) - .replace(/\+/g, "-") - .replace(/\//g, "_"); + return base64js.fromByteArray(new Uint8Array(buffer)).replace(/\+/g, '-').replace(/\//g, '_'); } diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 38ce91b..18f5944 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -2,8 +2,4 @@ import startAttestation from './methods/startAttestation'; import startAssertion from './methods/startAssertion'; import supportsWebauthn from './helpers/supportsWebauthn'; -export { - startAttestation, - startAssertion, - supportsWebauthn, -}; +export { startAttestation, startAssertion, supportsWebauthn }; diff --git a/packages/browser/src/methods/startAssertion.test.ts b/packages/browser/src/methods/startAssertion.test.ts index 259400c..2287b49 100644 --- a/packages/browser/src/methods/startAssertion.test.ts +++ b/packages/browser/src/methods/startAssertion.test.ts @@ -1,6 +1,9 @@ import base64js from 'base64-js'; -import { AssertionCredential, PublicKeyCredentialRequestOptionsJSON } from '@webauthntine/typescript-types'; +import { + AssertionCredential, + PublicKeyCredentialRequestOptionsJSON, +} from '@webauthntine/typescript-types'; import toUint8Array from '../helpers/toUint8Array'; import supportsWebauthn from '../helpers/supportsWebauthn'; @@ -10,8 +13,8 @@ import startAssertion from './startAssertion'; jest.mock('../helpers/supportsWebauthn'); -const mockNavigatorGet = (window.navigator.credentials.get as jest.Mock); -const mockSupportsWebauthn = (supportsWebauthn as jest.Mock); +const mockNavigatorGet = window.navigator.credentials.get as jest.Mock; +const mockSupportsWebauthn = supportsWebauthn as jest.Mock; const mockAuthenticatorData = toBase64String(toUint8Array('mockAuthenticatorData')); const mockClientDataJSON = toBase64String(toUint8Array('mockClientDataJSON')); @@ -21,11 +24,13 @@ const mockUserHandle = toBase64String(toUint8Array('mockUserHandle')); const goodOpts1: PublicKeyCredentialRequestOptionsJSON = { publicKey: { challenge: 'fizz', - allowCredentials: [{ - id: 'abcdefgfdnsdfunguisdfgs', - type: 'public-key', - transports: ['nfc'], - }], + allowCredentials: [ + { + id: 'abcdefgfdnsdfunguisdfgs', + type: 'public-key', + transports: ['nfc'], + }, + ], timeout: 1, }, }; @@ -35,15 +40,17 @@ beforeEach(() => { mockSupportsWebauthn.mockReset(); }); -test('should convert options before passing to navigator.credentials.get(...)', async (done) => { +test('should convert options before passing to navigator.credentials.get(...)', async done => { mockSupportsWebauthn.mockReturnValue(true); // Stub out a response so the method won't throw - mockNavigatorGet.mockImplementation((): Promise<any> => { - return new Promise((resolve) => { - resolve({ response: {} }); - }); - }); + mockNavigatorGet.mockImplementation( + (): Promise<any> => { + return new Promise(resolve => { + resolve({ response: {} }); + }); + }, + ); await startAssertion(goodOpts1); @@ -57,27 +64,29 @@ test('should convert options before passing to navigator.credentials.get(...)', done(); }); -test('should return base64-encoded response values', async (done) => { +test('should return base64-encoded response values', async done => { mockSupportsWebauthn.mockReturnValue(true); const credentialID = 'foobar'; - mockNavigatorGet.mockImplementation((): Promise<AssertionCredential> => { - return new Promise((resolve) => { - resolve({ - id: 'foobar', - rawId: toUint8Array('foobar'), - response: { - authenticatorData: base64js.toByteArray(mockAuthenticatorData), - clientDataJSON: base64js.toByteArray(mockClientDataJSON), - signature: base64js.toByteArray(mockSignature), - userHandle: base64js.toByteArray(mockUserHandle), - }, - getClientExtensionResults: () => ({}), - type: 'webauthn.get', + mockNavigatorGet.mockImplementation( + (): Promise<AssertionCredential> => { + return new Promise(resolve => { + resolve({ + id: 'foobar', + rawId: toUint8Array('foobar'), + response: { + authenticatorData: base64js.toByteArray(mockAuthenticatorData), + clientDataJSON: base64js.toByteArray(mockClientDataJSON), + signature: base64js.toByteArray(mockSignature), + userHandle: base64js.toByteArray(mockUserHandle), + }, + getClientExtensionResults: () => ({}), + type: 'webauthn.get', + }); }); - }); - }); + }, + ); const response = await startAssertion(goodOpts1); @@ -90,24 +99,28 @@ test('should return base64-encoded response values', async (done) => { }); done(); -}) +}); -test('should throw error if WebAuthn isn\'t supported', async (done) => { +test("should throw error if WebAuthn isn't supported", async done => { mockSupportsWebauthn.mockReturnValue(false); - await expect(startAssertion(goodOpts1)).rejects.toThrow('WebAuthn is not supported in this browser'); + await expect(startAssertion(goodOpts1)).rejects.toThrow( + 'WebAuthn is not supported in this browser', + ); done(); }); -test('should throw error if assertion is cancelled for some reason', async (done) => { +test('should throw error if assertion is cancelled for some reason', async done => { mockSupportsWebauthn.mockReturnValue(true); - mockNavigatorGet.mockImplementation((): Promise<null> => { - return new Promise((resolve) => { - resolve(null); - }); - }); + mockNavigatorGet.mockImplementation( + (): Promise<null> => { + return new Promise(resolve => { + resolve(null); + }); + }, + ); await expect(startAssertion(goodOpts1)).rejects.toThrow('Assertion was not completed'); diff --git a/packages/browser/src/methods/startAssertion.ts b/packages/browser/src/methods/startAssertion.ts index 0733194..36c7194 100644 --- a/packages/browser/src/methods/startAssertion.ts +++ b/packages/browser/src/methods/startAssertion.ts @@ -15,7 +15,7 @@ import supportsWebauthn from '../helpers/supportsWebauthn'; * @param requestOptionsJSON Output from @webauthntine/server's generateAssertionOptions(...) */ export default async function startAssertion( - requestOptionsJSON: PublicKeyCredentialRequestOptionsJSON + requestOptionsJSON: PublicKeyCredentialRequestOptionsJSON, ): Promise<AuthenticatorAssertionResponseJSON> { if (!supportsWebauthn()) { throw new Error('WebAuthn is not supported in this browser'); @@ -25,16 +25,16 @@ export default async function startAssertion( const publicKey: PublicKeyCredentialRequestOptions = { ...requestOptionsJSON.publicKey, challenge: toUint8Array(requestOptionsJSON.publicKey.challenge), - allowCredentials: requestOptionsJSON.publicKey.allowCredentials.map((cred) => { + allowCredentials: requestOptionsJSON.publicKey.allowCredentials.map(cred => { // Make sure the credential ID length is a multiple of 4 - const padLength = 4 - cred.id.length % 4; + const padLength = 4 - (cred.id.length % 4); let id = cred.id.padEnd(cred.id.length + padLength, '='); return { ...cred, id: base64js.toByteArray(id), }; - }) + }), }; // Wait for the user to complete assertion @@ -44,7 +44,7 @@ export default async function startAssertion( throw new Error('Assertion was not completed'); } - const { response } = (credential as AssertionCredential); + const { response } = credential as AssertionCredential; let base64UserHandle = undefined; if (response.userHandle) { diff --git a/packages/browser/src/methods/startAttestation.test.ts b/packages/browser/src/methods/startAttestation.test.ts index 0efec48..539ffe5 100644 --- a/packages/browser/src/methods/startAttestation.test.ts +++ b/packages/browser/src/methods/startAttestation.test.ts @@ -1,6 +1,9 @@ import base64js from 'base64-js'; -import { AttestationCredential, PublicKeyCredentialCreationOptionsJSON } from '@webauthntine/typescript-types'; +import { + AttestationCredential, + PublicKeyCredentialCreationOptionsJSON, +} from '@webauthntine/typescript-types'; import toUint8Array from '../helpers/toUint8Array'; import supportsWebauthn from '../helpers/supportsWebauthn'; @@ -9,8 +12,8 @@ import startAttestation from './startAttestation'; jest.mock('../helpers/supportsWebauthn'); -const mockNavigatorCreate = (window.navigator.credentials.create as jest.Mock); -const mockSupportsWebauthn = (supportsWebauthn as jest.Mock); +const mockNavigatorCreate = window.navigator.credentials.create as jest.Mock; +const mockSupportsWebauthn = supportsWebauthn as jest.Mock; const mockAttestationObject = 'mockAtte'; const mockClientDataJSON = 'mockClie'; @@ -19,10 +22,12 @@ const goodOpts1: PublicKeyCredentialCreationOptionsJSON = { publicKey: { challenge: 'fizz', attestation: 'direct', - pubKeyCredParams: [{ - alg: -7, - type: "public-key", - }], + pubKeyCredParams: [ + { + alg: -7, + type: 'public-key', + }, + ], rp: { id: '1234', name: 'webauthntine', @@ -41,15 +46,17 @@ beforeEach(() => { mockSupportsWebauthn.mockReset(); }); -test('should convert options before passing to navigator.credentials.create(...)', async (done) => { +test('should convert options before passing to navigator.credentials.create(...)', async done => { mockSupportsWebauthn.mockReturnValue(true); // Stub out a response so the method won't throw - mockNavigatorCreate.mockImplementation((): Promise<any> => { - return new Promise((resolve) => { - resolve({ response: {} }); - }); - }); + mockNavigatorCreate.mockImplementation( + (): Promise<any> => { + return new Promise(resolve => { + resolve({ response: {} }); + }); + }, + ); await startAttestation(goodOpts1); @@ -61,23 +68,25 @@ test('should convert options before passing to navigator.credentials.create(...) done(); }); -test('should return base64-encoded response values', async (done) => { +test('should return base64-encoded response values', async done => { mockSupportsWebauthn.mockReturnValue(true); - mockNavigatorCreate.mockImplementation((): Promise<AttestationCredential> => { - return new Promise((resolve) => { - resolve({ - id: 'foobar', - rawId: toUint8Array('foobar'), - response: { - attestationObject: base64js.toByteArray(mockAttestationObject), - clientDataJSON: base64js.toByteArray(mockClientDataJSON), - }, - getClientExtensionResults: () => ({}), - type: 'webauthn.create', + mockNavigatorCreate.mockImplementation( + (): Promise<AttestationCredential> => { + return new Promise(resolve => { + resolve({ + id: 'foobar', + rawId: toUint8Array('foobar'), + response: { + attestationObject: base64js.toByteArray(mockAttestationObject), + clientDataJSON: base64js.toByteArray(mockClientDataJSON), + }, + getClientExtensionResults: () => ({}), + type: 'webauthn.create', + }); }); - }); - }); + }, + ); const response = await startAttestation(goodOpts1); @@ -87,24 +96,28 @@ test('should return base64-encoded response values', async (done) => { }); done(); -}) +}); -test('should throw error if WebAuthn isn\'t supported', async (done) => { +test("should throw error if WebAuthn isn't supported", async done => { mockSupportsWebauthn.mockReturnValue(false); - await expect(startAttestation(goodOpts1)).rejects.toThrow('WebAuthn is not supported in this browser'); + await expect(startAttestation(goodOpts1)).rejects.toThrow( + 'WebAuthn is not supported in this browser', + ); done(); }); -test('should throw error if attestation is cancelled for some reason', async (done) => { +test('should throw error if attestation is cancelled for some reason', async done => { mockSupportsWebauthn.mockReturnValue(true); - mockNavigatorCreate.mockImplementation((): Promise<null> => { - return new Promise((resolve) => { - resolve(null); - }); - }); + mockNavigatorCreate.mockImplementation( + (): Promise<null> => { + return new Promise(resolve => { + resolve(null); + }); + }, + ); await expect(startAttestation(goodOpts1)).rejects.toThrow('Attestation was not completed'); diff --git a/packages/browser/src/methods/startAttestation.ts b/packages/browser/src/methods/startAttestation.ts index 1a4b13d..c095670 100644 --- a/packages/browser/src/methods/startAttestation.ts +++ b/packages/browser/src/methods/startAttestation.ts @@ -14,7 +14,7 @@ import supportsWebauthn from '../helpers/supportsWebauthn'; * @param creationOptionsJSON Output from @webauthntine/server's generateAttestationOptions(...) */ export default async function startAttestation( - creationOptionsJSON: PublicKeyCredentialCreationOptionsJSON + creationOptionsJSON: PublicKeyCredentialCreationOptionsJSON, ): Promise<AuthenticatorAttestationResponseJSON> { if (!supportsWebauthn()) { throw new Error('WebAuthn is not supported in this browser'); @@ -37,7 +37,7 @@ export default async function startAttestation( throw new Error('Attestation was not completed'); } - const { response } = (credential as AttestationCredential); + const { response } = credential as AttestationCredential; // Convert values to base64 to make it easier to send back to the server return { diff --git a/packages/browser/tsconfig.json b/packages/browser/tsconfig.json index 9ddf207..349e40c 100644 --- a/packages/browser/tsconfig.json +++ b/packages/browser/tsconfig.json @@ -2,6 +2,6 @@ "extends": "../../tsconfig.json", "compilerOptions": { "baseUrl": "./src", - "outDir": "./dist", + "outDir": "./dist" } } diff --git a/packages/browser/webpack.config.js b/packages/browser/webpack.config.js index c70d050..54e8f83 100644 --- a/packages/browser/webpack.config.js +++ b/packages/browser/webpack.config.js @@ -13,7 +13,7 @@ module.exports = { test: /.ts$/, use: 'ts-loader', exclude: /node_modules/, - } + }, ], }, resolve: { @@ -38,6 +38,6 @@ module.exports = { tag: 'Version: {version} - {date}', }, }, - }) + }), ], }; diff --git a/packages/server/README.md b/packages/server/README.md index 4610926..83bc845 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -1,5 +1,7 @@ <!-- omit in toc --> + # @webauthntine/server +  [](https://www.npmjs.com/package/@webauthntine/server)  @@ -23,7 +25,6 @@ It can then be imported into a Node project as usual: import WebAuthntineServer from '@webauthntine/server'; // CommonJS const WebAuthntineServer = require('@webauthntine/server'); - ``` ## Usage - Coming Soon diff --git a/packages/server/src/assertion/generateAssertionOptions.test.ts b/packages/server/src/assertion/generateAssertionOptions.test.ts index f979c74..c125f48 100644 --- a/packages/server/src/assertion/generateAssertionOptions.test.ts +++ b/packages/server/src/assertion/generateAssertionOptions.test.ts @@ -33,13 +33,10 @@ test('should generate credential request options suitable for sending via JSON', }); test('defaults to 60 seconds if no timeout is specified', () => { - const options = generateAssertionOptions( - 'totallyrandomvalue', - [ - Buffer.from('1234', 'ascii').toString('base64'), - Buffer.from('5678', 'ascii').toString('base64'), - ], - ); + const options = generateAssertionOptions('totallyrandomvalue', [ + Buffer.from('1234', 'ascii').toString('base64'), + Buffer.from('5678', 'ascii').toString('base64'), + ]); expect(options.publicKey.timeout).toEqual(60000); }); diff --git a/packages/server/src/assertion/generateAssertionOptions.ts b/packages/server/src/assertion/generateAssertionOptions.ts index e6107d6..5dc4c84 100644 --- a/packages/server/src/assertion/generateAssertionOptions.ts +++ b/packages/server/src/assertion/generateAssertionOptions.ts @@ -1,6 +1,5 @@ import { PublicKeyCredentialRequestOptionsJSON } from '@webauthntine/typescript-types'; - /** * Prepare a value to pass into navigator.credentials.get(...) for authenticator "login" * diff --git a/packages/server/src/assertion/verifyAssertionResponse.test.ts b/packages/server/src/assertion/verifyAssertionResponse.test.ts index 293a4a6..5895b66 100644 --- a/packages/server/src/assertion/verifyAssertionResponse.test.ts +++ b/packages/server/src/assertion/verifyAssertionResponse.test.ts @@ -47,7 +47,9 @@ test('should return authenticator info after verification', () => { ); expect(verification.authenticatorInfo.counter).toEqual(144); - expect(verification.authenticatorInfo.base64CredentialID).toEqual(authenticator.base64CredentialID); + expect(verification.authenticatorInfo.base64CredentialID).toEqual( + authenticator.base64CredentialID, + ); }); test('should throw when response challenge is not expected value', () => { @@ -81,12 +83,7 @@ test('should throw when assertion type is not webauthn.create', () => { }); expect(() => { - verifyAssertionResponse( - assertionResponse, - assertionChallenge, - assertionOrigin, - authenticator, - ); + verifyAssertionResponse(assertionResponse, assertionChallenge, assertionOrigin, authenticator); }).toThrow(/assertion type/i); }); @@ -96,12 +93,7 @@ test('should throw error if user was not present', () => { }); expect(() => { - verifyAssertionResponse( - assertionResponse, - assertionChallenge, - assertionOrigin, - authenticator, - ); + verifyAssertionResponse(assertionResponse, assertionChallenge, assertionOrigin, authenticator); }).toThrow(/not present/i); }); @@ -114,32 +106,29 @@ test('should throw error if previous counter value is not less than in response' }; expect(() => { - verifyAssertionResponse( - assertionResponse, - assertionChallenge, - assertionOrigin, - badDevice, - ); + verifyAssertionResponse(assertionResponse, assertionChallenge, assertionOrigin, badDevice); }).toThrow(/counter value/i); }); const assertionResponse = { - base64CredentialID: 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Px' + - 'g6jo_o0hYiew', + base64CredentialID: + 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Px' + 'g6jo_o0hYiew', base64AuthenticatorData: 'PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA==', - base64ClientDataJSON: 'eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJj' + + base64ClientDataJSON: + 'eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJj' + 'bGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5k' + 'b250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0=', - base64Signature: 'MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6' + - 'jhd45bDx92wjXKs900=' + base64Signature: + 'MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6' + + 'jhd45bDx92wjXKs900=', }; const assertionChallenge = 'totallyUniqueValueEveryTime'; const assertionOrigin = 'https://dev.dontneeda.pw'; const authenticator = { - base64PublicKey: 'BIheFp-u6GvFT2LNGovf3ZrT0iFVBsA_76rRysxRG9A18WGeA6hPmnab0HAViUYVRkwTNcN77QBf_' + - 'RR0dv3lIvQ', - base64CredentialID: 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Px' + - 'g6jo_o0hYiew', + base64PublicKey: + 'BIheFp-u6GvFT2LNGovf3ZrT0iFVBsA_76rRysxRG9A18WGeA6hPmnab0HAViUYVRkwTNcN77QBf_' + 'RR0dv3lIvQ', + base64CredentialID: + 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Px' + 'g6jo_o0hYiew', counter: 0, }; diff --git a/packages/server/src/assertion/verifyAssertionResponse.ts b/packages/server/src/assertion/verifyAssertionResponse.ts index 86f7a6b..6f840c8 100644 --- a/packages/server/src/assertion/verifyAssertionResponse.ts +++ b/packages/server/src/assertion/verifyAssertionResponse.ts @@ -3,9 +3,9 @@ import { AuthenticatorAssertionResponseJSON, AuthenticatorDevice, VerifiedAssertion, -} from "@webauthntine/typescript-types"; +} from '@webauthntine/typescript-types'; -import decodeClientDataJSON from "@helpers/decodeClientDataJSON"; +import decodeClientDataJSON from '@helpers/decodeClientDataJSON'; import toHash from '@helpers/toHash'; import convertASN1toPEM from '@helpers/convertASN1toPEM'; @@ -37,7 +37,7 @@ export default function verifyAssertionResponse( if (challenge !== expectedChallenge) { throw new Error( - `Unexpected assertion challenge "${challenge}", expected "${expectedChallenge}"` + `Unexpected assertion challenge "${challenge}", expected "${expectedChallenge}"`, ); } @@ -55,7 +55,7 @@ export default function verifyAssertionResponse( const authDataStruct = parseAuthenticatorData(authDataBuffer); const { flags, counter } = authDataStruct; - if (!(flags.up)) { + if (!flags.up) { throw new Error('User not present during assertion'); } @@ -69,19 +69,10 @@ export default function verifyAssertionResponse( ); } - const { - rpIdHash, - flagsBuf, - counterBuf, - } = authDataStruct; + const { rpIdHash, flagsBuf, counterBuf } = authDataStruct; const clientDataHash = toHash(base64url.toBuffer(base64ClientDataJSON)); - const signatureBase = Buffer.concat([ - rpIdHash, - flagsBuf, - counterBuf, - clientDataHash, - ]); + const signatureBase = Buffer.concat([rpIdHash, flagsBuf, counterBuf, clientDataHash]); const publicKey = convertASN1toPEM(base64url.toBuffer(authenticator.base64PublicKey)); const signature = base64url.toBuffer(base64Signature); diff --git a/packages/server/src/attestation/generateAttestationOptions.test.ts b/packages/server/src/attestation/generateAttestationOptions.test.ts index 4c6e605..d3d49c7 100644 --- a/packages/server/src/attestation/generateAttestationOptions.test.ts +++ b/packages/server/src/attestation/generateAttestationOptions.test.ts @@ -31,10 +31,12 @@ test('should generate credential request options suitable for sending via JSON', name: username, displayName: username, }, - pubKeyCredParams: [{ - alg: -7, - type: 'public-key', - }], + pubKeyCredParams: [ + { + alg: -7, + type: 'public-key', + }, + ], timeout, attestation: attestationType, }, diff --git a/packages/server/src/attestation/generateAttestationOptions.ts b/packages/server/src/attestation/generateAttestationOptions.ts index 68cac94..6d1f7d9 100644 --- a/packages/server/src/attestation/generateAttestationOptions.ts +++ b/packages/server/src/attestation/generateAttestationOptions.ts @@ -1,6 +1,5 @@ import { PublicKeyCredentialCreationOptionsJSON } from '@webauthntine/typescript-types'; - /** * Prepare a value to pass into navigator.credentials.create(...) for authenticator "registration" * @@ -35,10 +34,12 @@ export default function generateAttestationOptions( name: username, displayName: username, }, - pubKeyCredParams: [{ - alg: -7, - type: 'public-key', - }], + pubKeyCredParams: [ + { + alg: -7, + type: 'public-key', + }, + ], timeout, attestation: attestationType, }, diff --git a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts index 5705065..110e665 100644 --- a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts +++ b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts @@ -5,15 +5,14 @@ import { SafetyNetJWTHeader, SafetyNetJWTPayload, SafetyNetJWTSignature, -} from "@webauthntine/typescript-types"; +} from '@webauthntine/typescript-types'; -import toHash from "@helpers/toHash"; +import toHash from '@helpers/toHash'; import verifySignature from '@helpers/verifySignature'; import convertCOSEtoPKCS from '@helpers/convertCOSEtoPKCS'; import getCertificateInfo from '@helpers/getCertificateInfo'; import parseAuthenticatorData from '@helpers/parseAuthenticatorData'; - /** * Verify an attestation response with fmt 'android-safetynet' */ @@ -55,10 +54,7 @@ export default function verifyAttestationAndroidSafetyNet( const { nonce, ctsProfileMatch } = PAYLOAD; const clientDataHash = toHash(base64url.toBuffer(base64ClientDataJSON)); - const nonceBase = Buffer.concat([ - authData, - clientDataHash, - ]); + const nonceBase = Buffer.concat([authData, clientDataHash]); const nonceBuffer = toHash(nonceBase); const expectedNonce = nonceBuffer.toString('base64'); @@ -77,7 +73,7 @@ export default function verifyAttestationAndroidSafetyNet( * START Verify Header */ // Generate an array of certs constituting a full certificate chain - const fullpathCert = HEADER.x5c.concat([GlobalSignRootCAR2]).map((cert) => { + const fullpathCert = HEADER.x5c.concat([GlobalSignRootCAR2]).map(cert => { let pem = ''; // Take a string of characters and chop them up into 64-char lines (just like a PEM cert) for (let i = 0; i < cert.length; i += 64) { @@ -118,7 +114,6 @@ export default function verifyAttestationAndroidSafetyNet( * END Verify Signature */ - if (toReturn.verified) { toReturn.userVerified = flags.uv; @@ -141,7 +136,8 @@ export default function verifyAttestationAndroidSafetyNet( * * The certificate is valid until Dec 15, 2021 */ -const GlobalSignRootCAR2 = 'MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UEC' + +const GlobalSignRootCAR2 = + 'MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UEC' + 'xMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhc' + 'NMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA' + '1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKb' + diff --git a/packages/server/src/attestation/verifications/verifyFIDOU2F.ts b/packages/server/src/attestation/verifications/verifyFIDOU2F.ts index 6768abc..c12dc4a 100644 --- a/packages/server/src/attestation/verifications/verifyFIDOU2F.ts +++ b/packages/server/src/attestation/verifications/verifyFIDOU2F.ts @@ -7,7 +7,6 @@ import convertASN1toPEM from '@helpers/convertASN1toPEM'; import verifySignature from '@helpers/verifySignature'; import parseAuthenticatorData from '@helpers/parseAuthenticatorData'; - /** * Verify an attestation response with fmt 'fido-u2f' */ @@ -18,15 +17,9 @@ export default function verifyAttestationFIDOU2F( const { fmt, authData, attStmt } = attestationObject; const authDataStruct = parseAuthenticatorData(authData); - const { - flags, - COSEPublicKey, - rpIdHash, - credentialID, - counter, - } = authDataStruct; + const { flags, COSEPublicKey, rpIdHash, credentialID, counter } = authDataStruct; - if (!(flags.up)) { + if (!flags.up) { throw new Error('User was NOT present during authentication (FIDOU2F)'); } diff --git a/packages/server/src/attestation/verifications/verifyNone.ts b/packages/server/src/attestation/verifications/verifyNone.ts index e820ee9..1aeafd0 100644 --- a/packages/server/src/attestation/verifications/verifyNone.ts +++ b/packages/server/src/attestation/verifications/verifyNone.ts @@ -1,11 +1,9 @@ import base64url from 'base64url'; -import { AttestationObject, VerifiedAttestation } from "@webauthntine/typescript-types"; +import { AttestationObject, VerifiedAttestation } from '@webauthntine/typescript-types'; -import convertCOSEtoPKCS from "@helpers/convertCOSEtoPKCS"; +import convertCOSEtoPKCS from '@helpers/convertCOSEtoPKCS'; import parseAuthenticatorData from '@helpers/parseAuthenticatorData'; - - /** * Verify an attestation response with fmt 'none' * @@ -17,12 +15,7 @@ export default function verifyAttestationNone( const { fmt, authData } = attestationObject; const authDataStruct = parseAuthenticatorData(authData); - const { - credentialID, - COSEPublicKey, - counter, - flags, - } = authDataStruct; + const { credentialID, COSEPublicKey, counter, flags } = authDataStruct; if (!flags.up) { throw new Error('User was not present for attestation (None)'); diff --git a/packages/server/src/attestation/verifications/verifyPacked.ts b/packages/server/src/attestation/verifications/verifyPacked.ts index 0a64c27..a27962d 100644 --- a/packages/server/src/attestation/verifications/verifyPacked.ts +++ b/packages/server/src/attestation/verifications/verifyPacked.ts @@ -2,20 +2,25 @@ import base64url from 'base64url'; import cbor from 'cbor'; import elliptic from 'elliptic'; import NodeRSA, { SigningSchemeHash } from 'node-rsa'; -import { AttestationObject, VerifiedAttestation, COSEKEYS, COSEPublicKey } from "@webauthntine/typescript-types"; - -import convertCOSEtoPKCS from "@helpers/convertCOSEtoPKCS"; -import toHash from "@helpers/toHash"; +import { + AttestationObject, + VerifiedAttestation, + COSEKEYS, + COSEPublicKey, +} from '@webauthntine/typescript-types'; + +import convertCOSEtoPKCS from '@helpers/convertCOSEtoPKCS'; +import toHash from '@helpers/toHash'; import convertASN1toPEM from '@helpers/convertASN1toPEM'; import getCertificateInfo from '@helpers/getCertificateInfo'; import verifySignature from '@helpers/verifySignature'; import parseAuthenticatorData from '@helpers/parseAuthenticatorData'; - /** * Verify an attestation response with fmt 'packed' */ -export default function verifyAttestationPacked(attestationObject: AttestationObject, +export default function verifyAttestationPacked( + attestationObject: AttestationObject, base64ClientDataJSON: string, ): VerifiedAttestation { const { fmt, authData, attStmt } = attestationObject; @@ -43,10 +48,7 @@ export default function verifyAttestationPacked(attestationObject: AttestationOb const clientDataHash = toHash(base64url.toBuffer(base64ClientDataJSON)); - const signatureBase = Buffer.concat([ - authData, - clientDataHash, - ]); + const signatureBase = Buffer.concat([authData, clientDataHash]); const toReturn: VerifiedAttestation = { verified: false, @@ -59,12 +61,7 @@ export default function verifyAttestationPacked(attestationObject: AttestationOb const leafCertInfo = getCertificateInfo(leafCert); const { subject, basicConstraintsCA, version } = leafCertInfo; - const { - OU, - CN, - O, - C, - } = subject; + const { OU, CN, O, C } = subject; if (OU !== 'Authenticator Attestation') { throw new Error('Batch certificate OU was not "Authenticator Attestation" (Packed|Full'); @@ -105,7 +102,7 @@ export default function verifyAttestationPacked(attestationObject: AttestationOb throw new Error('COSE public key was missing kty (Packed|Self)'); } - const hashAlg: string = COSEALGHASH[(alg as number)]; + const hashAlg: string = COSEALGHASH[alg as number]; if (kty === COSEKTY.EC2) { const crv = cosePublicKey.get(COSEKEYS.crv); @@ -126,7 +123,7 @@ export default function verifyAttestationPacked(attestationObject: AttestationOb * For now, it's worth noting that this line is probably the reason why it can take * 5-6 seconds to run tests. */ - const ec = new elliptic.ec(COSECRV[(crv as number)]); + const ec = new elliptic.ec(COSECRV[crv as number]); const key = ec.keyFromPublic(pkcsPublicKey); toReturn.verified = key.verify(signatureBaseHash, sig); @@ -142,10 +139,13 @@ export default function verifyAttestationPacked(attestationObject: AttestationOb // TODO: Verify this works const key = new NodeRSA(); key.setOptions({ signingScheme }); - key.importKey({ - n: (n as Buffer), - e: 65537, - }, 'components-public'); + key.importKey( + { + n: n as Buffer, + e: 65537, + }, + 'components-public', + ); toReturn.verified = key.verify(signatureBase, sig); } else if (kty === COSEKTY.OKP) { @@ -158,7 +158,7 @@ export default function verifyAttestationPacked(attestationObject: AttestationOb const signatureBaseHash = toHash(signatureBase, hashAlg); const key = new elliptic.eddsa('ed25519'); - key.keyFromPublic((x as Buffer)); + key.keyFromPublic(x as Buffer); // TODO: is `publicKey` right here? toReturn.verified = key.verify(signatureBaseHash, sig, publicKey); @@ -190,8 +190,8 @@ const COSERSASCHEME: { [key: string]: SigningSchemeHash } = { '-65535': 'pkcs1-sha1', '-257': 'pkcs1-sha256', '-258': 'pkcs1-sha384', - '-259': 'pkcs1-sha512' -} + '-259': 'pkcs1-sha512', +}; const COSECRV: { [key: number]: string } = { 1: 'p256', @@ -209,5 +209,5 @@ const COSEALGHASH: { [key: string]: string } = { '-37': 'sha256', '-7': 'sha256', '-8': 'sha512', - '-36': 'sha512' -} + '-36': 'sha512', +}; diff --git a/packages/server/src/attestation/verifyAttestationResponse.test.ts b/packages/server/src/attestation/verifyAttestationResponse.test.ts index 4747066..375264a 100644 --- a/packages/server/src/attestation/verifyAttestationResponse.test.ts +++ b/packages/server/src/attestation/verifyAttestationResponse.test.ts @@ -38,8 +38,8 @@ test('should verify Packed (EC2) attestation', () => { const verification = verifyAttestationResponse( attestationPacked, attestationPackedChallenge, - 'https://dev.dontneeda.pw' - ) + 'https://dev.dontneeda.pw', + ); expect(verification.verified).toEqual(true); expect(verification.authenticatorInfo?.fmt).toEqual('packed'); @@ -49,11 +49,11 @@ test('should verify Packed (EC2) attestation', () => { ); expect(verification.authenticatorInfo?.base64CredentialID).toEqual( 'AYThY1csINY4JrbHyGmqTl1nL_F1zjAF3hSAIngz8kAcjugmAMNVvxZRwqpEH-bNHHAIv291OX5ko9eDf_5mu3U' + - 'B2BvsScr2K-ppM4owOpGsqwg5tZglqqmxIm1Q', + 'B2BvsScr2K-ppM4owOpGsqwg5tZglqqmxIm1Q', ); }); -test ('should verify Packed (X5C) attestation', () => { +test('should verify Packed (X5C) attestation', () => { const verification = verifyAttestationResponse( attestationPackedX5C, attestationPackedX5CChallenge, @@ -75,7 +75,7 @@ test('should verify None attestation', () => { const verification = verifyAttestationResponse( attestationNone, attestationNoneChallenge, - 'https://dev.dontneeda.pw' + 'https://dev.dontneeda.pw', ); expect(verification.verified).toEqual(true); @@ -93,7 +93,7 @@ test('should verify Android SafetyNet attestation', () => { const verification = verifyAttestationResponse( attestationAndroidSafetyNet, attestationAndroidSafetyNetChallenge, - 'https://dev.dontneeda.pw' + 'https://dev.dontneeda.pw', ); expect(verification.verified).toEqual(true); @@ -122,7 +122,7 @@ test('should throw when response origin is not expected value', () => { verifyAttestationResponse( attestationNone, attestationNoneChallenge, - 'https://different.address' + 'https://different.address', ); }).toThrow(/attestation origin/i); }); @@ -139,11 +139,7 @@ test('should throw when attestation type is not webauthn.create', () => { }); expect(() => { - verifyAttestationResponse( - attestationNone, - challenge, - origin, - ); + verifyAttestationResponse(attestationNone, challenge, origin); }).toThrow(/attestation type/i); }); @@ -165,7 +161,8 @@ test('should throw if an unexpected attestation format is specified', () => { }); const attestationFIDOU2F = { - base64AttestationObject: 'o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAK40WxA0t7py7AjEXvwGw' + + base64AttestationObject: + 'o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAK40WxA0t7py7AjEXvwGw' + 'TlmqlvrOks5g9lf+9zXzRiVAiEA3bv60xyXveKDOusYzniD7CDSostCet9PYK7FLdnTdZNjeDVjgVkCwTCCAr0wg' + 'gGloAMCAQICBCrnYmMwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhb' + 'CA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG4xCzAJBgNVBAYTAlNFMRIwEAYDV' + @@ -181,27 +178,31 @@ const attestationFIDOU2F = { 'NjDq86cN9vm6+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQGFYevaR71ptU5YtXOSnVzPQTsGgK+gLiBKnq' + 'PWBmZXNRvjISqlLxiwApzlrfkTc3lEMYMatjeACCnsijOkNEGOlAQIDJiABIVggdWLG6UvGyHFw/k/bv6/k6z/LL' + 'gSO5KXzXw2EcUxkEX8iWCBeaVLz/cbyoKvRIg/q+q7tan0VN+i3WR0BOBCcuNP7yw==', - base64ClientDataJSON: 'eyJjaGFsbGVuZ2UiOiJVMmQ0TjNZME0wOU1jbGRQYjFSNVpFeG5UbG95IiwiY2xpZW50' + + base64ClientDataJSON: + 'eyJjaGFsbGVuZ2UiOiJVMmQ0TjNZME0wOU1jbGRQYjFSNVpFeG5UbG95IiwiY2xpZW50' + 'RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly9jbG92ZXIu' + 'bWlsbGVydGltZS5kZXY6MzAwMCIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ==', }; const attestationFIDOU2FChallenge = 'Sgx7v43OLrWOoTydLgNZ2'; const attestationPacked = { - base64AttestationObject: 'o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIhANvrPZMUFrl_rvlgR' + + base64AttestationObject: + 'o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIhANvrPZMUFrl_rvlgR' + 'qz6lCPlF6B4y885FYUCCrhrzAYXAiAb4dQKXbP3IimsTTadkwXQlrRVdxzlbmPXt847-Oh6r2hhdXRoRGF0YVjhP' + 'dxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KBFXsOO-a3OAAI1vMYKZIsLJfHwVQMAXQGE4WNXLCDWOCa2x' + '8hpqk5dZy_xdc4wBd4UgCJ4M_JAHI7oJgDDVb8WUcKqRB_mzRxwCL9vdTl-ZKPXg3_-Zrt1Adgb7EnK9ivqaTOKM' + 'DqRrKsIObWYJaqpsSJtUKUBAgMmIAEhWCBKMVVaivqCBpqqAxMjuCo5jMeUdh3jDOC0EF4fLBNNTyJYILc7rqDDe' + 'X1pwCLrl3ZX7IThrtZNwKQVLQyfHiorqP-n', - base64ClientDataJSON: 'eyJjaGFsbGVuZ2UiOiJjelpRU1dKQ2JsQlFibkpIVGxOQ2VFNWtkRVJ5VkRkVmNsWlpT' + + base64ClientDataJSON: + 'eyJjaGFsbGVuZ2UiOiJjelpRU1dKQ2JsQlFibkpIVGxOQ2VFNWtkRVJ5VkRkVmNsWlpT' + 'a3M1U0UwIiwib3JpZ2luIjoiaHR0cHM6Ly9kZXYuZG9udG5lZWRhLnB3IiwidHlwZSI6IndlYmF1dGhuLmNyZWF0' + 'ZSJ9', }; const attestationPackedChallenge = 's6PIbBnPPnrGNSBxNdtDrT7UrVYJK9HM'; const attestationPackedX5C = { - base64AttestationObject: 'o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEcwRQIhAIMt_hGMtdgpIVIwMOeKK' + + base64AttestationObject: + 'o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEcwRQIhAIMt_hGMtdgpIVIwMOeKK' + 'w0IkUUFkXSY8arKh3Q0c5QQAiB9Sv9JavAEmppeH_XkZjB7TFM3jfxsgl97iIkvuJOUImN4NWOBWQLBMIICvTCCAaWgA' + 'wIBAgIEKudiYzANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwM' + 'DYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbjELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1Ymljb' + @@ -217,24 +218,28 @@ const attestationPackedX5C = { 'wBA4rrvMciHCkdLQ2HghazIp1sMc8TmV8W8RgoX-x8tqV_1AmlqWACqUK8mBGLandr-htduQKPzgb2yWxOFV56TlqUBA' + 'gMmIAEhWCBsJbGAjckW-AA_XMk8OnB-VUvrs35ZpjtVJXRhnvXiGiJYIL2ncyg_KesCi44GH8UcZXYwjBkVdGMjNd6LF' + 'myiD6xf', - base64ClientDataJSON: 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiZEc5MFlXeHNlVlZ1YVhG' + - 'MVpWWmhiSFZsUlhabGNubFVhVzFsIiwib3JpZ2luIjoiaHR0cHM6Ly9kZXYuZG9udG5lZWRhLnB3In0=' + base64ClientDataJSON: + 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiZEc5MFlXeHNlVlZ1YVhG' + + 'MVpWWmhiSFZsUlhabGNubFVhVzFsIiwib3JpZ2luIjoiaHR0cHM6Ly9kZXYuZG9udG5lZWRhLnB3In0=', }; const attestationPackedX5CChallenge = 'totallyUniqueValueEveryTime'; const attestationNone = { - base64AttestationObject: 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3I' + + base64AttestationObject: + 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFPdxHEOnAiLIp26idVjIguzn3I' + 'pr_RlsKZWsa-5qK-KBFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHSlyRHIdWleVqO24-6ix7JFWODqDWo_arvEz3Se' + '5EgIFHkcVjZ4F5XDSBreIHsWRilRnKmaaqlqK3V2_4XtYs2pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENs' + 'MH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow', - base64ClientDataJSON: 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiYUVWalkxQlhkWHBw' + + base64ClientDataJSON: + 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiYUVWalkxQlhkWHBw' + 'VURBd1NEQndOV2Q0YURKZmRUVmZVRU0wVG1WWloyUSIsIm9yaWdpbiI6Imh0dHBzOlwvXC9kZXYuZG9udG5lZWRh' + - 'LnB3IiwiYW5kcm9pZFBhY2thZ2VOYW1lIjoib3JnLm1vemlsbGEuZmlyZWZveCJ9' + 'LnB3IiwiYW5kcm9pZFBhY2thZ2VOYW1lIjoib3JnLm1vemlsbGEuZmlyZWZveCJ9', }; const attestationNoneChallenge = 'hEccPWuziP00H0p5gxh2_u5_PC4NeYgd'; const attestationAndroidSafetyNet = { - base64AttestationObject: 'o2NmbXRxYW5kcm9pZC1zYWZldHluZXRnYXR0U3RtdKJjdmVyaDE3MTIyMDM3aHJlc' + + base64AttestationObject: + 'o2NmbXRxYW5kcm9pZC1zYWZldHluZXRnYXR0U3RtdKJjdmVyaDE3MTIyMDM3aHJlc' + '3BvbnNlWRS9ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbmcxWXlJNld5Sk5TVWxHYTJwRFEwSkljV2RCZDBsQ1FXZEpVV' + 'kpZY205T01GcFBaRkpyUWtGQlFVRkJRVkIxYm5wQlRrSm5hM0ZvYTJsSE9YY3dRa0ZSYzBaQlJFSkRUVkZ6ZDBOU' + 'ldVUldVVkZIUlhkS1ZsVjZSV1ZOUW5kSFFURlZSVU5vVFZaU01qbDJXako0YkVsR1VubGtXRTR3U1VaT2JHTnVXb' + @@ -319,8 +324,9 @@ const attestationAndroidSafetyNet = { 'yKa_0ZbCmVrGvuaivigRQAAAAC5P9lh8uZGL7EiggAiR954AEEBDL2BKZVhBca7N3j3asDaoSrA3tJgT_E4KN25T' + 'hBVqBHCdffSZt9bvku7hPBcd76BzU7Y-ckXslUkD13Imbzde6UBAgMmIAEhWCCT4hId3ByJ_agRyznv1xIazx2nl' + 'VEGyvN7intoZr7C2CJYIKo3XB-cca9aUOLC-xhp3GfhyfTS0hjws5zL_bT_N1AL', - base64ClientDataJSON: 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiWDNaV1VHOUZOREpF' + + base64ClientDataJSON: + 'eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiWDNaV1VHOUZOREpF' + 'YUMxM2F6Tmlka2h0WVd0MGFWWjJSVmxETFV4M1FsZyIsIm9yaWdpbiI6Imh0dHBzOlwvXC9kZXYuZG9udG5lZWRh' + - 'LnB3IiwiYW5kcm9pZFBhY2thZ2VOYW1lIjoiY29tLmFuZHJvaWQuY2hyb21lIn0' + 'LnB3IiwiYW5kcm9pZFBhY2thZ2VOYW1lIjoiY29tLmFuZHJvaWQuY2hyb21lIn0', }; const attestationAndroidSafetyNetChallenge = '_vVPoE42Dh-wk3bvHmaktiVvEYC-LwBX'; diff --git a/packages/server/src/attestation/verifyAttestationResponse.ts b/packages/server/src/attestation/verifyAttestationResponse.ts index 41708fc..bd197ed 100644 --- a/packages/server/src/attestation/verifyAttestationResponse.ts +++ b/packages/server/src/attestation/verifyAttestationResponse.ts @@ -1,6 +1,10 @@ import decodeAttestationObject from '@helpers/decodeAttestationObject'; import decodeClientDataJSON from '@helpers/decodeClientDataJSON'; -import { ATTESTATION_FORMATS, AuthenticatorAttestationResponseJSON, VerifiedAttestation } from '@webauthntine/typescript-types'; +import { + ATTESTATION_FORMATS, + AuthenticatorAttestationResponseJSON, + VerifiedAttestation, +} from '@webauthntine/typescript-types'; import verifyFIDOU2F from './verifications/verifyFIDOU2F'; import verifyPacked from './verifications/verifyPacked'; @@ -28,7 +32,7 @@ export default function verifyAttestationResponse( if (challenge !== expectedChallenge) { throw new Error( - `Unexpected attestation challenge "${challenge}", expected "${expectedChallenge}"` + `Unexpected attestation challenge "${challenge}", expected "${expectedChallenge}"`, ); } diff --git a/packages/server/src/helpers/convertCOSEtoPKCS.test.ts b/packages/server/src/helpers/convertCOSEtoPKCS.test.ts index 1d0ad6e..5bc970c 100644 --- a/packages/server/src/helpers/convertCOSEtoPKCS.test.ts +++ b/packages/server/src/helpers/convertCOSEtoPKCS.test.ts @@ -3,7 +3,6 @@ import { COSEKEYS } from '@webauthntine/typescript-types'; import convertCOSEtoPKCS from './convertCOSEtoPKCS'; - test('should throw an error curve if, somehow, curve coordinate x is missing', () => { const mockCOSEKey = new Map<number, number | Buffer>(); diff --git a/packages/server/src/helpers/convertCOSEtoPKCS.ts b/packages/server/src/helpers/convertCOSEtoPKCS.ts index fbafd59..f3b4a7e 100644 --- a/packages/server/src/helpers/convertCOSEtoPKCS.ts +++ b/packages/server/src/helpers/convertCOSEtoPKCS.ts @@ -1,7 +1,6 @@ import cbor from 'cbor'; import { COSEKEYS, COSEPublicKey } from '@webauthntine/typescript-types'; - /** * Takes COSE-encoded public key and converts it to PKCS key * @@ -39,5 +38,5 @@ export default function convertCOSEtoPKCS(cosePublicKey: Buffer) { throw new Error('COSE public key was missing y'); } - return Buffer.concat([tag, (x as Buffer), (y as Buffer)]); + return Buffer.concat([tag, x as Buffer, y as Buffer]); } diff --git a/packages/server/src/helpers/decodeAttestationObject.test.ts b/packages/server/src/helpers/decodeAttestationObject.test.ts index d36201e..e8eb364 100644 --- a/packages/server/src/helpers/decodeAttestationObject.test.ts +++ b/packages/server/src/helpers/decodeAttestationObject.test.ts @@ -3,9 +3,9 @@ import decodeAttestationObject from './decodeAttestationObject'; test('should decode base64-encoded indirect attestationObject', () => { const decoded = decodeAttestationObject( 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEAbElFazplpnc037DORGDZNjDq86cN9vm6' + - '+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQKmPuEwByQJ3e89TccUSrCGDkNWquhevjLLn/' + - 'KNZZaxQQ0steueoG2g12dvnUNbiso8kVJDyLa+6UiA34eniujWlAQIDJiABIVggiUk8wN2j' + - '+3fkKI7KSiLBkKzs3FfhPZxHgHPnGLvOY/YiWCBv7+XyTqArnMVtQ947/8Xk8fnVCdLMRWJGM1VbNevVcQ==' + '+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQKmPuEwByQJ3e89TccUSrCGDkNWquhevjLLn/' + + 'KNZZaxQQ0steueoG2g12dvnUNbiso8kVJDyLa+6UiA34eniujWlAQIDJiABIVggiUk8wN2j' + + '+3fkKI7KSiLBkKzs3FfhPZxHgHPnGLvOY/YiWCBv7+XyTqArnMVtQ947/8Xk8fnVCdLMRWJGM1VbNevVcQ==', ); expect(decoded.fmt).toEqual('none'); @@ -16,20 +16,20 @@ test('should decode base64-encoded indirect attestationObject', () => { test('should decode base64-encoded direct attestationObject', () => { const decoded = decodeAttestationObject( 'o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAK40WxA0t7py7AjEXvwGwTlmqlvrOk' + - 's5g9lf+9zXzRiVAiEA3bv60xyXveKDOusYzniD7CDSostCet9PYK7FLdnTdZNjeDVjgVkCwTCCAr0wggGloAMCAQICBCrn' + - 'YmMwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMT' + - 'QwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG4xCzAJBgNVBAYTAlNFMRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNV' + - 'BAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xJzAlBgNVBAMMHll1YmljbyBVMkYgRUUgU2VyaWFsIDcxOTgwNzA3NT' + - 'BZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCoDhl5gQ9meEf8QqiVUV4S/Ca+Oax47MhcpIW9VEhqM2RDTmd3HaL3+SnvH' + - '49q8YubSRp/1Z1uP+okMynSGnj+jbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBgu' + - 'UcAgEBBAQDAgQwMCEGCysGAQQBguUcAQEEBBIEEG1Eupv27C5JuTAMj+kgy3MwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B' + - 'AQsFAAOCAQEAclfQPNzD4RVphJDW+A75W1MHI3PZ5kcyYysR3Nx3iuxr1ZJtB+F7nFQweI3jL05HtFh2/4xVIgKb6Th4eV' + - 'cjMecncBaCinEbOcdP1sEli9Hk2eVm1XB5A0faUjXAPw/+QLFCjgXG6ReZ5HVUcWkB7riLsFeJNYitiKrTDXFPLy+sNtVN' + - 'utcQnFsCerDKuM81TvEAigkIbKCGlq8M/NvBg5j83wIxbCYiyV7mIr3RwApHieShzLdJo1S6XydgQjC+/64G5r8C+8AVvN' + - 'FR3zXXCpio5C3KRIj88HEEIYjf6h1fdLfqeIsq+cUUqbq5T+c4nNoZUZCysTB9v5EY4akp+GhhdXRoRGF0YVjEAbElFazp' + - 'lpnc037DORGDZNjDq86cN9vm6+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQGFYevaR71ptU5YtXOSnVzPQTsGgK+' + - 'gLiBKnqPWBmZXNRvjISqlLxiwApzlrfkTc3lEMYMatjeACCnsijOkNEGOlAQIDJiABIVggdWLG6UvGyHFw/k/bv6/k6z/L' + - 'LgSO5KXzXw2EcUxkEX8iWCBeaVLz/cbyoKvRIg/q+q7tan0VN+i3WR0BOBCcuNP7yw==' + 's5g9lf+9zXzRiVAiEA3bv60xyXveKDOusYzniD7CDSostCet9PYK7FLdnTdZNjeDVjgVkCwTCCAr0wggGloAMCAQICBCrn' + + 'YmMwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMT' + + 'QwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG4xCzAJBgNVBAYTAlNFMRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNV' + + 'BAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xJzAlBgNVBAMMHll1YmljbyBVMkYgRUUgU2VyaWFsIDcxOTgwNzA3NT' + + 'BZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCoDhl5gQ9meEf8QqiVUV4S/Ca+Oax47MhcpIW9VEhqM2RDTmd3HaL3+SnvH' + + '49q8YubSRp/1Z1uP+okMynSGnj+jbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBgu' + + 'UcAgEBBAQDAgQwMCEGCysGAQQBguUcAQEEBBIEEG1Eupv27C5JuTAMj+kgy3MwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B' + + 'AQsFAAOCAQEAclfQPNzD4RVphJDW+A75W1MHI3PZ5kcyYysR3Nx3iuxr1ZJtB+F7nFQweI3jL05HtFh2/4xVIgKb6Th4eV' + + 'cjMecncBaCinEbOcdP1sEli9Hk2eVm1XB5A0faUjXAPw/+QLFCjgXG6ReZ5HVUcWkB7riLsFeJNYitiKrTDXFPLy+sNtVN' + + 'utcQnFsCerDKuM81TvEAigkIbKCGlq8M/NvBg5j83wIxbCYiyV7mIr3RwApHieShzLdJo1S6XydgQjC+/64G5r8C+8AVvN' + + 'FR3zXXCpio5C3KRIj88HEEIYjf6h1fdLfqeIsq+cUUqbq5T+c4nNoZUZCysTB9v5EY4akp+GhhdXRoRGF0YVjEAbElFazp' + + 'lpnc037DORGDZNjDq86cN9vm6+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQGFYevaR71ptU5YtXOSnVzPQTsGgK+' + + 'gLiBKnqPWBmZXNRvjISqlLxiwApzlrfkTc3lEMYMatjeACCnsijOkNEGOlAQIDJiABIVggdWLG6UvGyHFw/k/bv6/k6z/L' + + 'LgSO5KXzXw2EcUxkEX8iWCBeaVLz/cbyoKvRIg/q+q7tan0VN+i3WR0BOBCcuNP7yw==', ); expect(decoded.fmt).toEqual('fido-u2f'); diff --git a/packages/server/src/helpers/decodeAttestationObject.ts b/packages/server/src/helpers/decodeAttestationObject.ts index fa39454..509c129 100644 --- a/packages/server/src/helpers/decodeAttestationObject.ts +++ b/packages/server/src/helpers/decodeAttestationObject.ts @@ -2,7 +2,6 @@ import base64url from 'base64url'; import cbor from 'cbor'; import { AttestationObject } from '@webauthntine/typescript-types'; - /** * Convert an AttestationObject from base64 string to a proper object * diff --git a/packages/server/src/helpers/decodeClientDataJSON.test.ts b/packages/server/src/helpers/decodeClientDataJSON.test.ts index d3cea88..7674ec5 100644 --- a/packages/server/src/helpers/decodeClientDataJSON.test.ts +++ b/packages/server/src/helpers/decodeClientDataJSON.test.ts @@ -4,13 +4,14 @@ test('should convert base64-encoded attestation clientDataJSON to JSON', () => { expect( decodeClientDataJSON( 'eyJjaGFsbGVuZ2UiOiJVMmQ0TjNZME0wOU1jbGRQYjFSNVpFeG5UbG95IiwiY2xpZW50RXh0ZW5zaW9ucyI6e30' + - 'sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly9jbG92ZXIubWlsbGVydGltZS5kZX' + - 'Y6MzAwMCIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ==' - )).toEqual({ - challenge: 'Sgx7v43OLrWOoTydLgNZ2', - clientExtensions: {}, - hashAlgorithm: 'SHA-256', - origin: 'https://clover.millertime.dev:3000', - type: 'webauthn.create' - }); + 'sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly9jbG92ZXIubWlsbGVydGltZS5kZX' + + 'Y6MzAwMCIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ==', + ), + ).toEqual({ + challenge: 'Sgx7v43OLrWOoTydLgNZ2', + clientExtensions: {}, + hashAlgorithm: 'SHA-256', + origin: 'https://clover.millertime.dev:3000', + type: 'webauthn.create', + }); }); diff --git a/packages/server/src/helpers/getCertificateInfo.ts b/packages/server/src/helpers/getCertificateInfo.ts index 6bf6546..68f9c20 100644 --- a/packages/server/src/helpers/getCertificateInfo.ts +++ b/packages/server/src/helpers/getCertificateInfo.ts @@ -2,16 +2,16 @@ import jsrsasign from 'jsrsasign'; import { CertificateInfo } from '@webauthntine/typescript-types'; type ExtInfo = { - critical: boolean, - oid: string, - vidx: number, + critical: boolean; + oid: string; + vidx: number; }; interface x5cCertificate extends jsrsasign.X509 { version: number; foffset: number; aExtInfo: ExtInfo[]; -}; +} /** * Extract PEM certificate info @@ -26,12 +26,12 @@ export default function getCertificateInfo(pemCertificate: string): CertificateI const subjectParts = subjectString.slice(1).split('/'); const subject: { [key: string]: string } = {}; - subjectParts.forEach((field) => { + subjectParts.forEach(field => { const [key, val] = field.split('='); subject[key] = val; }); - const { version } = (subjectCert as x5cCertificate); + const { version } = subjectCert as x5cCertificate; const basicConstraintsCA = !!subjectCert.getExtBasicConstraints().cA; return { diff --git a/packages/server/src/helpers/parseAuthenticatorData.ts b/packages/server/src/helpers/parseAuthenticatorData.ts index a3dd868..969e9ec 100644 --- a/packages/server/src/helpers/parseAuthenticatorData.ts +++ b/packages/server/src/helpers/parseAuthenticatorData.ts @@ -1,4 +1,4 @@ -import { ParsedAuthenticatorData } from "@webauthntine/typescript-types"; +import { ParsedAuthenticatorData } from '@webauthntine/typescript-types'; /** * Make sense of the authData buffer contained in an Attestation diff --git a/packages/server/src/helpers/verifySignature.ts b/packages/server/src/helpers/verifySignature.ts index c938a23..79fcdba 100644 --- a/packages/server/src/helpers/verifySignature.ts +++ b/packages/server/src/helpers/verifySignature.ts @@ -12,7 +12,5 @@ export default function verifySignature( signatureBase: Buffer, publicKey: string, ): boolean { - return crypto.createVerify('SHA256') - .update(signatureBase) - .verify(publicKey, signature); + return crypto.createVerify('SHA256').update(signatureBase).verify(publicKey, signature); } diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 2267eb5..5dda000 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -4,7 +4,7 @@ "baseUrl": "./src", "outDir": "./dist", "paths": { - "@helpers/*": ["helpers/*"], + "@helpers/*": ["helpers/*"] }, "plugins": [ // These replace the path helpers above with relative paths at build time diff --git a/packages/typescript-types/README.md b/packages/typescript-types/README.md index da321f5..c51948a 100644 --- a/packages/typescript-types/README.md +++ b/packages/typescript-types/README.md @@ -1,4 +1,5 @@ # @webauthntine/typescript-types +  [](https://www.npmjs.com/package/@webauthntine/typescript-types) diff --git a/packages/typescript-types/src/index.ts b/packages/typescript-types/src/index.ts index 9e5ec5d..cab623c 100644 --- a/packages/typescript-types/src/index.ts +++ b/packages/typescript-types/src/index.ts @@ -9,24 +9,26 @@ */ export type PublicKeyCredentialCreationOptionsJSON = { publicKey: { - challenge: string, + challenge: string; // The organization registering and authenticating the user rp: { - name: string, - id: string, - }, + name: string; + id: string; + }; user: { - id: string, - name: string, - displayName: string, - }, - pubKeyCredParams: [{ - alg: -7, - type: 'public-key', - }], - timeout?: number, - attestation: 'direct' | 'indirect', - }, + id: string; + name: string; + displayName: string; + }; + pubKeyCredParams: [ + { + alg: -7; + type: 'public-key'; + }, + ]; + timeout?: number; + attestation: 'direct' | 'indirect'; + }; }; /** @@ -41,18 +43,18 @@ export type PublicKeyCredentialCreationOptionsJSON = { export type PublicKeyCredentialRequestOptionsJSON = { publicKey: { // - challenge: string, + challenge: string; allowCredentials: { // Will be converted to a Uint8Array in the browser - id: string, - type: 'public-key', - transports?: AuthenticatorTransport[], - }[], + id: string; + type: 'public-key'; + transports?: AuthenticatorTransport[]; + }[]; // extensions?: AuthenticationExtensionsClientInputs, - rpId?: string, - timeout?: number, - userVerification?: UserVerificationRequirement, - }, + rpId?: string; + timeout?: number; + userVerification?: UserVerificationRequirement; + }; }; /** @@ -73,10 +75,9 @@ export interface AssertionCredential extends PublicKeyCredential { * A slightly-modified AuthenticatorAttestationResponse to simplify working with ArrayBuffers that * are base64-encoded in the browser so that they can be sent as JSON to the server. */ -export interface AuthenticatorAttestationResponseJSON extends Omit< -AuthenticatorAttestationResponse, 'clientDataJSON' | 'attestationObject' -> { - base64ClientDataJSON: string, +export interface AuthenticatorAttestationResponseJSON + extends Omit<AuthenticatorAttestationResponse, 'clientDataJSON' | 'attestationObject'> { + base64ClientDataJSON: string; base64AttestationObject: string; } @@ -84,9 +85,11 @@ AuthenticatorAttestationResponse, 'clientDataJSON' | 'attestationObject' * A slightly-modified AuthenticatorAttestationResponse to simplify working with ArrayBuffers that * are base64-encoded in the browser so that they can be sent as JSON to the server. */ -export interface AuthenticatorAssertionResponseJSON extends Omit< -AuthenticatorAssertionResponse, 'clientDataJSON' | 'authenticatorData' | 'signature' | 'userHandle' -> { +export interface AuthenticatorAssertionResponseJSON + extends Omit< + AuthenticatorAssertionResponse, + 'clientDataJSON' | 'authenticatorData' | 'signature' | 'userHandle' + > { base64CredentialID: string; base64AuthenticatorData: string; base64ClientDataJSON: string; @@ -102,36 +105,36 @@ export enum ATTESTATION_FORMATS { } export type AttestationObject = { - fmt: ATTESTATION_FORMATS, + fmt: ATTESTATION_FORMATS; attStmt: { - sig?: Buffer, - x5c?: Buffer[], - response?: Buffer, - }, - authData: Buffer, + sig?: Buffer; + x5c?: Buffer[]; + response?: Buffer; + }; + authData: Buffer; }; export type ParsedAuthenticatorData = { - rpIdHash: Buffer, - flagsBuf: Buffer, + rpIdHash: Buffer; + flagsBuf: Buffer; flags: { - up: boolean, - uv: boolean, - at: boolean, - ed: boolean, - flagsInt: number, - }, - counter: number, - counterBuf: Buffer, - aaguid?: Buffer, - credentialID?: Buffer, - COSEPublicKey?: Buffer, + up: boolean; + uv: boolean; + at: boolean; + ed: boolean; + flagsInt: number; + }; + counter: number; + counterBuf: Buffer; + aaguid?: Buffer; + credentialID?: Buffer; + COSEPublicKey?: Buffer; }; export type ClientDataJSON = { - type: string, - challenge: string, - origin: string, + type: string; + challenge: string; + origin: string; }; /** @@ -149,14 +152,14 @@ export type ClientDataJSON = { * reference!** */ export type VerifiedAttestation = { - verified: boolean, + verified: boolean; userVerified: boolean; authenticatorInfo?: { - fmt: ATTESTATION_FORMATS, - counter: number, - base64PublicKey: string, - base64CredentialID: string, - }, + fmt: ATTESTATION_FORMATS; + counter: number; + base64PublicKey: string; + base64CredentialID: string; + }; }; /** @@ -172,15 +175,15 @@ export type VerifiedAttestation = { export type VerifiedAssertion = { verified: boolean; authenticatorInfo: { - counter: number, - base64CredentialID: string, - }, + counter: number; + base64CredentialID: string; + }; }; export type CertificateInfo = { - subject: { [key: string]: string }, - version: number, - basicConstraintsCA: boolean, + subject: { [key: string]: string }; + version: number; + basicConstraintsCA: boolean; }; export enum COSEKEYS { @@ -196,18 +199,18 @@ export enum COSEKEYS { export type COSEPublicKey = Map<COSEAlgorithmIdentifier, number | Buffer>; export type SafetyNetJWTHeader = { - alg: 'string', - x5c: string[], + alg: 'string'; + x5c: string[]; }; export type SafetyNetJWTPayload = { - nonce: string, - timestampMs: number, - apkPackageName: string, - apkDigestSha256: string, - ctsProfileMatch: boolean, - apkCertificateDigestSha256: string[], - basicIntegrity: boolean, + nonce: string; + timestampMs: number; + apkPackageName: string; + apkDigestSha256: string; + ctsProfileMatch: boolean; + apkCertificateDigestSha256: string[]; + basicIntegrity: boolean; }; export type SafetyNetJWTSignature = string; @@ -216,8 +219,8 @@ export type SafetyNetJWTSignature = string; * A WebAuthn-compatible device and the information needed to verify assertions by it */ export type AuthenticatorDevice = { - base64PublicKey: string, - base64CredentialID: string, + base64PublicKey: string; + base64CredentialID: string; // Number of times this device is expected to have been used - counter: number, + counter: number; }; diff --git a/packages/typescript-types/tsconfig.json b/packages/typescript-types/tsconfig.json index 9ddf207..349e40c 100644 --- a/packages/typescript-types/tsconfig.json +++ b/packages/typescript-types/tsconfig.json @@ -2,6 +2,6 @@ "extends": "../../tsconfig.json", "compilerOptions": { "baseUrl": "./src", - "outDir": "./dist", + "outDir": "./dist" } } |