fix: WebAuthn PRF JSON Base64URL serialization for optionsJSON
This commit is contained in:
+41
-13
@@ -47,6 +47,29 @@ export function setActiveMasterKey(key: ArrayBuffer | null) {
|
|||||||
// Convert string salt to 32-byte Uint8Array
|
// Convert string salt to 32-byte Uint8Array
|
||||||
const PRF_SALT = new TextEncoder().encode("KapteinsDaagboxPRFSaltForE2EKey_")
|
const PRF_SALT = new TextEncoder().encode("KapteinsDaagboxPRFSaltForE2EKey_")
|
||||||
|
|
||||||
|
function bufferToBase64URL(buffer: ArrayBuffer): string {
|
||||||
|
const bytes = new Uint8Array(buffer)
|
||||||
|
let binary = ''
|
||||||
|
for (let i = 0; i < bytes.byteLength; i++) {
|
||||||
|
binary += String.fromCharCode(bytes[i])
|
||||||
|
}
|
||||||
|
const base64 = window.btoa(binary)
|
||||||
|
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64urlToBuffer(base64url: string): ArrayBuffer {
|
||||||
|
let base64 = base64url.replace(/-/g, '+').replace(/_/g, '/')
|
||||||
|
while (base64.length % 4) {
|
||||||
|
base64 += '='
|
||||||
|
}
|
||||||
|
const binary = window.atob(base64)
|
||||||
|
const bytes = new Uint8Array(binary.length)
|
||||||
|
for (let i = 0; i < binary.length; i++) {
|
||||||
|
bytes[i] = binary.charCodeAt(i)
|
||||||
|
}
|
||||||
|
return bytes.buffer
|
||||||
|
}
|
||||||
|
|
||||||
export interface RegistrationResult {
|
export interface RegistrationResult {
|
||||||
verified: boolean
|
verified: boolean
|
||||||
recoveryPhrase: string
|
recoveryPhrase: string
|
||||||
@@ -67,15 +90,11 @@ export async function registerUser(username: string): Promise<RegistrationResult
|
|||||||
|
|
||||||
const options = await optionsRes.json()
|
const options = await optionsRes.json()
|
||||||
|
|
||||||
// Request the PRF extension in the browser options
|
// Request the PRF extension in the browser options (empty object is standard for enabling PRF)
|
||||||
if (!options.extensions) {
|
if (!options.extensions) {
|
||||||
options.extensions = {}
|
options.extensions = {}
|
||||||
}
|
}
|
||||||
options.extensions.prf = {
|
options.extensions.prf = {}
|
||||||
eval: {
|
|
||||||
first: PRF_SALT.buffer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Start biometric Passkey creation
|
// 2. Start biometric Passkey creation
|
||||||
const credentialResponse = await startRegistration({ optionsJSON: options })
|
const credentialResponse = await startRegistration({ optionsJSON: options })
|
||||||
@@ -95,7 +114,10 @@ export async function registerUser(username: string): Promise<RegistrationResult
|
|||||||
console.log('Registration PRF extension result:', prfResults)
|
console.log('Registration PRF extension result:', prfResults)
|
||||||
|
|
||||||
if (prfResults?.enabled && prfResults.results?.first) {
|
if (prfResults?.enabled && prfResults.results?.first) {
|
||||||
const prfKey = await deriveKeyFromPrf(prfResults.results.first)
|
const firstBuffer = typeof prfResults.results.first === 'string'
|
||||||
|
? base64urlToBuffer(prfResults.results.first)
|
||||||
|
: prfResults.results.first
|
||||||
|
const prfKey = await deriveKeyFromPrf(firstBuffer)
|
||||||
const encryptedPrf = await encryptBuffer(masterKey, prfKey)
|
const encryptedPrf = await encryptBuffer(masterKey, prfKey)
|
||||||
encryptedMasterKeyPrf = encryptedPrf.ciphertext
|
encryptedMasterKeyPrf = encryptedPrf.ciphertext
|
||||||
encryptedMasterKeyPrfIv = encryptedPrf.iv
|
encryptedMasterKeyPrfIv = encryptedPrf.iv
|
||||||
@@ -151,7 +173,7 @@ export interface LoginResult {
|
|||||||
encryptedMasterKeyRecTag: string
|
encryptedMasterKeyRecTag: string
|
||||||
userId: string
|
userId: string
|
||||||
username: string
|
username: string
|
||||||
prfFirst?: ArrayBuffer
|
prfFirst?: string | ArrayBuffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,13 +203,13 @@ export async function loginUser(username?: string): Promise<LoginResult> {
|
|||||||
|
|
||||||
const options = await optionsRes.json()
|
const options = await optionsRes.json()
|
||||||
|
|
||||||
// Add PRF extension evaluation input
|
// Add PRF extension evaluation input as a Base64URL string for JSON options
|
||||||
if (!options.extensions) {
|
if (!options.extensions) {
|
||||||
options.extensions = {}
|
options.extensions = {}
|
||||||
}
|
}
|
||||||
options.extensions.prf = {
|
options.extensions.prf = {
|
||||||
eval: {
|
eval: {
|
||||||
first: PRF_SALT.buffer
|
first: bufferToBase64URL(PRF_SALT.buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +250,10 @@ export async function loginUser(username?: string): Promise<LoginResult> {
|
|||||||
|
|
||||||
if (prfResults?.results?.first && result.encryptedMasterKeyPrf) {
|
if (prfResults?.results?.first && result.encryptedMasterKeyPrf) {
|
||||||
try {
|
try {
|
||||||
const prfKey = await deriveKeyFromPrf(prfResults.results.first)
|
const firstBuffer = typeof prfResults.results.first === 'string'
|
||||||
|
? base64urlToBuffer(prfResults.results.first)
|
||||||
|
: prfResults.results.first
|
||||||
|
const prfKey = await deriveKeyFromPrf(firstBuffer)
|
||||||
const decryptedMaster = await decryptBuffer(
|
const decryptedMaster = await decryptBuffer(
|
||||||
result.encryptedMasterKeyPrf,
|
result.encryptedMasterKeyPrf,
|
||||||
result.encryptedMasterKeyPrfIv,
|
result.encryptedMasterKeyPrfIv,
|
||||||
@@ -269,7 +294,7 @@ export async function completeLoginWithRecovery(
|
|||||||
encryptedMasterKeyRecIv: string
|
encryptedMasterKeyRecIv: string
|
||||||
encryptedMasterKeyRecTag: string
|
encryptedMasterKeyRecTag: string
|
||||||
userId: string
|
userId: string
|
||||||
prfFirst?: ArrayBuffer
|
prfFirst?: string | ArrayBuffer
|
||||||
}
|
}
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
@@ -285,7 +310,10 @@ export async function completeLoginWithRecovery(
|
|||||||
if (encryptedPayloads.prfFirst) {
|
if (encryptedPayloads.prfFirst) {
|
||||||
console.log('Attempting PRF enrollment on recovery login...')
|
console.log('Attempting PRF enrollment on recovery login...')
|
||||||
try {
|
try {
|
||||||
const prfKey = await deriveKeyFromPrf(encryptedPayloads.prfFirst)
|
const firstBuffer = typeof encryptedPayloads.prfFirst === 'string'
|
||||||
|
? base64urlToBuffer(encryptedPayloads.prfFirst)
|
||||||
|
: encryptedPayloads.prfFirst
|
||||||
|
const prfKey = await deriveKeyFromPrf(firstBuffer)
|
||||||
const encryptedPrf = await encryptBuffer(decryptedMaster, prfKey)
|
const encryptedPrf = await encryptBuffer(decryptedMaster, prfKey)
|
||||||
console.log('Sending PRF credentials to server...')
|
console.log('Sending PRF credentials to server...')
|
||||||
const enrollRes = await fetch(`${API_BASE}/enroll-prf`, {
|
const enrollRes = await fetch(`${API_BASE}/enroll-prf`, {
|
||||||
|
|||||||
Reference in New Issue
Block a user