feat: update libraries
This commit is contained in:
@@ -12,6 +12,11 @@ Publish script:
|
|||||||
hatter script pub --type deno-mod deno-commons-mod.ts
|
hatter script pub --type deno-mod deno-commons-mod.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Test script:
|
||||||
|
```shell
|
||||||
|
deno test --allow-all deno-commons-mod.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Published deno-mod at:
|
Published deno-mod at:
|
||||||
[script deno mod](https://hatter.ink/script/list_script.jssp?type=deno-mod)
|
[script deno mod](https://hatter.ink/script/list_script.jssp?type=deno-mod)
|
||||||
|
|||||||
@@ -3,8 +3,13 @@
|
|||||||
|
|
||||||
import { assert } from "jsr:@std/assert/assert";
|
import { assert } from "jsr:@std/assert/assert";
|
||||||
import { assertEquals } from "jsr:@std/assert";
|
import { assertEquals } from "jsr:@std/assert";
|
||||||
|
import { decodeBase64, encodeBase64 } from "jsr:@std/encoding/base64";
|
||||||
import { dirname } from "https://deno.land/std@0.208.0/path/mod.ts";
|
import { dirname } from "https://deno.land/std@0.208.0/path/mod.ts";
|
||||||
|
|
||||||
|
// reference: https://docs.deno.com/examples/hex_base64_encoding/
|
||||||
|
// import { decodeBase64, encodeBase64 } from "jsr:@std/encoding/base64";
|
||||||
|
// import { decodeHex, encodeHex } from "jsr:@std/encoding/hex";
|
||||||
|
|
||||||
export async function sleep(timeoutMillis: number): Promise<void> {
|
export async function sleep(timeoutMillis: number): Promise<void> {
|
||||||
await new Promise((resolve) => setTimeout(resolve, timeoutMillis));
|
await new Promise((resolve) => setTimeout(resolve, timeoutMillis));
|
||||||
}
|
}
|
||||||
@@ -28,7 +33,7 @@ export function compareVersion(ver1: string, ver2: string): 0 | 1 | -1 {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isOn(val: string | undefined): boolean {
|
export function isOn(val: string | undefined | null): boolean {
|
||||||
if ((val === null) || (val === undefined)) {
|
if ((val === null) || (val === undefined)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -37,7 +42,7 @@ export function isOn(val: string | undefined): boolean {
|
|||||||
lowerVal === "true";
|
lowerVal === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEnv(key: string): string {
|
export async function getEnv(key: string): Promise<string | null> {
|
||||||
const homeDir = getHomeDir();
|
const homeDir = getHomeDir();
|
||||||
if ((homeDir !== null) && key) {
|
if ((homeDir !== null) && key) {
|
||||||
const envValue = await readFileToString(
|
const envValue = await readFileToString(
|
||||||
@@ -47,11 +52,15 @@ export async function getEnv(key: string): string {
|
|||||||
return envValue.trim();
|
return envValue.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Deno.env.get(key);
|
return Deno.env.get(key) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEnvOn(envKey: string): boolean {
|
export function isEnvOn(envKey: string): boolean {
|
||||||
return isOn(getEnv(envKey));
|
return isOn(Deno.env.get(envKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isEnvOnAsync(envKey: string): Promise<boolean> {
|
||||||
|
return isOn(await getEnv(envKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatHumanTime(timeMillis: number): string {
|
export function formatHumanTime(timeMillis: number): string {
|
||||||
@@ -345,6 +354,22 @@ export function hexStringToUint8Array(hex: string): Uint8Array {
|
|||||||
return uint8;
|
return uint8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function decodeBase64Url(base64UrlString: string): Uint8Array {
|
||||||
|
let standardBase64 = base64UrlString.replace(/-/g, "+").replace(/_/g, "/");
|
||||||
|
while (standardBase64.length % 4) {
|
||||||
|
standardBase64 += "=";
|
||||||
|
}
|
||||||
|
return decodeBase64(standardBase64);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeBase64Url(input: ArrayBufferLike): string {
|
||||||
|
let standardBased64 = encodeBase64(input);
|
||||||
|
return standardBased64.replace(/\+/g, "-").replace(/\//g, "_").replace(
|
||||||
|
/=/g,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Deno.test("isOn", () => {
|
Deno.test("isOn", () => {
|
||||||
assertEquals(false, isOn(undefined));
|
assertEquals(false, isOn(undefined));
|
||||||
assertEquals(false, isOn(""));
|
assertEquals(false, isOn(""));
|
||||||
@@ -409,3 +434,18 @@ Deno.test("sleep", async () => {
|
|||||||
const t2 = new Date().getTime();
|
const t2 = new Date().getTime();
|
||||||
assert(Math.abs(1000 - (t2 - t1)) < 20);
|
assert(Math.abs(1000 - (t2 - t1)) < 20);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test("base64Url", () => {
|
||||||
|
assertEquals(
|
||||||
|
"_dxhVwI3qd9fMBlpEMmi6Q",
|
||||||
|
encodeBase64Url(decodeBase64Url("_dxhVwI3qd9fMBlpEMmi6Q")),
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
"1dxJeD7erjAYUNEmdVNE8KdhpPZs0pAHtb-kbSqYIe5j039PkTHbrQYOEoeEWN4UsDERhnUg7mY",
|
||||||
|
encodeBase64Url(
|
||||||
|
decodeBase64Url(
|
||||||
|
"1dxJeD7erjAYUNEmdVNE8KdhpPZs0pAHtb-kbSqYIe5j039PkTHbrQYOEoeEWN4UsDERhnUg7mY",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
|
decodeBase64Url,
|
||||||
|
encodeBase64Url,
|
||||||
getHomeDirOrDie,
|
getHomeDirOrDie,
|
||||||
hexStringToUint8Array,
|
hexStringToUint8Array,
|
||||||
uint8ArrayToHexString,
|
} from "https://global.hatter.ink/script/get/@8/deno-commons-mod.ts";
|
||||||
} from "https://global.hatter.ink/script/get/@6/deno-commons-mod.ts";
|
import {getRandomValues} from "node:crypto";
|
||||||
|
import {assertEquals} from "jsr:@std/assert";
|
||||||
|
|
||||||
const COMMONS_LOCAL_ENCRYPT_TINY_ENCRYPT_MASTER_KEY_FILE = getHomeDirOrDie() +
|
const COMMONS_LOCAL_ENCRYPT_TINY_ENCRYPT_MASTER_KEY_FILE = getHomeDirOrDie() +
|
||||||
"/.cache/commons-local-encrypt-tiny-encrypt-master-key";
|
"/.cache/commons-local-encrypt-tiny-encrypt-master-key";
|
||||||
@@ -12,6 +15,26 @@ interface TinyEncryptSimpleDecryptObject {
|
|||||||
result: string;
|
result: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cachedCryptoMasterKey: CryptoKey | null = null;
|
||||||
|
|
||||||
|
export async function lazyLoadCryptoMasterKey(): Promise<CryptoKey> {
|
||||||
|
if (cachedCryptoMasterKey == null) {
|
||||||
|
cachedCryptoMasterKey = await loadCryptoMasterKey();
|
||||||
|
}
|
||||||
|
return cachedCryptoMasterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCryptoMasterKey(): Promise<CryptoKey> {
|
||||||
|
const key = await loadMasterKey();
|
||||||
|
return await crypto.subtle.importKey(
|
||||||
|
"raw",
|
||||||
|
key,
|
||||||
|
"AES-GCM",
|
||||||
|
false,
|
||||||
|
["encrypt", "decrypt"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadMasterKey(): Promise<Uint8Array> {
|
async function loadMasterKey(): Promise<Uint8Array> {
|
||||||
const masterKeyContent = Deno.readTextFileSync(
|
const masterKeyContent = Deno.readTextFileSync(
|
||||||
COMMONS_LOCAL_ENCRYPT_TINY_ENCRYPT_MASTER_KEY_FILE,
|
COMMONS_LOCAL_ENCRYPT_TINY_ENCRYPT_MASTER_KEY_FILE,
|
||||||
@@ -38,8 +61,55 @@ stderr: ${new TextDecoder().decode(stderr)}`);
|
|||||||
return hexStringToUint8Array(tinyEncryptSimpleDecryptObject.result);
|
return hexStringToUint8Array(tinyEncryptSimpleDecryptObject.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
export async function teDecryptToString(ciphertext: string): Promise<string> {
|
||||||
// TODO ...
|
const decryptedValue = await teDecrypt(ciphertext);
|
||||||
console.log(uint8ArrayToHexString(await loadMasterKey()));
|
return new TextDecoder().decode(decryptedValue);
|
||||||
}
|
}
|
||||||
await main();
|
|
||||||
|
export async function teDecrypt(ciphertext: string): Promise<ArrayBufferLike> {
|
||||||
|
if (!ciphertext.startsWith("te:")) {
|
||||||
|
throw new Error(`Invalid ciphertext: ${ciphertext}`);
|
||||||
|
}
|
||||||
|
const ciphertextParts = ciphertext.split(":");
|
||||||
|
if (ciphertextParts.length !== 3) {
|
||||||
|
throw new Error(`Invalid ciphertext: ${ciphertext}`);
|
||||||
|
}
|
||||||
|
const nonce = decodeBase64Url(ciphertextParts[1]);
|
||||||
|
const ciphertextAndTag = decodeBase64Url(ciphertextParts[2]);
|
||||||
|
|
||||||
|
const cryptoKey = await lazyLoadCryptoMasterKey();
|
||||||
|
|
||||||
|
return await crypto.subtle.decrypt(
|
||||||
|
{ name: "AES-GCM", iv: nonce },
|
||||||
|
cryptoKey,
|
||||||
|
ciphertextAndTag,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function teEncrypt(plaintext: string): Promise<string> {
|
||||||
|
const nonce = randomNonce();
|
||||||
|
const plaintextBuffer = new TextEncoder().encode(plaintext);
|
||||||
|
|
||||||
|
const cryptoKey = await lazyLoadCryptoMasterKey();
|
||||||
|
const encryptedData = await crypto.subtle.encrypt(
|
||||||
|
{ name: "AES-GCM", iv: nonce },
|
||||||
|
cryptoKey,
|
||||||
|
plaintextBuffer,
|
||||||
|
);
|
||||||
|
return `te:${encodeBase64Url(nonce)}:${encodeBase64Url(encryptedData)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomNonce(): ArrayBufferLike {
|
||||||
|
const buffer = new Uint8Array(12);
|
||||||
|
getRandomValues(buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test("teEncryptDecrypt", async () => {
|
||||||
|
assertEquals(
|
||||||
|
"hello world",
|
||||||
|
await teDecryptToString(
|
||||||
|
await teEncrypt("hello world"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user