Skip to content

Commit

Permalink
feat: create comparison with nodejs test
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 14, 2024
1 parent 0d917ae commit 4556c36
Show file tree
Hide file tree
Showing 10 changed files with 11,083 additions and 903 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
"test": "node --import tsx --test ./tests/*.test.ts"
},
"dependencies": {
"@credo-ts/core": "^0.5.3",
"@hyperledger/aries-askar-shared": "^0.2.1",
"@noble/hashes": "^1.4.0",
"@peculiar/asn1-ecc": "^2.3.8",
"@peculiar/asn1-schema": "^2.3.8",
"@peculiar/asn1-x509": "^2.3.8",
"@peculiar/webcrypto": "^1.5.0",
"webcrypto-core": "^1.8.0"
},
"devDependencies": {
Expand Down
11,578 changes: 10,721 additions & 857 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

85 changes: 69 additions & 16 deletions src/EcdsaProvider.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
import { AsnConvert } from '@peculiar/asn1-schema'
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 { askarKeyGenerate, askarKeyGetPublicBytes, askarKeySign, askarKeyVerify } from './askar'
import type { CryptoKeyPair, EcKeyGenParams, EcdsaParams, JsonWebKey, KeyFormat, KeyUsage } from './types'
import {
askarExportKeyToJwk,
askarKeyFromSecretBytes,
askarKeyFromPublicBytes,
askarKeyGenerate,
askarKeyGetPublicBytes,
askarKeySign,
askarKeyVerify,
askarKeyFromJwk,
} from './askar'
import type {
CryptoKeyPair,
EcKeyGenParams,
EcKeyImportParams,
EcdsaParams,
JsonWebKey,
KeyFormat,
KeyUsage,
} from './types'

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)
const signature = askarKeySign({ key, data })

return signature
}
Expand All @@ -26,24 +43,23 @@ export class EcdsaProvider extends core.EcdsaProvider {
throw new Error(`Invalid hashing algorithm. Expected: 'SHA-256', received: ${algorithm.hash.name}`)
}

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

return isValid
}

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

return key
}

