This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
export {PrivateBox} from './asymmetric'
|
||||
export {SecretBox} from './symmetric'
|
||||
export {PwdBox} from './pwd'
|
||||
export {PrivateWrap} from './private-wrap'
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import {Result} from 'result'
|
||||
import * as misc from 'misc'
|
||||
|
||||
export type PubKey = CryptoKey
|
||||
export type PrivKey = CryptoKey
|
||||
|
||||
const algorithm: RsaOaepParams = {name: "RSA-OAEP"}
|
||||
|
||||
export class PrivateWrap {
|
||||
private readonly wrapped_key: Uint8Array
|
||||
private readonly usage: KeyUsage[]
|
||||
private readonly algorithm: Algorithm
|
||||
private readonly format: KeyFormat
|
||||
|
||||
private constructor(key: Uint8Array, algorithm: Algorithm, usage: KeyUsage[], format: KeyFormat) {
|
||||
this.wrapped_key = key
|
||||
this.algorithm = algorithm
|
||||
this.usage = usage
|
||||
this.format = format
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new keypair for signing
|
||||
* @param extractable if the keys must be extractable or not
|
||||
* @return [privkey, pubkey] keys
|
||||
*/
|
||||
public static async gen_key(extractable: boolean = false) : Promise<[PrivKey, PubKey]> {
|
||||
let key = await window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: "RSA-OAEP",
|
||||
modulusLength: 4096,
|
||||
publicExponent: new Uint8Array([1, 0, 1]),
|
||||
hash: {name: "SHA-512"},
|
||||
},
|
||||
extractable,
|
||||
["wrapKey", "unwrapKey"]
|
||||
)
|
||||
|
||||
return [key.privateKey, key.publicKey]
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a key using a public key
|
||||
*
|
||||
* Kinda makes sense, but it can't wrap an RSA encryption key
|
||||
* @param pubkey used to wrap
|
||||
* @param key to wrap
|
||||
* @return an error if the key is not extractable
|
||||
*/
|
||||
public static async wrap(pubkey: PubKey, key: CryptoKey) : Promise<Result<PrivateWrap>> {
|
||||
const format = key.type === "secret" ? "raw" : "pkcs8"
|
||||
|
||||
try {
|
||||
const wrapped = await window.crypto.subtle.wrapKey(
|
||||
format,
|
||||
key,
|
||||
pubkey,
|
||||
algorithm
|
||||
)
|
||||
|
||||
return Result.ok(new PrivateWrap(new Uint8Array(wrapped), key.algorithm, key.usages, format))
|
||||
} catch (_) {}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap a key using a private key
|
||||
* @param privkey used to unwrap
|
||||
*/
|
||||
public async unwrap(privkey: PrivKey) : Promise<Result<CryptoKey>> {
|
||||
try {
|
||||
const key = await window.crypto.subtle.unwrapKey(
|
||||
this.format,
|
||||
this.wrapped_key,
|
||||
privkey,
|
||||
algorithm,
|
||||
this.algorithm,
|
||||
true,
|
||||
this.usage
|
||||
)
|
||||
return Result.ok(key)
|
||||
} catch(_) {}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
|
||||
public toString() : string {
|
||||
const wrapped_key = misc.a2b64(this.wrapped_key)
|
||||
return JSON.stringify({
|
||||
wrapped_key,
|
||||
algorithm: this.algorithm,
|
||||
usage: this.usage,
|
||||
format: this.format,
|
||||
})
|
||||
}
|
||||
public static fromString(data: string) : Result<PrivateWrap> {
|
||||
try {
|
||||
const {wrapped_key: wrapped_key64, algorithm, usage, format}: {wrapped_key: string, algorithm: Algorithm, usage: KeyUsage[], format: KeyFormat} = JSON.parse(data)
|
||||
|
||||
const key = misc.b642a(wrapped_key64)
|
||||
if (key.error()) return Result.error([])
|
||||
|
||||
return Result.ok(new PrivateWrap(key.unwrap(), algorithm, usage, format))
|
||||
} catch (_) {}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
}
|
||||
+7
-3
@@ -11,16 +11,20 @@ const algorithm: EcdsaParams = {
|
||||
/**
|
||||
* Create a new keypair for signing
|
||||
* @param extractable if the keys must be extractable or not
|
||||
* @param canWrap if the keys can be used for wrapping WARNING : it is a bad idea to use the same key for signing and wrapping
|
||||
* @return [privkey, pubkey] keys
|
||||
*/
|
||||
export async function gen_keypair(extractable: boolean = false) : Promise<[PrivKey, PubKey]> {
|
||||
let key = await window.crypto.subtle.generateKey(
|
||||
export async function gen_keypair(extractable: boolean = false, canWrap: boolean = false) : Promise<[PrivKey, PubKey]> {
|
||||
const usage: KeyUsage[] = ['sign', 'verify']
|
||||
if(canWrap) usage.push('wrapKey', 'unwrapKey')
|
||||
|
||||
const key = await window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: "ECDSA",
|
||||
namedCurve: "P-521"
|
||||
} as EcKeyGenParams,
|
||||
extractable,
|
||||
['sign', 'verify']
|
||||
usage
|
||||
)
|
||||
|
||||
return [key.privateKey, key.publicKey]
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import {beforeAll, expect, test} from 'bun:test'
|
||||
|
||||
import {PrivateWrap, type PubKey, type PrivKey} from 'boxes/private-wrap'
|
||||
|
||||
import * as sym from 'boxes/symmetric'
|
||||
import * as signature from 'signature'
|
||||
|
||||
let k1!: [PrivKey, PubKey];
|
||||
let k2!: [PrivKey, PubKey];
|
||||
let kw_sym!: sym.Key;
|
||||
let kw_sig!: [signature.PrivKey, signature.PubKey];
|
||||
|
||||
let kw_sym_non!: sym.Key;
|
||||
let kw_sig_non!: [signature.PrivKey, signature.PubKey];
|
||||
|
||||
beforeAll(async () => {
|
||||
k1 = await PrivateWrap.gen_key(false)
|
||||
k2 = await PrivateWrap.gen_key(true)
|
||||
|
||||
expect(k1[0].extractable).toBe(false)
|
||||
expect(k1[1].extractable).toBe(true)
|
||||
expect(k2[0].extractable).toBe(true)
|
||||
expect(k2[1].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_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 [priv, pub] = k1
|
||||
expect(pub.type).toBe("public")
|
||||
|
||||
|
||||
const sym = (await PrivateWrap.wrap(pub, kw_sym)).expect("Should wrap the sym key")
|
||||
const rsym = (await sym.unwrap(priv)).expect("Should unwrap the sym key")
|
||||
expect(rsym).toEqual(kw_sym)
|
||||
|
||||
const sig = (await PrivateWrap.wrap(pub, kw_sig[0])).expect("Should wrap the signature key")
|
||||
const rsig = (await sig.unwrap(priv)).expect("Should unwrap the signature key")
|
||||
expect(rsig).toEqual(kw_sig[0])
|
||||
})
|
||||
test('toString and fromString and inverses', async () => {
|
||||
const [_priv, pub] = k1
|
||||
|
||||
const sym = (await PrivateWrap.wrap(pub, kw_sym)).expect("Should wrap the sym key")
|
||||
const sym_str = sym.toString()
|
||||
const rsym = (PrivateWrap.fromString(sym_str)).expect("Should parse the sym key")
|
||||
expect(rsym).toEqual(sym)
|
||||
|
||||
const sig = (await PrivateWrap.wrap(pub, kw_sig[0])).expect("Should wrap the signature key")
|
||||
const sig_str = sig.toString()
|
||||
const rsig = (PrivateWrap.fromString(sig_str)).expect("Should parse the signature key")
|
||||
expect(rsig).toEqual(sig)
|
||||
})
|
||||
test("Can't wrap with private key", async () => {
|
||||
const [priv, _pub] = k1
|
||||
|
||||
;(await PrivateWrap.wrap(priv, kw_sym)).expect_err("Shouldn't wrap with private key")
|
||||
})
|
||||
test("Can't unwrap with public key", async () => {
|
||||
const [_priv, pub] = k1
|
||||
const sym = (await PrivateWrap.wrap(pub, kw_sym)).expect("Should wrap the sym key")
|
||||
|
||||
;(await sym.unwrap(pub)).expect_err("Shouldn't unwrap with public key")
|
||||
})
|
||||
test("Can't unwrap with wrong private key", async () => {
|
||||
const [_priv, pub] = k1
|
||||
const sym = (await PrivateWrap.wrap(pub, kw_sym)).expect("Should wrap the sym key")
|
||||
|
||||
const [priv, _pub] = k2
|
||||
;(await sym.unwrap(priv)).expect_err("Shouldn't unwrap with wrong private key")
|
||||
})
|
||||
test("Can't wrap if not extractable", async () => {
|
||||
const [_priv, pub] = k1
|
||||
|
||||
;(await PrivateWrap.wrap(pub, kw_sym_non)).expect_err("Shouldn't wrap if not extractable")
|
||||
;(await PrivateWrap.wrap(pub, kw_sig_non[0])).expect_err("Shouldn't wrap if not extractable")
|
||||
})
|
||||
Reference in New Issue
Block a user