wg-lua/src/base64.ts
Erica Marigold 666a5f79e8
feat: generatePublicKey from base64 string
`Wireguard:generatePublicKey` now accepts the privateKey as a base64 encoded string, which gets decoded internally to produce the raw bytes.
2024-03-31 15:56:34 +05:30

104 lines
3.1 KiB
TypeScript

const { toBinary, getCharAt } = require<{
toBinary: (int: number) => string;
getCharAt: (str: string, pos: number) => string;
}>("./util.lua");
const BASE64_CHAR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// Adapted from https://gist.github.com/jonleighton/958841
export function encode(buf: number[]): string {
let base64 = "";
const byteLength = buf.size();
const byteRemainder = byteLength % 3;
const mainLength = byteLength - byteRemainder;
let a: number, b: number, c: number, d: number;
let chunk;
// Main loop deals with bytes in chunks of 3
for (let i = 0; i < mainLength; i = i + 3) {
// Combine the three bytes into a single integer
chunk = (buf[i] << 16) | (buf[i + 1] << 8) | buf[i + 2];
// Use bitmasks to extract 6-bit segments from the triplet
a = (chunk & 16515072) >> 18;
b = (chunk & 258048) >> 12;
c = (chunk & 4032) >> 6;
d = chunk & 63;
// Convert the raw binary segments to the appropriate ASCII encoding
base64 +=
string.char(string.byte(BASE64_CHAR, a + 1)[0]) +
string.char(string.byte(BASE64_CHAR, b + 1)[0]) +
string.char(string.byte(BASE64_CHAR, c + 1)[0]) +
string.char(string.byte(BASE64_CHAR, d + 1)[0]);
}
// Deal with the remaining bytes and padding
if (byteRemainder === 1) {
chunk = buf[mainLength];
a = (chunk & 252) >> 2;
// Set the 4 least significant bits to zero
b = (chunk & 3) << 4;
base64 += string.byte(BASE64_CHAR, a)[0] + string.byte(BASE64_CHAR, b)[0] + "==";
} else if (byteRemainder === 2) {
chunk = (buf[mainLength] << 8) | buf[mainLength + 1];
a = (chunk & 64512) >> 10;
b = (chunk & 1008) >> 4;
// Set the 2 least significant bits to zero
c = (chunk & 15) << 2;
base64 +=
string.char(string.byte(BASE64_CHAR, a + 1)[0]) +
string.char(string.byte(BASE64_CHAR, b + 1)[0]) +
string.char(string.byte(BASE64_CHAR, c + 1)[0]) +
"=";
}
return base64;
}
// FIXME: Ideally, you'd want to use bit math and mask off bytes and stuff,
// but I'm lazy, so this logic uses string manipulation instead
export function decode(base64: string): number[] {
// Strip padding from base64
base64 = base64.split("=")[0].gsub("%s", "")[0];
// Convert base64 chars to lookup table offsets
const chars = [];
for (let i = 1; i <= base64.size(); i++) {
const char = getCharAt(base64, i);
const [pos] = string.find(BASE64_CHAR, char);
pos !== undefined ? chars.push(pos - 1) : error("invalid base64 data");
}
// Convert offsets to 6 bit binary numbers
const bin = chars.map(toBinary);
// Combine all binary numbers into one
let combinedBin = "";
bin.forEach((b) => (combinedBin += b));
// Split the combined binary number into smaller ones of 8 bits each
const intermediaryBin = [];
while (combinedBin.size() > 0) {
intermediaryBin.push(string.sub(combinedBin, 1, 8));
combinedBin = string.sub(combinedBin, 9, combinedBin.size());
}
// Convert each individual 8 bit binary number to a base 10 integer
const decoded = [];
for (let i = 0; i < intermediaryBin.size() - 1; i++) {
const byte = tonumber(intermediaryBin[i], 2);
decoded.push(byte !== undefined ? byte : error("got invalid byte while decoding base64"));
}
return decoded;
}