public async onExportKey(format: KeyFormat, key: core.CryptoKey): Promise<JsonWebKey | ArrayBuffer> {
switch (format.toLowerCase()) {
case 'pkcs8':
case 'spki': {
const publicKeyInfo = new SubjectPublicKeyInfo({
algorithm: ecdsaWithSHA256,
Expand All @@ -53,20 +69,57 @@ export class EcdsaProvider extends core.EcdsaProvider {
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}`)
}
}

onImportKey(
_format: unknown,
_keyData: unknown,
_algorithm: unknown,
_extractable: boolean,
_keyUsages: KeyUsage[]
public async onImportKey(
format: KeyFormat,
keyData: JsonWebKey | ArrayBuffer,
algorithm: EcKeyImportParams,
extractable: boolean,
keyUsages: KeyUsage[]
): Promise<core.CryptoKey> {
throw new Error('onImportKey not implemented.')
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 'raw': {
const asnKey = new core.asn1.EcPublicKey(keyData as ArrayBuffer)
return askarKeyFromPublicBytes({
algorithm,
format,
keyUsages,
extractable,
keyData: new Uint8Array(asnKey.value),
})
}
case 'pkcs8': {
const keyInfo = AsnParser.parse(new Uint8Array(keyData as ArrayBuffer), core.asn1.PrivateKeyInfo)
const asnKey = AsnParser.parse(keyInfo.privateKey, core.asn1.EcPrivateKey)
return askarKeyFromSecretBytes({
algorithm,
keyData: new Uint8Array(asnKey.privateKey),
extractable,
keyUsages,
format,
})
}
default:
throw new core.OperationError("format: Must be 'raw', 'pkcs8'")
}
}
}
14 changes: 7 additions & 7 deletions src/Ed25519Provider.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as core from 'webcrypto-core'
import { askarKeyGenerate, askarKeySign, askarKeyVerify } from './askar'
import type { CryptoKeyPair } from './types'
import type { CryptoKeyPair, EcKeyGenParams, KeyUsage } from './types'

export class Ed25519Provider extends core.Ed25519Provider {
public async onSign(_algorithm: string, key: core.CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer> {
return askarKeySign(key, data)
return askarKeySign({ key, data })
}

public async onVerify(
Expand All @@ -13,14 +13,14 @@ export class Ed25519Provider extends core.Ed25519Provider {
signature: ArrayBuffer,
data: ArrayBuffer
): Promise<boolean> {
return askarKeyVerify(key, data, signature)
return askarKeyVerify({ key, data, signature })
}

public async onGenerateKey(
algorithm: { name: string },
_extractable: boolean,
_keyUsages: string[]
algorithm: EcKeyGenParams,
extractable: boolean,
keyUsages: KeyUsage[]
): Promise<CryptoKeyPair> {
return askarKeyGenerate(algorithm.name)
return askarKeyGenerate({ algorithm, keyUsages, extractable })
}
}
145 changes: 135 additions & 10 deletions src/askar.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import type * as core from 'webcrypto-core'
import { getCryptoKey, setCryptoKey } from './storage'
import { Key, type KeyAlgs, CryptoBox } from '@hyperledger/aries-askar-shared'
import { Key, type KeyAlgs, CryptoBox, Jwk } from '@hyperledger/aries-askar-shared'
import type { EcKeyGenParams, EcKeyImportParams, JsonWebKey, KeyFormat, KeyUsage } from './types'

const CBOX_NONCE_LENGTH = 24

export const askarKeySign = (key: core.CryptoKey, data: ArrayBuffer) => {
export const askarKeySign = ({
key,
data,
}: {
key: core.CryptoKey
data: ArrayBuffer
}) => {
const internalKey = getCryptoKey(key)
const signature = internalKey.signMessage({
message: new Uint8Array(data),
Expand All @@ -13,7 +20,15 @@ export const askarKeySign = (key: core.CryptoKey, data: ArrayBuffer) => {
return signature
}

export const askarKeyVerify = (key: core.CryptoKey, data: ArrayBuffer, signature: ArrayBuffer) => {
export const askarKeyVerify = ({
key,
data,
signature,
}: {
key: core.CryptoKey
data: ArrayBuffer
signature: ArrayBuffer
}) => {
const internalKey = getCryptoKey(key)

const isVerified = internalKey.verifySignature({
Expand All @@ -24,14 +39,31 @@ export const askarKeyVerify = (key: core.CryptoKey, data: ArrayBuffer, signature
return isVerified
}

export const askarKeyGenerate = (algorithm: string) => {
const key = Key.generate(algorithm as KeyAlgs)
export const askarKeyGenerate = ({
extractable,
algorithm,
keyUsages,
}: {
algorithm: EcKeyGenParams
extractable: boolean
keyUsages: KeyUsage[]
}) => {
const key = Key.generate(cryptoAlgorithmToAskarAlgorithm(algorithm))

// We add a key twice into the WeakStorage as a key can only have one type.
// Maybe in the future we can just store the bytes for the public and a reference
// for the secret/private key
const publicKey = setCryptoKey(key, 'public', true)
const secretKey = setCryptoKey(key, 'secret')
const publicKey = setCryptoKey({
askarKey: key,
extractable,
// Filter out properties that are not possible for the public key
keyUsages: keyUsages.filter((u) => u !== 'sign'),
keyType: 'public',
})

const secretKey = setCryptoKey({
askarKey: key,
keyType: 'private',
keyUsages,
extractable,
})

return { publicKey, privateKey: secretKey }
}
Expand All @@ -42,6 +74,67 @@ export const askarKeyGetPublicBytes = (key: core.CryptoKey) => {
return cKey.publicBytes
}

export const askarKeyFromPublicBytes = ({
algorithm,
keyData,
extractable,
keyUsages,
}: {
algorithm: EcKeyImportParams
keyData: Uint8Array
format: KeyFormat
extractable: boolean
keyUsages: KeyUsage[]
}) => {
const publicKey = Key.fromPublicBytes({
algorithm: cryptoAlgorithmToAskarAlgorithm(algorithm),
publicKey: keyData,
})

return setCryptoKey({
askarKey: publicKey,
extractable,
keyUsages,
keyType: 'public',
})
}

export const askarKeyFromSecretBytes = ({
algorithm,
keyData,
extractable,
keyUsages,
}: {
algorithm: EcKeyImportParams
keyData: Uint8Array
format: KeyFormat
extractable: boolean
keyUsages: KeyUsage[]
}) => {
const privateKey = Key.fromSecretBytes({
algorithm: cryptoAlgorithmToAskarAlgorithm(algorithm),
secretKey: keyData,
})

return setCryptoKey({
askarKey: privateKey,
extractable,
keyUsages,
keyType: 'private',
})
}

export const askarExportKeyToJwk = (key: core.CryptoKey) => {
const askarKey = getCryptoKey(key)

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

throw new Error(`key.type '${key.type}' is not a string of 'public'/'private'/'secret'`)
}

export const askarGetRandomValues = (buffer: Uint8Array): Uint8Array => {
const genCount = Math.ceil(buffer.length / CBOX_NONCE_LENGTH)
const buf = new Uint8Array(genCount * CBOX_NONCE_LENGTH)
Expand All @@ -52,3 +145,35 @@ export const askarGetRandomValues = (buffer: Uint8Array): Uint8Array => {
buffer.set(buf.subarray(0, buffer.length))
return buffer
}

export const askarKeyFromJwk = ({
keyData,
keyUsages,
extractable,
}: {
keyData: JsonWebKey
keyUsages: KeyUsage[]
extractable: boolean
}) => {
const askarKey = Key.fromJwk({ jwk: new Jwk(keyData) })
try {
askarKey.secretBytes
return setCryptoKey({
askarKey,
keyUsages,
extractable,
keyType: 'private',
})
} catch {
return setCryptoKey({
askarKey,
keyUsages,
extractable,
keyType: 'public',
})
}
}

// TODO: this needs a proper conversion
const cryptoAlgorithmToAskarAlgorithm = (algorithm: EcKeyGenParams) =>
(algorithm.namedCurve ? algorithm.namedCurve : algorithm.name) as KeyAlgs
18 changes: 12 additions & 6 deletions src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@ export function getCryptoKey(key: core.CryptoKey) {
return res
}

export function setCryptoKey(askarKey: AskarKey, keyType: KeyType, extractable = false) {
export function setCryptoKey({
extractable,
askarKey,
keyType,
keyUsages,
}: {
askarKey: AskarKey
keyType: KeyType
extractable: boolean
keyUsages: KeyUsage[]
}) {
const webCryptoAlgorithm = askarAlgorithmToWebCryptoAlgorithm(askarKey.algorithm)

const keyUsage: KeyUsage[] = []
if (keyType === 'public') keyUsage.push('verify')
if (keyType === 'secret' || keyType === 'private') keyUsage.push('sign')

const key = core.CryptoKey.create(webCryptoAlgorithm, keyType, extractable, keyUsage)
const key = core.CryptoKey.create(webCryptoAlgorithm, keyType, extractable, keyUsages)

Object.freeze(key)

Expand Down
Loading

0 comments on commit 4556c36

Please sign in to comment.