Implement strength for whole library

This commit is contained in:
2024-09-12 01:31:45 +02:00
parent 524e9026b8
commit 76cf4632e4
14 changed files with 97 additions and 66 deletions
+13
View File
@@ -5,6 +5,19 @@ import PrivateWrap from './src/private-wrap'
import PwdBox from './src/pwd-box'
import PwdWrap from './src/pwd-wrap'
export enum Strength {
weak,
moderate,
strong
}
export const set_strength = (new_strength: Strength) => {
if (!(new_strength in Strength)) throw 'Invalid strength !'
strength = new_strength
}
let strength = Strength.moderate
export let STRENGTH: () => Strength = () => strength
export * as kdf from './src/kdf'
export * as misc from './src/misc'
export * as signature from './src/signature'
+16 -4
View File
@@ -1,4 +1,16 @@
export const ECDH = {name: 'ECDH', namedCurve: 'P-521'}
export const SIGNATURE_KEY = {name: 'ECDSA', namedCurve: 'P-521'}
export const SIGNATURE_ALGO = {name: 'ECDSA', hash: 'SHA-512'}
export const ENCRYPTION = 'AES-GCM'
import {STRENGTH} from '..'
export const CURVE_NAME = () => ['P-256', 'P-384', 'P-521'][STRENGTH()]
export const HASH_NAME = () => ['SHA-256', 'SHA-384', 'SHA-512'][STRENGTH()]
export const JWT_ALGORITHM = () => ['HS256', 'HS384', 'HS512'][STRENGTH()]
export const ECDH_PARAMETERS = () => ({name: 'ECDH', namedCurve: CURVE_NAME()})
export const SIGNATURE_KEY = () => ({name: 'ECDSA', namedCurve: CURVE_NAME()})
export const SIGNATURE_PARAMETERS = () => ({name: 'ECDSA', hash: HASH_NAME()})
export const ENCRYPTION_ALGORITHM = 'AES-GCM'
export const BYTE_LENGTH = () => [16, 24, 32][STRENGTH()]
export const BIT_LENGTH = () => [128, 192, 256][STRENGTH()]
+6 -5
View File
@@ -1,16 +1,17 @@
import * as jose from 'jose'
import logger from 'log'
import {generateSecret, jwtVerify, type KeyLike, SignJWT} from 'jose'
import {JWT_ALGORITHM} from './const'
const log = logger('crypto:jwt')
export type Key = jose.KeyLike | Uint8Array
export type Key = KeyLike | Uint8Array
export class JWTcontext {
private constructor(private readonly key: Key) {}
public static async gen_key(): Promise<Key> {
log.trace('generate key')
return await jose.generateSecret('HS512')
return await generateSecret(JWT_ALGORITHM())
}
public static new(key: Key): JWTcontext {
return new JWTcontext(key)
@@ -34,7 +35,7 @@ export class JWTcontext {
issuer
})
let jwt = new jose.SignJWT({message}).setProtectedHeader({alg: 'HS512'})
let jwt = new SignJWT({message}).setProtectedHeader({alg: JWT_ALGORITHM()})
if (set_issued) jwt = jwt.setIssuedAt()
if (issuer !== undefined) jwt = jwt.setIssuer(issuer)
@@ -50,7 +51,7 @@ export class JWTcontext {
log.trace('Audience :', audience)
try {
let payload = await jose.jwtVerify(jwt, this.key, {audience, issuer})
let payload = await jwtVerify(jwt, this.key, {audience, issuer})
return payload.payload.message as T
} catch (e) {
log.warn('JWT verification failed')
+19 -24
View File
@@ -1,5 +1,7 @@
import logger from 'log'
import * as consts from './const'
import {STRENGTH, Strength} from '..'
import {BIT_LENGTH, BYTE_LENGTH, CURVE_NAME, ENCRYPTION_ALGORITHM, HASH_NAME} from './const'
const log = logger('crypto:kdf')
@@ -12,19 +14,13 @@ export enum DHusage {
box,
wrap
}
export enum Strength {
weak,
moderate,
strong
}
/** Minimum seed size : 32 bytes */
export async function hkdf(key: Uint8Array, usage: Usage, context?: string): Promise<CryptoKey | null> {
log.trace('HKDF')
log.trace(`usage : ${usage}`)
if (key.length < 32) {
log.warn("Seed's size is less than 32")
if (key.length < BYTE_LENGTH()) {
log.warn("Seed's size is less than", BYTE_LENGTH())
return null
}
@@ -49,8 +45,8 @@ export async function hkdf(key: Uint8Array, usage: Usage, context?: string): Pro
case Usage.box:
{
algo = {
name: consts.ENCRYPTION,
length: 256
name: ENCRYPTION_ALGORITHM,
length: BIT_LENGTH()
}
usages = ['encrypt', 'decrypt']
info_txt = 'box'
@@ -59,8 +55,8 @@ export async function hkdf(key: Uint8Array, usage: Usage, context?: string): Pro
case Usage.wrap:
{
algo = {
name: consts.ENCRYPTION,
length: 256
name: ENCRYPTION_ALGORITHM,
length: BIT_LENGTH()
}
usages = ['wrapKey', 'unwrapKey']
info_txt = 'wrap'
@@ -77,7 +73,7 @@ export async function hkdf(key: Uint8Array, usage: Usage, context?: string): Pro
return await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-512',
hash: HASH_NAME(),
salt: new Uint8Array(16),
info
},
@@ -91,8 +87,7 @@ export async function hkdf(key: Uint8Array, usage: Usage, context?: string): Pro
/** Minimum salt size : 16 bytes */
export async function pbkdf(
salt: Uint8Array,
password: string,
strength: Strength = Strength.moderate
password: string
): Promise<Uint8Array | null> {
log.trace('PBKDF')
@@ -107,7 +102,7 @@ export async function pbkdf(
])
let iterations: number
switch (strength) {
switch (STRENGTH()) {
case Strength.weak:
iterations = 100_000
break
@@ -128,10 +123,10 @@ export async function pbkdf(
name: 'PBKDF2',
salt,
iterations,
hash: 'SHA-512'
hash: HASH_NAME()
},
material,
256
BIT_LENGTH()
)
return new Uint8Array(buffer)
@@ -148,11 +143,11 @@ export async function ecdh(
try {
seed_bits = await crypto.subtle.deriveBits(
{
name: consts.ECDH.name,
name: 'ECDH',
public: pubkey
},
privkey,
256
BIT_LENGTH()
)
} catch (e) {
log.warn('Failed to derive bits from keypair')
@@ -166,14 +161,14 @@ export async function ecdh(
return await crypto.subtle.deriveKey(
{
name: 'HKDF',
hash: 'SHA-512',
hash: HASH_NAME(),
salt: new Uint8Array(16),
info: new TextEncoder().encode('ECDH-' + (context ?? 'key'))
},
seed,
{
name: consts.ENCRYPTION,
length: 256
name: ENCRYPTION_ALGORITHM,
length: BIT_LENGTH()
},
false,
outputUsage
+2 -2
View File
@@ -1,6 +1,6 @@
import logger from 'log'
import {a2b64, b642a} from 'misc'
import * as consts from './const'
import {ECDH_PARAMETERS, SIGNATURE_KEY} from './const'
const log = logger('misc')
@@ -24,7 +24,7 @@ export async function pubkey_fromString(pubkey_str: string, usage: Usage): Promi
return await crypto.subtle.importKey(
'spki',
pubkey,
usage === Usage.ecdh ? consts.ECDH : consts.SIGNATURE_KEY,
usage === Usage.ecdh ? ECDH_PARAMETERS() : SIGNATURE_KEY(),
true,
usage === Usage.ecdh ? [] : ['verify']
)
+3 -3
View File
@@ -1,8 +1,8 @@
import logger from 'log'
import SecretBox from './secret-box'
import {DHusage, ecdh} from './kdf'
import * as consts from './const'
import * as misc from './misc.ts'
import {ECDH_PARAMETERS} from './const'
import * as misc from './misc'
const log = logger('crypto:private-box')
@@ -14,7 +14,7 @@ export default class PrivateBox {
public static async gen(extractable: boolean = true): Promise<CryptoKeyPair> {
log.trace('generate keypair')
return await crypto.subtle.generateKey(consts.ECDH, extractable, ['deriveBits'])
return await crypto.subtle.generateKey(ECDH_PARAMETERS(), extractable, ['deriveBits'])
}
public static async encrypt(data: Uint8Array, pubkey: CryptoKey, context?: string): Promise<PrivateBox | null> {
+2 -2
View File
@@ -1,8 +1,8 @@
import logger from 'log'
import {DHusage, ecdh} from './kdf'
import SecretWrap from './secret-wrap'
import {ECDH_PARAMETERS} from './const'
import * as misc from './misc'
import * as consts from './const'
const log = logger('crypto:private-wrap')
@@ -14,7 +14,7 @@ export default class PrivateWrap {
public static async gen(extractable: boolean = true): Promise<CryptoKeyPair> {
log.trace('generate keypair')
return (await crypto.subtle.generateKey(consts.ECDH, extractable, ['deriveBits'])) as CryptoKeyPair
return await crypto.subtle.generateKey(ECDH_PARAMETERS(), extractable, ['deriveBits'])
}
public static async wrap(data: CryptoKey, pubkey: CryptoKey, context?: string): Promise<PrivateWrap | null> {
+5 -5
View File
@@ -1,6 +1,6 @@
import logger from 'log'
import {a2b64, b642a} from 'misc'
import * as consts from './const'
import {BIT_LENGTH, ENCRYPTION_ALGORITHM} from './const'
const log = logger('crypto:secret-box')
@@ -14,8 +14,8 @@ export default class SecretBox {
log.trace('generate key')
return await crypto.subtle.generateKey(
{
name: consts.ENCRYPTION,
length: 256
name: ENCRYPTION_ALGORITHM,
length: BIT_LENGTH()
},
extractable,
['encrypt', 'decrypt']
@@ -31,7 +31,7 @@ export default class SecretBox {
try {
cipher = await crypto.subtle.encrypt(
{
name: consts.ENCRYPTION,
name: ENCRYPTION_ALGORITHM,
iv
},
key,
@@ -50,7 +50,7 @@ export default class SecretBox {
try {
const buffer = await crypto.subtle.decrypt(
{
name: consts.ENCRYPTION,
name: ENCRYPTION_ALGORITHM,
iv: this.iv
},
key,
+5 -5
View File
@@ -1,6 +1,6 @@
import logger from 'log'
import {a2b64, b642a} from 'misc'
import * as consts from './const'
import {BIT_LENGTH, ENCRYPTION_ALGORITHM} from './const'
const log = logger('crypto:secret-wrap')
@@ -17,8 +17,8 @@ export default class SecretWrap {
log.trace('generate key')
return await crypto.subtle.generateKey(
{
name: consts.ENCRYPTION,
length: 256
name: ENCRYPTION_ALGORITHM,
length: BIT_LENGTH()
},
extractable,
['wrapKey', 'unwrapKey']
@@ -47,7 +47,7 @@ export default class SecretWrap {
let box: ArrayBuffer
try {
box = await crypto.subtle.wrapKey(format, data, key, {name: consts.ENCRYPTION, iv})
box = await crypto.subtle.wrapKey(format, data, key, {name: ENCRYPTION_ALGORITHM, iv})
} catch (e) {
log.warn('Failed to wrap key')
log.debug('Error :', e)
@@ -63,7 +63,7 @@ export default class SecretWrap {
this.type,
this.cipher,
key,
{name: consts.ENCRYPTION, iv: this.iv},
{name: ENCRYPTION_ALGORITHM, iv: this.iv},
this.algorithm,
true,
this.usages
+9 -8
View File
@@ -1,18 +1,19 @@
import logger from 'log'
const log = logger('crypto:signature')
import * as consts from './const'
import {SIGNATURE_KEY, SIGNATURE_PARAMETERS} from './const'
import {a2b64, b642a} from 'misc'
import * as misc from './misc'
import * as libmisc from 'misc'
const log = logger('crypto:signature')
export async function gen(extractable: boolean = true): Promise<CryptoKeyPair> {
return await crypto.subtle.generateKey(consts.SIGNATURE_KEY, extractable, ['sign', 'verify'])
return await crypto.subtle.generateKey(SIGNATURE_KEY(), extractable, ['sign', 'verify'])
}
export async function sign(message: Uint8Array, privkey: CryptoKey): Promise<Uint8Array | null> {
log.trace('sign')
let buffer: ArrayBuffer
try {
buffer = await crypto.subtle.sign(consts.SIGNATURE_ALGO, privkey, message)
buffer = await crypto.subtle.sign(SIGNATURE_PARAMETERS(), privkey, message)
} catch (e) {
log.warn('Failed to sign')
log.debug('Error :', e)
@@ -24,7 +25,7 @@ export async function sign(message: Uint8Array, privkey: CryptoKey): Promise<Uin
export async function verify(message: Uint8Array, pubkey: CryptoKey, signature: Uint8Array): Promise<boolean> {
log.trace('verify')
try {
return await crypto.subtle.verify(consts.SIGNATURE_ALGO, pubkey, signature, message)
return await crypto.subtle.verify(SIGNATURE_PARAMETERS(), pubkey, signature, message)
} catch (e) {
log.warn('Failed to verify')
log.debug('Error :', e)
@@ -40,8 +41,8 @@ export async function pubkey_fromString(pubkey: string): Promise<CryptoKey | nul
}
export function signature_toString(signature: Uint8Array): string {
return libmisc.a2b64(signature)
return a2b64(signature)
}
export function signature_fromString(signature: string): Uint8Array | null {
return libmisc.b642a(signature)
return b642a(signature)
}
+4 -1
View File
@@ -1,5 +1,8 @@
import {Console} from 'logger-console'
import {writers, Level} from 'log'
import {Level, writers, options} from 'log'
import {set_strength, Strength} from '..'
options.verbose = false
const logger = new Console({minLevel: Level.TRACE, with_color: true})
writers.set('console', logger)
set_strength(Strength.weak)
+9 -5
View File
@@ -1,5 +1,6 @@
import {describe, expect, test} from 'bun:test'
import {kdf, PrivateWrap, SecretBox, SecretWrap} from '..'
import {kdf, PrivateWrap, SecretBox, SecretWrap, set_strength, Strength} from '..'
import {BYTE_LENGTH} from '../src/const'
async function test_keys(
k1: CryptoKey,
@@ -84,7 +85,7 @@ describe('HKDF', () => {
await test_keys(k1!, k2!, usage1, true, false)
})
test('Minimum size', async () => {
const k = await kdf.hkdf(new Uint8Array(31), usage1)
const k = await kdf.hkdf(new Uint8Array(BYTE_LENGTH() - 1), usage1)
expect(k).toBeNull()
})
})
@@ -114,9 +115,12 @@ describe('PBKDF', () => {
expect(k1).not.toEqual(k2)
})
test('Different strengths', async () => {
const k1 = await kdf.pbkdf(salt1, pwd2, kdf.Strength.weak)
const k2 = await kdf.pbkdf(salt1, pwd1, kdf.Strength.moderate)
const k3 = await kdf.pbkdf(salt1, pwd2, kdf.Strength.strong)
set_strength(Strength.weak)
const k1 = await kdf.pbkdf(salt1, pwd2)
set_strength(Strength.moderate)
const k2 = await kdf.pbkdf(salt1, pwd1)
set_strength(Strength.strong)
const k3 = await kdf.pbkdf(salt1, pwd2)
expect(k1).not.toEqual(k2)
expect(k1).not.toEqual(k3)
expect(k2).not.toEqual(k3)
+2 -1
View File
@@ -39,11 +39,12 @@ test('Signature', 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_toString(k.publicKey)
const de = await pubkey_fromString(ser, Usage.sign)
expect(de).not.toBeNull()
const verification = await signature.verify(message, de!, signed)
const verification = await signature.verify(message, de!, signed!)
expect(verification).toBeTrue()
})
+2 -1
View File
@@ -26,7 +26,8 @@ test('Signature', async () => {
const message = new Uint8Array(8)
const signed = await signature.sign(message, privk)
const verification = await signature.verify(message, k.publicKey, signed)
expect(signed).not.toBeNull()
const verification = await signature.verify(message, k.publicKey, signed!)
expect(verification).toBeTrue()
})
test('SecretWrap', async () => {