@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/dist/index.cjs +1434 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +231 -0
- package/dist/index.d.ts +228 -9
- package/dist/index.js +1401 -26
- package/dist/index.js.map +1 -1
- package/package.json +26 -13
- package/src/conversion.ts +8 -6
- package/src/digest-methods.ts +12 -11
- package/src/functions.ts +299 -53
- package/src/jwk-jcs.ts +3 -1
- package/src/types/key-util-types.ts +1 -1
- package/dist/conversion.d.ts +0 -12
- package/dist/conversion.d.ts.map +0 -1
- package/dist/conversion.js +0 -206
- package/dist/conversion.js.map +0 -1
- package/dist/digest-methods.d.ts +0 -11
- package/dist/digest-methods.d.ts.map +0 -1
- package/dist/digest-methods.js +0 -106
- package/dist/digest-methods.js.map +0 -1
- package/dist/functions.d.ts +0 -100
- package/dist/functions.d.ts.map +0 -1
- package/dist/functions.js +0 -756
- package/dist/functions.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/jwk-jcs.d.ts +0 -41
- package/dist/jwk-jcs.d.ts.map +0 -1
- package/dist/jwk-jcs.js +0 -182
- package/dist/jwk-jcs.js.map +0 -1
- package/dist/types/index.d.ts +0 -2
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -18
- package/dist/types/index.js.map +0 -1
- package/dist/types/key-util-types.d.ts +0 -46
- package/dist/types/key-util-types.d.ts.map +0 -1
- package/dist/types/key-util-types.js +0 -19
- package/dist/types/key-util-types.js.map +0 -1
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/
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
|
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 =>
|
|
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
|
-
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
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 =
|
|
323
|
-
const y =
|
|
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' +
|
|
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 =
|
|
404
|
+
const x = fromString(jwk.x.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url')
|
|
341
405
|
|
|
342
|
-
return
|
|
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 =
|
|
421
|
+
const key = fromString(jwk.k.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''), 'base64url')
|
|
358
422
|
|
|
359
|
-
return
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
636
|
+
const jwk = PEMToJwk(publicKeyPEM, 'public') as JWK
|
|
637
|
+
return jwk
|
|
492
638
|
}
|
|
493
639
|
|
|
494
|
-
|
|
495
|
-
|
|
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
|
|
763
|
+
return toString(rawPublicKey, 'base16')
|
|
625
764
|
} else if (keyType === 'Ed25519') {
|
|
626
765
|
// Ed25519 keys are always in compressed form
|
|
627
|
-
return
|
|
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 =>
|
|
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
|
-
|
|
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 =
|
|
922
|
+
const byteArray = fromString(jwkProp, 'base64url')
|
|
776
923
|
|
|
777
924
|
// Convert Uint8Array to hexadecimal string and then to BigInt
|
|
778
|
-
const 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,
|
|
947
|
+
return ed25519.verify(signature, data, fromString(publicKeyHex, 'hex'))
|
|
801
948
|
case 'Bls12381G1':
|
|
802
949
|
case 'Bls12381G2':
|
|
803
|
-
return bls12_381.verify(signature, data,
|
|
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()
|
package/dist/conversion.d.ts
DELETED
|
@@ -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
|
package/dist/conversion.d.ts.map
DELETED
|
@@ -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"}
|