@sphereon/ssi-sdk-ext.key-utils 0.28.1-feature.oyd.cmsm.improv.21 → 0.28.1-next.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/functions.ts CHANGED
@@ -6,31 +6,43 @@ import { p256 } from '@noble/curves/p256'
6
6
  import { p384 } from '@noble/curves/p384'
7
7
  import { p521 } from '@noble/curves/p521'
8
8
  import { secp256k1 } from '@noble/curves/secp256k1'
9
- import { sha256 } from '@noble/hashes/sha256'
10
- import { sha384, sha512 } from '@noble/hashes/sha512'
11
- import { generateRSAKeyAsPEM, hexToBase64, hexToPEM, PEMToJwk, privateKeyHexFromPEM } from '@sphereon/ssi-sdk-ext.x509-utils'
12
- import { JoseCurve, JoseSignatureAlgorithm, JWK, JwkKeyType, Loggers } from '@sphereon/ssi-types'
9
+ import { sha256, sha384, sha512 } from '@noble/hashes/sha2'
10
+ import {
11
+ cryptoSubtleImportRSAKey,
12
+ generateRSAKeyAsPEM,
13
+ hexToBase64,
14
+ hexToPEM,
15
+ PEMToJwk,
16
+ privateKeyHexFromPEM,
17
+ } from '@sphereon/ssi-sdk-ext.x509-utils'
18
+ import { JoseCurve, JoseSignatureAlgorithm, type JWK, JwkKeyType, Loggers } from '@sphereon/ssi-types'
13
19
  import { generateKeyPair as generateSigningKeyPair } from '@stablelib/ed25519'
14
- import { IAgentContext, IKey, IKeyManager, ManagedKeyInfo, MinimalImportableKey } from '@veramo/core'
20
+ import type { IAgentContext, IKey, IKeyManager, ManagedKeyInfo, MinimalImportableKey } from '@veramo/core'
15
21
  import debug from 'debug'
16
22
 
17
- import { JsonWebKey } from 'did-resolver'
23
+ import type { JsonWebKey } from 'did-resolver'
18
24
  import elliptic from 'elliptic'
19
25
  import * as rsa from 'micro-rsa-dsa-dh/rsa.js'
26
+
27
+ // @ts-ignore
28
+ import { Crypto } from 'node'
29
+ // @ts-ignore
20
30
  import * as u8a from 'uint8arrays'
21
31
  import { digestMethodParams } from './digest-methods'
22
32
  import { validateJwk } from './jwk-jcs'
23
33
  import {
24
34
  ENC_KEY_ALGS,
25
- IImportProvidedOrGeneratedKeyArgs,
35
+ type IImportProvidedOrGeneratedKeyArgs,
26
36
  JwkKeyUse,
27
- KeyTypeFromCryptographicSuiteArgs,
37
+ type KeyTypeFromCryptographicSuiteArgs,
28
38
  SIG_KEY_ALGS,
29
- SignatureAlgorithmFromKeyArgs,
30
- SignatureAlgorithmFromKeyTypeArgs,
31
- TKeyType,
39
+ type SignatureAlgorithmFromKeyArgs,
40
+ type SignatureAlgorithmFromKeyTypeArgs,
41
+ type TKeyType,
32
42
  } from './types'
33
43
 
44
+ const { fromString, toString } = u8a
45
+
34
46
  export const logger = Loggers.DEFAULT.get('sphereon:key-utils')
35
47
 
