@sphereon/ssi-sdk-ext.key-utils 0.36.1-next.11 → 0.36.1-next.113

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.
@@ -3,25 +3,27 @@ import { sha384, sha512 } from '@noble/hashes/sha512'
3
3
  import type { HasherSync } from '@sphereon/ssi-types'
4
4
  // @ts-ignore
5
5
  import * as u8a from 'uint8arrays'
6
+ import { normalizeHashAlgorithm } from './functions'
7
+ import { DigestAlgorithm } from './types'
6
8
  const { fromString, toString, SupportedEncodings } = u8a
7
9
 
8
- export type HashAlgorithm = 'SHA-256' | 'SHA-384' | 'SHA-512'
9
10
  export type TDigestMethod = (input: string, encoding?: typeof SupportedEncodings) => string
10
11
 
11
12
  export const digestMethodParams = (
12
- hashAlgorithm: HashAlgorithm,
13
- ): { hashAlgorithm: HashAlgorithm; digestMethod: TDigestMethod; hash: (data: Uint8Array) => Uint8Array } => {
14
- if (hashAlgorithm === 'SHA-256') {
15
- return { hashAlgorithm: 'SHA-256', digestMethod: sha256DigestMethod, hash: sha256 }
16
- } else if (hashAlgorithm === 'SHA-384') {
17
- return { hashAlgorithm: 'SHA-384', digestMethod: sha384DigestMethod, hash: sha384 }
18
- } else {
19
- return { hashAlgorithm: 'SHA-512', digestMethod: sha512DigestMethod, hash: sha512 }
13
+ hashAlgorithm: DigestAlgorithm,
14
+ ): { hashAlgorithm: DigestAlgorithm; digestMethod: TDigestMethod; hash: (data: Uint8Array) => Uint8Array } => {
15
+ switch (normalizeHashAlgorithm(hashAlgorithm)) {
16
+ case 'SHA-256':
17
+ return { hashAlgorithm: 'SHA-256', digestMethod: sha256DigestMethod, hash: sha256 }
18
+ case 'SHA-384':
19
+ return { hashAlgorithm: 'SHA-384', digestMethod: sha384DigestMethod, hash: sha384 }
20
+ case 'SHA-512':
21
+ return { hashAlgorithm: 'SHA-512', digestMethod: sha512DigestMethod, hash: sha512 }
20
22
  }
21
23
  }
22
24
 
23
- export const shaHasher: HasherSync = (input: string | ArrayBuffer, alg: string): Uint8Array => {
24
- const hashAlgorithm: HashAlgorithm = alg.includes('384') ? 'SHA-384' : alg.includes('512') ? 'SHA-512' : 'SHA-256'
25
+ export const shaHasher: HasherSync = (input: string | ArrayBuffer | SharedArrayBuffer, alg: string): Uint8Array => {
26
+ const hashAlgorithm: DigestAlgorithm = alg.includes('384') ? 'SHA-384' : alg.includes('512') ? 'SHA-512' : 'SHA-256'
25
27
  return digestMethodParams(hashAlgorithm).hash(typeof input === 'string' ? fromString(input, 'utf-8') : new Uint8Array(input))
26
28
  }
27
29
 
package/src/functions.ts CHANGED
@@ -31,6 +31,7 @@ import * as u8a from 'uint8arrays'
31
31
  import { digestMethodParams } from './digest-methods'
32
32
  import { validateJwk } from './jwk-jcs'
33
33
  import {
34
+ DigestAlgorithm,
34
35
  ENC_KEY_ALGS,
35
36
  type IImportProvidedOrGeneratedKeyArgs,
36
37
  JwkKeyUse,
@@ -198,8 +199,8 @@ export const toBase64url = (input: string): string => toString(fromString(input)
198
199
  * Calculate the JWK thumbprint
199
200
  * @param args
200
201
  */
201
- export const calculateJwkThumbprint = (args: { jwk: JWK; digestAlgorithm?: 'sha256' | 'sha512' }): string => {
202
- const { digestAlgorithm = 'sha256' } = args
202
+ export const calculateJwkThumbprint = (args: { jwk: JWK; digestAlgorithm?: DigestAlgorithm }): string => {
203
+ const digestAlgorithm = normalizeHashAlgorithm(args.digestAlgorithm ?? 'SHA-256')
203
204
  const jwk = sanitizedJwk(args.jwk)
204
205
  let components
205
206
  switch (jwk.kty) {
@@ -227,10 +228,7 @@ export const calculateJwkThumbprint = (args: { jwk: JWK; digestAlgorithm?: 'sha2
227
228
  throw new Error('"kty" (Key Type) Parameter missing or unsupported')
228
229
  }
229
230
  const data = JSON.stringify(components)
230
-
231
- return digestAlgorithm === 'sha512'
232
- ? digestMethodParams('SHA-512').digestMethod(data, 'base64url')
233
- : digestMethodParams('SHA-256').digestMethod(data, 'base64url')
231
+ return digestMethodParams(digestAlgorithm).digestMethod(data, 'base64url')
234
232
  }
235
233
 
236
234
  export const toJwkFromKey = (
@@ -791,11 +789,49 @@ export const hexStringFromUint8Array = (value: Uint8Array): string => toString(v
791
789
 
792
790
  export const signatureAlgorithmFromKey = async (args: SignatureAlgorithmFromKeyArgs): Promise<JoseSignatureAlgorithm> => {
793
791
  const { key } = args
794
- return signatureAlgorithmFromKeyType({ type: key.type })
792
+ return signatureAlgorithmFromKeyType({ type: key.type, algorithms: key.meta?.algorithms })
793
+ }
794
+
795
+ export function signatureAlgorithmToJoseAlgorithm(alg: string): JoseSignatureAlgorithm {
796
+ switch (alg) {
797
+ case 'RSA_SHA256':
798
+ return JoseSignatureAlgorithm.RS256
799
+ case 'RSA_SHA384':
800
+ return JoseSignatureAlgorithm.RS384
801
+ case 'RSA_SHA512':
802
+ return JoseSignatureAlgorithm.RS512
803
+ case 'RSA_SSA_PSS_SHA256_MGF1':
804
+ return JoseSignatureAlgorithm.PS256
805
+ case 'RSA_SSA_PSS_SHA384_MGF1':
806
+ return JoseSignatureAlgorithm.PS384
807
+ case 'RSA_SSA_PSS_SHA512_MGF1':
808
+ return JoseSignatureAlgorithm.PS512
809
+ case 'ECDSA_SHA256':
810
+ return JoseSignatureAlgorithm.ES256
811
+ case 'ECDSA_SHA384':
812
+ return JoseSignatureAlgorithm.ES384
813
+ case 'ECDSA_SHA512':
814
+ return JoseSignatureAlgorithm.ES512
815
+ case 'ES256K':
816
+ return JoseSignatureAlgorithm.ES256K
817
+ case 'ED25519':
818
+ case 'EdDSA':
819
+ return JoseSignatureAlgorithm.EdDSA
820
+ default:
821
+ // If already in JOSE format, return as-is
822
+ return alg as JoseSignatureAlgorithm
823
+ }
795
824
  }
796
825
 
797
826
  export const signatureAlgorithmFromKeyType = (args: SignatureAlgorithmFromKeyTypeArgs): JoseSignatureAlgorithm => {
798
- const { type } = args
827
+ const { type, algorithms } = args
828
+
829
+ // If algorithms metadata is provided, use the first one
830
+ if (algorithms && algorithms.length > 0) {
831
+ return signatureAlgorithmToJoseAlgorithm(algorithms[0])
832
+ }
833
+
834
+ // Fallback to type-based defaults
799
835
  switch (type) {
800
836
  case 'Ed25519':
801
837
  case 'X25519':
@@ -809,7 +845,7 @@ export const signatureAlgorithmFromKeyType = (args: SignatureAlgorithmFromKeyTyp
809
845
  case 'Secp256k1':
810
846
  return JoseSignatureAlgorithm.ES256K
811
847
  case 'RSA':
812
- return JoseSignatureAlgorithm.PS256
848
+ return JoseSignatureAlgorithm.RS256 // Default to RS256 instead of PS256
813
849
  default:
814
850
  throw new Error(`Key type '${type}' not supported`)
815
851
  }
@@ -910,7 +946,7 @@ export const sanitizedJwk = (input: JWK | JsonWebKey): JWK => {
910
946
  return removeNulls(jwk)
911
947
  }
912
948
 
913
- const base64ToBase64Url = (input: string): string => {
949
+ export const base64ToBase64Url = (input: string): string => {
914
950
  return input.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
915
951
  }
916
952
 
@@ -1122,3 +1158,91 @@ export function toPkcs1FromHex(publicKeyHex: string) {
1122
1158
  const pkcs1 = toPkcs1(fromString(publicKeyHex, 'hex'))
1123
1159
  return toString(pkcs1, 'hex')
1124
1160
  }
1161
+
1162
+ export function joseAlgorithmToDigest(alg: string): DigestAlgorithm {
1163
+ // Normalize the algorithm string by converting to uppercase and removing hyphens
1164
+ const normalized = alg.toUpperCase().replace(/-/g, '')
1165
+
1166
+ switch (normalized) {
1167
+ case 'RS256':
1168
+ case 'ES256':
1169
+ case 'ES256K':
1170
+ case 'PS256':
1171
+ case 'HS256':
1172
+ return 'SHA-256'
1173
+ case 'RS384':
1174
+ case 'ES384':
1175
+ case 'PS384':
1176
+ case 'HS384':
1177
+ return 'SHA-384'
1178
+ case 'RS512':
1179
+ case 'ES512':
1180
+ case 'PS512':
1181
+ case 'HS512':
1182
+ return 'SHA-512'
1183
+ case 'EDDSA':
1184
+ case 'ED25519':
1185
+ return 'SHA-512'
1186
+ default:
1187
+ throw new Error(`Unsupported JOSE algorithm: ${alg}. Cannot determine digest algorithm.`)
1188
+ }
1189
+ }
1190
+
1191
+ export function isHash(input: string): boolean {
1192
+ const length = input.length
1193
+ // SHA-256: 64 hex chars, SHA-384: 96 hex chars, SHA-512: 128 hex chars
1194
+ if (length !== 64 && length !== 96 && length !== 128) {
1195
+ return false
1196
+ }
1197
+ return input.match(/^([0-9A-Fa-f])+$/g) !== null
1198
+ }
1199
+
1200
+ export function isHashString(input: Uint8Array): boolean {
1201
+ const length = input.length
1202
+ // SHA-256: 32 bytes, SHA-384: 48 bytes, SHA-512: 64 bytes
1203
+ if (length !== 32 && length !== 48 && length !== 64) {
1204
+ return false
1205
+ }
1206
+
1207
+ // A hash digest is raw binary data (any byte values 0x00-0xFF are valid).
1208
+ // We should NOT check if bytes are ASCII hex characters, as that would only detect
1209
+ // hex-encoded strings, not actual binary hash digests.
1210
+ // Instead, we use a heuristic: if the data looks like it has high entropy
1211
+ // and is the right length, we assume it's already a hash.
1212
+
1213
+ // Simple heuristic: Check if data is all printable ASCII (which would indicate it's NOT a hash)
1214
+ // Printable ASCII is roughly 0x20-0x7E
1215
+ let printableCount = 0
1216
+ for (let i = 0; i < length; i++) {
1217
+ const byte = input[i]
1218
+ if (byte === undefined) {
1219
+ return false
1220
+ }
1221
+ // Count printable ASCII characters
1222
+ if (byte >= 0x20 && byte <= 0x7e) {
1223
+ printableCount++
1224
+ }
1225
+ }
1226
+
1227
+ // If more than 90% of bytes are printable ASCII, it's likely NOT a raw binary hash
1228
+ // Raw binary hashes should have a more uniform distribution across all byte values
1229
+ const printableRatio = printableCount / length
1230
+ return printableRatio < 0.9
1231
+ }
1232
+
1233
+ export type HashAlgorithm = 'SHA-256' | 'sha256' | 'SHA-384' | 'sha384' | 'SHA-512' | 'sha512'
1234
+
1235
+ export function normalizeHashAlgorithm(alg?: HashAlgorithm): 'SHA-256' | 'SHA-384' | 'SHA-512' {
1236
+ if (!alg) {
1237
+ return 'SHA-256'
1238
+ }
1239
+ const upper = alg.toUpperCase()
1240
+ if (upper.includes('256')) return 'SHA-256'
1241
+ if (upper.includes('384')) return 'SHA-384'
1242
+ if (upper.includes('512')) return 'SHA-512'
1243
+ throw new Error(`Invalid hash algorithm: ${alg}`)
1244
+ }
1245
+
1246
+ export function isSameHash(left: HashAlgorithm, right: HashAlgorithm): boolean {
1247
+ return normalizeHashAlgorithm(left) === normalizeHashAlgorithm(right)
1248
+ }
@@ -21,6 +21,8 @@ export const ENC_KEY_ALGS = ['X25519', 'ECDH_ES_A256KW', 'RSA_OAEP_256']
21
21
 
22
22
  export type KeyVisibility = 'public' | 'private'
23
23
 
24
+ export type DigestAlgorithm = 'SHA-256' | 'sha256' | 'SHA-384' | 'sha384' | 'SHA-512' | 'sha512'
25
+
24
26
  export interface X509Opts {
25
27
  cn?: string // The certificate Common Name. Will be used as the KID for the private key. Uses alias if not provided.
26
28
  privateKeyPEM?: string // Optional as you also need to provide it in hex format, but advisable to use it
@@ -53,6 +55,7 @@ export type SignatureAlgorithmFromKeyArgs = {
53
55
 
54
56
  export type SignatureAlgorithmFromKeyTypeArgs = {
55
57
  type: TKeyType
58
+ algorithms?: string[]
56
59
  }
57
60
 
58
61
  export type KeyTypeFromCryptographicSuiteArgs = {