93 lines
2.4 KiB
TypeScript
93 lines
2.4 KiB
TypeScript
/**
|
|
* Base64URL encoding/decoding utilities for WebAuthn and encryption
|
|
* Uses URL-safe alphabet without padding (per RFC 4648)
|
|
*/
|
|
|
|
export function base64UrlEncode(buffer: ArrayBuffer | Uint8Array): string {
|
|
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)
|
|
let binary = ''
|
|
|
|
for (let i = 0; i < bytes.byteLength; i++) {
|
|
binary += String.fromCharCode(bytes[i])
|
|
}
|
|
|
|
const base64 = btoa(binary)
|
|
|
|
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
|
|
}
|
|
|
|
export function base64UrlDecode(str: string): Uint8Array {
|
|
let base64 = str.replace(/-/g, '+').replace(/_/g, '/')
|
|
|
|
const paddingNeeded = (4 - (base64.length % 4)) % 4
|
|
base64 += '='.repeat(paddingNeeded)
|
|
|
|
const binary = atob(base64)
|
|
const bytes = new Uint8Array(binary.length)
|
|
|
|
for (let i = 0; i < binary.length; i++) {
|
|
bytes[i] = binary.charCodeAt(i)
|
|
}
|
|
|
|
return bytes
|
|
}
|
|
|
|
export function arrayBufferToBase64Url(buffer: ArrayBuffer): string {
|
|
return base64UrlEncode(buffer)
|
|
}
|
|
|
|
export function base64UrlToArrayBuffer(str: string): ArrayBuffer {
|
|
return base64UrlDecode(str).buffer as ArrayBuffer
|
|
}
|
|
|
|
export function stringToUint8Array(str: string): Uint8Array {
|
|
const encoder = new TextEncoder()
|
|
return encoder.encode(str)
|
|
}
|
|
|
|
export function uint8ArrayToString(bytes: Uint8Array): string {
|
|
const decoder = new TextDecoder()
|
|
return decoder.decode(bytes)
|
|
}
|
|
|
|
export function hexEncode(buffer: ArrayBuffer | Uint8Array): string {
|
|
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)
|
|
return Array.from(bytes)
|
|
.map((b) => b.toString(16).padStart(2, '0'))
|
|
.join('')
|
|
}
|
|
|
|
export function hexDecode(hex: string): Uint8Array {
|
|
const bytes = new Uint8Array(hex.length / 2)
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)
|
|
}
|
|
return bytes
|
|
}
|
|
|
|
export function concatUint8Arrays(...arrays: Uint8Array[]): Uint8Array {
|
|
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0)
|
|
const result = new Uint8Array(totalLength)
|
|
let offset = 0
|
|
|
|
for (const arr of arrays) {
|
|
result.set(arr, offset)
|
|
offset += arr.length
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
export function compareUint8Arrays(a: Uint8Array, b: Uint8Array): boolean {
|
|
if (a.length !== b.length) {
|
|
return false
|
|
}
|
|
|
|
let result = 0
|
|
for (let i = 0; i < a.length; i++) {
|
|
result |= a[i] ^ b[i]
|
|
}
|
|
|
|
return result === 0
|
|
}
|