Skip to content
This repository was archived by the owner on Jul 25, 2021. It is now read-only.

Commit 419a2fb

Browse files
committed
Added basic structure for PSK registration
1 parent 9523e78 commit 419a2fb

File tree

7 files changed

+247
-23
lines changed

7 files changed

+247
-23
lines changed

src/background.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {getLogger} from './logging';
55
import {getOriginFromUrl, webauthnParse, webauthnStringify} from './utils';
66

77
import {createPublicKeyCredential, getPublicKeyCredential} from "./webauthn_client";
8+
import {PSK} from "./webauthn_psk";
89

910
const log = getLogger('background');
1011

@@ -83,6 +84,22 @@ const getCredential = async (msg, sender: chrome.runtime.MessageSender) => {
8384
}
8485
};
8586

87+
const pskSetup = async () => {
88+
try {
89+
await PSK.setup();
90+
} catch (e) {
91+
log.error('failed to setup psk', { errorType: `${(typeof e)}` }, e);
92+
}
93+
};
94+
95+
const pskOptions = async (url) => {
96+
try {
97+
await PSK.setOptions(url);
98+
} catch (e) {
99+
log.error('failed to set psk options', { errorType: `${(typeof e)}` }, e);
100+
}
101+
};
102+
86103
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
87104
switch (msg.type) {
88105
case 'create_credential':
@@ -91,6 +108,12 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
91108
case 'get_credential':
92109
getCredential(msg, sender).then(sendResponse);
93110
break;
111+
case 'psk_setup':
112+
pskSetup().then(() => alert('PSK setup was successfully!'), null);
113+
break;
114+
case 'psk_options':
115+
pskOptions(msg.url).then(() => alert('PSK options was successfully!'), null);
116+
break;
94117
case 'user_consent':
95118
const cb = userConsentCallbacks[msg.tabId];
96119
if (!cb) {

src/constants.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ export const enabledIcons = {
1717
};
1818

1919
export const ES256_COSE = -7;
20-
export const ES256 = "P-256";
20+
export const ES256 = 'P-256';
2121
export const SHA256_COSE = 1;
2222

23-
export const PIN = "0000";
23+
export const PIN = '0000';
24+
25+
export const PSK_EXTENSION_IDENTIFIER = 'psk';
26+
export const BACKUP_KEY = 'backup_key';
27+
export const BD_ENDPOINT = 'bd_endpoint';
28+
export const DEFAULT_BD_ENDPOINT = 'http://localhost:8005';

src/options.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
import $ from 'jquery';
2+
import {PSK} from "./webauthn_psk";
23

34
$(() => {
45
$('#Setup').on('click', function(evt: Event) {
56
evt.preventDefault();
67
chrome.runtime.sendMessage({
7-
type: 'setup',
8+
type: 'psk_setup',
89
});
910
});
1011

11-
//$.when(getBackupDeviceBaseUrl()).then((url) => $('#BackupDeviceUrl').val(url));
12+
$.when(PSK.bdDeviceUrl()).then((url) => $('#BackupDeviceUrl').val(url));
1213

1314
$('#Recovery').on('click', function(evt: Event) {
1415
evt.preventDefault();
1516
chrome.runtime.sendMessage({
16-
type: 'recovery',
17+
type: 'psk_recovery',
1718
});
1819
});
1920

2021
$('#SaveBackupDeviceUrl').on('click', function(evt: Event) {
2122
evt.preventDefault();
2223
chrome.runtime.sendMessage({
23-
type: 'saveOptions',
24+
type: 'psk_options',
2425
url: $('#BackupDeviceUrl').val(),
2526
});
2627
});

src/webauth_storage.ts

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,106 @@
11
import {base64ToByteArray, byteArrayToBase64, concatenate} from "./utils";
2-
import {ivLength, keyExportFormat, PIN, saltLength} from "./constants";
2+
import {BACKUP_KEY, BD_ENDPOINT, DEFAULT_BD_ENDPOINT, ivLength, keyExportFormat, PIN, saltLength} from "./constants";
33
import {getLogger} from "./logging";
4+
import {BackupKey} from "./webauthn_psk";
45

56
const log = getLogger('auth_storage');
67

8+
export class PSKStorage {
9+
public static async getBDEndpoint(): Promise<string> {
10+
return new Promise<string>(async (res, rej) => {
11+
chrome.storage.local.get({[BD_ENDPOINT]: null}, async (resp) => {
12+
if (!!chrome.runtime.lastError) {
13+
log.error('Could not perform PSKStorage.getBDEndpoint', chrome.runtime.lastError.message);
14+
rej(chrome.runtime.lastError);
15+
return;
16+
}
17+
18+
if (resp[BACKUP_KEY] == null) {
19+
log.warn(`No endpoint found, use default endpoint`);
20+
res(DEFAULT_BD_ENDPOINT);
21+
}
22+
log.debug('Loaded BD endpoint successfully');
23+
res(resp[BACKUP_KEY]);
24+
});
25+
});
26+
}
27+
28+
public static async setBDEndpoint(endpoint: string): Promise<void> {
29+
log.debug('Set BD endpoint to', endpoint);
30+
return new Promise<void>(async (res, rej) => {
31+
chrome.storage.local.set({[BD_ENDPOINT]: endpoint}, () => {
32+
if (!!chrome.runtime.lastError) {
33+
log.error('Could not perform PSKStorage.setBDEndpoint', chrome.runtime.lastError.message);
34+
rej(chrome.runtime.lastError);
35+
} else {
36+
res();
37+
}
38+
});
39+
});
40+
}
41+
42+
public static async storeBackupKeys(backupKeys: BackupKey[], override: boolean = false): Promise<void> {
43+
log.debug(`Storing backup keys`);
44+
const backupKeysExists = await this.existBackupKeys();
45+
if (backupKeysExists && !override) {
46+
log.debug('Backup keys already exist. Update entry.');
47+
const entries = await this.loadBackupKeys();
48+
backupKeys = entries.concat(backupKeys);
49+
}
50+
51+
let exportJSON = JSON.stringify(backupKeys);
52+
return new Promise<void>(async (res, rej) => {
53+
chrome.storage.local.set({[BACKUP_KEY]: exportJSON}, () => {
54+
if (!!chrome.runtime.lastError) {
55+
log.error('Could not perform PSKStorage.storeBackupKeys', chrome.runtime.lastError.message);
56+
rej(chrome.runtime.lastError);
57+
} else {
58+
res();
59+
}
60+
});
61+
});
62+
};
63+
64+
public static async loadBackupKeys(): Promise<BackupKey[]> {
65+
log.debug(`Loading backup keys`);
66+
return new Promise<BackupKey[]>(async (res, rej) => {
67+
chrome.storage.local.get({[BACKUP_KEY]: null}, async (resp) => {
68+
if (!!chrome.runtime.lastError) {
69+
log.error('Could not perform PSKStorage.loadBackupKeys', chrome.runtime.lastError.message);
70+
rej(chrome.runtime.lastError);
71+
return;
72+
}
73+
74+
if (resp[BACKUP_KEY] == null) {
75+
log.warn(`No backup keys found`);
76+
res([]);
77+
}
78+
79+
const backupKeys = await JSON.parse(resp[BACKUP_KEY]);
80+
log.debug('Loaded backup keys successfully');
81+
res(backupKeys);
82+
});
83+
});
84+
}
85+
86+
private static async existBackupKeys(): Promise<boolean> {
87+
return new Promise<boolean>(async (res, rej) => {
88+
chrome.storage.local.get({[BACKUP_KEY]: null}, async (resp) => {
89+
if (!!chrome.runtime.lastError) {
90+
log.error('Could not perform PSKStorage.existBackupKeys', chrome.runtime.lastError.message);
91+
rej(chrome.runtime.lastError);
92+
} else {
93+
res(!(resp[BACKUP_KEY] == null));
94+
}
95+
});
96+
});
97+
};
98+
}
799

8100
export class CredentialsMap {
9101
public static async put(rpId: string, credSrc: PublicKeyCredentialSource): Promise<void> {
10102
log.debug(`Storing credential map entry for ${rpId}`);
11-
const mapEntryExists = await this.exits(rpId);
103+
const mapEntryExists = await this.exists(rpId);
12104
let credSrcs: PublicKeyCredentialSource[];
13105
if (mapEntryExists) {
14106
log.debug('Credential map entry does already exist. Update entry.');
@@ -75,11 +167,11 @@ export class CredentialsMap {
75167
}
76168
}
77169

78-
public static async exits(rpId: string): Promise<boolean> {
170+
public static async exists(rpId: string): Promise<boolean> {
79171
return new Promise<boolean>(async (res, rej) => {
80172
chrome.storage.local.get({[rpId]: null}, async (resp) => {
81173
if (!!chrome.runtime.lastError) {
82-
log.error('Could not perform CredentialsMap.exits', chrome.runtime.lastError.message);
174+
log.error('Could not perform CredentialsMap.exists', chrome.runtime.lastError.message);
83175
rej(chrome.runtime.lastError);
84176
} else {
85177
res(!(resp[rpId] == null));
@@ -91,7 +183,6 @@ export class CredentialsMap {
91183

92184
export class PublicKeyCredentialSource {
93185
public static async import(json: any): Promise<PublicKeyCredentialSource> {
94-
log.debug('Import PublicKeyCredentialSource', json);
95186
const _id = json.id;
96187
const _rpId = json.rpId;
97188
const _userHandle = json.userHandle;
@@ -124,7 +215,7 @@ export class PublicKeyCredentialSource {
124215
true,
125216
['sign'],
126217
);
127-
log.debug('Imported PublicKeyCredentialSource with id', _id)
218+
128219
return new PublicKeyCredentialSource(_id, _privateKey, _rpId, _userHandle);
129220
}
130221

@@ -178,7 +269,6 @@ export class PublicKeyCredentialSource {
178269
type: this.type
179270
}
180271

181-
log.debug('Exported PublicKeyCredentialSource with id', this.id)
182272
return json;
183273
}
184274
}

src/webauthn_authenticator.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {base64ToByteArray, byteArrayToBase64, counterToBytes} from "./utils";
44
import * as CBOR from 'cbor';
55
import {createAttestationSignature, getAttestationCertificate} from "./webauthn_attestation";
66
import {getLogger} from "./logging";
7-
import {ES256_COSE} from "./constants";
7+
import {ES256_COSE, PSK_EXTENSION_IDENTIFIER} from "./constants";
8+
import {PSK} from "./webauthn_psk";
89

910
const log = getLogger('webauthn_authenticator');
1011

@@ -111,7 +112,7 @@ export class Authenticator {
111112
requireUserVerification: boolean,
112113
credTypesAndPubKeyAlgs: PublicKeyCredentialParameters[],
113114
excludeCredentialDescriptorList?: PublicKeyCredentialDescriptor[],
114-
extensions?: any): Promise<AttestationObjectWrapper> {
115+
extensions?: Map<string, string>): Promise<AttestationObjectWrapper> {
115116
log.debug('Called authenticatorMakeCredential');
116117

117118
// Step 2
@@ -157,8 +158,22 @@ export class Authenticator {
157158
await CredentialsMap.put(rpEntity.id, credentialSource);
158159

159160
// Step 9
160-
// ToDo Include Extension Processing
161-
const extensionData = undefined;
161+
let processedExtensions = undefined;
162+
if (extensions) {
163+
log.debug(extensions);
164+
if (extensions.has(PSK_EXTENSION_IDENTIFIER)) {
165+
log.debug('PSK requested');
166+
const pskOutPut = await PSK.authenticatorGetCredentialExtensionOutput();
167+
log.debug('PSK extension output created');
168+
// ToDo Check if input is cbor encoded null
169+
processedExtensions = new Map([[PSK_EXTENSION_IDENTIFIER, pskOutPut]]);
170+
// ToDo Return credId from PSK to replace created credential id
171+
}
172+
}
173+
if (processedExtensions) {
174+
processedExtensions = new Uint8Array(CBOR.encodeCanonical(processedExtensions));
175+
}
176+
162177

163178
// Step 10
164179
const sigCnt = this.getSignatureCounter();
@@ -168,7 +183,7 @@ export class Authenticator {
168183
const attestedCredentialData = await this.generateAttestedCredentialData(rawCredentialId, keyPair);
169184

170185
// Step 12
171-
const authenticatorData = await this.generateAuthenticatorData(rpEntity.id, sigCnt, attestedCredentialData, extensionData);
186+
const authenticatorData = await this.generateAuthenticatorData(rpEntity.id, sigCnt, attestedCredentialData, processedExtensions);
172187

173188
// Step 13
174189
const attObj = await this.generateAttestationObject(hash, authenticatorData);

src/webauthn_client.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import * as CBOR from 'cbor';
12
import {base64ToByteArray, byteArrayToBase64, getDomainFromOrigin} from "./utils";
23
import {Authenticator} from "./webauthn_authenticator";
34
import {getLogger} from "./logging";
5+
import {PSK_EXTENSION_IDENTIFIER} from "./constants";
46

57
type FunctionType = string;
68
const Create: FunctionType = "webauthn.create";
79
const Get: FunctionType = "webauthn.get";
810

9-
const log = getLogger('webauthn_authenticator');
11+
const log = getLogger('webauthn_client');
1012

1113
export async function createPublicKeyCredential(origin: string, options: CredentialCreationOptions, sameOriginWithAncestors: boolean, userConsentCallback: Promise<boolean>): Promise<PublicKeyCredential> {
1214
log.debug('Called createPublicKeyCredential');
@@ -20,7 +22,19 @@ export async function createPublicKeyCredential(origin: string, options: Credent
2022
options.publicKey.rp.id = options.publicKey.rp.id || getDomainFromOrigin(origin);
2123

2224
// Step 11
23-
// ToDo clientExtensions + authenticatorExtensions
25+
const clientExtensions = undefined; // ToDo clientExtensions
26+
let authenticatorExtensions = undefined;
27+
if (options.publicKey.extensions) {
28+
const reqExt: any = options.publicKey.extensions;
29+
if (reqExt.hasOwnProperty(PSK_EXTENSION_IDENTIFIER)) {
30+
log.debug('PSK extension requested');
31+
if (reqExt[PSK_EXTENSION_IDENTIFIER] == true) {
32+
log.debug('PSK extension has valid client input');
33+
const authenticatorExtensionInput = new Uint8Array(CBOR.encodeCanonical(null));
34+
authenticatorExtensions = new Map([[PSK_EXTENSION_IDENTIFIER, byteArrayToBase64(authenticatorExtensionInput, true)]]);
35+
}
36+
}
37+
}
2438

2539
// Step 13 + 14
2640
const clientDataJSON = generateClientDataJSON(Create, options.publicKey.challenge as ArrayBuffer, origin);
@@ -30,18 +44,24 @@ export async function createPublicKeyCredential(origin: string, options: Credent
3044
const clientDataHash = new Uint8Array(clientDataHashDigest);
3145

3246
// Step 20: Simplified, just for 1 authenticator
33-
const userVerification = options.publicKey.authenticatorSelection.requireUserVerification === "required";
47+
let userVerification = false;
48+
let residentKey = false;
49+
if (options.publicKey.authenticatorSelection) {
50+
userVerification = options.publicKey.authenticatorSelection.requireUserVerification === "required";
51+
residentKey = options.publicKey.authenticatorSelection.requireResidentKey;
52+
}
3453
const userPresence = !userVerification;
3554

3655
const attObjWrapper = await Authenticator.authenticatorMakeCredential(userConsentCallback,
3756
clientDataHash,
3857
options.publicKey.rp,
3958
options.publicKey.user,
40-
options.publicKey.authenticatorSelection.requireResidentKey,
59+
residentKey,
4160
userPresence,
4261
userVerification,
4362
options.publicKey.pubKeyCredParams,
44-
options.publicKey.excludeCredentials);
63+
options.publicKey.excludeCredentials,
64+
authenticatorExtensions);
4565

4666
log.debug('Received attestation object');
4767

0 commit comments

Comments
 (0)