Implement strength for whole library
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user