36
48
  /**
@@ -58,13 +70,13 @@ export const generatePrivateKeyHex = async (type: TKeyType): Promise<string> =>
58
70
  switch (type) {
59
71
  case 'Ed25519': {
60
72
  const keyPairEd25519 = generateSigningKeyPair()
61
- return u8a.toString(keyPairEd25519.secretKey, 'base16')
73
+ return toString(keyPairEd25519.secretKey, 'base16')
62
74
  }
63
75
  // The Secp256 types use the same method to generate the key
64
76
  case 'Secp256r1':
65
77
  case 'Secp256k1': {
66
78
  const privateBytes = randomBytes(32)
67
- return u8a.toString(privateBytes, 'base16')
79
+ return toString(privateBytes, 'base16')
68
80
  }
69
81
  case 'RSA': {
70
82
  const pem = await generateRSAKeyAsPEM('RSA-PSS', 'SHA-256', 2048)
@@ -173,7 +185,7 @@ const assertJwkClaimPresent = (value: unknown, description: string) => {
173
185
  throw new Error(`${description} missing or invalid`)
174
186
  }
175
187
  }
176
- export const toBase64url = (input: string): string => u8a.toString(u8a.fromString(input), 'base64url')
188
+ export const toBase64url = (input: string): string => toString(fromString(input), 'base64url')
177
189
 
178
190
  /**
179
191
  * Calculate the JWK thumbprint
@@ -294,17 +306,69 @@ export const jwkToRawHexKey = async (jwk: JWK): Promise<string> => {
294
306
  * @param jwk - The RSA JWK object.
295
307
  * @returns A string representing the RSA key in raw hexadecimal format.
296
308
  */
