Add byte serialization
This commit is contained in:
+22
@@ -35,6 +35,28 @@ export async function pubkey_fromString(pubkey_str: string, usage: Usage): Promi
|
||||
}
|
||||
}
|
||||
|
||||
export async function pubkey_toBytes(pubkey: CryptoKey): Promise<Uint8Array> {
|
||||
log.trace('pubkey_toBytes')
|
||||
return new Uint8Array(await crypto.subtle.exportKey('raw', pubkey))
|
||||
}
|
||||
export async function pubkey_fromBytes(pubkey: Uint8Array, usage: Usage): Promise<CryptoKey | null> {
|
||||
log.trace('pubkey_fromBytes')
|
||||
|
||||
try {
|
||||
return await crypto.subtle.importKey(
|
||||
'raw',
|
||||
pubkey,
|
||||
usage === Usage.ecdh ? ECDH_PARAMETERS() : SIGNATURE_KEY(),
|
||||
true,
|
||||
usage === Usage.ecdh ? [] : ['verify']
|
||||
)
|
||||
} catch (e) {
|
||||
log.warn('Failed to import public key')
|
||||
log.debug('Error :', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function payload_fromString(text: string): Uint8Array {
|
||||
return new TextEncoder().encode(text)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import SecretBox from './secret-box'
|
||||
import {DHusage, ecdh} from './kdf'
|
||||
import {ECDH_PARAMETERS} from './const'
|
||||
import * as misc from './misc'
|
||||
import {pubkey_fromBytes, pubkey_toBytes, Usage} from './misc'
|
||||
|
||||
const log = logger('crypto:private-box')
|
||||
|
||||
@@ -57,6 +58,32 @@ export default class PrivateBox {
|
||||
return new PrivateBox(pubkey, box)
|
||||
}
|
||||
|
||||
public async toBytes(): Promise<Uint8Array> {
|
||||
const pubkey = await pubkey_toBytes(this.pubkey)
|
||||
const box = this.box.toBytes()
|
||||
return new Uint8Array([pubkey.length, ...pubkey, ...box])
|
||||
}
|
||||
public static async fromBytes(data: Uint8Array): Promise<PrivateBox | null> {
|
||||
const length = data.at(0)
|
||||
if (length === undefined) {
|
||||
log.warn('Data is invalid')
|
||||
return null
|
||||
}
|
||||
|
||||
const pubkey_buff = data.slice(1, 1 + length)
|
||||
if (pubkey_buff.length !== length) {
|
||||
log.warn('Data too short to contain pubkey')
|
||||
return null
|
||||
}
|
||||
const pubkey = await pubkey_fromBytes(pubkey_buff, Usage.ecdh)
|
||||
if (pubkey === null) return null
|
||||
|
||||
const box = SecretBox.fromBytes(data.slice(1 + length))
|
||||
if (box === null) return null
|
||||
|
||||
return new this(pubkey, box)
|
||||
}
|
||||
|
||||
public static async pubkey_toString(pubkey: CryptoKey): Promise<string> {
|
||||
return await misc.pubkey_toString(pubkey)
|
||||
}
|
||||
|
||||
+21
-1
@@ -5,6 +5,8 @@ import {a2b64, b642a} from 'misc'
|
||||
|
||||
const log = logger('crypto:pwd-box')
|
||||
|
||||
const SALT_byte_length = 16
|
||||
|
||||
export default class PwdBox {
|
||||
private constructor(
|
||||
private readonly box: SecretBox,
|
||||
@@ -20,7 +22,7 @@ export default class PwdBox {
|
||||
public static async encrypt(data: Uint8Array, pwd: string, context?: string): Promise<PwdBox | null> {
|
||||
log.trace('encrypt')
|
||||
|
||||
const salt = crypto.getRandomValues(new Uint8Array(16))
|
||||
const salt = crypto.getRandomValues(new Uint8Array(SALT_byte_length))
|
||||
|
||||
const k = await PwdBox.derive(pwd, salt, context)
|
||||
if (k === null) return null
|
||||
@@ -58,4 +60,22 @@ export default class PwdBox {
|
||||
|
||||
return new PwdBox(box, salt)
|
||||
}
|
||||
|
||||
public toBytes(): Uint8Array {
|
||||
const box = this.box.toBytes()
|
||||
return new Uint8Array([...this.salt, ...box])
|
||||
}
|
||||
public static fromBytes(data: Uint8Array): PwdBox | null {
|
||||
if (data.length <= SALT_byte_length) {
|
||||
log.warn('Data is smaller than salt size')
|
||||
log.debug('Size :', data.length)
|
||||
return null
|
||||
}
|
||||
|
||||
const salt = data.slice(0, SALT_byte_length)
|
||||
const box = SecretBox.fromBytes(data.slice(SALT_byte_length))
|
||||
if (box === null) return null
|
||||
|
||||
return new this(box, salt)
|
||||
}
|
||||
}
|
||||
|
||||
+21
-3
@@ -4,6 +4,8 @@ import {BIT_LENGTH, ENCRYPTION_ALGORITHM} from './const'
|
||||
|
||||
const log = logger('crypto:secret-box')
|
||||
|
||||
const IV_byte_length = 12
|
||||
|
||||
export default class SecretBox {
|
||||
private constructor(
|
||||
private readonly iv: Uint8Array,
|
||||
@@ -25,7 +27,7 @@ export default class SecretBox {
|
||||
public static async encrypt(data: Uint8Array, key: CryptoKey): Promise<SecretBox | null> {
|
||||
log.trace('encrypt')
|
||||
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12))
|
||||
const iv = crypto.getRandomValues(new Uint8Array(IV_byte_length))
|
||||
|
||||
let cipher: ArrayBuffer
|
||||
try {
|
||||
@@ -43,7 +45,7 @@ export default class SecretBox {
|
||||
return null
|
||||
}
|
||||
|
||||
return new SecretBox(iv, new Uint8Array(cipher))
|
||||
return new this(iv, new Uint8Array(cipher))
|
||||
}
|
||||
public async decrypt(key: CryptoKey): Promise<Uint8Array | null> {
|
||||
log.trace('decrypt')
|
||||
@@ -86,6 +88,22 @@ export default class SecretBox {
|
||||
const cipher = b642a(parts[1])
|
||||
if (cipher === null) return null
|
||||
|
||||
return new SecretBox(iv, cipher)
|
||||
return new this(iv, cipher)
|
||||
}
|
||||
|
||||
public toBytes(): Uint8Array {
|
||||
return new Uint8Array([...this.iv, ...this.cipher])
|
||||
}
|
||||
public static fromBytes(data: Uint8Array): SecretBox | null {
|
||||
if (data.length <= IV_byte_length) {
|
||||
log.warn(`Data is too short (minimum size : more than ${IV_byte_length} bytes)`)
|
||||
log.debug('Actual size :', data.length)
|
||||
return null
|
||||
}
|
||||
|
||||
const iv = data.slice(0, IV_byte_length)
|
||||
const cipher = data.slice(IV_byte_length)
|
||||
|
||||
return new this(iv, cipher)
|
||||
}
|
||||
}
|
||||
|
||||
+14
-1
@@ -1,6 +1,6 @@
|
||||
import {expect, test} from 'bun:test'
|
||||
import {PrivateBox, PrivateWrap, SecretBox, signature} from '..'
|
||||
import {pubkey_fromString, pubkey_toString, Usage} from '../src/misc'
|
||||
import {pubkey_fromBytes, pubkey_fromString, pubkey_toBytes, pubkey_toString, Usage} from '../src/misc'
|
||||
|
||||
test('Private box', async () => {
|
||||
const message = crypto.getRandomValues(new Uint8Array(8))
|
||||
@@ -48,3 +48,16 @@ test('Signature', async () => {
|
||||
const verification = await signature.verify(message, de!, signed!)
|
||||
expect(verification).toBeTrue()
|
||||
})
|
||||
test('Byte serialization', async () => {
|
||||
const k = await signature.gen(false)
|
||||
const message = crypto.getRandomValues(new Uint8Array(8))
|
||||
const signed = await signature.sign(message, k.privateKey)
|
||||
expect(signed).not.toBeNull()
|
||||
|
||||
const ser = await pubkey_toBytes(k.publicKey)
|
||||
const de = await pubkey_fromBytes(ser, Usage.sign)
|
||||
expect(de).not.toBeNull()
|
||||
|
||||
const verification = await signature.verify(message, de!, signed!)
|
||||
expect(verification).toBeTrue()
|
||||
})
|
||||
|
||||
@@ -52,7 +52,7 @@ test('Encrypt with different context', async () => {
|
||||
expect(unboxed).toBeNull()
|
||||
})
|
||||
|
||||
test('serialization', async () => {
|
||||
test('String serialization', async () => {
|
||||
const box = await PrivateBox.encrypt(message, k1.publicKey)
|
||||
expect(box).not.toBeNull()
|
||||
|
||||
@@ -66,3 +66,17 @@ test('serialization', async () => {
|
||||
expect(unboxed).not.toBeNull()
|
||||
expect(unboxed).toEqual(message)
|
||||
})
|
||||
test('Byte serialization', async () => {
|
||||
const box = await PrivateBox.encrypt(message, k1.publicKey)
|
||||
expect(box).not.toBeNull()
|
||||
|
||||
const ser = await box!.toBytes()
|
||||
const de = await PrivateBox.fromBytes(ser)
|
||||
|
||||
expect(de).not.toBeNull()
|
||||
expect(de).toEqual(box)
|
||||
|
||||
const unboxed = await de!.decrypt(k1.privateKey)
|
||||
expect(unboxed).not.toBeNull()
|
||||
expect(unboxed).toEqual(message)
|
||||
})
|
||||
|
||||
+15
-1
@@ -39,7 +39,7 @@ test("Encryption doesn't work with different context", async () => {
|
||||
expect(unboxed).toBeNull()
|
||||
})
|
||||
|
||||
test('serialization', async () => {
|
||||
test('String serialization', async () => {
|
||||
const box = await PwdBox.encrypt(message, k1)
|
||||
expect(box).not.toBeNull()
|
||||
|
||||
@@ -53,3 +53,17 @@ test('serialization', async () => {
|
||||
expect(unboxed).not.toBeNull()
|
||||
expect(unboxed).toEqual(message)
|
||||
})
|
||||
test('Byte serialization', async () => {
|
||||
const box = await PwdBox.encrypt(message, k1)
|
||||
expect(box).not.toBeNull()
|
||||
|
||||
const ser = box!.toBytes()
|
||||
const de = PwdBox.fromBytes(ser)
|
||||
|
||||
expect(de).not.toBeNull()
|
||||
expect(de).toEqual(box)
|
||||
|
||||
const unboxed = await de!.decrypt(k1)
|
||||
expect(unboxed).not.toBeNull()
|
||||
expect(unboxed).toEqual(message)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user