feat: establish NekoNest Cloud control and relay
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
const REPLAY_DOMAIN = "nekonest-cloud/device-registration-replay/v1";
|
||||
export const DEVICE_REGISTRATION_REPLAY_TTL_MS = 10 * 60_000;
|
||||
|
||||
function bytesToBase64(bytes: Uint8Array): string {
|
||||
let binary = "";
|
||||
for (const byte of bytes) binary += String.fromCharCode(byte);
|
||||
return btoa(binary);
|
||||
}
|
||||
|
||||
function base64ToBytes(value: string): Uint8Array {
|
||||
const binary = atob(value);
|
||||
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
||||
}
|
||||
|
||||
async function registrationReplayKey(retryKey: string): Promise<CryptoKey> {
|
||||
const material = await crypto.subtle.digest(
|
||||
"SHA-256",
|
||||
new TextEncoder().encode(`${REPLAY_DOMAIN}\0${retryKey}`),
|
||||
);
|
||||
return crypto.subtle.importKey("raw", material, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
||||
}
|
||||
|
||||
export async function encryptRegistrationReplay<T>(input: {
|
||||
retryKey: string;
|
||||
pairingId: string;
|
||||
requestHash: string;
|
||||
response: T;
|
||||
}): Promise<{ ciphertext: string; nonce: string }> {
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(12));
|
||||
const ciphertext = await crypto.subtle.encrypt(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv: nonce,
|
||||
additionalData: new TextEncoder().encode(`${input.pairingId}\0${input.requestHash}`),
|
||||
},
|
||||
await registrationReplayKey(input.retryKey),
|
||||
new TextEncoder().encode(JSON.stringify(input.response)),
|
||||
);
|
||||
return {
|
||||
ciphertext: bytesToBase64(new Uint8Array(ciphertext)),
|
||||
nonce: bytesToBase64(nonce),
|
||||
};
|
||||
}
|
||||
|
||||
export async function decryptRegistrationReplay<T>(input: {
|
||||
retryKey: string;
|
||||
pairingId: string;
|
||||
requestHash: string;
|
||||
ciphertext: string;
|
||||
nonce: string;
|
||||
}): Promise<T> {
|
||||
const plaintext = await crypto.subtle.decrypt(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv: Uint8Array.from(base64ToBytes(input.nonce)).buffer,
|
||||
additionalData: new TextEncoder().encode(`${input.pairingId}\0${input.requestHash}`),
|
||||
},
|
||||
await registrationReplayKey(input.retryKey),
|
||||
Uint8Array.from(base64ToBytes(input.ciphertext)).buffer,
|
||||
);
|
||||
return JSON.parse(new TextDecoder().decode(plaintext)) as T;
|
||||
}
|
||||
Reference in New Issue
Block a user