Skip to content

Commit

Permalink
fix: Move away from using crypto.subtle for signature verifications, …
Browse files Browse the repository at this point in the history
…as it is too problematic in React-native. Replaced with audited noble implementations
  • Loading branch information
nklomp committed Nov 30, 2024
1 parent c33024d commit 69ec9a6
Show file tree
Hide file tree
Showing 11 changed files with 722 additions and 463 deletions.
2 changes: 1 addition & 1 deletion packages/did-utils/src/did-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ export function verificationMethodToJwk(vm: VerificationMethod): JWK {
let jwk: JWK | undefined = vm.publicKeyJwk as JWK
if (!jwk) {
let publicKeyHex = vm.publicKeyHex ?? u8a.toString(extractPublicKeyBytes(vm), 'hex')
jwk = toJwk(publicKeyHex, keyTypeFromCryptographicSuite({ suite: vm.type }))
jwk = toJwk(publicKeyHex, keyTypeFromCryptographicSuite({ crv: vm.type }))
}
if (!jwk) {
throw Error(`Could not convert verification method to jwk`)
Expand Down
6 changes: 3 additions & 3 deletions packages/jwt-service/src/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
ManagedIdentifierResult,
resolveExternalJwkIdentifier,
} from '@sphereon/ssi-sdk-ext.identifier-resolution'
import { keyTypeFromCryptographicSuite, verifySignatureWithSubtle } from '@sphereon/ssi-sdk-ext.key-utils'
import { keyTypeFromCryptographicSuite, verifyRawSignature } from '@sphereon/ssi-sdk-ext.key-utils'
import { contextHasPlugin } from '@sphereon/ssi-sdk.agent-config'
import { JWK } from '@sphereon/ssi-types'
import { IAgentContext } from '@veramo/core'
Expand Down Expand Up @@ -323,12 +323,12 @@ export const verifyJws = async (args: VerifyJwsArgs, context: IAgentContext<IIde
signature: sigWithId.signature,
data,
publicKeyHex,
type: keyTypeFromCryptographicSuite({ suite: jwkInfo.jwk.crv ?? 'ES256' }),
type: keyTypeFromCryptographicSuite({ crv: jwkInfo.jwk.crv ?? 'ES256' }),
// no kms arg, as the current key manager needs a bit more work
})
} else {
const signature = base64ToBytes(sigWithId.signature)
valid = await verifySignatureWithSubtle({ data, signature, key: jwkInfo.jwk })
valid = await verifyRawSignature({ data, signature, key: jwkInfo.jwk })
}
if (!valid) {
errorMessages.push(`Signature ${index} was not valid`)
Expand Down
4 changes: 2 additions & 2 deletions packages/key-manager/src/agent/SphereonKeyManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { calculateJwkThumbprintForKey, toJwk, verifySignatureWithSubtle } from '@sphereon/ssi-sdk-ext.key-utils'
import { calculateJwkThumbprintForKey, toJwk, verifyRawSignature } from '@sphereon/ssi-sdk-ext.key-utils'
import { IKey, KeyMetadata, ManagedKeyInfo } from '@veramo/core'
import { AbstractKeyManagementSystem, AbstractKeyStore, KeyManager as VeramoKeyManager } from '@veramo/key-manager'

Expand Down Expand Up @@ -93,7 +93,7 @@ export class SphereonKeyManager extends VeramoKeyManager {
return await kms.verify(args)
}
}
return await verifySignatureWithSubtle({
return await verifyRawSignature({
key: toJwk(args.publicKeyHex, args.type),
data: args.data,
signature: u8a.fromString(args.signature, 'utf-8'),
Expand Down
37 changes: 36 additions & 1 deletion packages/key-utils/__tests__/functions.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { generatePrivateKeyHex, Key, padLeft } from '../src'
import {JoseSignatureAlgorithm} from "@sphereon/ssi-types";
import * as u8a from 'uint8arrays'
import {generatePrivateKeyHex, Key, padLeft, toJwk, verifyRawSignature} from '../src'

describe('functions: key generator', () => {
it('Secp256k1 should generate random keys', async () => {
Expand Down Expand Up @@ -62,3 +64,36 @@ describe('functions: Leftpad', () => {
expect(result).toEqual(`${data}`)
})
})


describe('functions: verifySignature', () => {
it('should verify signature with secp256k1', async () => {
const publicKeyHex = '04782c8ed17e3b2a783b5464f33b09652a71c678e05ec51e84e2bcfc663a3de963af9acb4280b8c7f7c42f4ef9aba6245ec1ec1712fd38a0fa96418d8cd6aa6152';
const message = '4d7367';// from project whycheproof, in hex!
const signatureHex = '109cd8ae0374358984a8249c0a843628f2835ffad1df1a9a69aa2fe72355545cac6f00daf53bd8b1e34da329359b6e08019c5b037fed79ee383ae39f85a159c6';
await expect(verifyRawSignature({data: u8a.fromString(message, 'hex'), signature: u8a.fromString(signatureHex, "hex"), key: toJwk(publicKeyHex, 'Secp256k1')})).resolves.toEqual(true)
})

it('should verify signature with secp256r1', async () => {
const publicKeyHex = '042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e';
const message = '313233343030'; // from project whycheproof, in hex!
const signatureHex = '2ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e184cd60b855d442f5b3c7b11eb6c4e0ae7525fe710fab9aa7c77a67f79e6fadd76';
await expect(verifyRawSignature({data: u8a.fromString(message, 'hex'), signature: u8a.fromString(signatureHex, "hex"), key: toJwk(publicKeyHex, 'Secp256r1')})).resolves.toEqual(true)
})

it('should verify signature with ed25519', async () => {
const publicKeyHex = '7d4d0e7f6153a69b6242b522abbee685fda4420f8834b108c3bdae369ef549fa';
const message = '313233343030'; // from project whycheproof, in hex!
const signatureHex = '657c1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2bf0cf5b3a289976458a1be6277a5055545253b45b07dcc1abd96c8b989c00f301';
await expect(verifyRawSignature({data: u8a.fromString(message, 'hex'), signature: u8a.fromString(signatureHex, "hex"), key: toJwk(publicKeyHex, 'Ed25519')})).resolves.toEqual(true)
})


it('should verify signature with rsa PSS', async () => {
const publicKeyHex = 'a2b451a07d0aa5f96e455671513550514a8a5b462ebef717094fa1fee82224e637f9746d3f7cafd31878d80325b6ef5a1700f65903b469429e89d6eac8845097b5ab393189db92512ed8a7711a1253facd20f79c15e8247f3d3e42e46e48c98e254a2fe9765313a03eff8f17e1a029397a1fa26a8dce26f490ed81299615d9814c22da610428e09c7d9658594266f5c021d0fceca08d945a12be82de4d1ece6b4c03145b5d3495d4ed5411eb878daf05fd7afc3e09ada0f1126422f590975a1969816f48698bcbba1b4d9cae79d460d8f9f85e7975005d9bc22c4e5ac0f7c1a45d12569a62807d3b9a02e5a530e773066f453d1f5b4c2e9cf7820283f742b9d510001';
const message = '313233343030';
const signatureHex = '5e91b5dcbf02d6f19621d41a83dc8f15ea83c0edb83765ef029b0acac2e1ec8918b1d2afe1fadf11c48d27594cb9c01fed79d90e5d5a8085c438450111aa7d9fa39c2345b14fc3c2cb34128f86db5eb00bdf8dfe38d61f29a41fe31342e7aaefcb4b122eb5d63c2f5c263c8df8450e9428ffef974d535818d51dc03a7d60c8b2d16c999ae46d73ab40515fe601d9b89b1d09c6d60cd51639a97c1d211e097609ba5e8c319c6fbd21b34a634ec8fb8971c5aae21c70b847a4539cc10dc314ddd8a9629e8a0e51c66c0cb61fd1f7228c01c6769190abe9bac9a3897800050014358594e0fb20dbb458b12aa1346826cc9f7e9c5352b073d62853dafe77c848cb1f';
await expect(verifyRawSignature({data: u8a.fromString(message, 'hex'), signature: u8a.fromString(signatureHex, "hex"), key: toJwk(publicKeyHex, 'RSA'), opts: {signatureAlg: JoseSignatureAlgorithm.PS256}})).resolves.toEqual(true)
})

})
4 changes: 4 additions & 0 deletions packages/key-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
"@stablelib/ed25519": "^1.0.3",
"@stablelib/sha256": "^1.0.1",
"@stablelib/sha512": "^1.0.1",
"@noble/secp256k1": "^2.1.0",
"@noble/curves": "^1.7.0",
"@noble/hashes": "^1.6.1",
"micro-rsa-dsa-dh": "^0.1.0",
"@trust/keyto": "^1.0.1",
"@veramo/core": "4.2.0",
"base64url": "^3.0.1",
Expand Down
9 changes: 5 additions & 4 deletions packages/key-utils/src/conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,32 @@ import {
JwkKeyType,
JwkKeyTypeString,
} from '@sphereon/ssi-types'
import {removeNulls} from "./functions";

export function coseKeyToJwk(coseKey: ICoseKeyJson): JWK {
const { x5chain, key_ops, crv, alg, baseIV, kty, ...rest } = coseKey
return {
return removeNulls({
...rest,
kty: coseToJoseKty(kty),
...(crv && { crv: coseToJoseCurve(crv) }),
...(key_ops && { key_ops: key_ops.map(coseToJoseKeyOperation) }),
...(alg && { alg: coseToJoseSignatureAlg(alg) }),
...(baseIV && { iv: baseIV }),
...(x5chain && { x5c: x5chain }),
} satisfies JWK
}) satisfies JWK
}

export function jwkToCoseKey(jwk: JWK): ICoseKeyJson {
const { x5c, key_ops, crv, alg, iv, kty, ...rest } = jwk
return {
return removeNulls({
...rest,
kty: joseToCoseKty(kty),
...(crv && { crv: joseToCoseCurve(crv) }),
...(key_ops && { key_ops: key_ops.map(joseToCoseKeyOperation) }),
...(alg && { alg: joseToCoseSignatureAlg(alg) }),
...(iv && { baseIV: iv }),
...(x5c && { x5chain: x5c }),
} satisfies ICoseKeyJson
} satisfies ICoseKeyJson)
}

export function coseToJoseKty(kty: ICoseKeyType): JwkKeyType {
Expand Down
Loading

0 comments on commit 69ec9a6

Please sign in to comment.