297
- function rsaJwkToRawHexKey(jwk: JsonWebKey): string {
309
+ export function rsaJwkToRawHexKey(jwk: JsonWebKey): string {
310
+ /**
311
+ * Encode an integer value (given as a Uint8Array) into DER INTEGER:
312
+ * 0x02 || length || value (with a leading 0x00 if the high bit is set).
313
+ */
314
+ function encodeInteger(bytes: Uint8Array): Uint8Array {
315
+ // if high bit set, prefix a 0x00
316
+ if (bytes[0] & 0x80) {
317
+ bytes = Uint8Array.from([0x00, ...bytes])
318
+ }
319
+ const len = encodeLength(bytes.length)
320
+ return Uint8Array.from([0x02, ...len, ...bytes])
321
+ }
322
+
323
+ /**
324
+ * Encode length per DER rules:
325
+ * - If <128, one byte
326
+ * - Else 0x80|numBytes followed by big-endian length
327
+ */
328
+ function encodeLength(len: any) {
329
+ if (len < 0x80) {
330
+ return Uint8Array.of(len)
331
+ }
332
+ let hex = len.toString(16)
333
+ if (hex.length % 2 === 1) {
334
+ hex = '0' + hex
335
+ }
336
+ const lenBytes = Uint8Array.from(hex.match(/.{2}/g)!.map((h: any) => parseInt(h, 16)))
337
+ return Uint8Array.of(0x80 | lenBytes.length, ...lenBytes)
338
+ }
339
+
340
+ /**
341
+ * Wrap one or more DER elements in a SEQUENCE:
342
+ * 0x30 || totalLength || concatenatedElements
343
+ */
344
+ function encodeSequence(elements: any) {
345
+ const content = elements.reduce((acc: any, elm: any) => Uint8Array.from([...acc, ...elm]), new Uint8Array())
346
+ const len = encodeLength(content.length)
347
+ return Uint8Array.from([0x30, ...len, ...content])
348
+ }
349
+
350
+ /**
351
+ * Convert a Base64-URL string into a Uint8Array (handles padding & “-_/”).
352
+ */
353
+ function base64UrlToBytes(b64url: string): Uint8Array {
354
+ return fromString(b64url, 'base64url')
355
+ }
356
+
298
357
  jwk = sanitizedJwk(jwk)
299
358
  if (!jwk.n || !jwk.e) {
300
359
  throw new Error("RSA JWK must contain 'n' and 'e' properties.")
301
360
  }
302
-
303
- // We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string
304
- const modulus = u8a.fromString(jwk.n.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url') // 'n' is the modulus
305
- const exponent = u8a.fromString(jwk.e.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url') // 'e' is the exponent
306
-
307
- return u8a.toString(modulus, 'hex') + u8a.toString(exponent, 'hex')
361
+ const modulusBytes = base64UrlToBytes(jwk.n)
362
+ const exponentBytes = base64UrlToBytes(jwk.e)
363
+ const sequence = encodeSequence([encodeInteger(modulusBytes), encodeInteger(exponentBytes)])
364
+ const result = toString(sequence, 'hex')
365
+ return result
366
+ /*
367
+ // We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string
368
+ const modulus = fromString(jwk.n.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url') // 'n' is the modulus
369
+ const exponent = fromString(jwk.e.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url') // 'e' is the exponent
370
+
371
+ return toString(modulus, 'hex') + toString(exponent, 'hex')*/
308
372
  }
309
373
 
310
374
  /**
@@ -319,10 +383,10 @@ function ecJwkToRawHexKey(jwk: JsonWebKey): string {
319
383
  }
320
384
 
321
385
  // We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string
322
- const x = u8a.fromString(jwk.x.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url')
323
- const y = u8a.fromString(jwk.y.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url')
386
+ const x = fromString(jwk.x.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url')
387
+ const y = fromString(jwk.y.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url')
324
388
 
325
- return '04' + u8a.toString(x, 'hex') + u8a.toString(y, 'hex')
389
+ return '04' + toString(x, 'hex') + toString(y, 'hex')
326
390
  }
327
391
 
328
392
  /**
@@ -337,9 +401,9 @@ function okpJwkToRawHexKey(jwk: JsonWebKey): string {
337
401
  }
338
402
 
339
403
  // We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string
340
- const x = u8a.fromString(jwk.x.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url')
404
+ const x = fromString(jwk.x.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url')
341
405
 
342
- return u8a.toString(x, 'hex')
406
+ return toString(x, 'hex')
343
407
  }
344
408
 
345
409
  /**
@@ -354,9 +418,9 @@ function octJwkToRawHexKey(jwk: JsonWebKey): string {
354
418
  }
355
419
 
356
420
  // We are converting from base64 to base64url to be sure. The spec uses base64url, but in the wild we sometimes encounter a base64 string
357
- const key = u8a.fromString(jwk.k.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url')
421
+ const key = fromString(jwk.k.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url')
358
422
 
359
- return u8a.toString(key, 'hex')
423
+ return toString(key, 'hex')
360
424
  }
361
425
 
362
426
  /**
@@ -411,7 +475,7 @@ const toSecp256k1Jwk = (keyHex: string, opts?: { use?: JwkKeyUse; isPrivateKey?:
411
475
  }
412
476
 
413
477
  const secp256k1 = new elliptic.ec('secp256k1')
414
- const keyBytes = u8a.fromString(keyHex, 'base16')
478
+ const keyBytes = fromString(keyHex, 'base16')
415
479
  const keyPair = opts?.isPrivateKey ? secp256k1.keyFromPrivate(keyBytes) : secp256k1.keyFromPublic(keyBytes)
416
480
  const pubPoint = keyPair.getPublic()
417
481
 
@@ -442,7 +506,7 @@ const toSecp256r1Jwk = (keyHex: string, opts?: { use?: JwkKeyUse; isPrivateKey?:
442
506
  }
443
507
 
444
508
  const secp256r1 = new elliptic.ec('p256')
445
- const keyBytes = u8a.fromString(keyHex, 'base16')
509
+ const keyBytes = fromString(keyHex, 'base16')
446
510
  logger.debug(`keyBytes length: ${keyBytes}`)
447
511
  const keyPair = opts?.isPrivateKey ? secp256r1.keyFromPrivate(keyBytes) : secp256r1.keyFromPublic(keyBytes)
448
512
  const pubPoint = keyPair.getPublic()
@@ -482,29 +546,104 @@ const toEd25519OrX25519Jwk = (
482
546
  }
483
547
 
484
548
  const toRSAJwk = (publicKeyHex: string, opts?: { use?: JwkKeyUse; key?: IKey | MinimalImportableKey }): JWK => {
549
+ function parseDerIntegers(pubKeyHex: string): { modulus: string; exponent: string } {
550
+ const bytes = Buffer.from(pubKeyHex, 'hex')
551
+ let offset = 0
552
+
553
+ // 1) Outer SEQUENCE
554
+ if (bytes[offset++] !== 0x30) throw new Error('Not a SEQUENCE')
555
+ let len = bytes[offset++]
556
+ if (len & 0x80) {
557
+ const nBytes = len & 0x7f
558
+ len = 0
559
+ for (let i = 0; i < nBytes; i++) {
560
+ len = (len << 8) + bytes[offset++]
561
+ }
562
+ }
563
+
564
+ // 2) Look at next tag: INTEGER(0x02) means raw PKCS#1,
565
+ // otherwise assume X.509/SPKI wrapper.
566
+ if (bytes[offset] !== 0x02) {
567
+ // --- skip AlgorithmIdentifier SEQUENCE ---
568
+ if (bytes[offset++] !== 0x30) throw new Error('Expected alg-ID SEQUENCE')
569
+ let algLen = bytes[offset++]
570
+ if (algLen & 0x80) {
571
+ const nB = algLen & 0x7f
572
+ algLen = 0
573
+ for (let i = 0; i < nB; i++) algLen = (algLen << 8) + bytes[offset++]
574
+ }
575
+ offset += algLen
576
+
577
+ // --- skip BIT STRING wrapper ---
578
+ if (bytes[offset++] !== 0x03) throw new Error('Expected BIT STRING')
579
+ let bitLen = bytes[offset++]
580
+ if (bitLen & 0x80) {
581
+ const nB = bitLen & 0x7f
582
+ bitLen = 0
583
+ for (let i = 0; i < nB; i++) bitLen = (bitLen << 8) + bytes[offset++]
584
+ }
585
+ // skip the “unused bits” byte
586
+ offset += 1
587
+
588
+ // now the next byte should be 0x30 for the inner SEQUENCE
589
+ if (bytes[offset++] !== 0x30) throw new Error('Expected inner SEQUENCE')
590
+ let innerLen = bytes[offset++]
591
+ if (innerLen & 0x80) {
592
+ const nB = innerLen & 0x7f
593
+ innerLen = 0
594
+ for (let i = 0; i < nB; i++) innerLen = (innerLen << 8) + bytes[offset++]
595
+ }
596
+ }
597
+
598
+ // 3) Parse modulus INTEGER
599
+ if (bytes[offset++] !== 0x02) throw new Error('Expected INTEGER for modulus')
600
+ let modLen = bytes[offset++]
601
+ if (modLen & 0x80) {
602
+ const nB = modLen & 0x7f
603
+ modLen = 0
604
+ for (let i = 0; i < nB; i++) modLen = (modLen << 8) + bytes[offset++]
605
+ }
606
+ let modulusBytes = bytes.slice(offset, offset + modLen)
607
+ offset += modLen
608
+
609
+ // strip leading zero if present (unsigned integer in JWK)
610
+ if (modulusBytes[0] === 0x00) {
611
+ modulusBytes = modulusBytes.slice(1)
612
+ }
613
+
614
+ // 4) Parse exponent INTEGER
615
+ if (bytes[offset++] !== 0x02) throw new Error('Expected INTEGER for exponent')
616
+ let expLen = bytes[offset++]
617
+ if (expLen & 0x80) {
618
+ const nB = expLen & 0x7f
619
+ expLen = 0
620
+ for (let i = 0; i < nB; i++) expLen = (expLen << 8) + bytes[offset++]
621
+ }
622
+ const exponentBytes = bytes.slice(offset, offset + expLen)
623
+
624
+ return {
625
+ modulus: modulusBytes.toString('hex'),
626
+ exponent: exponentBytes.toString('hex'),
627
+ }
628
+ }
629
+
485
630
  const meta = opts?.key?.meta
486
631
  if (meta?.publicKeyJwk || meta?.publicKeyPEM) {
487
632
  if (meta?.publicKeyJwk) {
488
633
  return meta.publicKeyJwk as JWK
489
634
  }
490
635
  const publicKeyPEM = meta?.publicKeyPEM ?? hexToPEM(publicKeyHex, 'public')
491
- return PEMToJwk(publicKeyPEM, 'public') as JWK
636
+ const jwk = PEMToJwk(publicKeyPEM, 'public') as JWK
637
+ return jwk
492
638
  }
493
639
 
494
- // exponent (e) is 5 chars long, rest is modulus (n)
495
- // const publicKey = publicKeyHex
496
- // assertProperKeyLength(publicKey, [2048, 3072, 4096])
497
- const exponent = publicKeyHex.slice(-5)
498
- const modulus = publicKeyHex.slice(0, -5)
499
- // const modulusBitLength = (modulus.length / 2) * 8
500
-
501
- // const alg = modulusBitLength === 2048 ? JoseSignatureAlgorithm.RS256 : modulusBitLength === 3072 ? JoseSignatureAlgorithm.RS384 : modulusBitLength === 4096 ? JoseSignatureAlgorithm.RS512 : undefined
502
- return sanitizedJwk({
640
+ const { modulus, exponent } = parseDerIntegers(publicKeyHex)
641
+ const sanitized = sanitizedJwk({
503
642
  kty: 'RSA',
504
643
  n: hexToBase64(modulus, 'base64url'),
505
644
  e: hexToBase64(exponent, 'base64url'),
506
- // ...(alg && { alg }),
507
645
  })
646
+ return sanitized
508
647
  }
509
648
 
510
649
  export const padLeft = (args: { data: string; size?: number; padString?: string }): string => {
@@ -621,16 +760,16 @@ export const toRawCompressedHexPublicKey = (rawPublicKey: Uint8Array, keyType: T
621
760
  logger.debug(`converted public key ${hexStringFromUint8Array(rawPublicKey)} to ${resultKey}`)
622
761
  return resultKey
623
762
  }
624
- return u8a.toString(rawPublicKey, 'base16')
763
+ return toString(rawPublicKey, 'base16')
625
764
  } else if (keyType === 'Ed25519') {
626
765
  // Ed25519 keys are always in compressed form
627
- return u8a.toString(rawPublicKey, 'base16')
766
+ return toString(rawPublicKey, 'base16')
628
767
  }
629
768
 
630
769
  throw new Error(`Unsupported key type: ${keyType}`)
631
770
  }
632
771
 
633
- export const hexStringFromUint8Array = (value: Uint8Array): string => u8a.toString(value, 'base16')
772
+ export const hexStringFromUint8Array = (value: Uint8Array): string => toString(value, 'base16')
634
773
 
635
774
  export const signatureAlgorithmFromKey = async (args: SignatureAlgorithmFromKeyArgs): Promise<JoseSignatureAlgorithm> => {
636
775
  const { key } = args
@@ -651,6 +790,8 @@ export const signatureAlgorithmFromKeyType = (args: SignatureAlgorithmFromKeyTyp
651
790
  return JoseSignatureAlgorithm.ES512
652
791
  case 'Secp256k1':
653
792
  return JoseSignatureAlgorithm.ES256K
793
+ case 'RSA':
794
+ return JoseSignatureAlgorithm.PS256
654
795
  default:
655
796
  throw new Error(`Key type '${type}' not supported`)
656
797
  }
@@ -692,6 +833,8 @@ export const keyTypeFromCryptographicSuite = (args: KeyTypeFromCryptographicSuit
692
833
  case 'EcdsaSecp256k1Signature2019':
693
834
  case 'secp256k1':
694
835
  case 'ES256K':
836
+ case 'EcdsaSecp256k1VerificationKey2019':
837
+ case 'EcdsaSecp256k1RecoveryMethod2020':
695
838
  return 'Secp256k1'
696
839
  }
697
840
  if (kty) {
@@ -717,10 +860,14 @@ export const globalCrypto = (setGlobal: boolean, suppliedCrypto?: Crypto): Crypt
717
860
  webcrypto = crypto
718
861
  } else if (typeof global.crypto !== 'undefined') {
719
862
  webcrypto = global.crypto
720
- } else if (typeof global.window?.crypto?.subtle !== 'undefined') {
721
- webcrypto = global.window.crypto
722
863
  } else {
723
- webcrypto = require('crypto') as Crypto
864
+ // @ts-ignore
865
+ if (typeof global.window?.crypto?.subtle !== 'undefined') {
866
+ // @ts-ignore
867
+ webcrypto = global.window.crypto
868
+ } else {
869
+ webcrypto = import('crypto') as Crypto
870
+ }
724
871
  }
725
872
  if (setGlobal) {
726
873
  global.crypto = webcrypto
@@ -730,7 +877,7 @@ export const globalCrypto = (setGlobal: boolean, suppliedCrypto?: Crypto): Crypt
730
877
  }
731
878
 
732
879
  export const sanitizedJwk = (input: JWK | JsonWebKey): JWK => {
733
- const inputJwk = typeof input['toJsonDTO'] === 'function' ? input['toJsonDTO']() : {...input} as JWK // KMP code can expose this. It converts a KMP JWK with mangled names into a clean JWK
880
+ const inputJwk = typeof input['toJsonDTO'] === 'function' ? input['toJsonDTO']() : ({ ...input } as JWK) // KMP code can expose this. It converts a KMP JWK with mangled names into a clean JWK
734
881
 
735
882
  const jwk = {
736
883
  ...inputJwk,
@@ -772,10 +919,10 @@ export async function verifyRawSignature({
772
919
  */
773
920
  function jwkPropertyToBigInt(jwkProp: string): bigint {
774
921
  // Decode Base64URL to Uint8Array
775
- const byteArray = u8a.fromString(jwkProp, 'base64url')
922
+ const byteArray = fromString(jwkProp, 'base64url')
776
923
 
777
924
  // Convert Uint8Array to hexadecimal string and then to BigInt
778
- const hex = u8a.toString(byteArray, 'hex')
925
+ const hex = toString(byteArray, 'hex')
779
926
  return BigInt(`0x${hex}`)
780
927
  }
781
928
 
@@ -797,12 +944,12 @@ export async function verifyRawSignature({
797
944
  case 'Secp521r1':
798
945
  return p521.verify(signature, data, publicKeyHex, { format: 'compact', prehash: true })
799
946
  case 'Ed25519':
800
- return ed25519.verify(signature, data, u8a.fromString(publicKeyHex, 'hex'))
947
+ return ed25519.verify(signature, data, fromString(publicKeyHex, 'hex'))
801
948
  case 'Bls12381G1':
802
949
  case 'Bls12381G2':
803
- return bls12_381.verify(signature, data, u8a.fromString(publicKeyHex, 'hex'))
950
+ return bls12_381.verify(signature, data, fromString(publicKeyHex, 'hex'))
804
951
  case 'RSA': {
805
- const signatureAlgorithm = opts?.signatureAlg ?? jwk.alg as JoseSignatureAlgorithm | undefined ?? JoseSignatureAlgorithm.PS256
952
+ const signatureAlgorithm = opts?.signatureAlg ?? (jwk.alg as JoseSignatureAlgorithm | undefined) ?? JoseSignatureAlgorithm.PS256
806
953
  const hashAlg =
807
954
  signatureAlgorithm === (JoseSignatureAlgorithm.RS512 || JoseSignatureAlgorithm.PS512)
808
955
  ? sha512
@@ -840,6 +987,15 @@ export async function verifyRawSignature({
840
987
  case JoseSignatureAlgorithm.PS256:
841
988
  case JoseSignatureAlgorithm.PS384:
842
989
  case JoseSignatureAlgorithm.PS512:
990
+ if (typeof crypto !== 'undefined' && typeof crypto.subtle !== 'undefined') {
991
+ const key = await cryptoSubtleImportRSAKey(jwk, 'RSA-PSS')
992
+ const saltLength =
993
+ signatureAlgorithm === JoseSignatureAlgorithm.PS256 ? 32 : signatureAlgorithm === JoseSignatureAlgorithm.PS384 ? 48 : 64
994
+ return crypto.subtle.verify({ name: 'rsa-pss', hash: hashAlg, saltLength }, key, signature, data)
995
+ }
996
+
997
+ // FIXME
998
+ console.warn(`Using fallback for RSA-PSS verify signature, which is known to be flaky!!`)
843
999
  return rsa.PSS(hashAlg, rsa.mgf1(hashAlg)).verify(
844
1000
  {
845
1001
  n: jwkPropertyToBigInt(jwk.n!),
@@ -858,3 +1014,93 @@ export async function verifyRawSignature({
858
1014
  throw error
859
1015
  }
860
1016
  }
1017
+
1018
+ /**
1019
+ * Minimal DER parser to unwrap X.509/SPKI‐wrapped RSA keys
1020
+ * into raw PKCS#1 RSAPublicKey format, using only Uint8Array.
1021
+ */
1022
+
1023
+ /**
1024
+ * Read a DER length at the given offset.
1025
+ * @param bytes – full DER buffer
1026
+ * @param offset – index of the length byte
1027
+ * @returns the parsed length, and how many bytes were used to encode it
1028
+ */
1029
+ function readLength(bytes: Uint8Array, offset: number): { length: number; lengthBytes: number } {
1030
+ const first = bytes[offset]
1031
+ if (first < 0x80) {
1032
+ return { length: first, lengthBytes: 1 }
1033
+ }
1034
+ const numBytes = first & 0x7f
1035
+ let length = 0
1036
+ for (let i = 0; i < numBytes; i++) {
1037
+ length = (length << 8) | bytes[offset + 1 + i]
1038
+ }
1039
+ return { length, lengthBytes: 1 + numBytes }
1040
+ }
1041
+
1042
+ /**
1043
+ * Ensure the given DER‐encoded RSA public key (Uint8Array)
1044
+ * is raw PKCS#1. If it's X.509/SPKI‐wrapped, we strip the wrapper.
1045
+ *
1046
+ * @param derBytes – DER‐encoded public key, either PKCS#1 or X.509/SPKI
1047
+ * @returns DER‐encoded PKCS#1 RSAPublicKey
1048
+ */
1049
+ export function toPkcs1(derBytes: Uint8Array): Uint8Array {
1050
+ if (derBytes[0] !== 0x30) {
1051
+ throw new Error('Invalid DER: expected SEQUENCE')
1052
+ }
1053
+
1054
+ // Parse outer SEQUENCE length
1055
+ const { lengthBytes: outerLenBytes } = readLength(derBytes, 1)
1056
+ const outerHeaderLen = 1 + outerLenBytes
1057
+ const innerTag = derBytes[outerHeaderLen]
1058
+
1059
+ // If next tag is INTEGER (0x02), it's already raw PKCS#1
1060
+ if (innerTag === 0x02) {
1061
+ return derBytes
1062
+ }
1063
+
1064
+ // Otherwise expect X.509/SPKI: SEQUENCE { algId, BIT STRING }
1065
+ if (innerTag !== 0x30) {
1066
+ throw new Error('Unexpected DER tag, not PKCS#1 or SPKI')
1067
+ }
1068
+
1069
+ // Skip the algId SEQUENCE
1070
+ const { length: algLen, lengthBytes: algLenBytes } = readLength(derBytes, outerHeaderLen + 1)
1071
+ const algHeaderLen = 1 + algLenBytes
1072
+ const algIdEnd = outerHeaderLen + algHeaderLen + algLen
1073
+
1074
+ // Next tag should be BIT STRING (0x03)
1075
+ if (derBytes[algIdEnd] !== 0x03) {
1076
+ throw new Error('Expected BIT STRING after algId')
1077
+ }
1078
+
1079
+ const { length: bitStrLen, lengthBytes: bitStrLenBytes } = readLength(derBytes, algIdEnd + 1)
1080
+ const bitStrHeaderLen = 1 + bitStrLenBytes
1081
+ const bitStrStart = algIdEnd + bitStrHeaderLen
1082
+
1083
+ // First byte of the BIT STRING is the "unused bits" count; usually 0x00
1084
+ const unusedBits = derBytes[bitStrStart]
1085
+ if (unusedBits !== 0x00) {
1086
+ throw new Error(`Unexpected unused bits: ${unusedBits}`)
1087
+ }
1088
+
1089
+ // The rest is the PKCS#1 DER
1090
+ const pkcs1Start = bitStrStart + 1
1091
+ const pkcs1Len = bitStrLen - 1
1092
+
1093
+ return derBytes.slice(pkcs1Start, pkcs1Start + pkcs1Len)
1094
+ }
1095
+
1096
+ /**
1097
+ * Ensure the given DER‐encoded RSA public key in Hex
1098
+ * is raw PKCS#1. If it's X.509/SPKI‐wrapped, we strip the wrapper.
1099
+ *
1100
+ * @param derBytes – DER‐encoded public key, either PKCS#1 or X.509/SPKI
1101
+ * @returns DER‐encoded PKCS#1 RSAPublicKey in hex
1102
+ */
1103
+ export function toPkcs1FromHex(publicKeyHex: string) {
1104
+ const pkcs1 = toPkcs1(fromString(publicKeyHex, 'hex'))
1105
+ return toString(pkcs1, 'hex')
1106
+ }
package/src/jwk-jcs.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { JWK } from '@sphereon/ssi-types'
1
+ import { JsonWebKey, JWK } from '@sphereon/ssi-types'
2
+ // @ts-ignore
2
3
  import type { ByteView } from 'multiformats/codecs/interface'
4
+ // @ts-ignore
3
5
  import { TextDecoder, TextEncoder } from 'web-encoding'
4
6
 
5
7
  const textEncoder = new TextEncoder()
@@ -1,4 +1,4 @@
1
- import { IKey, MinimalImportableKey } from '@veramo/core'
1
+ import type { IKey, MinimalImportableKey } from '@veramo/core'
2
2
 
3
3
  export const JWK_JCS_PUB_NAME = 'jwk_jcs-pub' as const
4
4
  export const JWK_JCS_PUB_PREFIX = 0xeb51
@@ -1,12 +0,0 @@
1
- import { ICoseCurve, ICoseKeyJson, ICoseKeyOperation, ICoseKeyType, ICoseSignatureAlgorithm, JoseCurve, JoseCurveString, JoseKeyOperation, JoseKeyOperationString, JoseSignatureAlgorithm, JoseSignatureAlgorithmString, JWK, JwkKeyType, JwkKeyTypeString } from '@sphereon/ssi-types';
2
- export declare function coseKeyToJwk(coseKey: ICoseKeyJson): JWK;
3
- export declare function jwkToCoseKey(jwk: JWK): ICoseKeyJson;
4
- export declare function coseToJoseKty(kty: ICoseKeyType): JwkKeyType;
5
- export declare function joseToCoseKty(kty: JwkKeyType | JwkKeyTypeString): ICoseKeyType;
6
- export declare function coseToJoseSignatureAlg(coseAlg: ICoseSignatureAlgorithm): JoseSignatureAlgorithm;
7
- export declare function joseToCoseSignatureAlg(joseAlg: JoseSignatureAlgorithm | JoseSignatureAlgorithmString): ICoseSignatureAlgorithm;
8
- export declare function joseToCoseKeyOperation(keyOp: JoseKeyOperation | JoseKeyOperationString): ICoseKeyOperation;
9
- export declare function coseToJoseKeyOperation(keyOp: ICoseKeyOperation): JoseKeyOperation;
10
- export declare function joseToCoseCurve(curve: JoseCurve | JoseCurveString): ICoseCurve;
11
- export declare function coseToJoseCurve(curve: ICoseCurve): JoseCurve;
12
- //# sourceMappingURL=conversion.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"conversion.d.ts","sourceRoot":"","sources":["../src/conversion.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,uBAAuB,EACvB,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,sBAAsB,EACtB,4BAA4B,EAC5B,GAAG,EACH,UAAU,EACV,gBAAgB,EACjB,MAAM,qBAAqB,CAAA;AAG5B,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,GAAG,CAWvD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,YAAY,CAWnD;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,YAAY,GAAG,UAAU,CAa3D;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,gBAAgB,GAAG,YAAY,CAa9E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CA2B/F;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,sBAAsB,GAAG,4BAA4B,GAAG,uBAAuB,CA2B9H;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,sBAAsB,GAAG,iBAAiB,CAqB1G;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,iBAAiB,GAAG,gBAAgB,CAqBjF;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,eAAe,GAAG,UAAU,CAqB9E;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAqB5D"}