fmt
This commit is contained in:
+50
-46
@@ -6,53 +6,57 @@ const log = logger('crypto:jwt')
|
||||
export type Key = jose.KeyLike | Uint8Array
|
||||
|
||||
export class JWTcontext {
|
||||
private constructor(
|
||||
private readonly key: Key,
|
||||
) {}
|
||||
private constructor(private readonly key: Key) {}
|
||||
|
||||
public static async gen_key(): Promise<Key> {
|
||||
log.trace("generate key")
|
||||
return jose.generateSecret("HS512")
|
||||
}
|
||||
public static new(key: Key) : JWTcontext {
|
||||
return new JWTcontext(key)
|
||||
}
|
||||
public static async new_random() : Promise<JWTcontext> {
|
||||
const k = await JWTcontext.gen_key()
|
||||
return new JWTcontext(k)
|
||||
public static async gen_key(): Promise<Key> {
|
||||
log.trace('generate key')
|
||||
return jose.generateSecret('HS512')
|
||||
}
|
||||
public static new(key: Key): JWTcontext {
|
||||
return new JWTcontext(key)
|
||||
}
|
||||
public static async new_random(): Promise<JWTcontext> {
|
||||
const k = await JWTcontext.gen_key()
|
||||
return new JWTcontext(k)
|
||||
}
|
||||
|
||||
public async sign<T>(
|
||||
message: T,
|
||||
set_issued: boolean = false,
|
||||
exp?: number | string | Date,
|
||||
audience?: string | string[],
|
||||
issuer?: string
|
||||
): Promise<string> {
|
||||
log.trace('sign JWT')
|
||||
log.trace('Config :', {
|
||||
set_issued,
|
||||
exp,
|
||||
issuer
|
||||
})
|
||||
|
||||
let jwt = new jose.SignJWT({message}).setProtectedHeader({alg: 'HS512'})
|
||||
|
||||
if (set_issued) jwt = jwt.setIssuedAt()
|
||||
if (issuer !== undefined) jwt = jwt.setIssuer(issuer)
|
||||
if (audience !== undefined) jwt = jwt.setAudience(audience)
|
||||
if (exp !== undefined) jwt = jwt.setExpirationTime(exp)
|
||||
|
||||
return await jwt.sign(this.key)
|
||||
}
|
||||
|
||||
public async verify<T>(jwt: string, audience?: string | string[], issuer?: string | string[]): Promise<T | null> {
|
||||
log.debug('Verify JWT')
|
||||
log.trace('Issuer :', issuer)
|
||||
log.trace('Audience :', audience)
|
||||
|
||||
try {
|
||||
let payload = await jose.jwtVerify(jwt, this.key, {audience, issuer})
|
||||
return payload.payload.message as T
|
||||
} catch (e) {
|
||||
log.warn('JWT verification failed')
|
||||
log.debug(`Error : ${e}`)
|
||||
}
|
||||
|
||||
public async sign<T>(message: T, set_issued: boolean = false, exp?: number | string | Date, audience?: string | string[], issuer?: string): Promise<string> {
|
||||
log.trace('sign JWT')
|
||||
log.trace('Config :', {
|
||||
set_issued,
|
||||
exp,
|
||||
issuer,
|
||||
})
|
||||
|
||||
let jwt = new jose.SignJWT({message}).setProtectedHeader({alg: "HS512"})
|
||||
|
||||
if (set_issued) jwt = jwt.setIssuedAt()
|
||||
if (issuer !== undefined) jwt = jwt.setIssuer(issuer)
|
||||
if (audience !== undefined) jwt = jwt.setAudience(audience)
|
||||
if (exp !== undefined) jwt = jwt.setExpirationTime(exp)
|
||||
|
||||
return await jwt.sign(this.key)
|
||||
}
|
||||
|
||||
public async verify<T>(jwt: string, audience?: string | string[], issuer?: string | string[]): Promise<T | null> {
|
||||
log.debug('Verify JWT')
|
||||
log.trace('Issuer :', issuer)
|
||||
log.trace('Audience :', audience)
|
||||
|
||||
try {
|
||||
let payload = await jose.jwtVerify(jwt, this.key, {audience, issuer})
|
||||
return payload.payload.message as T
|
||||
} catch (e) {
|
||||
log.warn('JWT verification failed')
|
||||
log.debug(`Error : ${e}`)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
+70
-80
@@ -2,89 +2,79 @@ import logger from 'log'
|
||||
const log = logger('crypto:kdf')
|
||||
|
||||
export enum Usage {
|
||||
sign,
|
||||
wrap,
|
||||
sign,
|
||||
wrap
|
||||
}
|
||||
export async function hkdf(key: Uint8Array, usage: Usage) : Promise<CryptoKey | Uint8Array> {
|
||||
log.trace("HKDF")
|
||||
log.trace(`usage : ${usage === Usage.sign ? 'sign' : 'wrap'}`)
|
||||
export async function hkdf(key: Uint8Array, usage: Usage): Promise<CryptoKey | Uint8Array> {
|
||||
log.trace('HKDF')
|
||||
log.trace(`usage : ${usage === Usage.sign ? 'sign' : 'wrap'}`)
|
||||
|
||||
const material = await crypto.subtle.importKey(
|
||||
"raw",
|
||||
key,
|
||||
"HKDF",
|
||||
false,
|
||||
["deriveKey", "deriveBits"],
|
||||
)
|
||||
const material = await crypto.subtle.importKey('raw', key, 'HKDF', false, ['deriveKey', 'deriveBits'])
|
||||
|
||||
if (usage === Usage.wrap) {
|
||||
return crypto.subtle.deriveKey(
|
||||
{
|
||||
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(
|
||||
{
|
||||
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."
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
["deriveBits"],
|
||||
)
|
||||
|
||||
const buffer = await crypto.subtle.deriveBits(
|
||||
{
|
||||
name: "PBKDF2",
|
||||
salt,
|
||||
iterations: 250_000,
|
||||
hash: "SHA-512",
|
||||
},
|
||||
material,
|
||||
256,
|
||||
)
|
||||
|
||||
return new Uint8Array(buffer)
|
||||
}
|
||||
|
||||
export function ecdh(privkey: CryptoKey, pubkey: CryptoKey) : Promise<CryptoKey> {
|
||||
log.trace("ECDH")
|
||||
if (usage === Usage.wrap) {
|
||||
return crypto.subtle.deriveKey(
|
||||
{
|
||||
name: "ECDH",
|
||||
public: pubkey,
|
||||
},
|
||||
privkey,
|
||||
{
|
||||
name: "AES-GCM",
|
||||
length: 256,
|
||||
},
|
||||
false,
|
||||
["wrapKey", "unwrapKey"],
|
||||
{
|
||||
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(
|
||||
{
|
||||
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."
|
||||
}
|
||||
}
|
||||
|
||||
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, [
|
||||
'deriveBits'
|
||||
])
|
||||
|
||||
const buffer = await crypto.subtle.deriveBits(
|
||||
{
|
||||
name: 'PBKDF2',
|
||||
salt,
|
||||
iterations: 250_000,
|
||||
hash: 'SHA-512'
|
||||
},
|
||||
material,
|
||||
256
|
||||
)
|
||||
|
||||
return new Uint8Array(buffer)
|
||||
}
|
||||
|
||||
export function ecdh(privkey: CryptoKey, pubkey: CryptoKey): Promise<CryptoKey> {
|
||||
log.trace('ECDH')
|
||||
return crypto.subtle.deriveKey(
|
||||
{
|
||||
name: 'ECDH',
|
||||
public: pubkey
|
||||
},
|
||||
privkey,
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
length: 256
|
||||
},
|
||||
false,
|
||||
['wrapKey', 'unwrapKey']
|
||||
)
|
||||
}
|
||||
|
||||
+34
-47
@@ -7,57 +7,44 @@ import {ecdh} from './kdf'
|
||||
const log = logger('crypto:private-wrap')
|
||||
|
||||
const algorithm = {
|
||||
name: "ECDH",
|
||||
namedCurve: "P-521",
|
||||
name: 'ECDH',
|
||||
namedCurve: 'P-521'
|
||||
}
|
||||
|
||||
export class PrivateWrap {
|
||||
private constructor(
|
||||
private readonly box: SecretWrap,
|
||||
private readonly pubkey: CryptoKey,
|
||||
) {}
|
||||
private constructor(private readonly box: SecretWrap, private readonly pubkey: CryptoKey) {}
|
||||
|
||||
public static gen_keypair(extractable : boolean = true) : Promise<CryptoKeyPair> {
|
||||
log.trace("generate keypair")
|
||||
return crypto.subtle.generateKey(
|
||||
algorithm,
|
||||
extractable,
|
||||
["deriveKey"],
|
||||
)
|
||||
}
|
||||
public static gen_keypair(extractable: boolean = true): Promise<CryptoKeyPair> {
|
||||
log.trace('generate keypair')
|
||||
return crypto.subtle.generateKey(algorithm, extractable, ['deriveKey'])
|
||||
}
|
||||
|
||||
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 box = await SecretWrap.encrypt(data, kd)
|
||||
return new this(box, k.publicKey)
|
||||
}
|
||||
public async decrypt(privkey: CryptoKey) : Promise<CryptoKey> {
|
||||
log.trace("decrypt")
|
||||
const kd = await ecdh(privkey, this.pubkey)
|
||||
return this.box.decrypt(kd)
|
||||
}
|
||||
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 box = await SecretWrap.encrypt(data, kd)
|
||||
return new this(box, k.publicKey)
|
||||
}
|
||||
public async decrypt(privkey: CryptoKey): Promise<CryptoKey> {
|
||||
log.trace('decrypt')
|
||||
const kd = await ecdh(privkey, this.pubkey)
|
||||
return this.box.decrypt(kd)
|
||||
}
|
||||
|
||||
public async toString(): Promise<string> {
|
||||
log.trace("toString")
|
||||
const pubkey_spki = await crypto.subtle.exportKey("spki", this.pubkey)
|
||||
const pubkey = a2b64(new Uint8Array(pubkey_spki))
|
||||
const box = this.box.toString()
|
||||
return `${pubkey}.${box}`
|
||||
}
|
||||
public static async fromString(data: string): Promise<PrivateWrap> {
|
||||
log.trace("fromString")
|
||||
const parts = data.split(".", 2)
|
||||
const pubkey_str = b642a(parts[0]).expect("Failed to decode pubkey")
|
||||
const pubkey = await crypto.subtle.importKey(
|
||||
"spki",
|
||||
pubkey_str,
|
||||
algorithm,
|
||||
true,
|
||||
[],
|
||||
)
|
||||
const box = SecretWrap.fromString(parts[1])
|
||||
return new PrivateWrap(box, pubkey)
|
||||
}
|
||||
public async toString(): Promise<string> {
|
||||
log.trace('toString')
|
||||
const pubkey_spki = await crypto.subtle.exportKey('spki', this.pubkey)
|
||||
const pubkey = a2b64(new Uint8Array(pubkey_spki))
|
||||
const box = this.box.toString()
|
||||
return `${pubkey}.${box}`
|
||||
}
|
||||
public static async fromString(data: string): Promise<PrivateWrap> {
|
||||
log.trace('fromString')
|
||||
const parts = data.split('.', 2)
|
||||
const pubkey_str = b642a(parts[0]).expect('Failed to decode pubkey')
|
||||
const pubkey = await crypto.subtle.importKey('spki', pubkey_str, algorithm, true, [])
|
||||
const box = SecretWrap.fromString(parts[1])
|
||||
return new PrivateWrap(box, pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
+50
-53
@@ -4,60 +4,57 @@ import {a2b64, b642a} from 'misc'
|
||||
const log = logger('crypto:secret-box')
|
||||
|
||||
export class SecretBox {
|
||||
private constructor(
|
||||
private readonly iv: Uint8Array,
|
||||
private readonly cipher: Uint8Array,
|
||||
) {}
|
||||
private constructor(private readonly iv: Uint8Array, private readonly cipher: Uint8Array) {}
|
||||
|
||||
public static gen_key(extractable : boolean = true) : Promise<CryptoKey> {
|
||||
log.trace("generate key")
|
||||
return crypto.subtle.generateKey(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
length: 256,
|
||||
},
|
||||
extractable,
|
||||
["encrypt", "decrypt"],
|
||||
)
|
||||
}
|
||||
public static gen_key(extractable: boolean = true): Promise<CryptoKey> {
|
||||
log.trace('generate key')
|
||||
return crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
length: 256
|
||||
},
|
||||
extractable,
|
||||
['encrypt', 'decrypt']
|
||||
)
|
||||
}
|
||||
|
||||
public static async encrypt(data: Uint8Array, key: CryptoKey) : Promise<SecretBox> {
|
||||
log.trace("encrypt")
|
||||
const iv = crypto.getRandomValues(new Uint8Array(11))
|
||||
const cipher = await crypto.subtle.encrypt(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv,
|
||||
},
|
||||
key,
|
||||
data
|
||||
)
|
||||
return new SecretBox(iv, new Uint8Array(cipher))
|
||||
}
|
||||
public async decrypt(key: CryptoKey) : Promise<Uint8Array> {
|
||||
log.trace("decrypt")
|
||||
const buffer = await crypto.subtle.decrypt(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv: this.iv
|
||||
},
|
||||
key,
|
||||
this.cipher
|
||||
)
|
||||
return new Uint8Array(buffer)
|
||||
}
|
||||
public static async encrypt(data: Uint8Array, key: CryptoKey): Promise<SecretBox> {
|
||||
log.trace('encrypt')
|
||||
const iv = crypto.getRandomValues(new Uint8Array(11))
|
||||
const cipher = await crypto.subtle.encrypt(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv
|
||||
},
|
||||
key,
|
||||
data
|
||||
)
|
||||
return new SecretBox(iv, new Uint8Array(cipher))
|
||||
}
|
||||
public async decrypt(key: CryptoKey): Promise<Uint8Array> {
|
||||
log.trace('decrypt')
|
||||
const buffer = await crypto.subtle.decrypt(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: this.iv
|
||||
},
|
||||
key,
|
||||
this.cipher
|
||||
)
|
||||
return new Uint8Array(buffer)
|
||||
}
|
||||
|
||||
public toString() : string {
|
||||
log.trace("toString")
|
||||
const iv = a2b64(this.iv)
|
||||
const cipher = a2b64(this.iv)
|
||||
return `${iv}.${cipher}`
|
||||
}
|
||||
public static fromString(data: string) : SecretBox {
|
||||
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")
|
||||
return new SecretBox(iv, cipher)
|
||||
}
|
||||
public toString(): string {
|
||||
log.trace('toString')
|
||||
const iv = a2b64(this.iv)
|
||||
const cipher = a2b64(this.iv)
|
||||
return `${iv}.${cipher}`
|
||||
}
|
||||
public static fromString(data: string): SecretBox {
|
||||
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')
|
||||
return new SecretBox(iv, cipher)
|
||||
}
|
||||
}
|
||||
|
||||
+69
-71
@@ -4,80 +4,78 @@ import {a2b64, b642a} from 'misc'
|
||||
const log = logger('crypto:secret-wrap')
|
||||
|
||||
export class SecretWrap {
|
||||
constructor(
|
||||
private readonly cipher: Uint8Array,
|
||||
private readonly algorithm: KeyAlgorithm,
|
||||
private readonly usages: KeyUsage[],
|
||||
private readonly type: "raw" | "pkcs8",
|
||||
private readonly iv: Uint8Array,
|
||||
) {}
|
||||
constructor(
|
||||
private readonly cipher: Uint8Array,
|
||||
private readonly algorithm: KeyAlgorithm,
|
||||
private readonly usages: KeyUsage[],
|
||||
private readonly type: 'raw' | 'pkcs8',
|
||||
private readonly iv: Uint8Array
|
||||
) {}
|
||||
|
||||
public static gen_key(extractable : boolean = true) : Promise<CryptoKey> {
|
||||
log.trace("generate key")
|
||||
return crypto.subtle.generateKey(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
length: 256,
|
||||
},
|
||||
extractable,
|
||||
["wrapKey", "unwrapKey"],
|
||||
)
|
||||
}
|
||||
public static gen_key(extractable: boolean = true): Promise<CryptoKey> {
|
||||
log.trace('generate key')
|
||||
return crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
length: 256
|
||||
},
|
||||
extractable,
|
||||
['wrapKey', 'unwrapKey']
|
||||
)
|
||||
}
|
||||
|
||||
private static format(type: KeyType) : "raw" | "pkcs8" {
|
||||
switch (type) {
|
||||
case "private": return "pkcs8"
|
||||
case "secret": return "raw"
|
||||
default: throw "Don't wrap public keys please..."
|
||||
}
|
||||
private static format(type: KeyType): 'raw' | 'pkcs8' {
|
||||
switch (type) {
|
||||
case 'private':
|
||||
return 'pkcs8'
|
||||
case 'secret':
|
||||
return 'raw'
|
||||
default:
|
||||
throw "Don't wrap public keys please..."
|
||||
}
|
||||
}
|
||||
|
||||
public static async encrypt(data: CryptoKey, key: CryptoKey) : Promise<SecretWrap> {
|
||||
log.trace("encrypt")
|
||||
const format = this.format(data.type)
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12))
|
||||
const box = await crypto.subtle.wrapKey(
|
||||
format,
|
||||
data,
|
||||
key,
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv,
|
||||
},
|
||||
)
|
||||
return new SecretWrap(new Uint8Array(box), data.algorithm, data.usages, format, iv)
|
||||
}
|
||||
public async decrypt(key: CryptoKey) : Promise<CryptoKey> {
|
||||
log.trace("decrypt")
|
||||
return crypto.subtle.unwrapKey(
|
||||
this.type,
|
||||
this.cipher,
|
||||
key,
|
||||
{
|
||||
name: "AES-GCM",
|
||||
iv: this.iv,
|
||||
},
|
||||
this.algorithm,
|
||||
true,
|
||||
this.usages
|
||||
)
|
||||
}
|
||||
public static async encrypt(data: CryptoKey, key: CryptoKey): Promise<SecretWrap> {
|
||||
log.trace('encrypt')
|
||||
const format = this.format(data.type)
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12))
|
||||
const box = await crypto.subtle.wrapKey(format, data, key, {
|
||||
name: 'AES-GCM',
|
||||
iv
|
||||
})
|
||||
return new SecretWrap(new Uint8Array(box), data.algorithm, data.usages, format, iv)
|
||||
}
|
||||
public async decrypt(key: CryptoKey): Promise<CryptoKey> {
|
||||
log.trace('decrypt')
|
||||
return crypto.subtle.unwrapKey(
|
||||
this.type,
|
||||
this.cipher,
|
||||
key,
|
||||
{
|
||||
name: 'AES-GCM',
|
||||
iv: this.iv
|
||||
},
|
||||
this.algorithm,
|
||||
true,
|
||||
this.usages
|
||||
)
|
||||
}
|
||||
|
||||
public toString() : string {
|
||||
log.trace("toString")
|
||||
return JSON.stringify({
|
||||
cipher: a2b64(this.cipher),
|
||||
iv: a2b64(this.iv),
|
||||
algorithm: this.algorithm,
|
||||
usages: this.usages,
|
||||
type: this.type,
|
||||
})
|
||||
}
|
||||
public static fromString(data: string) : SecretWrap {
|
||||
log.trace("fromString")
|
||||
const obj = JSON.parse(data)
|
||||
const cipher = b642a(obj.cipher).expect("Failed to decode cipher")
|
||||
const iv = b642a(obj.iv).expect("Failed to decode IV")
|
||||
return new SecretWrap(cipher, obj.algorithm, obj.usages, obj.type, iv)
|
||||
}
|
||||
public toString(): string {
|
||||
log.trace('toString')
|
||||
return JSON.stringify({
|
||||
cipher: a2b64(this.cipher),
|
||||
iv: a2b64(this.iv),
|
||||
algorithm: this.algorithm,
|
||||
usages: this.usages,
|
||||
type: this.type
|
||||
})
|
||||
}
|
||||
public static fromString(data: string): SecretWrap {
|
||||
log.trace('fromString')
|
||||
const obj = JSON.parse(data)
|
||||
const cipher = b642a(obj.cipher).expect('Failed to decode cipher')
|
||||
const iv = b642a(obj.iv).expect('Failed to decode IV')
|
||||
return new SecretWrap(cipher, obj.algorithm, obj.usages, obj.type, iv)
|
||||
}
|
||||
}
|
||||
|
||||
+60
-62
@@ -9,74 +9,72 @@ import type {ProjPointType} from '@noble/curves/abstract/weierstrass'
|
||||
* @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)
|
||||
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)
|
||||
const pubkey = await crypto_pubkey(point)
|
||||
const privkey = await crypto_privkey(d, point)
|
||||
|
||||
return [privkey, pubkey]
|
||||
return [privkey, pubkey]
|
||||
}
|
||||
function get_pubkey(d: bigint) : ProjPointType<bigint> {
|
||||
return p521.ProjectivePoint.BASE.multiply(d)
|
||||
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_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 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 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, '_')
|
||||
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
|
||||
const last = input.lastIndexOf('=')
|
||||
if (last > 0) return input.substring(0, last)
|
||||
return input
|
||||
}
|
||||
function a2bg(data: Uint8Array): bigint {
|
||||
return hexToNumber(bytesToHex(data))
|
||||
}
|
||||
function a2bg(data: Uint8Array) : bigint {
|
||||
return hexToNumber(bytesToHex(data))
|
||||
}
|
||||
+31
-31
@@ -1,38 +1,38 @@
|
||||
import logger from 'log'
|
||||
const log = logger('crypto:signature')
|
||||
|
||||
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_keypair(extractable: boolean = true): Promise<CryptoKeyPair> {
|
||||
return crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'ECDSA',
|
||||
namedCurve: 'P-521'
|
||||
},
|
||||
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
|
||||
)
|
||||
return new Uint8Array(buffer)
|
||||
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
|
||||
)
|
||||
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,
|
||||
)
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user