Rework API
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import {ecdh, hkdf, pbkdf, Usage} from "./src/kdf"
|
||||
import SecretBox from './src/secret-box'
|
||||
import SecretWrap from './src/secret-wrap'
|
||||
import PrivateBox from './src/private-box'
|
||||
import PrivateWrap from './src/private-wrap'
|
||||
import PwdBox from './src/pwd-box'
|
||||
import PwdWrap from './src/pwd-wrap'
|
||||
|
||||
export const kdf = {ecdh, hkdf, pbkdf, Usage}
|
||||
export * as signature from "./src/signature"
|
||||
export * as JWT from "./src/jwt"
|
||||
export {SecretBox} from './src/secret-box'
|
||||
export {PrivateWrap} from './src/private-wrap'
|
||||
export {SecretWrap} from './src/secret-wrap'
|
||||
export {PwdWrap} from './src/pwd-wrap'
|
||||
export * as kdf from './src/kdf'
|
||||
export * as misc from './src/misc'
|
||||
export * as signature from './src/signature'
|
||||
export * as JWT from './src/jwt'
|
||||
|
||||
export {SecretBox,SecretWrap,PrivateBox,PrivateWrap,PwdBox,PwdWrap}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
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'
|
||||
+61
-36
@@ -1,47 +1,72 @@
|
||||
import logger from 'log'
|
||||
import * as consts from './const'
|
||||
const log = logger('crypto:kdf')
|
||||
|
||||
export enum Usage {
|
||||
sign,
|
||||
box,
|
||||
wrap
|
||||
// sign
|
||||
}
|
||||
export enum DHusage {
|
||||
box,
|
||||
wrap
|
||||
}
|
||||
export async function hkdf(key: Uint8Array, usage: Usage): Promise<CryptoKey | Uint8Array> {
|
||||
|
||||
export async function hkdf(key: Uint8Array, usage: Usage): Promise<CryptoKey> {
|
||||
log.trace('HKDF')
|
||||
log.trace(`usage : ${usage === Usage.sign ? 'sign' : 'wrap'}`)
|
||||
log.trace(`usage : ${usage}`)
|
||||
|
||||
const material = await crypto.subtle.importKey('raw', key, 'HKDF', false, ['deriveKey', 'deriveBits'])
|
||||
|
||||
if (usage === Usage.wrap) {
|
||||
return crypto.subtle.deriveKey(
|
||||
let algo
|
||||
let usages: KeyUsage[]
|
||||
let info_txt: string
|
||||
switch (usage) {
|
||||
/*case Usage.sign: {
|
||||
algo = 'Ed215519'
|
||||
usages = ['sign', 'verify']
|
||||
info_txt = 'sign'
|
||||
} break;*/
|
||||
case Usage.box:
|
||||
{
|
||||
name: 'HKDF',
|
||||
hash: 'SHA-512',
|
||||
salt: new Uint8Array(32),
|
||||
info: new TextEncoder().encode('wrap')
|
||||
},
|
||||
material,
|
||||
{name: 'AES-GCM', length: 256},
|
||||
false,
|
||||
['wrapKey', 'unwrapKey']
|
||||
)
|
||||
} else if (usage === Usage.sign) {
|
||||
const buffer = await crypto.subtle.deriveBits(
|
||||
algo = {
|
||||
name: consts.ENCRYPTION,
|
||||
length: 256
|
||||
}
|
||||
usages = ['encrypt', 'decrypt']
|
||||
info_txt = 'box'
|
||||
}
|
||||
break
|
||||
case Usage.wrap:
|
||||
{
|
||||
name: 'HKDF',
|
||||
hash: 'SHA-512',
|
||||
salt: new Uint8Array(32),
|
||||
info: new TextEncoder().encode('sign')
|
||||
},
|
||||
material,
|
||||
512
|
||||
)
|
||||
return new Uint8Array(buffer)
|
||||
} else {
|
||||
log.warn(`Called HKDF with unknown enum value : ${usage}`)
|
||||
throw "I don't even know what to say."
|
||||
algo = {
|
||||
name: consts.ENCRYPTION,
|
||||
length: 256
|
||||
}
|
||||
usages = ['wrapKey', 'unwrapKey']
|
||||
info_txt = 'wrap'
|
||||
}
|
||||
break
|
||||
default: {
|
||||
log.warn(`Unknown usage '${usage}'`)
|
||||
throw 'Unknown usage for HDKF'
|
||||
}
|
||||
}
|
||||
}
|
||||
const info = new TextEncoder().encode(info_txt)
|
||||
|
||||
return crypto.subtle.deriveKey(
|
||||
{
|
||||
name: 'HKDF',
|
||||
hash: 'SHA-512',
|
||||
salt: new Uint8Array(32),
|
||||
info
|
||||
},
|
||||
material,
|
||||
algo,
|
||||
false,
|
||||
usages
|
||||
)
|
||||
}
|
||||
export async function pbkdf(salt: Uint8Array, password: string): Promise<Uint8Array> {
|
||||
log.trace('PBKDF')
|
||||
const material = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, [
|
||||
@@ -61,20 +86,20 @@ export async function pbkdf(salt: Uint8Array, password: string): Promise<Uint8Ar
|
||||
|
||||
return new Uint8Array(buffer)
|
||||
}
|
||||
|
||||
export function ecdh(privkey: CryptoKey, pubkey: CryptoKey): Promise<CryptoKey> {
|
||||
log.trace('ECDH')
|
||||
export function ecdh(privkey: CryptoKey, pubkey: CryptoKey, usage: DHusage): Promise<CryptoKey> {
|
||||
log.trace("ecdh")
|
||||
const outputUsage: KeyUsage[] = usage === DHusage.box ? ['encrypt', 'decrypt'] : ['wrapKey', 'unwrapKey']
|
||||
return crypto.subtle.deriveKey(
|
||||
{
|
||||
name: 'ECDH',
|
||||
name: consts.ECDH.name,
|
||||
public: pubkey
|
||||
},
|
||||
privkey,
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
name: consts.ENCRYPTION,
|
||||
length: 256
|
||||
},
|
||||
false,
|
||||
['wrapKey', 'unwrapKey']
|
||||
outputUsage
|
||||
)
|
||||
}
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import logger from 'log'
|
||||
import {a2b64, b642a} from 'misc'
|
||||
import * as consts from './const'
|
||||
|
||||
const log = logger('misc')
|
||||
|
||||
export enum Usage {
|
||||
sign,
|
||||
ecdh,
|
||||
}
|
||||
|
||||
export async function pubkey_toString(pubkey: CryptoKey): Promise<string> {
|
||||
log.trace('pubkey_toString')
|
||||
const pubkey_buff = await crypto.subtle.exportKey('spki', pubkey)
|
||||
return a2b64(new Uint8Array(pubkey_buff))
|
||||
}
|
||||
export async function pubkey_fromString(pubkey_str: string, usage: Usage): Promise<CryptoKey | null> {
|
||||
log.trace('pubkey_fromString')
|
||||
|
||||
const pubkey = b642a(pubkey_str)
|
||||
if (pubkey === null) return null
|
||||
|
||||
try {
|
||||
return await crypto.subtle.importKey(
|
||||
'spki',
|
||||
pubkey,
|
||||
usage === Usage.ecdh ? consts.ECDH : consts.SIGNATURE_KEY,
|
||||
true,
|
||||
usage === Usage.ecdh ? [] : ['verify']
|
||||
)
|
||||
} catch (e) {
|
||||
log.warn("Failed to import public key")
|
||||
log.debug("Error :", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import logger from 'log'
|
||||
import SecretBox from './secret-box'
|
||||
import {DHusage, ecdh} from './kdf'
|
||||
import {pubkey_fromString, pubkey_toString, Usage} from './misc'
|
||||
import * as consts from './const'
|
||||
|
||||
const log = logger('crypto:private-box')
|
||||
|
||||
export default class PrivateBox {
|
||||
private constructor(
|
||||
private readonly pubkey: CryptoKey,
|
||||
private readonly box: SecretBox
|
||||
) {}
|
||||
|
||||
public static gen(extractable: boolean = true): Promise<CryptoKeyPair> {
|
||||
log.trace('generate keypair')
|
||||
log.trace('Extractable :', extractable)
|
||||
try {
|
||||
return crypto.subtle.generateKey(consts.ECDH, extractable, ['deriveKey']) as Promise<CryptoKeyPair>
|
||||
} catch(e) {
|
||||
log.warn("Failed to generate a key")
|
||||
log.debug("Error :", e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
public static async encrypt(data: Uint8Array, pubkey: CryptoKey): Promise<PrivateBox> {
|
||||
log.trace('encrypt')
|
||||
const tmp_pair = await PrivateBox.gen(false)
|
||||
const key = await ecdh(tmp_pair.privateKey, pubkey, DHusage.box)
|
||||
const box = await SecretBox.encrypt(data, key)
|
||||
return new this(tmp_pair.publicKey, box)
|
||||
}
|
||||
public async decrypt(privkey: CryptoKey): Promise<Uint8Array | null> {
|
||||
log.trace('decrypt')
|
||||
const key = await ecdh(privkey, this.pubkey, DHusage.box)
|
||||
return this.box.decrypt(key)
|
||||
}
|
||||
|
||||
public async toString(): Promise<string> {
|
||||
log.trace('toString')
|
||||
const pubkey = await pubkey_toString(this.pubkey)
|
||||
const box = this.box.toString()
|
||||
return `${pubkey}.${box}`
|
||||
}
|
||||
public static async fromString(data: string): Promise<PrivateBox | null> {
|
||||
log.trace('fromString')
|
||||
|
||||
const parts = data.split('.')
|
||||
if (parts.length < 2) return null
|
||||
|
||||
const pubkey = await pubkey_fromString(parts[0], Usage.ecdh)
|
||||
if (pubkey === null) return null
|
||||
const box = SecretBox.fromString(parts.slice(1).join("."))
|
||||
if (box === null) return null
|
||||
|
||||
return new PrivateBox(pubkey, box)
|
||||
}
|
||||
}
|
||||
+21
-37
@@ -1,68 +1,52 @@
|
||||
import {a2b64, b642a} from 'misc'
|
||||
import logger from 'log'
|
||||
|
||||
import {SecretWrap} from './secret-wrap'
|
||||
import {ecdh} from './kdf'
|
||||
import {DHusage, ecdh} from './kdf'
|
||||
import SecretWrap from './secret-wrap'
|
||||
import {pubkey_fromString, pubkey_toString, Usage} from './misc'
|
||||
import * as consts from './const'
|
||||
|
||||
const log = logger('crypto:private-wrap')
|
||||
|
||||
const algorithm = {
|
||||
name: 'ECDH',
|
||||
namedCurve: 'P-521'
|
||||
}
|
||||
export default class PrivateWrap {
|
||||
private constructor(
|
||||
private readonly box: SecretWrap,
|
||||
private readonly pubkey: CryptoKey
|
||||
) {}
|
||||
|
||||
export class PrivateWrap {
|
||||
private constructor(private readonly box: SecretWrap, private readonly pubkey: CryptoKey) {}
|
||||
|
||||
public static gen_keypair(extractable: boolean = true): Promise<CryptoKeyPair> {
|
||||
public static gen(extractable: boolean = true): Promise<CryptoKeyPair> {
|
||||
log.trace('generate keypair')
|
||||
return crypto.subtle.generateKey(algorithm, extractable, ['deriveKey'])
|
||||
return crypto.subtle.generateKey(consts.ECDH, extractable, ['deriveKey']) as Promise<CryptoKeyPair>
|
||||
}
|
||||
|
||||
public static async encrypt(data: CryptoKey, pubkey: CryptoKey): Promise<PrivateWrap> {
|
||||
log.trace('encrypt')
|
||||
const k = await this.gen_keypair()
|
||||
const kd = await ecdh(k.privateKey, pubkey)
|
||||
const tmp_keypair = await PrivateWrap.gen()
|
||||
const kd = await ecdh(tmp_keypair.privateKey, pubkey, DHusage.wrap)
|
||||
const box = await SecretWrap.encrypt(data, kd)
|
||||
return new this(box, k.publicKey)
|
||||
return new this(box, tmp_keypair.publicKey)
|
||||
}
|
||||
public async decrypt(privkey: CryptoKey): Promise<CryptoKey> {
|
||||
public async decrypt(privkey: CryptoKey): Promise<CryptoKey | null> {
|
||||
log.trace('decrypt')
|
||||
const kd = await ecdh(privkey, this.pubkey)
|
||||
const kd = await ecdh(privkey, this.pubkey, DHusage.wrap)
|
||||
return this.box.decrypt(kd)
|
||||
}
|
||||
|
||||
public async toString(): Promise<string> {
|
||||
log.trace('toString')
|
||||
const pubkey = await PrivateWrap.publicKey_toString(this.pubkey)
|
||||
const pubkey = await pubkey_toString(this.pubkey)
|
||||
const box = this.box.toString()
|
||||
return `${pubkey}.${box}`
|
||||
}
|
||||
public static async fromString(data: string): Promise<PrivateWrap | null> {
|
||||
log.trace('fromString')
|
||||
|
||||
const parts = data.split('.', 2)
|
||||
if (parts.length !== 2) return null
|
||||
const parts = data.split('.')
|
||||
if (parts.length < 2) return null
|
||||
|
||||
const pubkey = await this.publicKey_fromString(parts[0])
|
||||
const pubkey = await pubkey_fromString(parts[0], Usage.ecdh)
|
||||
if (pubkey === null) return null
|
||||
const box = SecretWrap.fromString(parts[1])
|
||||
const box = SecretWrap.fromString(parts.slice(1).join("."))
|
||||
if (box === null) return null
|
||||
|
||||
return new PrivateWrap(box, pubkey)
|
||||
}
|
||||
|
||||
public static async publicKey_toString(publicKey: CryptoKey): Promise<string> {
|
||||
const pubkey_spki = await crypto.subtle.exportKey('spki', publicKey)
|
||||
return a2b64(new Uint8Array(pubkey_spki))
|
||||
}
|
||||
public static async publicKey_fromString(data: string): Promise<CryptoKey | null> {
|
||||
const pubkey_str = b642a(data)
|
||||
if (pubkey_str.is_err()) return null
|
||||
try {
|
||||
return crypto.subtle.importKey('spki', pubkey_str.unwrap(), algorithm, true, [])
|
||||
} catch(e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import logger from 'log'
|
||||
import {hkdf, pbkdf, Usage} from './kdf'
|
||||
import SecretBox from './secret-box'
|
||||
import {a2b64, b642a} from 'misc'
|
||||
|
||||
const log = logger('crypto:pwd-box')
|
||||
|
||||
export default class PwdBox {
|
||||
private constructor(
|
||||
private readonly box: SecretBox,
|
||||
private readonly salt: Uint8Array
|
||||
) {}
|
||||
|
||||
private static async derive(pwd: string, salt: Uint8Array): Promise<CryptoKey> {
|
||||
const k = await pbkdf(salt, pwd)
|
||||
return (await hkdf(k, Usage.box)) as CryptoKey
|
||||
}
|
||||
|
||||
public static async encrypt(data: Uint8Array, pwd: string): Promise<PwdBox> {
|
||||
log.trace('encrypt')
|
||||
const salt = crypto.getRandomValues(new Uint8Array(16))
|
||||
const k = await PwdBox.derive(pwd, salt)
|
||||
const box = await SecretBox.encrypt(data, k)
|
||||
return new PwdBox(box, salt)
|
||||
}
|
||||
public async decrypt(pwd: string): Promise<Uint8Array | null> {
|
||||
log.trace('decrypt')
|
||||
const k = await PwdBox.derive(pwd, this.salt)
|
||||
return this.box.decrypt(k)
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
log.trace('toString')
|
||||
const salt = a2b64(this.salt)
|
||||
const box = this.box.toString()
|
||||
return `${salt}.${box}`
|
||||
}
|
||||
public static fromString(data: string): PwdBox | null {
|
||||
log.trace('fromString')
|
||||
|
||||
const parts = data.split('.')
|
||||
if (parts.length < 2) return null
|
||||
|
||||
const salt = b642a(parts[0])
|
||||
if (salt === null) return null
|
||||
const box = SecretBox.fromString(parts.slice(1).join("."))
|
||||
if (box === null) return null
|
||||
|
||||
return new PwdBox(box, salt)
|
||||
}
|
||||
}
|
||||
+39
-36
@@ -1,49 +1,52 @@
|
||||
import {hkdf, pbkdf, Usage} from './kdf'
|
||||
import {SecretWrap} from './secret-wrap'
|
||||
import SecretWrap from './secret-wrap'
|
||||
import logger from 'log'
|
||||
import {a2b64, b642a} from "misc";
|
||||
import {a2b64, b642a} from 'misc'
|
||||
|
||||
const log = logger('crypto:pwd-wrap')
|
||||
|
||||
export class PwdWrap {
|
||||
private constructor(private readonly box: SecretWrap, private readonly salt: Uint8Array) {}
|
||||
export default class PwdWrap {
|
||||
private constructor(
|
||||
private readonly box: SecretWrap,
|
||||
private readonly salt: Uint8Array
|
||||
) {}
|
||||
|
||||
private static async derive(pwd: string, salt: Uint8Array) : Promise<CryptoKey> {
|
||||
const k = await pbkdf(salt, pwd)
|
||||
return await hkdf(k, Usage.wrap) as CryptoKey
|
||||
}
|
||||
private static async derive(pwd: string, salt: Uint8Array): Promise<CryptoKey> {
|
||||
const k = await pbkdf(salt, pwd)
|
||||
return (await hkdf(k, Usage.wrap)) as CryptoKey
|
||||
}
|
||||
|
||||
public static async encrypt(data: CryptoKey, pwd: string, salt?: Uint8Array) : Promise<PwdWrap> {
|
||||
log.trace('encrypt')
|
||||
salt = salt ?? crypto.getRandomValues(new Uint8Array(16))
|
||||
const k = await PwdWrap.derive(pwd, salt)
|
||||
const box = await SecretWrap.encrypt(data, k)
|
||||
return new PwdWrap(box, salt)
|
||||
}
|
||||
public async decrypt(pwd: string) : Promise<CryptoKey> {
|
||||
log.trace('decrypt')
|
||||
const k = await PwdWrap.derive(pwd, this.salt)
|
||||
return this.box.decrypt(k)
|
||||
}
|
||||
public static async encrypt(data: CryptoKey, pwd: string, salt?: Uint8Array): Promise<PwdWrap> {
|
||||
log.trace('encrypt')
|
||||
salt = salt ?? crypto.getRandomValues(new Uint8Array(16))
|
||||
const k = await PwdWrap.derive(pwd, salt)
|
||||
const box = await SecretWrap.encrypt(data, k)
|
||||
return new PwdWrap(box, salt)
|
||||
}
|
||||
public async decrypt(pwd: string): Promise<CryptoKey | null> {
|
||||
log.trace('decrypt')
|
||||
const k = await PwdWrap.derive(pwd, this.salt)
|
||||
return this.box.decrypt(k)
|
||||
}
|
||||
|
||||
public toString(): String {
|
||||
log.trace('toString')
|
||||
const salt = a2b64(this.salt)
|
||||
const box = this.box.toString()
|
||||
return `${salt}.${box}`
|
||||
}
|
||||
public toString(): string {
|
||||
log.trace('toString')
|
||||
const salt = a2b64(this.salt)
|
||||
const box = this.box.toString()
|
||||
return `${salt}.${box}`
|
||||
}
|
||||
|
||||
public fromString(data: string): PwdWrap | null {
|
||||
log.trace('fromString')
|
||||
public static fromString(data: string): PwdWrap | null {
|
||||
log.trace('fromString')
|
||||
|
||||
const parts = data.split('.', 2)
|
||||
if (parts.length !== 2) return null
|
||||
const parts = data.split('.')
|
||||
if (parts.length < 2) return null
|
||||
|
||||
const salt = b642a(parts[0])
|
||||
if (salt.is_err()) return null
|
||||
const box = SecretWrap.fromString(parts[1])
|
||||
if (box === null) return null
|
||||
const salt = b642a(parts[0])
|
||||
if (salt === null) return null
|
||||
const box = SecretWrap.fromString(parts.slice(1).join("."))
|
||||
if (box === null) return null
|
||||
|
||||
return new PwdWrap(box, salt.unwrap())
|
||||
}
|
||||
return new PwdWrap(box, salt)
|
||||
}
|
||||
}
|
||||
|
||||
+41
-20
@@ -1,16 +1,20 @@
|
||||
import logger from 'log'
|
||||
import {a2b64, b642a} from 'misc'
|
||||
import * as consts from './const'
|
||||
|
||||
const log = logger('crypto:secret-box')
|
||||
|
||||
export class SecretBox {
|
||||
private constructor(private readonly iv: Uint8Array, private readonly cipher: Uint8Array) {}
|
||||
export default class SecretBox {
|
||||
private constructor(
|
||||
private readonly iv: Uint8Array,
|
||||
private readonly cipher: Uint8Array
|
||||
) {}
|
||||
|
||||
public static gen_key(extractable: boolean = true): Promise<CryptoKey> {
|
||||
public static gen(extractable: boolean = true): Promise<CryptoKey> {
|
||||
log.trace('generate key')
|
||||
return crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
name: consts.ENCRYPTION,
|
||||
length: 256
|
||||
},
|
||||
extractable,
|
||||
@@ -23,7 +27,7 @@ export class SecretBox {
|
||||
const iv = crypto.getRandomValues(new Uint8Array(11))
|
||||
const cipher = await crypto.subtle.encrypt(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
name: consts.ENCRYPTION,
|
||||
iv
|
||||
},
|
||||
key,
|
||||
@@ -31,30 +35,47 @@ export class SecretBox {
|
||||
)
|
||||
return new SecretBox(iv, new Uint8Array(cipher))
|
||||
}
|
||||
public async decrypt(key: CryptoKey): Promise<Uint8Array> {
|
||||
public async decrypt(key: CryptoKey): Promise<Uint8Array | null> {
|
||||
log.trace('decrypt')
|
||||
const buffer = await crypto.subtle.decrypt(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: this.iv
|
||||
},
|
||||
key,
|
||||
this.cipher
|
||||
)
|
||||
return new Uint8Array(buffer)
|
||||
try {
|
||||
const buffer = await crypto.subtle.decrypt(
|
||||
{
|
||||
name: consts.ENCRYPTION,
|
||||
iv: this.iv
|
||||
},
|
||||
key,
|
||||
this.cipher
|
||||
)
|
||||
return new Uint8Array(buffer)
|
||||
} catch(e) {
|
||||
log.warn("Failed to decrypt")
|
||||
log.debug("Error :", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
log.trace('toString')
|
||||
|
||||
const iv = a2b64(this.iv)
|
||||
const cipher = a2b64(this.iv)
|
||||
const cipher = a2b64(this.cipher)
|
||||
|
||||
return `${iv}.${cipher}`
|
||||
}
|
||||
public static fromString(data: string): SecretBox {
|
||||
public static fromString(data: string): SecretBox | null {
|
||||
log.trace('fromString')
|
||||
const parts = data.split('.', 2)
|
||||
const iv = b642a(parts[0]).expect('Failed decode IV')
|
||||
const cipher = b642a(parts[1]).expect('Failed to decode cipher')
|
||||
|
||||
const parts = data.split('.')
|
||||
if (parts.length < 2) {
|
||||
log.warn("Invalid parts count")
|
||||
return null
|
||||
}
|
||||
|
||||
const iv = b642a(parts[0])
|
||||
if (iv === null) return null
|
||||
const cipher = b642a(parts[1])
|
||||
if (cipher === null) return null
|
||||
|
||||
return new SecretBox(iv, cipher)
|
||||
}
|
||||
}
|
||||
|
||||
+26
-29
@@ -1,9 +1,10 @@
|
||||
import logger from 'log'
|
||||
import {a2b64, b642a} from 'misc'
|
||||
import * as consts from './const'
|
||||
|
||||
const log = logger('crypto:secret-wrap')
|
||||
|
||||
export class SecretWrap {
|
||||
export default class SecretWrap {
|
||||
constructor(
|
||||
private readonly cipher: Uint8Array,
|
||||
private readonly algorithm: KeyAlgorithm,
|
||||
@@ -12,11 +13,11 @@ export class SecretWrap {
|
||||
private readonly iv: Uint8Array
|
||||
) {}
|
||||
|
||||
public static gen_key(extractable: boolean = true): Promise<CryptoKey> {
|
||||
public static gen(extractable: boolean = true): Promise<CryptoKey> {
|
||||
log.trace('generate key')
|
||||
return crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
name: consts.ENCRYPTION,
|
||||
length: 256
|
||||
},
|
||||
extractable,
|
||||
@@ -37,55 +38,51 @@ export class SecretWrap {
|
||||
|
||||
public static async encrypt(data: CryptoKey, key: CryptoKey): Promise<SecretWrap> {
|
||||
log.trace('encrypt')
|
||||
const format = this.format(data.type)
|
||||
const format = SecretWrap.format(data.type)
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12))
|
||||
const box = await crypto.subtle.wrapKey(format, data, key, {
|
||||
name: 'AES-GCM',
|
||||
iv
|
||||
})
|
||||
const box = await crypto.subtle.wrapKey(format, data, key, {name: consts.ENCRYPTION, iv})
|
||||
return new SecretWrap(new Uint8Array(box), data.algorithm, data.usages, format, iv)
|
||||
}
|
||||
public async decrypt(key: CryptoKey): Promise<CryptoKey> {
|
||||
public async decrypt(key: CryptoKey): Promise<CryptoKey | null> {
|
||||
log.trace('decrypt')
|
||||
return crypto.subtle.unwrapKey(
|
||||
this.type,
|
||||
this.cipher,
|
||||
key,
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: this.iv
|
||||
},
|
||||
this.algorithm,
|
||||
true,
|
||||
this.usages
|
||||
)
|
||||
try {
|
||||
return await crypto.subtle.unwrapKey(this.type, this.cipher, key, {name: consts.ENCRYPTION, iv: this.iv}, this.algorithm, true, this.usages)
|
||||
} catch(e) {
|
||||
log.warn("Failed to unwrap")
|
||||
log.debug("Error :", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
log.trace('toString')
|
||||
const iv = a2b64(this.iv)
|
||||
return JSON.stringify({
|
||||
cipher: a2b64(this.cipher),
|
||||
iv: a2b64(this.iv),
|
||||
algorithm: this.algorithm,
|
||||
usages: this.usages,
|
||||
type: this.type
|
||||
type: this.type,
|
||||
iv,
|
||||
})
|
||||
}
|
||||
public static fromString(data: string): SecretWrap | null {
|
||||
log.trace('fromString')
|
||||
|
||||
const obj = this.parseJSON(data)
|
||||
const obj = SecretWrap.parseJSON(data)
|
||||
if (obj === null) return null
|
||||
|
||||
const cipher = b642a(obj.cipher)
|
||||
if (cipher.is_err()) return null
|
||||
const iv = b642a(obj.iv)
|
||||
if (iv.is_err()) return null
|
||||
if (cipher === null) return null
|
||||
|
||||
return new SecretWrap(cipher.unwrap(), obj.algorithm, obj.usages, obj.type, iv.unwrap())
|
||||
const iv = b642a(obj.iv)
|
||||
if (iv === null) return null
|
||||
|
||||
return new SecretWrap(cipher, obj.algorithm, obj.usages, obj.type, iv)
|
||||
}
|
||||
|
||||
private static parseJSON(data: string): {cipher: string, iv: string, algorithm: KeyAlgorithm, usages: KeyUsage[], type: 'raw' | 'pkcs8'} | null {
|
||||
private static parseJSON(
|
||||
data: string
|
||||
): {cipher: string; iv: string; algorithm: KeyAlgorithm; usages: KeyUsage[]; type: 'raw' | 'pkcs8'} | null {
|
||||
log.trace('parseJSON')
|
||||
try {
|
||||
// TODO : Check the fields
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import {p521} from '@noble/curves/p521'
|
||||
import {hexToNumber, bytesToHex, hexToBytes, numberToHexUnpadded} from '@noble/curves/abstract/utils'
|
||||
import {sha512} from '@noble/hashes/sha512'
|
||||
import {hkdf} from '@noble/hashes/hkdf'
|
||||
import * as mod from '@noble/curves/abstract/modular'
|
||||
import type {ProjPointType} from '@noble/curves/abstract/weierstrass'
|
||||
|
||||
/** Derive a keypair from random data with sufficient entropy
|
||||
* @param source entropy source, 64 bytes
|
||||
* @returns [PrivKey, PubKey]
|
||||
*/
|
||||
export async function derive_keypair(source: Uint8Array): Promise<[CryptoKey, CryptoKey]> {
|
||||
const derived = hkdf(sha512, source, undefined, 'sign', 512)
|
||||
const validPrivateKey = mod.mapHashToField(derived, p521.CURVE.n) // Dodge modulus bias :) (kinda)
|
||||
const d = a2bg(validPrivateKey)
|
||||
const point = get_pubkey(d)
|
||||
|
||||
const pubkey = await crypto_pubkey(point)
|
||||
const privkey = await crypto_privkey(d, point)
|
||||
|
||||
return [privkey, pubkey]
|
||||
}
|
||||
function get_pubkey(d: bigint): ProjPointType<bigint> {
|
||||
return p521.ProjectivePoint.BASE.multiply(d)
|
||||
}
|
||||
function crypto_privkey(d: bigint, point: ProjPointType<bigint>): Promise<CryptoKey> {
|
||||
const jwk = {
|
||||
crv: 'P-521',
|
||||
d: bg2b64url(d),
|
||||
ext: false,
|
||||
key_ops: ['sign'],
|
||||
kty: 'EC',
|
||||
x: bg2b64url(point.x),
|
||||
y: bg2b64url(point.y)
|
||||
}
|
||||
return crypto.subtle.importKey(
|
||||
'jwk',
|
||||
jwk,
|
||||
{
|
||||
name: 'ECDSA',
|
||||
namedCurve: 'P-521'
|
||||
},
|
||||
false,
|
||||
['sign']
|
||||
)
|
||||
}
|
||||
function crypto_pubkey(point: ProjPointType<bigint>): Promise<CryptoKey> {
|
||||
const jwk = {
|
||||
crv: 'P-521',
|
||||
ext: true,
|
||||
key_ops: ['verify'],
|
||||
kty: 'EC',
|
||||
x: bg2b64url(point.x),
|
||||
y: bg2b64url(point.y)
|
||||
}
|
||||
return crypto.subtle.importKey(
|
||||
'jwk',
|
||||
jwk,
|
||||
{
|
||||
name: 'ECDSA',
|
||||
namedCurve: 'P-521'
|
||||
},
|
||||
true,
|
||||
['verify']
|
||||
)
|
||||
}
|
||||
function bg2b64url(n: bigint): string {
|
||||
return encode(btoa(String.fromCharCode(...hexToBytes(numberToHexUnpadded(n)))))
|
||||
}
|
||||
function encode(input: string): string {
|
||||
// Replace non-url compatible chars with base64 standard chars
|
||||
input = input.replace(/\+/g, '-').replace(/\//g, '_')
|
||||
|
||||
const last = input.lastIndexOf('=')
|
||||
if (last > 0) return input.substring(0, last)
|
||||
return input
|
||||
}
|
||||
function a2bg(data: Uint8Array): bigint {
|
||||
return hexToNumber(bytesToHex(data))
|
||||
}
|
||||
+5
-27
@@ -1,38 +1,16 @@
|
||||
import logger from 'log'
|
||||
const log = logger('crypto:signature')
|
||||
import * as consts from './const'
|
||||
|
||||
export async function gen_keypair(extractable: boolean = true): Promise<CryptoKeyPair> {
|
||||
return crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'ECDSA',
|
||||
namedCurve: 'P-521'
|
||||
},
|
||||
extractable,
|
||||
['sign', 'verify']
|
||||
)
|
||||
export async function gen(extractable: boolean = true): Promise<CryptoKeyPair> {
|
||||
return crypto.subtle.generateKey(consts.SIGNATURE_KEY, extractable, ['sign', 'verify'])
|
||||
}
|
||||
export {derive_keypair} from './signature.derive'
|
||||
export async function sign(message: Uint8Array, privkey: CryptoKey): Promise<Uint8Array> {
|
||||
log.trace('sign')
|
||||
const buffer = await crypto.subtle.sign(
|
||||
{
|
||||
name: 'ECDSA',
|
||||
hash: 'SHA-512'
|
||||
},
|
||||
privkey,
|
||||
message
|
||||
)
|
||||
const buffer = await crypto.subtle.sign(consts.SIGNATURE_ALGO, privkey, message)
|
||||
return new Uint8Array(buffer)
|
||||
}
|
||||
export async function verify(message: Uint8Array, pubkey: CryptoKey, signature: Uint8Array): Promise<boolean> {
|
||||
log.trace('verify')
|
||||
return crypto.subtle.verify(
|
||||
{
|
||||
name: 'ECDSA',
|
||||
hash: 'SHA-512'
|
||||
},
|
||||
pubkey,
|
||||
signature,
|
||||
message
|
||||
)
|
||||
return crypto.subtle.verify(consts.SIGNATURE_ALGO, pubkey, signature, message)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user