Add byte serialization

This commit is contained in:
2024-09-14 15:05:07 +02:00
parent f2f39fd318
commit dc66f8ee73
7 changed files with 135 additions and 7 deletions
+22
View File
@@ -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)
}
+27
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()
})
+15 -1
View File
@@ -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
View File
@@ -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)
})