@@ -2,3 +2,4 @@ export {PrivateBox} from './asymmetric'
|
||||
export {SecretBox} from './symmetric'
|
||||
export {PwdBox} from './pwd'
|
||||
export {PrivateWrap} from './private-wrap'
|
||||
export {SecretWrap} from './secret-wrap'
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import {Result} from 'result'
|
||||
import * as misc from 'misc'
|
||||
|
||||
export type Key = CryptoKey
|
||||
|
||||
export class SecretWrap {
|
||||
private constructor(
|
||||
private readonly wrapped_key: Uint8Array,
|
||||
private readonly algorithm: Algorithm,
|
||||
private readonly usage: KeyUsage[],
|
||||
private readonly format: KeyFormat,
|
||||
private readonly iv: Uint8Array) {}
|
||||
|
||||
public static async gen_key(extractable: boolean = false) : Promise<Key> {
|
||||
return crypto.subtle.generateKey(
|
||||
{name: 'AES-GCM', length: 256},
|
||||
extractable,
|
||||
['wrapKey', 'unwrapKey']
|
||||
)
|
||||
}
|
||||
|
||||
public static async wrap_key(wrapping_key: Key, key_to_wrap: CryptoKey) : Promise<Result<SecretWrap>> {
|
||||
const format = key_to_wrap.type === "secret" ? "raw" : "pkcs8"
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12))
|
||||
|
||||
try {
|
||||
const wrapped_key = await crypto.subtle.wrapKey(
|
||||
format,
|
||||
key_to_wrap,
|
||||
wrapping_key,
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv,
|
||||
}
|
||||
)
|
||||
|
||||
return Result.ok(new SecretWrap(new Uint8Array(wrapped_key), key_to_wrap.algorithm, key_to_wrap.usages, format, iv))
|
||||
} catch (_) {}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
|
||||
public async unwrap(wrapping_key: Key) : Promise<Result<CryptoKey>> {
|
||||
try {
|
||||
const key = await crypto.subtle.unwrapKey(
|
||||
this.format,
|
||||
this.wrapped_key,
|
||||
wrapping_key,
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: this.iv,
|
||||
},
|
||||
this.algorithm,
|
||||
true,
|
||||
this.usage,
|
||||
)
|
||||
|
||||
return Result.ok(key)
|
||||
} catch (_) {}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
|
||||
public toString() : string {
|
||||
const wrapped_key = misc.a2b64(this.wrapped_key)
|
||||
const iv = misc.a2b64(this.iv)
|
||||
return JSON.stringify({
|
||||
wrapped_key,
|
||||
algorithm: this.algorithm,
|
||||
usage: this.usage,
|
||||
format: this.format,
|
||||
iv,
|
||||
})
|
||||
}
|
||||
public static fromString(s: string) : Result<SecretWrap> {
|
||||
const {iv: iv64, wrapped_key: wrapped_key64, algorithm, usage, format}: {wrapped_key: string, algorithm: Algorithm, usage: KeyUsage[], format: KeyFormat, iv: string} = JSON.parse(s)
|
||||
|
||||
const iv = misc.b642a(iv64)
|
||||
if (iv.error()) return Result.error([])
|
||||
const wrapped_key = misc.b642a(wrapped_key64)
|
||||
if (wrapped_key.error()) return Result.error([])
|
||||
|
||||
return Result.ok(new SecretWrap(wrapped_key.unwrap(), algorithm, usage, format, iv.unwrap()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import {beforeAll, expect, test} from 'bun:test'
|
||||
|
||||
import {SecretWrap, type Key} from 'boxes/secret-wrap'
|
||||
|
||||
import * as sym from 'boxes/symmetric'
|
||||
import * as asym from 'boxes/asymmetric'
|
||||
import * as signature from 'signature'
|
||||
import * as pwrap from 'boxes/private-wrap'
|
||||
|
||||
let k1!: Key;
|
||||
let k2!: Key;
|
||||
|
||||
let kw_wrap!: sym.Key;
|
||||
let kw_sym!: sym.Key;
|
||||
let kw_sig!: signature.KeyPair;
|
||||
let kw_asym!: asym.KeyPair;
|
||||
let kw_priv!: pwrap.KeyPair;
|
||||
|
||||
let kw_wrap_non!: sym.Key;
|
||||
let kw_sym_non!: sym.Key;
|
||||
let kw_sig_non!: signature.KeyPair;
|
||||
let kw_asym_non!: asym.KeyPair;
|
||||
let kw_priv_non!: pwrap.KeyPair;
|
||||
|
||||
beforeAll(async () => {
|
||||
k1 = await SecretWrap.gen_key(false)
|
||||
k2 = await SecretWrap.gen_key(true)
|
||||
|
||||
expect(k1.extractable).toBe(false)
|
||||
expect(k2.extractable).toBe(true)
|
||||
|
||||
kw_wrap = await sym.SecretBox.gen_key(true)
|
||||
expect(kw_wrap.extractable).toBe(true)
|
||||
kw_asym = await asym.PrivateBox.gen_keypair(true)
|
||||
expect(kw_asym[0].extractable).toBe(true)
|
||||
kw_priv = await pwrap.PrivateWrap.gen_key(true)
|
||||
expect(kw_priv[0].extractable).toBe(true)
|
||||
kw_sym = await sym.SecretBox.gen_key(true)
|
||||
expect(kw_sym.extractable).toBe(true)
|
||||
kw_sig = await signature.gen_keypair(true)
|
||||
expect(kw_sig[0].extractable).toBe(true)
|
||||
|
||||
kw_wrap_non = await sym.SecretBox.gen_key(false)
|
||||
expect(kw_wrap_non.extractable).toBe(false)
|
||||
kw_asym_non = await asym.PrivateBox.gen_keypair(false)
|
||||
expect(kw_asym_non[0].extractable).toBe(false)
|
||||
kw_priv_non = await pwrap.PrivateWrap.gen_key(false)
|
||||
expect(kw_priv_non[0].extractable).toBe(false)
|
||||
kw_sym_non = await sym.SecretBox.gen_key(false)
|
||||
expect(kw_sym_non.extractable).toBe(false)
|
||||
kw_sig_non = await signature.gen_keypair(false)
|
||||
expect(kw_sig_non[0].extractable).toBe(false)
|
||||
})
|
||||
|
||||
test('base case', async () => {
|
||||
const testit = async (key: CryptoKey) => {
|
||||
console.log(`Testing ${key.type} key with usage ${key.usages}`)
|
||||
const wrapped = (await SecretWrap.wrap_key(k1, key)).expect("Should wrap the key")
|
||||
const unwrapped = (await wrapped.unwrap(k1)).expect("Should unwrap the key")
|
||||
expect(unwrapped).toEqual(key)
|
||||
}
|
||||
|
||||
await testit(kw_wrap)
|
||||
await testit(kw_asym[0])
|
||||
await testit(kw_priv[0])
|
||||
await testit(kw_sym)
|
||||
await testit(kw_sig[0])
|
||||
})
|
||||
test("toString and fromString and inverses", async () => {
|
||||
const testit = async (key: CryptoKey) => {
|
||||
console.log(`Testing ${key.type} key with usage ${key.usages}`)
|
||||
const wrapped = (await SecretWrap.wrap_key(k1, key)).expect("Should wrap the key")
|
||||
const wrapped_str = wrapped.toString()
|
||||
const unwrapped = (SecretWrap.fromString(wrapped_str)).expect("Should parse the key")
|
||||
expect(unwrapped).toEqual(wrapped)
|
||||
}
|
||||
|
||||
await testit(kw_wrap)
|
||||
await testit(kw_asym[0])
|
||||
await testit(kw_priv[0])
|
||||
await testit(kw_sym)
|
||||
await testit(kw_sig[0])
|
||||
})
|
||||
test("Can't unwrap with wrong key", async () => {
|
||||
const wrapped = (await SecretWrap.wrap_key(k1, kw_wrap)).expect("Should wrap the key")
|
||||
;(await wrapped.unwrap(k2)).expect_err("Shouldn't unwrap with wrong key")
|
||||
})
|
||||
test("Can't wrap if key is not extractable", async () => {
|
||||
;(await SecretWrap.wrap_key(k1, kw_wrap_non)).expect_err("Shouldn't wrap if key is not extractable")
|
||||
;(await SecretWrap.wrap_key(k1, kw_asym_non[0])).expect_err("Shouldn't wrap if key is not extractable")
|
||||
;(await SecretWrap.wrap_key(k1, kw_priv_non[0])).expect_err("Shouldn't wrap if key is not extractable")
|
||||
;(await SecretWrap.wrap_key(k1, kw_sym_non)).expect_err("Shouldn't wrap if key is not extractable")
|
||||
;(await SecretWrap.wrap_key(k1, kw_sig_non[0])).expect_err("Shouldn't wrap if key is not extractable")
|
||||
})
|
||||
test("tampered IV", async () => {
|
||||
const wrapped = (await SecretWrap.wrap_key(k1, kw_wrap)).expect("Should wrap the key")
|
||||
|
||||
// @ts-expect-error
|
||||
wrapped.iv[0] += 1
|
||||
|
||||
;(await wrapped.unwrap(k1)).expect_err("Shouldn't unwrap with tampered IV")
|
||||
})
|
||||
Reference in New Issue
Block a user