diff --git a/readme.md b/readme.md index 965ba49..c086379 100644 --- a/readme.md +++ b/readme.md @@ -5,20 +5,22 @@ * `src` 源代码 `source` * `ont` 基础计算 `ontic` - `base.ts` 进制转换 - - `utc.ts` 时间格式 - - `adm.ts` 行政区划 + - `utc.ts` 时间格式 `Universal Time Convention` + - `crypt.ts` 签名验证 `cryptography` + - `jwt.ts` 身份验证 `JSON Web Token` + - `adm.ts` 行政区划 `administrative region` - `json.ts` JSON数据声明 * `eid` 核心操作 `eidetic` - - `typ.ts` 数据类型声明 - - `db.ts` 数据库初始化与数据操作类型声明 + - `typ.ts` 数据类型声明 `type` + - `db.ts` 数据库初始化与数据操作类型声明 `database` - `is.ts` 数据定义与检查 - - `id.ts` 实体数据操作 - - `usr.ts` 用户数据操作 - - `soc.ts` 俱乐部数据操作 - - `agd.ts` 活动数据操作 - - `rec.ts` 记录数据操作 - - `msg.ts` 文章数据操作 - - `aut.ts` 权限数据操作 + - `id.ts` 实体数据操作 `identity` + - `usr.ts` 用户数据操作 `user` + - `soc.ts` 俱乐部数据操作 `sociation` `social` + - `agd.ts` 活动数据操作 `agenda` + - `rec.ts` 记录数据操作 `record` + - `msg.ts` 文章数据操作 `message` + - `aut.ts` 权限数据操作 `author` `authority * `pra` 业务操作 `praxic` * `tst` 测试代码 `tests` diff --git a/src/ont/crypt.ts b/src/ont/crypt.ts new file mode 100644 index 0000000..9e11f99 --- /dev/null +++ b/src/ont/crypt.ts @@ -0,0 +1,37 @@ +import { to_u8 } from "./base.ts" + +const alg = { + name: "HMAC", + hash: "SHA-256", +} + +export function key( + k: string | ArrayBuffer, +): Promise { + if (typeof k == "string") k = to_u8(k) + return crypto.subtle.importKey("raw", k, alg, false, ["sign", "verify"]) +} + +export function sign( + key: CryptoKey, + data: string, +) { + return crypto.subtle.sign(alg, key, to_u8(data)) +} +export function verify( + key: CryptoKey, + data: string, + sig: Uint8Array, +) { + return crypto.subtle.verify(alg, key, sig, to_u8(data)) +} + +export async function digest( + data: string | ArrayBuffer, + nitr = 1 +) { + if (typeof data == "string") data = to_u8(data) + for (let n = 0; n < nitr; ++n) + data = await crypto.subtle.digest(alg.hash, data) + return data +} diff --git a/src/ont/jwt.ts b/src/ont/jwt.ts new file mode 100644 index 0000000..c970c05 --- /dev/null +++ b/src/ont/jwt.ts @@ -0,0 +1,34 @@ +import type { Json } from "./json.ts" +import { key, sign, verify } from "./crypt.ts" +import { frm_base64, frm_u8, to_base64, to_u8 } from "./base.ts" + +const jwk_json = "./jwk.json" +let jwk = await key(`${Date.now() * Math.random()}`) + +export async function jwk_set( + k: string +) { + jwk = await key(k) +} +export function jwk_load( +) { + jwk_set(Deno.readTextFileSync(jwk_json)) +} + +export async function jwt_sign( + json: Json +): Promise { + const p = to_base64(to_u8(JSON.stringify(json))) + const s = to_base64(await sign(jwk, p)) + return `${p}.${s}` +} +export async function jwt_verify< + T extends Json +>( + jwt: string +): Promise { + const [p, s] = jwt.split(".") + if (!jwk || !s) return null + const v = await verify(jwk, p, frm_base64(s)) + return v ? JSON.parse(frm_u8(frm_base64(p))) : null +} diff --git a/tst/mod.test.ts b/tst/mod.test.ts index 74ea0fb..7409a88 100644 --- a/tst/mod.test.ts +++ b/tst/mod.test.ts @@ -1 +1,2 @@ export { assertEquals } from "https://deno.land/std@0.203.0/assert/assert_equals.ts" +export { assertNotEquals } from "https://deno.land/std@0.203.0/assert/assert_not_equals.ts" diff --git a/tst/ont.test.ts b/tst/ont.test.ts index 30d71ce..b2e35e7 100644 --- a/tst/ont.test.ts +++ b/tst/ont.test.ts @@ -1,7 +1,8 @@ import { is_adm } from "../src/ont/adm.ts" import { frm_base64, frm_hex, frm_u8, to_base64, to_hex, to_u8 } from "../src/ont/base.ts" +import { jwk_set, jwt_sign, jwt_verify } from "../src/ont/jwt.ts"; import { utc_dt } from "../src/ont/utc.ts" -import { assertEquals } from "./mod.test.ts" +import { assertEquals, assertNotEquals } from "./mod.test.ts" Deno.test("base", () => { const t = "this is a test 1234" @@ -28,6 +29,20 @@ Deno.test("utc", () => { assertEquals("2023-04-17", utc_dt(utc, "padutc")) }) +Deno.test("jwt", async () => { + const json = { usr: 1001, nam: "用户名", utc: Date.now() } + assertEquals(null, await jwt_verify("")) + const token = await jwt_sign(json) + assertEquals([true, 2], [token.length > 0, token.split(".").length]) + assertEquals(null, await jwt_verify(token.substring(1))) + assertEquals(json, await jwt_verify(token)) + await jwk_set("anotherkey") + assertEquals(null, await jwt_verify(token)) + const token2 = await jwt_sign(json) + assertNotEquals(token, token2) + assertEquals(json, await jwt_verify(token2)) +}) + Deno.test("adm", () => { assertEquals([true, false, false, true], [ is_adm("江苏", "苏州"),