diff --git a/README.md b/README.md index 3493b57..4adbade 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@ const decoded = base64Decode(encoded); // Restored. ```ts const file = await Deno.readFile("/path/to/binary.bin"); -const hash = await deriveHash(false, file); // byte array of hash value. -const keyEcdh = await generateKeyPair(false); // public/private key pair for ECDH, each in byte array. -const keyEcdsa = await generateKeyPair(true); // public/private key pair for ECDSA, each in byte array. +const hash = await cryptoHash(false, file); // byte array of hash value. +const keyEcdh = await cryptoGenerateKey(false); // public/private key pair for ECDH, each in byte array. +const keyEcdsa = await cryptoGenerateKey(true); // public/private key pair for ECDSA, each in byte array. const encrypted = await cryptoEncrypt(keyEcdh, file); // encrypted byte array. const decrypted = await cryptoDecrypt(keyEcdh, encrypted); // Restored. const signature = await cryptoSign(keyEcdsa.privateKey, data); // signature byte array. @@ -137,5 +137,18 @@ const {default: data} = await import("./data.json", {assert: {type: "json"}});

+# Browser Compatible +Some methods and classes in this module don't use Deno objects internally and are browser compatible. + +I have prepared browser compatible code only export as [mod.compatible.ts](./mod.compatible.ts). + +By bundling this, you can easily create universal utility scripts. + +```sh +deno bundle https://deno.land/x/simple_utility@(version)/mod.compatible.ts | esbuild --minify | head -c -1 | tee ./simple_utility.esm.min.js +``` + +The example uses [esbuild](https://esbuild.github.io) to minify. + # API See [Deno Document](https://deno.land/x/simple_utility/mod.ts) for details. \ No newline at end of file diff --git a/src/crypto.ts b/src/crypto.ts index 5bb32b4..cde26b4 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -6,23 +6,12 @@ export type PortableCryptoKey = Uint8Array; /** * Each is `PortableCryptoKey` public/private key pair. */ -export interface PortableCryptoKeyPair{ - privateKey: PortableCryptoKey; - publicKey: PortableCryptoKey; -} +export type PortableCryptoKeyPair = Record; -async function parseCommonKey(kp:PortableCryptoKeyPair){ +async function deriveSecretKey(kp:PortableCryptoKeyPair){ const ec:EcKeyAlgorithm = { - namedCurve: "P-384", - name: "ECDH" - }; - - const publicKey = await crypto.subtle.importKey("spki", kp.publicKey, ec, false, []); - const privateKey = await crypto.subtle.importKey("pkcs8", kp.privateKey, ec, false, ["deriveKey", "deriveBits"]); - - const dh:EcdhKeyDeriveParams = { name: "ECDH", - public: publicKey + namedCurve: "P-384" }; const aes:AesDerivedKeyParams = { @@ -30,29 +19,25 @@ async function parseCommonKey(kp:PortableCryptoKeyPair){ length: 256 }; - return await crypto.subtle.deriveKey(dh, privateKey, aes, false, ["encrypt", "decrypt"]); -} - -async function parseSignKey(k:PortableCryptoKey, isPrivate:boolean){ - const format:KeyFormat = isPrivate ? "pkcs8" : "spki"; - const usage:KeyUsage[] = isPrivate ? ["sign"] : ["verify"]; + const publicKey = await crypto.subtle.importKey("spki", kp.publicKey, ec, false, []); + const privateKey = await crypto.subtle.importKey("pkcs8", kp.privateKey, ec, false, ["deriveKey", "deriveBits"]); - const ec:EcKeyAlgorithm = { - namedCurve: "P-384", - name: "ECDSA" + const dh:EcdhKeyDeriveParams = { + name: "ECDH", + public: publicKey }; - return await crypto.subtle.importKey(format, k, ec, false, usage); + return await crypto.subtle.deriveKey(dh, privateKey, aes, false, ["encrypt", "decrypt"]); } /** * Derive SHA2 hash value from byte array. -* @param isHalf Use the hash length 256 bits if `true`, 512 bits if `false`. +* @param is256 Use the hash length 256 bits if `true`, 512 bits if `false`. * @param data byte array. * @return byte array of hash value. */ -export async function deriveHash(isHalf:boolean, data:Uint8Array){ - const sha = isHalf ? "SHA-256" : "SHA-512"; +export async function cryptoHash(is256:boolean, data:Uint8Array){ + const sha = is256 ? "SHA-256" : "SHA-512"; return new Uint8Array(await crypto.subtle.digest(sha, data)); } @@ -62,12 +47,12 @@ export async function deriveHash(isHalf:boolean, data:Uint8Array){ * @param isDsa Outputs the key for ECDSA if `true`, for ECDH if `false`. * @return public/private key pair, each in byte array. */ -export async function generateKeyPair(isDsa:boolean){ +export async function cryptoGenerateKey(isDsa:boolean){ const usage:KeyUsage[] = isDsa ? ["sign", "verify"] : ["deriveKey", "deriveBits"]; const ec:EcKeyAlgorithm = { - namedCurve: "P-384", - name: isDsa ? "ECDSA" : "ECDH" + name: isDsa ? "ECDSA" : "ECDH", + namedCurve: "P-384" }; const {publicKey, privateKey} = await crypto.subtle.generateKey(ec, true, usage); @@ -95,11 +80,11 @@ export async function cryptoEncrypt(kp:PortableCryptoKeyPair, data:Uint8Array){ iv: crypto.getRandomValues(new Uint8Array(sizeIv)) }; - const commonKey = await parseCommonKey(kp); + const secretKey = await deriveSecretKey(kp); const output = new Uint8Array(sizeTag + sizeIv + data.byteLength); output.set(gcm.iv, 0); - output.set(new Uint8Array(await crypto.subtle.encrypt(gcm, commonKey, data)), gcm.iv.byteLength); + output.set(new Uint8Array(await crypto.subtle.encrypt(gcm, secretKey, data)), gcm.iv.byteLength); return output; } @@ -121,7 +106,7 @@ export async function cryptoDecrypt(kp:PortableCryptoKeyPair, data:Uint8Array){ iv: data.subarray(0, sizeIv) }; - const commonKey = await parseCommonKey(kp); + const commonKey = await deriveSecretKey(kp); return new Uint8Array(await crypto.subtle.decrypt(gcm, commonKey, data.subarray(sizeIv))); } @@ -133,6 +118,11 @@ export async function cryptoDecrypt(kp:PortableCryptoKeyPair, data:Uint8Array){ * @return signature byte array. */ export async function cryptoSign(k:PortableCryptoKey, data:Uint8Array){ + const ec:EcKeyAlgorithm = { + name: "ECDSA", + namedCurve: "P-384" + }; + const dsa:EcdsaParams = { name: "ECDSA", hash: { @@ -140,7 +130,7 @@ export async function cryptoSign(k:PortableCryptoKey, data:Uint8Array){ } }; - const privateKey = await parseSignKey(k, true); + const privateKey = await crypto.subtle.importKey("pkcs8", k, ec, false, ["sign"]); return new Uint8Array(await crypto.subtle.sign(dsa, privateKey, data)); } @@ -153,6 +143,11 @@ export async function cryptoSign(k:PortableCryptoKey, data:Uint8Array){ * @return `true` if correct. */ export async function cryptoVerify(signature:Uint8Array, k:PortableCryptoKey, data:Uint8Array){ + const ec:EcKeyAlgorithm = { + name: "ECDSA", + namedCurve: "P-384" + }; + const dsa:EcdsaParams = { name: "ECDSA", hash: { @@ -160,7 +155,7 @@ export async function cryptoVerify(signature:Uint8Array, k:PortableCryptoKey, da } }; - const publicKey = await parseSignKey(k, false); + const publicKey = await crypto.subtle.importKey("spki", k, ec, false, ["verify"]); return await crypto.subtle.verify(dsa, publicKey, signature, data); } \ No newline at end of file diff --git a/src/minipack.ts b/src/minipack.ts index 7e5438e..e485a4c 100644 --- a/src/minipack.ts +++ b/src/minipack.ts @@ -1,11 +1,11 @@ -import {deriveHash} from "./crypto.ts"; +import {cryptoHash} from "./crypto.ts"; import {ucEncode, ucDecode, hexEncode} from "./text.ts"; -const sizeOf = { +const sizeOf = Object.freeze({ hash: 32, name: 1, body: 4 -}; +}); const sizeTotal = Object.values(sizeOf).reduce((a, c) => a + c, 0); @@ -23,7 +23,7 @@ export async function minipackEncode(files:[string, Uint8Array][]){ const name = ucEncode(k); const body = v; - archive.set(await deriveHash(true, body), offset); + archive.set(await cryptoHash(true, body), offset); offset += sizeOf.hash; new DataView(archive.buffer, offset).setUint8(0, name.byteLength); @@ -65,7 +65,7 @@ export async function minipackDecode(archive:Uint8Array){ const body = archive.subarray(offset, offset += bs); - if(hexEncode(hash) !== hexEncode(await deriveHash(true, body))){ + if(hexEncode(hash) !== hexEncode(await cryptoHash(true, body))){ throw new Error(); } diff --git a/src/platform.ts b/src/platform.ts index 40ba43a..b69139e 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -4,20 +4,14 @@ import {dirname, fromFileUrl} from "../deps.ts"; * @return `true` if running on Windows. */ export function isWin(){ - const os = Deno?.build.os; - - if(!os){ - throw new Error(); - } - - return os === "windows"; + return Deno.build.os === "windows"; } /** * @return `"C:/Windows/Temp"` if running on Windows, or `"/tmp"` if running on Linux or Mac. */ export function tmpPath(){ - switch(Deno?.build.os){ + switch(Deno.build.os){ case "windows": return "C:/Windows/Temp"; case "linux": return "/tmp"; case "darwin": return "/tmp"; @@ -29,11 +23,5 @@ export function tmpPath(){ * Move current directory to `Deno.mainModule`. */ export function cwdMain(){ - const ep = Deno?.mainModule; - - if(!ep){ - throw new Error(); - } - - Deno?.chdir(fromFileUrl(dirname(ep))); + Deno.chdir(fromFileUrl(dirname(Deno.mainModule))); } \ No newline at end of file diff --git a/src/web.d.ts b/src/web.d.ts index 7046177..7f2478e 100644 --- a/src/web.d.ts +++ b/src/web.d.ts @@ -1,7 +1,7 @@ /** * Possible types of JSON. */ -export type JsonStruct = string | number | boolean | null | JsonStruct[] | {[key: string]: JsonStruct;}; +export type JsonStruct = string | number | boolean | null | JsonStruct[] | {[key in string]: JsonStruct}; /** * Possible input types for `URLSearchParams`. diff --git a/test/crypto.test.ts b/test/crypto.test.ts index 3455027..943d828 100644 --- a/test/crypto.test.ts +++ b/test/crypto.test.ts @@ -1,5 +1,5 @@ import {assertEquals} from "../deps.test.ts"; -import {deriveHash, generateKeyPair, cryptoEncrypt, cryptoDecrypt, cryptoSign, cryptoVerify} from "../src/crypto.ts"; +import {cryptoHash, cryptoGenerateKey, cryptoEncrypt, cryptoDecrypt, cryptoSign, cryptoVerify} from "../src/crypto.ts"; const sample = new Uint8Array([0x02, 0xF2, 0x5D, 0x1F, 0x1C, 0x34, 0xB9, 0x2F]); @@ -17,7 +17,7 @@ const hashResult = new Uint8Array([ Deno.test({ name: "Crypto: Hash", async fn(){ - const hash = await deriveHash(false, sample); + const hash = await cryptoHash(false, sample); assertEquals(hash, hashResult); } @@ -26,8 +26,8 @@ Deno.test({ Deno.test({ name: "Crypto: Encrypt and Decrypt", async fn(){ - const key1 = await generateKeyPair(false); - const key2 = await generateKeyPair(false); + const key1 = await cryptoGenerateKey(false); + const key2 = await cryptoGenerateKey(false); const encrypt = await cryptoEncrypt({ publicKey: key1.publicKey, @@ -46,7 +46,7 @@ Deno.test({ Deno.test({ name: "Crypto: Sign and Verify", async fn(){ - const key = await generateKeyPair(true); + const key = await cryptoGenerateKey(true); const signature = await cryptoSign(key.privateKey, sample); const verify = await cryptoVerify(signature, key.publicKey, sample);