Implement boxes
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import {Result} from 'rust'
|
||||
import * as misc from 'misc'
|
||||
import logger from 'log'
|
||||
|
||||
export type PubKey = CryptoKey
|
||||
export type PrivKey = CryptoKey
|
||||
export type KeyPair = [PrivKey, PubKey]
|
||||
|
||||
const algorithm: RsaOaepParams = {name: "RSA-OAEP"}
|
||||
const log = logger("crypto:boxes:private")
|
||||
|
||||
export class PrivateBox<T> {
|
||||
readonly _phantom!: T;
|
||||
|
||||
private constructor(
|
||||
private readonly cipher: Uint8Array
|
||||
) {}
|
||||
|
||||
public static async gen_keypair(extractable: boolean = false): Promise<KeyPair> {
|
||||
log.trace('generate keypair')
|
||||
log.debug('extractable :', extractable ? 'yes' : 'no')
|
||||
|
||||
const keys = await window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: "RSA-OAEP",
|
||||
modulusLength: 4096,
|
||||
publicExponent: new Uint8Array([1, 0, 1]),
|
||||
hash: {name: "SHA-512"}
|
||||
},
|
||||
extractable,
|
||||
["encrypt", "decrypt"]
|
||||
)
|
||||
|
||||
return [keys.privateKey, keys.publicKey]
|
||||
}
|
||||
|
||||
public static async encrypt<T>(key: PubKey, data: Uint8Array): Promise<Result<PrivateBox<T>>> {
|
||||
log.trace('encrypt')
|
||||
|
||||
try {
|
||||
const cipher = await window.crypto.subtle.encrypt(algorithm, key, data)
|
||||
return Result.ok(new PrivateBox(new Uint8Array(cipher)))
|
||||
} catch(e) {
|
||||
log.warn('encryption failed')
|
||||
log.debug(`error : ${e}`)
|
||||
}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
public async decrypt(key: PrivKey): Promise<Result<Uint8Array>> {
|
||||
log.trace('decrypt')
|
||||
|
||||
try {
|
||||
const plain = await window.crypto.subtle.decrypt(algorithm, key, this.cipher)
|
||||
return Result.ok(new Uint8Array(plain))
|
||||
} catch(e) {
|
||||
log.warn('decryption failed')
|
||||
log.debug(`error : ${e}`)
|
||||
}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
|
||||
public toString() : string {
|
||||
log.trace('toString')
|
||||
return misc.a2b64(new Uint8Array(this.cipher))
|
||||
}
|
||||
public static fromString<T>(data: string) : Result<PrivateBox<T>> {
|
||||
log.trace('fromString')
|
||||
const res = misc.b642a(data)
|
||||
if (res.is_err()) return Result.error([])
|
||||
return Result.ok(new PrivateBox(res.unwrap()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import {Result} from 'rust'
|
||||
import * as misc from 'misc'
|
||||
import logger from 'log'
|
||||
|
||||
export type PubKey = CryptoKey
|
||||
export type PrivKey = CryptoKey
|
||||
export type KeyPair = [PrivKey, PubKey]
|
||||
|
||||
const algorithm: RsaOaepParams = {name: "RSA-OAEP"}
|
||||
const log = logger("crypto:boxes:private-wrap")
|
||||
|
||||
export class PrivateWrap {
|
||||
private constructor(
|
||||
private readonly wrapped_key: Uint8Array,
|
||||
private readonly algorithm: Algorithm,
|
||||
private readonly usage: KeyUsage[],
|
||||
private readonly format: KeyFormat
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 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<KeyPair> {
|
||||
log.trace('generate keypair')
|
||||
log.debug('extractable :', extractable ? 'yes' : 'no')
|
||||
|
||||
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>> {
|
||||
log.trace('wrap key')
|
||||
|
||||
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 (e) {
|
||||
log.warn('wrap failed')
|
||||
log.debug(`error : ${e}`)
|
||||
}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap a key using a private key
|
||||
* @param privkey used to unwrap
|
||||
*/
|
||||
public async unwrap(privkey: PrivKey) : Promise<Result<CryptoKey>> {
|
||||
log.trace('unwrap key')
|
||||
|
||||
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(e) {
|
||||
log.warn('unwrap failed')
|
||||
log.debug(`error : ${e}`)
|
||||
}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
|
||||
public toString() : string {
|
||||
log.trace('toString')
|
||||
|
||||
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> {
|
||||
log.trace('fromString')
|
||||
|
||||
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.is_err()) return Result.error([])
|
||||
|
||||
return Result.ok(new PrivateWrap(key.unwrap(), algorithm, usage, format))
|
||||
} catch (e) {
|
||||
log.warn('parsing failed')
|
||||
log.debug(`error : ${e}`)
|
||||
}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import {Result} from 'rust'
|
||||
import * as misc from 'misc'
|
||||
import {SecretWrap} from './secret-wrap'
|
||||
import {pbkdf} from '../pbkdf'
|
||||
import logger from 'log'
|
||||
|
||||
const log = logger('crypto:boxes:pwd-wrap')
|
||||
|
||||
export class PwdWrap {
|
||||
private constructor(
|
||||
private readonly secret_wrap: SecretWrap,
|
||||
private readonly salt: Uint8Array,
|
||||
) {}
|
||||
|
||||
public static async wrap(pwd: string, key_to_wrap: CryptoKey) : Promise<Result<PwdWrap>> {
|
||||
log.trace('wrap key')
|
||||
|
||||
const salt = crypto.getRandomValues(new Uint8Array(18))
|
||||
const key = await PwdWrap.get_key(pwd, salt)
|
||||
|
||||
const box = await SecretWrap.wrap_key(key, key_to_wrap)
|
||||
if (box.is_err()) return Result.error([])
|
||||
|
||||
return Result.ok(new PwdWrap(box.unwrap(), salt))
|
||||
}
|
||||
|
||||
public async unwrap(pwd: string) : Promise<Result<CryptoKey>> {
|
||||
log.trace('unwrap key')
|
||||
|
||||
const key = await PwdWrap.get_key(pwd, this.salt)
|
||||
|
||||
const unwrapped_key = await this.secret_wrap.unwrap(key)
|
||||
if (unwrapped_key.is_err()) return Result.error([])
|
||||
|
||||
return Result.ok(unwrapped_key.unwrap())
|
||||
}
|
||||
|
||||
private static async get_key(pwd: string, salt: Uint8Array) : Promise<CryptoKey> {
|
||||
return pbkdf(pwd, salt, ['wrapKey', 'unwrapKey'])
|
||||
}
|
||||
|
||||
public toString() : string {
|
||||
const salt = misc.a2b64(this.salt)
|
||||
const box = this.secret_wrap.toString()
|
||||
return `${salt}${box}`
|
||||
}
|
||||
public static fromString(data: string) : Result<PwdWrap> {
|
||||
log.trace('fromString')
|
||||
|
||||
const salt64 = data.slice(0, 24)
|
||||
const salt = misc.b642a(salt64)
|
||||
if (salt.is_err()) return Result.error([])
|
||||
|
||||
const box64 = data.slice(24)
|
||||
const box = SecretWrap.fromString(box64)
|
||||
if (box.is_err()) return Result.error([])
|
||||
|
||||
return Result.ok(new PwdWrap(box.unwrap(), salt.unwrap()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import {Result} from 'rust'
|
||||
import * as misc from 'misc'
|
||||
import {SecretBox} from './symmetric'
|
||||
import {pbkdf} from '../pbkdf'
|
||||
import logger from 'log'
|
||||
|
||||
const log = logger('crypto:boxes:pwd')
|
||||
|
||||
export class PwdBox<T> {
|
||||
|
||||
private constructor(
|
||||
private readonly secret_box: SecretBox<T>,
|
||||
private readonly salt: Uint8Array
|
||||
) {}
|
||||
|
||||
public static async encrypt<T>(pwd: string, data: Uint8Array): Promise<PwdBox<T>> {
|
||||
log.trace('encrypt')
|
||||
|
||||
const salt = crypto.getRandomValues(new Uint8Array(18))
|
||||
const key = await PwdBox.get_key(pwd, salt)
|
||||
|
||||
const box = (await SecretBox.encrypt<T>(key, data)).unwrap() // I just created the key, I control it
|
||||
|
||||
return new PwdBox(box, salt)
|
||||
}
|
||||
public async decrypt(pwd: string): Promise<Result<Uint8Array>> {
|
||||
log.trace('decrypt')
|
||||
const key = await PwdBox.get_key(pwd, this.salt)
|
||||
return this.secret_box.decrypt(key)
|
||||
}
|
||||
|
||||
private static async get_key(pwd: string, salt: Uint8Array) : Promise<CryptoKey> {
|
||||
return pbkdf(pwd, salt, ["encrypt", "decrypt"])
|
||||
}
|
||||
|
||||
public toString() : string {
|
||||
const salt = misc.a2b64(this.salt)
|
||||
const box = this.secret_box.toString()
|
||||
return `${salt}${box}`
|
||||
}
|
||||
public static fromString<T>(data: string) : Result<PwdBox<T>> {
|
||||
log.trace('fromString')
|
||||
|
||||
const salt64 = data.slice(0, 24)
|
||||
const salt = misc.b642a(salt64)
|
||||
if (salt.is_err()) return Result.error([])
|
||||
|
||||
const box64 = data.slice(24)
|
||||
const box = SecretBox.fromString<T>(box64)
|
||||
if (box.is_err()) return Result.error([])
|
||||
|
||||
return Result.ok(new PwdBox(box.unwrap(), salt.unwrap()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import {Result} from 'rust'
|
||||
import * as misc from 'misc'
|
||||
import logger from 'log'
|
||||
|
||||
export type Key = CryptoKey
|
||||
|
||||
const log = logger('crypto:boxes:secret-wrap')
|
||||
|
||||
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> {
|
||||
log.trace('Generating key')
|
||||
log.debug('Extractable :', extractable ? 'yes' : 'no')
|
||||
|
||||
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>> {
|
||||
log.trace('wrap key')
|
||||
|
||||
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 (e) {
|
||||
log.warn('Wrapping failed')
|
||||
log.debug(`Error : ${e}`)
|
||||
}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
|
||||
public async unwrap(wrapping_key: Key) : Promise<Result<CryptoKey>> {
|
||||
log.trace('unwrap key')
|
||||
|
||||
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 (e) {
|
||||
log.warn('Unwrapping failed')
|
||||
log.debug(`Error : ${e}`)
|
||||
}
|
||||
|
||||
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> {
|
||||
log.trace('fromString')
|
||||
|
||||
try {
|
||||
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.is_err()) return Result.error([])
|
||||
const wrapped_key = misc.b642a(wrapped_key64)
|
||||
if (wrapped_key.is_err()) return Result.error([])
|
||||
|
||||
return Result.ok(new SecretWrap(wrapped_key.unwrap(), algorithm, usage, format, iv.unwrap()))
|
||||
} catch(e) {
|
||||
log.warn('Parsing fromString failed')
|
||||
log.debug(`Error : ${e}`)
|
||||
}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import {Result} from 'rust'
|
||||
import * as misc from 'misc'
|
||||
import logger from 'log'
|
||||
|
||||
export type Key = CryptoKey
|
||||
|
||||
const log = logger("crypto:boxes:secret")
|
||||
|
||||
export class SecretBox<T> {
|
||||
private readonly phantom!: T
|
||||
|
||||
private constructor(
|
||||
private readonly iv: Uint8Array,
|
||||
private readonly cipher: Uint8Array
|
||||
) {}
|
||||
|
||||
public static async gen_key(extractable: boolean = false): Promise<Key> {
|
||||
log.trace('Generate key')
|
||||
log.debug('Extractable :', extractable ? 'yes' : 'no')
|
||||
|
||||
return window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
length: 256,
|
||||
},
|
||||
extractable,
|
||||
["encrypt", "decrypt"]
|
||||
)
|
||||
}
|
||||
|
||||
public static async encrypt<T>(key: Key, data: Uint8Array): Promise<Result<SecretBox<T>>> {
|
||||
log.trace('Encrypt data')
|
||||
|
||||
const iv = window.crypto.getRandomValues(new Uint8Array(12))
|
||||
const algorithm = {name: "AES-GCM", iv}
|
||||
|
||||
try {
|
||||
const cipher = await window.crypto.subtle.encrypt(algorithm, key, data)
|
||||
return Result.ok(new SecretBox(iv, new Uint8Array(cipher)))
|
||||
} catch (e) {
|
||||
log.warn('Encryption failed')
|
||||
log.debug(`Error : ${e}`)
|
||||
}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
public async decrypt(key: Key): Promise<Result<Uint8Array>> {
|
||||
log.trace('Decrypt data')
|
||||
|
||||
const algorithm = {name: "AES-GCM", iv: this.iv}
|
||||
|
||||
try {
|
||||
const cipher = await window.crypto.subtle.decrypt(algorithm, key, this.cipher)
|
||||
const buffer = new Uint8Array(cipher)
|
||||
return Result.ok(buffer)
|
||||
} catch (e) {
|
||||
log.warn('Decryption failed')
|
||||
log.debug(`Error : ${e}`)
|
||||
}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
|
||||
public toString() : string {
|
||||
const iv = misc.a2b64(this.iv)
|
||||
const cipher = misc.a2b64(this.cipher)
|
||||
return `${iv}.${cipher}`
|
||||
}
|
||||
public static fromString<T>(data: string) : Result<SecretBox<T>> {
|
||||
log.trace('fromString')
|
||||
|
||||
const parts = data.split(".")
|
||||
if (parts.length !== 2) {
|
||||
log.warn("Invalid secret box, part count doesn't match")
|
||||
log.debug(`Count : ${parts.length}`)
|
||||
return Result.error([])
|
||||
}
|
||||
|
||||
const iv = misc.b642a(parts[0])
|
||||
const cipher = misc.b642a(parts[1])
|
||||
|
||||
if (iv.is_err()) return Result.error([])
|
||||
if (cipher.is_err()) return Result.error([])
|
||||
|
||||
return Result.ok(new SecretBox(iv.unwrap(), cipher.unwrap()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import {beforeAll, expect, test} from 'bun:test'
|
||||
|
||||
import {type KeyPair, PrivateBox} from 'boxes/asymmetric'
|
||||
|
||||
let k1!: KeyPair;
|
||||
let k2!: KeyPair;
|
||||
|
||||
beforeAll(async () => {
|
||||
k1 = await PrivateBox.gen_keypair(false)
|
||||
k2 = await PrivateBox.gen_keypair(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)
|
||||
})
|
||||
|
||||
test('base case', async () => {
|
||||
const [priv, pub] = k1
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await PrivateBox.encrypt<Uint8Array>(pub, data)).expect("Should encrypt the data")
|
||||
const result = (await box.decrypt(priv)).expect("Should decrypt the data")
|
||||
|
||||
expect(result).toEqual(data)
|
||||
})
|
||||
|
||||
test('toString and fromString are inverses', async () => {
|
||||
const [priv, pub] = k1
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await PrivateBox.encrypt<Uint8Array>(pub, data)).expect("Should encrypt the data")
|
||||
const str = box.toString()
|
||||
const box2 = PrivateBox.fromString<Uint8Array>(str).expect("Should parse the string")
|
||||
expect(box).toEqual(box2)
|
||||
const plain = (await box2.decrypt(priv)).expect("Should decrypt the data")
|
||||
|
||||
expect(plain).toEqual(data)
|
||||
})
|
||||
|
||||
test('Tampered cipher fails', async () => {
|
||||
const [priv, pub] = k1
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await PrivateBox.encrypt<Uint8Array>(pub, data)).expect("Should encrypt the data")
|
||||
// @ts-expect-error : Bypass privacy for test
|
||||
box.cipher[0] += 1
|
||||
;(await box.decrypt(priv)).expect_err("Should fail to decrypt the data")
|
||||
})
|
||||
|
||||
test('Wrong pubkey should fail', async () => {
|
||||
const [_priv, pub] = k1
|
||||
const [priv, _pub] = k2
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await PrivateBox.encrypt(pub, data)).expect("Should encrypt the data")
|
||||
;(await box.decrypt(priv)).expect_err("Should fail to decrypt the data")
|
||||
})
|
||||
@@ -0,0 +1,86 @@
|
||||
import {beforeAll, expect, test} from 'bun:test'
|
||||
|
||||
import {PrivateWrap, type KeyPair} from 'boxes/private-wrap'
|
||||
|
||||
import * as sym from 'boxes/symmetric'
|
||||
import * as signature from 'signature'
|
||||
|
||||
let k1!: KeyPair;
|
||||
let k2!: KeyPair;
|
||||
|
||||
let kw_sym!: sym.Key;
|
||||
let kw_sig!: signature.KeyPair;
|
||||
|
||||
let kw_sym_non!: sym.Key;
|
||||
let kw_sig_non!: signature.KeyPair;
|
||||
|
||||
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")
|
||||
})
|
||||
@@ -0,0 +1,53 @@
|
||||
import {beforeAll, expect, test} from 'bun:test'
|
||||
|
||||
import {PwdWrap} from '../../src/boxes'
|
||||
|
||||
import * as sym from '../../src/boxes/symmetric'
|
||||
import * as asym from '../../src/boxes/asymmetric'
|
||||
|
||||
let kw_sym!: sym.Key;
|
||||
let kw_asym!: asym.KeyPair;
|
||||
|
||||
beforeAll(async () => {
|
||||
kw_sym = await sym.SecretBox.gen_key(true)
|
||||
expect(kw_sym.extractable).toBe(true)
|
||||
|
||||
kw_asym = await asym.PrivateBox.gen_keypair(true)
|
||||
expect(kw_asym[0].extractable).toBe(true)
|
||||
})
|
||||
|
||||
test('base case', async () => {
|
||||
const pwd = "password"
|
||||
const testit = async (key: CryptoKey) => {
|
||||
console.log(`Testing ${key.type} key with usage ${key.usages}`)
|
||||
const wrapped = (await PwdWrap.wrap(pwd, key)).expect("Should wrap the key")
|
||||
const unwrapped = (await wrapped.unwrap(pwd)).expect("Should unwrap the key")
|
||||
|
||||
expect(unwrapped).toEqual(key)
|
||||
}
|
||||
|
||||
await testit(kw_sym)
|
||||
await testit(kw_asym[0])
|
||||
})
|
||||
|
||||
test('Fails with wrong password', async () => {
|
||||
const pwd1 = "AwesomePassword123!"
|
||||
const pwd2 = "AwesomePassword321!"
|
||||
expect(pwd1).not.toEqual(pwd2)
|
||||
|
||||
const wrapped = (await PwdWrap.wrap(pwd1, kw_sym)).expect("Should wrap the key")
|
||||
;(await wrapped.unwrap(pwd2)).expect_err("Shouldn't unwrap the key with wrong password")
|
||||
})
|
||||
|
||||
test('toString and fromString are inverses', async () => {
|
||||
const pwd = "password"
|
||||
|
||||
const wrapped = (await PwdWrap.wrap(pwd, kw_sym)).expect("Should wrap the key")
|
||||
|
||||
const str = wrapped.toString()
|
||||
const wrapped2 = PwdWrap.fromString(str).expect("Should unwrap the key")
|
||||
expect(wrapped2).toEqual(wrapped)
|
||||
|
||||
const unwrapped = (await wrapped.unwrap(pwd)).expect("Should unwrap the key")
|
||||
expect(unwrapped).toEqual(kw_sym)
|
||||
})
|
||||
@@ -0,0 +1,52 @@
|
||||
import {expect, test} from 'bun:test'
|
||||
|
||||
import * as pwd from 'boxes/pwd'
|
||||
|
||||
test('base case', async () => {
|
||||
const password = "AwesomePassword123!"
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = await pwd.PwdBox.encrypt<Uint8Array>(password, data)
|
||||
const result = (await box.decrypt(password)).expect("Should decrypt the data")
|
||||
|
||||
expect(result).toEqual(data)
|
||||
})
|
||||
|
||||
test('wrong password', async () => {
|
||||
const password1 = "AwesomePassword123!"
|
||||
const password2 = "AwesomePassword321!"
|
||||
expect(password1).not.toEqual(password2)
|
||||
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = await pwd.PwdBox.encrypt<Uint8Array>(password1, data)
|
||||
|
||||
;(await box.decrypt(password2)).expect_err("Should not decrypt the data with the wrong password")
|
||||
})
|
||||
|
||||
test('toString and fromString are inverses', async () => {
|
||||
const password = "AwesomePassword123!"
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = await pwd.PwdBox.encrypt<Uint8Array>(password, data)
|
||||
|
||||
const str = box.toString()
|
||||
const box2 = pwd.PwdBox.fromString<Uint8Array>(str).expect("Should be able to parse the string")
|
||||
expect(box2).toEqual(box)
|
||||
|
||||
const result = (await box2.decrypt(password)).expect("Should decrypt the data")
|
||||
|
||||
expect(result).toEqual(data)
|
||||
})
|
||||
|
||||
test('tampered salt should fail', async () => {
|
||||
const password = "AwesomePassword123!"
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = await pwd.PwdBox.encrypt<Uint8Array>(password, data)
|
||||
|
||||
// @ts-expect-error : I know salt is private, but I want to test it
|
||||
box.salt[0] += 1
|
||||
|
||||
;(await box.decrypt(password)).expect_err("Should not decrypt the data with a tampered salt")
|
||||
})
|
||||
@@ -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")
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
import {expect, test} from 'bun:test'
|
||||
|
||||
import * as symmetric from 'boxes/symmetric'
|
||||
|
||||
test('base case', async () => {
|
||||
const key = await symmetric.SecretBox.gen_key()
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await symmetric.SecretBox.encrypt<Uint8Array>(key, data)).expect("Should encrypt the data")
|
||||
const result = (await box.decrypt(key)).expect("Should decrypt the data")
|
||||
|
||||
expect(result).toEqual(data)
|
||||
})
|
||||
|
||||
test('toString and fromString are inverses', async () => {
|
||||
const key = await symmetric.SecretBox.gen_key()
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await symmetric.SecretBox.encrypt<Uint8Array>(key, data)).expect("Should encrypt the data")
|
||||
const str = box.toString()
|
||||
const box2 = symmetric.SecretBox.fromString<Uint8Array>(str).expect("Should parse the string")
|
||||
expect(box).toEqual(box2)
|
||||
const plain = (await box2.decrypt(key)).expect("Should decrypt the data")
|
||||
|
||||
expect(plain).toEqual(data)
|
||||
})
|
||||
|
||||
test('tampered cipher fails', async () => {
|
||||
const key = await symmetric.SecretBox.gen_key()
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await symmetric.SecretBox.encrypt<Uint8Array>(key, data)).expect("Should encrypt the data")
|
||||
// @ts-expect-error : This is a test, so it's OK to access private field
|
||||
box.cipher[0] += 1
|
||||
|
||||
;(await box.decrypt(key)).expect_err("Should fail to decrypt the data")
|
||||
})
|
||||
|
||||
test('Wrong key fails', async () => {
|
||||
const key1 = await symmetric.SecretBox.gen_key()
|
||||
const key2 = await symmetric.SecretBox.gen_key()
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await symmetric.SecretBox.encrypt(key1, data)).expect("Should encrypt the data")
|
||||
;(await box.decrypt(key2)).expect_err("Should fail to decrypt the data")
|
||||
})
|
||||
|
||||
test('tampered IV fails', async () => {
|
||||
const key = await symmetric.SecretBox.gen_key()
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await symmetric.SecretBox.encrypt<Uint8Array>(key, data)).expect("Should encrypt the data")
|
||||
// @ts-expect-error : This is a test, so it's OK to access private field
|
||||
box.iv[0] += 1
|
||||
|
||||
;(await box.decrypt(key)).expect_err("Should fail to decrypt the data")
|
||||
})
|
||||
Reference in New Issue
Block a user