@turnkey/encoding
Version:
Encoding utility functions
152 lines (149 loc) • 6.11 kB
JavaScript
/**
* Code modified from https://github.com/github/webauthn-json/blob/e932b3585fa70b0bd5b5a4012ba7dbad7b0a0d0f/src/webauthn-json/base64url.ts#L23
*/
const DEFAULT_JWK_MEMBER_BYTE_LENGTH = 32;
function stringToBase64urlString(input) {
// string to base64 -- we do not rely on the browser's btoa since it's not present in React Native environments
const base64String = btoa(input);
return base64StringToBase64UrlEncodedString(base64String);
}
function hexStringToBase64url(input, length) {
// Add an extra 0 to the start of the string to get a valid hex string (even length)
// (e.g. 0x0123 instead of 0x123)
const hexString = input.padStart(Math.ceil(input.length / 2) * 2, "0");
const buffer = uint8ArrayFromHexString(hexString, length);
return stringToBase64urlString(buffer.reduce((result, x) => result + String.fromCharCode(x), ""));
}
function base64StringToBase64UrlEncodedString(input) {
return input.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
function uint8ArrayToHexString(input) {
return input.reduce((result, x) => result + x.toString(16).padStart(2, "0"), "");
}
const uint8ArrayFromHexString = (hexString, length) => {
const hexRegex = /^[0-9A-Fa-f]+$/;
if (!hexString || hexString.length % 2 != 0 || !hexRegex.test(hexString)) {
throw new Error(`cannot create uint8array from invalid hex string: "${hexString}"`);
}
const buffer = new Uint8Array(hexString.match(/../g).map((h) => parseInt(h, 16)));
if (!length) {
return buffer;
}
if (hexString.length / 2 > length) {
throw new Error("hex value cannot fit in a buffer of " + length + " byte(s)");
}
// If a length is specified, ensure we sufficiently pad
let paddedBuffer = new Uint8Array(length);
paddedBuffer.set(buffer, length - buffer.length);
return paddedBuffer;
};
/**
* Converts a hex string to an ASCII string.
* @param {string} hexString - The input hex string to convert.
* @returns {string} - The converted ASCII string.
*/
function hexToAscii(hexString) {
let asciiStr = "";
for (let i = 0; i < hexString.length; i += 2) {
asciiStr += String.fromCharCode(parseInt(hexString.substr(i, 2), 16));
}
return asciiStr;
}
/**
* Function to normalize padding of byte array with 0's to a target length.
*
* @param {Uint8Array} byteArray - The byte array to pad or trim.
* @param {number} targetLength - The target length after padding or trimming.
* @returns {Uint8Array} - The normalized byte array.
*/
const normalizePadding = (byteArray, targetLength) => {
const paddingLength = targetLength - byteArray.length;
// Add leading 0's to array
if (paddingLength > 0) {
const padding = new Uint8Array(paddingLength).fill(0);
return new Uint8Array([...padding, ...byteArray]);
}
// Remove leading 0's from array
if (paddingLength < 0) {
const expectedZeroCount = paddingLength * -1;
let zeroCount = 0;
for (let i = 0; i < expectedZeroCount && i < byteArray.length; i++) {
if (byteArray[i] === 0) {
zeroCount++;
}
}
// Check if the number of zeros found equals the number of zeroes expected
if (zeroCount !== expectedZeroCount) {
throw new Error(`invalid number of starting zeroes. Expected number of zeroes: ${expectedZeroCount}. Found: ${zeroCount}.`);
}
return byteArray.slice(expectedZeroCount, expectedZeroCount + targetLength);
}
return byteArray;
};
// Pure JS implementation of btoa. This is adapted from the following:
// https://github.com/jsdom/abab/blob/80874ae1fe1cde2e587bb6e51b6d7c9b42ca1d34/lib/btoa.js
function btoa(s) {
if (arguments.length === 0) {
throw new TypeError("1 argument required, but only 0 present.");
}
let i;
// String conversion as required by Web IDL.
s = `${s}`;
// "The btoa() method must throw an "InvalidCharacterError" DOMException if
// data contains any character whose code point is greater than U+00FF."
for (i = 0; i < s.length; i++) {
if (s.charCodeAt(i) > 255) {
throw new Error(`InvalidCharacterError: found code point greater than 255:${s.charCodeAt(i)} at position ${i}`);
}
}
let out = "";
for (i = 0; i < s.length; i += 3) {
const groupsOfSix = [
undefined,
undefined,
undefined,
undefined,
];
groupsOfSix[0] = s.charCodeAt(i) >> 2;
groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4;
if (s.length > i + 1) {
groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4;
groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2;
}
if (s.length > i + 2) {
groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6;
groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f;
}
for (let j = 0; j < groupsOfSix.length; j++) {
if (typeof groupsOfSix[j] === "undefined") {
out += "=";
}
else {
out += btoaLookup(groupsOfSix[j]);
}
}
}
return out;
}
function btoaLookup(index) {
/**
* Lookup table for btoa(), which converts a six-bit number into the
* corresponding ASCII character.
*/
const keystr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
if (index >= 0 && index < 64) {
return keystr[index];
}
// Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests.
return undefined;
}
exports.DEFAULT_JWK_MEMBER_BYTE_LENGTH = DEFAULT_JWK_MEMBER_BYTE_LENGTH;
exports.base64StringToBase64UrlEncodedString = base64StringToBase64UrlEncodedString;
exports.hexStringToBase64url = hexStringToBase64url;
exports.hexToAscii = hexToAscii;
exports.normalizePadding = normalizePadding;
exports.stringToBase64urlString = stringToBase64urlString;
exports.uint8ArrayFromHexString = uint8ArrayFromHexString;
exports.uint8ArrayToHexString = uint8ArrayToHexString;
//# sourceMappingURL=index.js.map
;