Skip to content

Commit

Permalink
feat: remove storage
Browse files Browse the repository at this point in the history
Signed-off-by: Berend Sliedrecht <[email protected]>
  • Loading branch information
Berend Sliedrecht committed Jun 25, 2024
1 parent f983a5b commit e71679b
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 306 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@peculiar/asn1-ecc": "^2.3.8",
"@peculiar/asn1-schema": "^2.3.8",
"@peculiar/asn1-x509": "^2.3.8",
"dtor": "^0.1.2",
"webcrypto-core": "^1.8.0"
},
"devDependencies": {
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

209 changes: 209 additions & 0 deletions src/CryptoKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import * as core from 'webcrypto-core'

import { Key as AskarKey, Jwk } from '@hyperledger/aries-askar-shared'
import type { EcdsaParams, JsonWebKey, KeyAlgorithm, KeyFormat, KeyImportParams, KeyType, KeyUsage } from './types'
import { askarAlgorithmToCryptoAlgorithm, cryptoAlgorithmToAskarAlgorithm } from './askar'
import { AsnConvert, AsnParser } from '@peculiar/asn1-schema'
import { ecdsaWithSHA256 } from '@peculiar/asn1-ecc'
import { SubjectPublicKeyInfo } from '@peculiar/asn1-x509'

export class AskarCryptoKey extends core.CryptoKey {
public askarKey: AskarKey

public constructor({
askarKey,
algorithm,
extractable = false,
usages,
type,
}: {
askarKey: AskarKey
extractable?: boolean
usages: Array<KeyUsage>
type?: KeyType
algorithm: KeyAlgorithm
}) {
super()
this.askarKey = askarKey
this.extractable = extractable
this.type = type
this.usages = usages
this.algorithm = algorithm
}

public [Symbol.dispose]() {
this.askarKey.handle.free()
}

public sign(algorithm: EcdsaParams, data: ArrayBuffer) {
if (algorithm.hash && algorithm.hash.name !== 'SHA-256') {
throw new Error(`Invalid hashing algorithm. Expected: 'SHA-256', received: ${algorithm.hash.name}`)
}
return this.askarKey.signMessage({ message: new Uint8Array(data) })
}

public verify(algorithm: EcdsaParams, signature: ArrayBuffer, data: ArrayBuffer) {
if (algorithm.hash && algorithm.hash.name !== 'SHA-256') {
throw new Error(`Invalid hashing algorithm. Expected: 'SHA-256', received: ${algorithm.hash.name}`)
}

return this.askarKey.verifySignature({
message: new Uint8Array(data),
signature: new Uint8Array(signature),
})
}

public get publicBytes() {
return this.askarKey.publicBytes
}

/**
*
* @todo - Deal with key format
*
*/
public static fromPublicBytes(
algorithm: KeyImportParams,
keyData: Uint8Array,
_format: KeyFormat,
extractable: boolean,
keyUsages: Array<KeyUsage>
) {
const publicKey = AskarKey.fromPublicBytes({
algorithm: cryptoAlgorithmToAskarAlgorithm(algorithm),
publicKey: keyData,
})

return new AskarCryptoKey({
askarKey: publicKey,
type: 'public',
algorithm,
usages: keyUsages,
extractable,
})
}

/**
*
* @todo - Deal with key format
*
*/
public static fromSecret(
algorithm: KeyImportParams,
keyData: Uint8Array,
_format: KeyFormat,
extractable: boolean,
keyUsages: Array<KeyUsage>
) {
const publicKey = AskarKey.fromSecretBytes({
algorithm: cryptoAlgorithmToAskarAlgorithm(algorithm),
secretKey: keyData,
})

return new AskarCryptoKey({
askarKey: publicKey,
type: 'private',
algorithm,
usages: keyUsages,
extractable,
})
}

public toJwk() {
if (this.type === 'public') return this.askarKey.jwkPublic
if (this.type === 'private' || this.type === 'secret') {
return this.askarKey.jwkSecret
}
}

public static fromJwk(keyData: JsonWebKey, keyUsages: Array<KeyUsage>, extractable: boolean) {
const key = AskarKey.fromJwk({ jwk: new Jwk(keyData) })

let type: KeyType = 'public'
try {
key.secretBytes
type = 'private'
} catch {}

return new AskarCryptoKey({
askarKey: key,
extractable,
usages: keyUsages,
type,
algorithm: askarAlgorithmToCryptoAlgorithm(key.algorithm),
})
}

public static override create<T extends core.CryptoKey = AskarCryptoKey>(
algorithm: KeyAlgorithm,
type: KeyType,
extractable: boolean,
usages: core.KeyUsages
): T {
return new AskarCryptoKey({
askarKey: AskarKey.generate(cryptoAlgorithmToAskarAlgorithm(algorithm)),
algorithm,
extractable,
usages,
type,
}) as unknown as T
}

public exportKey(format: KeyFormat): JsonWebKey | ArrayBuffer {
switch (format.toLowerCase()) {
case 'spki': {
const publicKeyInfo = new SubjectPublicKeyInfo({
algorithm: ecdsaWithSHA256,
subjectPublicKey: this.publicBytes.buffer,
})

const derEncoded = AsnConvert.serialize(publicKeyInfo)
return derEncoded
}
case 'jwk':
return this.toJwk() as JsonWebKey
case 'raw':
// TODO: likely incorrect
return this.publicBytes.buffer
default:
throw new Error(`Not supported format: ${format}`)
}
}

public static importKey(
format: KeyFormat,
keyData: JsonWebKey | ArrayBuffer,
algorithm: KeyAlgorithm,
extractable: boolean,
keyUsages: Array<KeyUsage>
) {
if (format !== 'jwk' && ArrayBuffer.isView(keyData)) {
throw new core.OperationError('non-jwk formats can only be used with an ArrayBuffer')
}

switch (format.toLowerCase()) {
case 'jwk':
return AskarCryptoKey.fromJwk(keyData as JsonWebKey, keyUsages, extractable)
case 'spki': {
const keyInfo = AsnParser.parse(new Uint8Array(keyData as ArrayBuffer), core.asn1.PublicKeyInfo)

return AskarCryptoKey.fromPublicBytes(
algorithm,
new Uint8Array(keyInfo.publicKey),
format,
extractable,
keyUsages
)
}
default:
throw new core.OperationError(
`Only format 'jwt' and 'spki' are supported for importing keys. Received: ${format}`
)
}
}
}

export const assertIsAskarCryptoKey = (askarKey: core.CryptoKey): AskarCryptoKey => {
if (askarKey instanceof AskarCryptoKey) return askarKey
throw new Error('key is not an instance of AskarCryptoKey')
}
102 changes: 25 additions & 77 deletions src/EcdsaProvider.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,4 @@
import { AsnConvert, AsnParser } from '@peculiar/asn1-schema'
import { SubjectPublicKeyInfo } from '@peculiar/asn1-x509'
import { ecdsaWithSHA256 } from '@peculiar/asn1-ecc'
import * as core from 'webcrypto-core'
import {
askarExportKeyToJwk,
askarKeyGenerate,
askarKeyGetPublicBytes,
askarKeySign,
askarKeyVerify,
askarKeyFromJwk,
askarKeyFromPublicBytes,
} from './askar'
import type {
CryptoKeyPair,
EcKeyGenParams,
Expand All @@ -20,61 +8,47 @@ import type {
KeyFormat,
KeyUsage,
} from './types'
import { AskarCryptoKey, assertIsAskarCryptoKey } from './CryptoKey'

export class EcdsaProvider extends core.EcdsaProvider {
public async onSign(algorithm: EcdsaParams, key: core.CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
if (algorithm.hash.name !== 'SHA-256') {
throw new Error(`Invalid hashing algorithm. Expected: 'SHA-256', received: ${algorithm.hash.name}`)
}

const signature = askarKeySign({ key, data })

return signature
public async onSign(algorithm: EcdsaParams, key: AskarCryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
assertIsAskarCryptoKey(key)
return key.sign(algorithm, data)
}

public async onVerify(
algorithm: EcdsaParams,
key: core.CryptoKey,
key: AskarCryptoKey,
signature: ArrayBuffer,
data: ArrayBuffer
): Promise<boolean> {
if (algorithm.hash.name !== 'SHA-256') {
throw new Error(`Invalid hashing algorithm. Expected: 'SHA-256', received: ${algorithm.hash.name}`)
}

const isValid = askarKeyVerify({ key, data, signature })

return isValid
assertIsAskarCryptoKey(key)
return key.verify(algorithm, signature, data)
}

public async onGenerateKey(
algorithm: EcKeyGenParams,
extractable: boolean,
keyUsages: KeyUsage[]
): Promise<CryptoKeyPair> {
const key = askarKeyGenerate({ algorithm, extractable, keyUsages })

return key
const privateKey = AskarCryptoKey.create(algorithm, 'private', extractable, keyUsages)
const publicKey = new AskarCryptoKey({
askarKey: privateKey.askarKey,
type: 'public',
usages: keyUsages,
algorithm,
extractable,
})

return {
publicKey,
privateKey,
}
}

public async onExportKey(format: KeyFormat, key: core.CryptoKey): Promise<JsonWebKey | ArrayBuffer> {
switch (format.toLowerCase()) {
case 'spki': {
const publicKeyInfo = new SubjectPublicKeyInfo({
algorithm: ecdsaWithSHA256,
subjectPublicKey: askarKeyGetPublicBytes(key).buffer,
})

const derEncoded = AsnConvert.serialize(publicKeyInfo)
return derEncoded
}
case 'jwk':
return askarExportKeyToJwk(key)
case 'raw':
return askarKeyGetPublicBytes(key).buffer
default:
throw new Error(`Not supported format: ${format}`)
}
public async onExportKey(format: KeyFormat, key: AskarCryptoKey): Promise<JsonWebKey | ArrayBuffer> {
assertIsAskarCryptoKey(key)
return key.exportKey(format)
}

public async onImportKey(
Expand All @@ -83,33 +57,7 @@ export class EcdsaProvider extends core.EcdsaProvider {
algorithm: EcKeyImportParams,
extractable: boolean,
keyUsages: KeyUsage[]
): Promise<core.CryptoKey> {
if (format !== 'jwk' && ArrayBuffer.isView(keyData)) {
throw new core.OperationError('non-jwk formats can only be used with an ArrayBuffer')
}

switch (format.toLowerCase()) {
case 'jwk':
return askarKeyFromJwk({
extractable,
keyUsages,
keyData: keyData as JsonWebKey,
})
case 'spki': {
const keyInfo = AsnParser.parse(new Uint8Array(keyData as ArrayBuffer), core.asn1.PublicKeyInfo)

return askarKeyFromPublicBytes({
format,
keyData: new Uint8Array(keyInfo.publicKey),
keyUsages,
extractable,
algorithm,
})
}
default:
throw new core.OperationError(
`Only format 'jwt' and 'spki' are supported for importing keys. Received: ${format}`
)
}
): Promise<AskarCryptoKey> {
return AskarCryptoKey.importKey(format, keyData, algorithm, extractable, keyUsages)
}
}
Loading

0 comments on commit e71679b

Please sign in to comment.