Skip to content

Commit

Permalink
Merge pull request #16 from dojyorin/dev
Browse files Browse the repository at this point in the history
feat: cwd to main module and easy WebCrypto.
  • Loading branch information
dojyorin authored Nov 24, 2022
2 parents e92dad7 + cf8b25b commit 161cdcc
Show file tree
Hide file tree
Showing 15 changed files with 297 additions and 27 deletions.
31 changes: 23 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ const encoded = base64Encode(file); // base64 code.
const decoded = base64Decode(encoded); // Restored.
```

**Easy WebCrypto**

```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 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.
const verify = await cryptoVerify(signature, keyEcdsa.publicKey, data); // `true` if correct.
```

**Date UnixTime**

```ts
Expand Down Expand Up @@ -57,13 +71,6 @@ const encoded = await minipackEncode(files); // byte array in "minipack" format.
const decoded = await minipackDecode(encoded); // Restored.
```

**Platform Specific**

```ts
const win = isWin(); // "true" if running on Windows.
const tmp = tmpPath(); // "C:/Windows/Temp" if running on Windows, or "/tmp" if running on Linux or Mac.
```

**Text Convert**

```ts
Expand All @@ -75,6 +82,14 @@ const hexadecimal = hexEncode(encoded); // hexadecimal string.
const formatted = trimExtend(decoded); // formatted string.
```

**Platform Specific**

```ts
const win = isWin(); // "true" if running on Windows.
const tmp = tmpPath(); // "C:/Windows/Temp" if running on Windows, or "/tmp" if running on Linux or Mac.
cwdMain(); // Move current directory to `Deno.mainModule`.
```

</p>
</details>
</p>
Expand Down Expand Up @@ -115,7 +130,7 @@ This section is not directly related to this module, but provides a few line sni

**JSON Import**
```ts
const {default: config} = await import("./config.json", {assert: {type: "json"}});
const {default: data} = await import("./data.json", {assert: {type: "json"}});
```

</p>
Expand Down
1 change: 1 addition & 0 deletions deps.test.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export {assertEquals} from "https://deno.land/[email protected]/testing/asserts.ts";
export {dirname, fromFileUrl} from "https://deno.land/[email protected]/path/mod.ts";
export {serve} from "https://deno.land/[email protected]/http/mod.ts";
5 changes: 2 additions & 3 deletions deps.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
/**
* no dependencies.
*/
export {} from "https://deno.land/[email protected]/bytes/mod.ts";
export {dirname, fromFileUrl} from "https://deno.land/[email protected]/path/mod.ts";
1 change: 1 addition & 0 deletions mod.compatible.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./src/base64.ts";
export * from "./src/crypto.ts";
export * from "./src/date.ts";
export * from "./src/deflate.ts";
export * from "./src/fetch.ts";
Expand Down
1 change: 1 addition & 0 deletions mod.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "./test/base64.test.ts";
import "./test/crypto.test.ts";
import "./test/date.test.ts";
import "./test/deflate.test.ts";
import "./test/fetch.test.ts";
Expand Down
4 changes: 3 additions & 1 deletion mod.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
export * from "./src/base64.ts";
export * from "./src/crypto.ts";
export * from "./src/date.ts";
export * from "./src/deflate.ts";
export * from "./src/fetch.ts";
export * from "./src/minipack.ts";
export * from "./src/platform.ts";
export * from "./src/text.ts";

export * from "./src/platform.ts";

export * from "./src/core.d.ts";
export * from "./src/web.d.ts";
7 changes: 0 additions & 7 deletions src/_utility.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/core.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/**
* function returning arbitrary types.
* Function returning arbitrary types.
*/
export type SyncFunction<T extends unknown = void> = () => T;

/**
* function returning arbitrary async types.
* Function returning arbitrary async types.
*/
export type AsyncFunction<T extends unknown = void> = SyncFunction<Promise<T>>;
166 changes: 166 additions & 0 deletions src/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/**
* Means the byte array after exporting `CryptoKey`.
*/
export type PortableCryptoKey = Uint8Array;

/**
* Each is `PortableCryptoKey` public/private key pair.
*/
export interface PortableCryptoKeyPair{
privateKey: PortableCryptoKey;
publicKey: PortableCryptoKey;
}

async function parseCommonKey(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
};

const aes:AesDerivedKeyParams = {
name: "AES-GCM",
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 ec:EcKeyAlgorithm = {
namedCurve: "P-384",
name: "ECDSA"
};

return await crypto.subtle.importKey(format, k, ec, false, usage);
}

/**
* Derive SHA2 hash value from byte array.
* @param isHalf 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";

return new Uint8Array(await crypto.subtle.digest(sha, data));
}

/**
* Generate and export public/private key pair as a portable byte array.
* @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){
const usage:KeyUsage[] = isDsa ? ["sign", "verify"] : ["deriveKey", "deriveBits"];

const ec:EcKeyAlgorithm = {
namedCurve: "P-384",
name: isDsa ? "ECDSA" : "ECDH"
};

const {publicKey, privateKey} = await crypto.subtle.generateKey(ec, true, usage);

return <PortableCryptoKeyPair>{
publicKey: new Uint8Array(await crypto.subtle.exportKey("spki", publicKey)),
privateKey: new Uint8Array(await crypto.subtle.exportKey("pkcs8", privateKey))
};
}

/**
* Encrypt byte array using AES-GCM with 256 bits key, 128 bits tag and 96 bits IV.
* The IV is prepended to the byte array.
* @param kp public/private key pair, each in byte array.
* @param data byte array.
* @return encrypted byte array.
*/
export async function cryptoEncrypt(kp:PortableCryptoKeyPair, data:Uint8Array){
const sizeTag = 16;
const sizeIv = 12;

const gcm:AesGcmParams = {
name: "AES-GCM",
tagLength: sizeTag * 8,
iv: crypto.getRandomValues(new Uint8Array(sizeIv))
};

const commonKey = await parseCommonKey(kp);

const output = new Uint8Array(sizeTag + sizeIv + data.byteLength);
output.set(<Uint8Array>gcm.iv, 0);
output.set(new Uint8Array(await crypto.subtle.encrypt(gcm, commonKey, data)), gcm.iv.byteLength);

return output;
}

/**
* Decrypt encrypted byte array using AES-GCM with 256 bits key 128 bits tag and 96 bits IV.
* Read the IV prepended to the byte array.
* @param kp public/private key pair, each in byte array.
* @param data encrypted byte array.
* @return byte array.
*/
export async function cryptoDecrypt(kp:PortableCryptoKeyPair, data:Uint8Array){
const sizeTag = 16;
const sizeIv = 12;

const gcm:AesGcmParams = {
name: "AES-GCM",
tagLength: sizeTag * 8,
iv: data.subarray(0, sizeIv)
};

const commonKey = await parseCommonKey(kp);

return new Uint8Array(await crypto.subtle.decrypt(gcm, commonKey, data.subarray(sizeIv)));
}

/**
* Create byte array signature using the private key and SHA384 hash algorithm.
* @param k private key.
* @param data byte array.
* @return signature byte array.
*/
export async function cryptoSign(k:PortableCryptoKey, data:Uint8Array){
const dsa:EcdsaParams = {
name: "ECDSA",
hash: {
name: "SHA-384"
}
};

const privateKey = await parseSignKey(k, true);

return new Uint8Array(await crypto.subtle.sign(dsa, privateKey, data));
}

/**
* Verifies the correct signature of a byte array using the public key and SHA384 hash algorithm.
* @param signature signature byte array.
* @param k public key.
* @param data byte array.
* @return `true` if correct.
*/
export async function cryptoVerify(signature:Uint8Array, k:PortableCryptoKey, data:Uint8Array){
const dsa:EcdsaParams = {
name: "ECDSA",
hash: {
name: "SHA-384"
}
};

const publicKey = await parseSignKey(k, false);

return await crypto.subtle.verify(dsa, publicKey, signature, data);
}
4 changes: 3 additions & 1 deletion src/deflate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {streamConvert} from "./_utility.ts";
async function streamConvert(data:Uint8Array, ts:TransformStream<Uint8Array, Uint8Array>){
return new Uint8Array(await new Response(new Blob([data]).stream().pipeThrough(ts)).arrayBuffer());
}

/**
* Compress binary in "deflate" format (RFC1951).
Expand Down
6 changes: 6 additions & 0 deletions src/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import {JsonStruct, QueryInit} from "./web.d.ts";

/**
* Option to remove `window` from `RequestInit` and add `query` for query string.
*/
export interface FetchInit extends Omit<RequestInit, "window">{
query?: QueryInit;
}

/**
* A map of fetch response types and strings specifying them.
*/
export interface FetchResponseType{
"text": string;
"json": JsonStruct;
Expand Down
6 changes: 3 additions & 3 deletions src/minipack.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {deriveHash} from "./_utility.ts";
import {deriveHash} from "./crypto.ts";
import {ucEncode, ucDecode, hexEncode} from "./text.ts";

const sizeOf = <const>{
Expand All @@ -23,7 +23,7 @@ export async function minipackEncode(files:[string, Uint8Array][]){
const name = ucEncode(k);
const body = v;

archive.set(await deriveHash(body), offset);
archive.set(await deriveHash(true, body), offset);
offset += sizeOf.hash;

new DataView(archive.buffer, offset).setUint8(0, name.byteLength);
Expand Down Expand Up @@ -65,7 +65,7 @@ export async function minipackDecode(archive:Uint8Array){

const body = archive.subarray(offset, offset += bs);

if(hexEncode(hash) !== hexEncode(await deriveHash(body))){
if(hexEncode(hash) !== hexEncode(await deriveHash(true, body))){
throw new Error();
}

Expand Down
15 changes: 15 additions & 0 deletions src/platform.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {dirname, fromFileUrl} from "../deps.ts";

/**
* @return `true` if running on Windows.
*/
Expand All @@ -21,4 +23,17 @@ export function tmpPath(){
case "darwin": return "/tmp";
default: throw new Error();
}
}

/**
* Move current directory to `Deno.mainModule`.
*/
export function cwdMain(){
const ep = Deno?.mainModule;

if(!ep){
throw new Error();
}

Deno?.chdir(fromFileUrl(dirname(ep)));
}
Loading

0 comments on commit 161cdcc

Please sign in to comment.