SecretWrap : Simplify serialization
This commit is contained in:
+55
-48
@@ -1,16 +1,21 @@
|
||||
import logger from 'log'
|
||||
import {a2b64, b642a} from 'misc'
|
||||
import {BIT_LENGTH, ENCRYPTION_ALGORITHM} from './const'
|
||||
import {BIT_LENGTH, ECDH_PARAMETERS, ENCRYPTION_ALGORITHM, SIGNATURE_KEY} from './const'
|
||||
|
||||
const log = logger('crypto:secret-wrap')
|
||||
|
||||
enum Type {
|
||||
SecretBox, // secret, decrypt, encrypt
|
||||
SecretWrap, // secret, wrap, unwrap
|
||||
Private, // private, deriveBits
|
||||
signature // private, sign
|
||||
}
|
||||
|
||||
export default class SecretWrap {
|
||||
constructor(
|
||||
private readonly cipher: Uint8Array,
|
||||
private readonly algorithm: KeyAlgorithm,
|
||||
private readonly usages: KeyUsage[],
|
||||
private readonly type: 'raw' | 'pkcs8',
|
||||
private readonly iv: Uint8Array
|
||||
private readonly iv: Uint8Array,
|
||||
private readonly type: Type
|
||||
) {}
|
||||
|
||||
public static async gen(extractable: boolean = true): Promise<CryptoKey> {
|
||||
@@ -25,25 +30,30 @@ export default class SecretWrap {
|
||||
)
|
||||
}
|
||||
|
||||
private static format(type: KeyType): 'raw' | 'pkcs8' | null {
|
||||
switch (type) {
|
||||
case 'private':
|
||||
return 'pkcs8'
|
||||
case 'secret':
|
||||
return 'raw'
|
||||
default:
|
||||
log.warn('Trying to wrap public key')
|
||||
return null
|
||||
private static identify(data: CryptoKey): Type {
|
||||
if (data.type === 'secret') {
|
||||
if (data.usages.includes('wrapKey')) return Type.SecretWrap
|
||||
else return Type.SecretBox
|
||||
} else {
|
||||
if (data.usages.includes('sign')) return Type.signature
|
||||
else return Type.Private
|
||||
}
|
||||
}
|
||||
private information(): {format: KeyFormat, algorithm: AesKeyAlgorithm | EcKeyImportParams, usages: KeyUsage[]} {
|
||||
switch (this.type) {
|
||||
case Type.SecretWrap: return {algorithm: {name: ENCRYPTION_ALGORITHM, length: BIT_LENGTH()}, format: 'raw', usages: ['wrapKey', 'unwrapKey']}
|
||||
case Type.SecretBox: return {algorithm: {name: ENCRYPTION_ALGORITHM, length: BIT_LENGTH()}, format: 'raw', usages: ['encrypt', 'decrypt']}
|
||||
case Type.signature: return {algorithm: SIGNATURE_KEY(), format: 'pkcs8', usages: ['sign']}
|
||||
case Type.Private: return {algorithm: ECDH_PARAMETERS(), format: 'pkcs8', usages: ['deriveBits']}
|
||||
}
|
||||
}
|
||||
|
||||
public static async wrap(data: CryptoKey, key: CryptoKey): Promise<SecretWrap | null> {
|
||||
log.trace('wrap')
|
||||
|
||||
const format = SecretWrap.format(data.type)
|
||||
if (format === null) return null
|
||||
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12))
|
||||
const type = this.identify(data)
|
||||
const format = (type === Type.SecretBox || type === Type.SecretWrap) ? 'raw' : 'pkcs8'
|
||||
|
||||
let box: ArrayBuffer
|
||||
try {
|
||||
@@ -54,19 +64,22 @@ export default class SecretWrap {
|
||||
return null
|
||||
}
|
||||
|
||||
return new SecretWrap(new Uint8Array(box), data.algorithm, data.usages, format, iv)
|
||||
return new SecretWrap(new Uint8Array(box), iv, type)
|
||||
}
|
||||
public async unwrap(key: CryptoKey): Promise<CryptoKey | null> {
|
||||
log.trace('unwrap')
|
||||
|
||||
const info = this.information()
|
||||
|
||||
try {
|
||||
return await crypto.subtle.unwrapKey(
|
||||
this.type,
|
||||
info.format,
|
||||
this.cipher,
|
||||
key,
|
||||
{name: ENCRYPTION_ALGORITHM, iv: this.iv},
|
||||
this.algorithm,
|
||||
info.algorithm,
|
||||
true,
|
||||
this.usages
|
||||
info.usages
|
||||
)
|
||||
} catch (e) {
|
||||
log.warn('Failed to unwrap')
|
||||
@@ -77,45 +90,39 @@ export default class SecretWrap {
|
||||
|
||||
public toString(): string {
|
||||
log.trace('toString')
|
||||
const cipher = a2b64(this.cipher)
|
||||
const iv = a2b64(this.iv)
|
||||
return JSON.stringify({
|
||||
cipher: a2b64(this.cipher),
|
||||
algorithm: this.algorithm,
|
||||
usages: this.usages,
|
||||
type: this.type,
|
||||
iv
|
||||
})
|
||||
return `${this.type}.${iv}.${cipher}`
|
||||
}
|
||||
public static fromString(data: string): SecretWrap | null {
|
||||
log.trace('fromString')
|
||||
|
||||
const obj = SecretWrap.parseJSON(data)
|
||||
if (obj === null) return null
|
||||
const parts = data.split('.')
|
||||
if (parts.length !== 3) {
|
||||
log.warn(`Part count doesn't match : ${parts.length} != 3`)
|
||||
return null
|
||||
}
|
||||
|
||||
let type: Type
|
||||
try {
|
||||
const cipher = b642a(obj.cipher)
|
||||
if (cipher === null) return null
|
||||
|
||||
const iv = b642a(obj.iv)
|
||||
if (iv === null) return null
|
||||
|
||||
return new SecretWrap(cipher, obj.algorithm, obj.usages, obj.type, iv)
|
||||
type = parseInt(parts[0])
|
||||
} catch (e) {
|
||||
log.warn('Probable access to undefined field')
|
||||
log.warn('Failed to parse type')
|
||||
log.debug('Type :', parts[0])
|
||||
log.debug('Error :', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private static parseJSON(
|
||||
data: string
|
||||
): {cipher: string; iv: string; algorithm: KeyAlgorithm; usages: KeyUsage[]; type: 'raw' | 'pkcs8'} | null {
|
||||
log.trace('parseJSON')
|
||||
try {
|
||||
return JSON.parse(data)
|
||||
} catch (e) {
|
||||
log.warn('Failed to parse JSON')
|
||||
if (!(type in Type)) {
|
||||
log.warn('Unknown type', type)
|
||||
return null
|
||||
}
|
||||
|
||||
const iv = b642a(parts[1])
|
||||
if (iv === null) return null
|
||||
|
||||
const cipher = b642a(parts[2])
|
||||
if (cipher === null) return null
|
||||
|
||||
return new this(cipher, iv, type)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user