@opentdf/sdk 0.9.0-beta.92 → 0.9.0-beta.94

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.
Files changed (187) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/src/access/access-fetch.js +1 -2
  3. package/dist/cjs/src/access/access-rpc.js +1 -3
  4. package/dist/cjs/src/access.js +1 -14
  5. package/dist/cjs/src/auth/auth.js +13 -10
  6. package/dist/cjs/src/auth/dpop.js +121 -0
  7. package/dist/cjs/src/auth/oidc-clientcredentials-provider.js +37 -3
  8. package/dist/cjs/src/auth/oidc-externaljwt-provider.js +37 -3
  9. package/dist/cjs/src/auth/oidc-refreshtoken-provider.js +37 -3
  10. package/dist/cjs/src/auth/oidc.js +10 -8
  11. package/dist/cjs/src/auth/providers.js +35 -12
  12. package/dist/cjs/src/crypto/index.js +16 -2
  13. package/dist/cjs/src/crypto/pemPublicToCrypto.js +17 -11
  14. package/dist/cjs/src/opentdf.js +50 -13
  15. package/dist/cjs/src/policy/discovery.js +2 -2
  16. package/dist/cjs/tdf3/index.js +4 -2
  17. package/dist/cjs/tdf3/src/assertions.js +71 -31
  18. package/dist/cjs/tdf3/src/ciphers/aes-gcm-cipher.js +1 -1
  19. package/dist/cjs/tdf3/src/ciphers/symmetric-cipher-base.js +4 -2
  20. package/dist/cjs/tdf3/src/client/index.js +23 -33
  21. package/dist/cjs/tdf3/src/crypto/crypto-utils.js +12 -5
  22. package/dist/cjs/tdf3/src/crypto/declarations.js +1 -1
  23. package/dist/cjs/tdf3/src/crypto/index.js +849 -88
  24. package/dist/cjs/tdf3/src/crypto/jose/jwt-claims-set.js +11 -0
  25. package/dist/cjs/tdf3/src/crypto/jose/validate-crit.js +8 -0
  26. package/dist/cjs/tdf3/src/crypto/jose/vendor/lib/buffer_utils.js +41 -0
  27. package/dist/cjs/tdf3/src/crypto/jose/vendor/lib/epoch.js +6 -0
  28. package/dist/cjs/tdf3/src/crypto/jose/vendor/lib/is_object.js +21 -0
  29. package/dist/cjs/tdf3/src/crypto/jose/vendor/lib/jwt_claims_set.js +112 -0
  30. package/dist/cjs/tdf3/src/crypto/jose/vendor/lib/secs.js +60 -0
  31. package/dist/cjs/tdf3/src/crypto/jose/vendor/lib/validate_crit.js +38 -0
  32. package/dist/cjs/tdf3/src/crypto/jose/vendor/util/errors.js +135 -0
  33. package/dist/cjs/tdf3/src/crypto/jwt.js +183 -0
  34. package/dist/cjs/tdf3/src/crypto/salt.js +14 -8
  35. package/dist/cjs/tdf3/src/models/encryption-information.js +17 -20
  36. package/dist/cjs/tdf3/src/models/key-access.js +43 -63
  37. package/dist/cjs/tdf3/src/tdf.js +75 -75
  38. package/dist/cjs/tdf3/src/utils/index.js +5 -39
  39. package/dist/types/src/access/access-fetch.d.ts.map +1 -1
  40. package/dist/types/src/access/access-rpc.d.ts.map +1 -1
  41. package/dist/types/src/access.d.ts +0 -5
  42. package/dist/types/src/access.d.ts.map +1 -1
  43. package/dist/types/src/auth/auth.d.ts +9 -6
  44. package/dist/types/src/auth/auth.d.ts.map +1 -1
  45. package/dist/types/src/auth/dpop.d.ts +60 -0
  46. package/dist/types/src/auth/dpop.d.ts.map +1 -0
  47. package/dist/types/src/auth/oidc-clientcredentials-provider.d.ts +3 -2
  48. package/dist/types/src/auth/oidc-clientcredentials-provider.d.ts.map +1 -1
  49. package/dist/types/src/auth/oidc-externaljwt-provider.d.ts +3 -2
  50. package/dist/types/src/auth/oidc-externaljwt-provider.d.ts.map +1 -1
  51. package/dist/types/src/auth/oidc-refreshtoken-provider.d.ts +3 -2
  52. package/dist/types/src/auth/oidc-refreshtoken-provider.d.ts.map +1 -1
  53. package/dist/types/src/auth/oidc.d.ts +6 -4
  54. package/dist/types/src/auth/oidc.d.ts.map +1 -1
  55. package/dist/types/src/auth/providers.d.ts +5 -4
  56. package/dist/types/src/auth/providers.d.ts.map +1 -1
  57. package/dist/types/src/crypto/index.d.ts +2 -1
  58. package/dist/types/src/crypto/index.d.ts.map +1 -1
  59. package/dist/types/src/crypto/pemPublicToCrypto.d.ts +18 -0
  60. package/dist/types/src/crypto/pemPublicToCrypto.d.ts.map +1 -1
  61. package/dist/types/src/opentdf.d.ts +26 -7
  62. package/dist/types/src/opentdf.d.ts.map +1 -1
  63. package/dist/types/src/policy/discovery.d.ts +2 -2
  64. package/dist/types/tdf3/index.d.ts +3 -3
  65. package/dist/types/tdf3/index.d.ts.map +1 -1
  66. package/dist/types/tdf3/src/assertions.d.ts +23 -8
  67. package/dist/types/tdf3/src/assertions.d.ts.map +1 -1
  68. package/dist/types/tdf3/src/ciphers/aes-gcm-cipher.d.ts +3 -3
  69. package/dist/types/tdf3/src/ciphers/aes-gcm-cipher.d.ts.map +1 -1
  70. package/dist/types/tdf3/src/ciphers/symmetric-cipher-base.d.ts +4 -4
  71. package/dist/types/tdf3/src/ciphers/symmetric-cipher-base.d.ts.map +1 -1
  72. package/dist/types/tdf3/src/client/builders.d.ts +2 -2
  73. package/dist/types/tdf3/src/client/builders.d.ts.map +1 -1
  74. package/dist/types/tdf3/src/client/index.d.ts +6 -5
  75. package/dist/types/tdf3/src/client/index.d.ts.map +1 -1
  76. package/dist/types/tdf3/src/crypto/crypto-utils.d.ts +14 -4
  77. package/dist/types/tdf3/src/crypto/crypto-utils.d.ts.map +1 -1
  78. package/dist/types/tdf3/src/crypto/declarations.d.ts +283 -18
  79. package/dist/types/tdf3/src/crypto/declarations.d.ts.map +1 -1
  80. package/dist/types/tdf3/src/crypto/index.d.ts +105 -28
  81. package/dist/types/tdf3/src/crypto/index.d.ts.map +1 -1
  82. package/dist/types/tdf3/src/crypto/jose/jwt-claims-set.d.ts +3 -0
  83. package/dist/types/tdf3/src/crypto/jose/jwt-claims-set.d.ts.map +1 -0
  84. package/dist/types/tdf3/src/crypto/jose/validate-crit.d.ts +5 -0
  85. package/dist/types/tdf3/src/crypto/jose/validate-crit.d.ts.map +1 -0
  86. package/dist/types/tdf3/src/crypto/jose/vendor/lib/buffer_utils.d.ts +6 -0
  87. package/dist/types/tdf3/src/crypto/jose/vendor/lib/buffer_utils.d.ts.map +1 -0
  88. package/dist/types/tdf3/src/crypto/jose/vendor/lib/epoch.d.ts +3 -0
  89. package/dist/types/tdf3/src/crypto/jose/vendor/lib/epoch.d.ts.map +1 -0
  90. package/dist/types/tdf3/src/crypto/jose/vendor/lib/is_object.d.ts +3 -0
  91. package/dist/types/tdf3/src/crypto/jose/vendor/lib/is_object.d.ts.map +1 -0
  92. package/dist/types/tdf3/src/crypto/jose/vendor/lib/jwt_claims_set.d.ts +3 -0
  93. package/dist/types/tdf3/src/crypto/jose/vendor/lib/jwt_claims_set.d.ts.map +1 -0
  94. package/dist/types/tdf3/src/crypto/jose/vendor/lib/secs.d.ts +3 -0
  95. package/dist/types/tdf3/src/crypto/jose/vendor/lib/secs.d.ts.map +1 -0
  96. package/dist/types/tdf3/src/crypto/jose/vendor/lib/validate_crit.d.ts +3 -0
  97. package/dist/types/tdf3/src/crypto/jose/vendor/lib/validate_crit.d.ts.map +1 -0
  98. package/dist/types/tdf3/src/crypto/jose/vendor/util/errors.d.ts +76 -0
  99. package/dist/types/tdf3/src/crypto/jose/vendor/util/errors.d.ts.map +1 -0
  100. package/dist/types/tdf3/src/crypto/jwt.d.ts +76 -0
  101. package/dist/types/tdf3/src/crypto/jwt.d.ts.map +1 -0
  102. package/dist/types/tdf3/src/crypto/salt.d.ts +6 -1
  103. package/dist/types/tdf3/src/crypto/salt.d.ts.map +1 -1
  104. package/dist/types/tdf3/src/models/encryption-information.d.ts +4 -4
  105. package/dist/types/tdf3/src/models/encryption-information.d.ts.map +1 -1
  106. package/dist/types/tdf3/src/models/key-access.d.ts +8 -5
  107. package/dist/types/tdf3/src/models/key-access.d.ts.map +1 -1
  108. package/dist/types/tdf3/src/tdf.d.ts +8 -8
  109. package/dist/types/tdf3/src/tdf.d.ts.map +1 -1
  110. package/dist/types/tdf3/src/utils/index.d.ts +4 -3
  111. package/dist/types/tdf3/src/utils/index.d.ts.map +1 -1
  112. package/dist/web/src/access/access-fetch.js +3 -4
  113. package/dist/web/src/access/access-rpc.js +3 -5
  114. package/dist/web/src/access.js +1 -13
  115. package/dist/web/src/auth/auth.js +13 -10
  116. package/dist/web/src/auth/dpop.js +118 -0
  117. package/dist/web/src/auth/oidc-clientcredentials-provider.js +4 -3
  118. package/dist/web/src/auth/oidc-externaljwt-provider.js +4 -3
  119. package/dist/web/src/auth/oidc-refreshtoken-provider.js +4 -3
  120. package/dist/web/src/auth/oidc.js +11 -9
  121. package/dist/web/src/auth/providers.js +13 -12
  122. package/dist/web/src/crypto/index.js +4 -2
  123. package/dist/web/src/crypto/pemPublicToCrypto.js +11 -9
  124. package/dist/web/src/opentdf.js +17 -13
  125. package/dist/web/src/policy/discovery.js +2 -2
  126. package/dist/web/tdf3/index.js +3 -2
  127. package/dist/web/tdf3/src/assertions.js +71 -31
  128. package/dist/web/tdf3/src/ciphers/aes-gcm-cipher.js +1 -1
  129. package/dist/web/tdf3/src/ciphers/symmetric-cipher-base.js +4 -2
  130. package/dist/web/tdf3/src/client/index.js +25 -35
  131. package/dist/web/tdf3/src/crypto/crypto-utils.js +12 -5
  132. package/dist/web/tdf3/src/crypto/declarations.js +1 -1
  133. package/dist/web/tdf3/src/crypto/index.js +830 -84
  134. package/dist/web/tdf3/src/crypto/jose/jwt-claims-set.js +5 -0
  135. package/dist/web/tdf3/src/crypto/jose/validate-crit.js +3 -0
  136. package/dist/web/tdf3/src/crypto/jose/vendor/lib/buffer_utils.js +35 -0
  137. package/dist/web/tdf3/src/crypto/jose/vendor/lib/epoch.js +4 -0
  138. package/dist/web/tdf3/src/crypto/jose/vendor/lib/is_object.js +19 -0
  139. package/dist/web/tdf3/src/crypto/jose/vendor/lib/jwt_claims_set.js +107 -0
  140. package/dist/web/tdf3/src/crypto/jose/vendor/lib/secs.js +58 -0
  141. package/dist/web/tdf3/src/crypto/jose/vendor/lib/validate_crit.js +36 -0
  142. package/dist/web/tdf3/src/crypto/jose/vendor/util/errors.js +117 -0
  143. package/dist/web/tdf3/src/crypto/jwt.js +174 -0
  144. package/dist/web/tdf3/src/crypto/salt.js +13 -7
  145. package/dist/web/tdf3/src/models/encryption-information.js +11 -14
  146. package/dist/web/tdf3/src/models/key-access.js +44 -31
  147. package/dist/web/tdf3/src/tdf.js +71 -71
  148. package/dist/web/tdf3/src/utils/index.js +5 -6
  149. package/package.json +11 -4
  150. package/src/access/access-fetch.ts +2 -8
  151. package/src/access/access-rpc.ts +0 -7
  152. package/src/access.ts +0 -17
  153. package/src/auth/auth.ts +21 -12
  154. package/src/auth/dpop.ts +222 -0
  155. package/src/auth/oidc-clientcredentials-provider.ts +23 -15
  156. package/src/auth/oidc-externaljwt-provider.ts +23 -15
  157. package/src/auth/oidc-refreshtoken-provider.ts +23 -15
  158. package/src/auth/oidc.ts +21 -10
  159. package/src/auth/providers.ts +46 -29
  160. package/src/crypto/index.ts +21 -1
  161. package/src/crypto/pemPublicToCrypto.ts +11 -9
  162. package/src/opentdf.ts +36 -17
  163. package/src/policy/discovery.ts +2 -2
  164. package/tdf3/index.ts +32 -5
  165. package/tdf3/src/assertions.ts +99 -30
  166. package/tdf3/src/ciphers/aes-gcm-cipher.ts +7 -2
  167. package/tdf3/src/ciphers/symmetric-cipher-base.ts +7 -4
  168. package/tdf3/src/client/builders.ts +2 -2
  169. package/tdf3/src/client/index.ts +60 -59
  170. package/tdf3/src/crypto/crypto-utils.ts +15 -8
  171. package/tdf3/src/crypto/declarations.ts +338 -22
  172. package/tdf3/src/crypto/index.ts +1021 -118
  173. package/tdf3/src/crypto/jose/jwt-claims-set.ts +10 -0
  174. package/tdf3/src/crypto/jose/validate-crit.ts +9 -0
  175. package/tdf3/src/crypto/jose/vendor/lib/buffer_utils.ts +34 -0
  176. package/tdf3/src/crypto/jose/vendor/lib/epoch.ts +3 -0
  177. package/tdf3/src/crypto/jose/vendor/lib/is_object.ts +18 -0
  178. package/tdf3/src/crypto/jose/vendor/lib/jwt_claims_set.ts +106 -0
  179. package/tdf3/src/crypto/jose/vendor/lib/secs.ts +57 -0
  180. package/tdf3/src/crypto/jose/vendor/lib/validate_crit.ts +35 -0
  181. package/tdf3/src/crypto/jose/vendor/util/errors.ts +101 -0
  182. package/tdf3/src/crypto/jwt.ts +256 -0
  183. package/tdf3/src/crypto/salt.ts +16 -8
  184. package/tdf3/src/models/encryption-information.ts +14 -21
  185. package/tdf3/src/models/key-access.ts +57 -41
  186. package/tdf3/src/tdf.ts +110 -93
  187. package/tdf3/src/utils/index.ts +5 -6
@@ -1,8 +1,14 @@
1
1
  import { canonicalizeEx } from 'json-canonicalize';
2
- import { SignJWT, jwtVerify, importJWK, importX509 } from 'jose';
3
2
  import { base64, hex } from '../../src/encodings/index.js';
4
3
  import { ConfigurationError, IntegrityError, InvalidFileError } from '../../src/errors.js';
5
4
  import { tdfSpecVersion, version as sdkVersion } from '../../src/version.js';
5
+ import {
6
+ type CryptoService,
7
+ type PrivateKey,
8
+ type PublicKey,
9
+ type SymmetricKey,
10
+ } from './crypto/declarations.js';
11
+ import { decodeProtectedHeader, signJwt, verifyJwt, type JwtHeader } from './crypto/jwt.js';
6
12
 
7
13
  export type AssertionKeyAlg = 'ES256' | 'RS256' | 'HS256';
8
14
  export type AssertionType = 'handling' | 'other';
@@ -41,39 +47,69 @@ export type AssertionPayload = {
41
47
  /**
42
48
  * Computes the SHA-256 hash of the assertion object, excluding the 'binding' and 'hash' properties.
43
49
  *
50
+ * @param a - The assertion to hash
51
+ * @param cryptoService - The crypto service to use for hashing
44
52
  * @returns the hexadecimal string representation of the hash
45
53
  */
46
- export async function hash(a: Assertion): Promise<string> {
54
+ export async function hash(a: Assertion, cryptoService: CryptoService): Promise<string> {
47
55
  const result = canonicalizeEx(a, {
48
56
  exclude: ['binding', 'hash', 'sign', 'verify', 'signingKey'],
49
57
  });
50
58
 
51
- const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(result));
52
- return hex.encodeArrayBuffer(hash);
59
+ const hashBytes = await cryptoService.digest('SHA-256', new TextEncoder().encode(result));
60
+ return hex.encodeArrayBuffer(hashBytes.buffer);
53
61
  }
54
62
 
55
63
  /**
56
64
  * Signs the given hash and signature using the provided key and sets the binding method and signature.
57
65
  *
58
- * @param hash - The hash to be signed.
66
+ * @param thiz - The assertion to sign.
67
+ * @param assertionHash - The hash to be signed.
59
68
  * @param sig - The signature to be signed.
60
- * @param {AssertionKey} key - The key used for signing.
61
- * @returns {Promise<void>} A promise that resolves when the signing is complete.
69
+ * @param key - The key used for signing.
70
+ * @param cryptoService - The crypto service to use for signing.
71
+ * @returns A promise that resolves to the signed assertion.
62
72
  */
63
73
  async function sign(
64
74
  thiz: Assertion,
65
75
  assertionHash: string,
66
76
  sig: string,
67
- key: AssertionKey
77
+ key: AssertionKey,
78
+ cryptoService: CryptoService
68
79
  ): Promise<Assertion> {
69
80
  const payload: AssertionPayload = {
70
81
  assertionHash,
71
82
  assertionSig: sig,
72
83
  };
73
84
 
85
+ const header: JwtHeader = { alg: key.alg };
86
+
87
+ if (typeof key.key === 'object' && '_brand' in key.key && key.key._brand === 'PublicKey') {
88
+ throw new ConfigurationError(
89
+ 'Cannot sign assertion with PublicKey. Use PrivateKey or SymmetricKey for signing.'
90
+ );
91
+ }
92
+
93
+ let signingMaterial: PrivateKey | SymmetricKey;
94
+ if (typeof key.key === 'string') {
95
+ if (!cryptoService.importPrivateKey) {
96
+ throw new ConfigurationError(
97
+ 'CryptoService does not support importing private keys. Cannot sign assertion with a PEM string. Use PrivateKey or SymmetricKey for signing.'
98
+ );
99
+ }
100
+ signingMaterial = await cryptoService.importPrivateKey(key.key, {
101
+ usage: 'sign',
102
+ extractable: false,
103
+ });
104
+ } else if (key.key instanceof Uint8Array) {
105
+ signingMaterial = await cryptoService.importSymmetricKey(key.key);
106
+ } else {
107
+ signingMaterial = key.key as PrivateKey | SymmetricKey;
108
+ }
109
+
74
110
  let token: string;
75
111
  try {
76
- token = await new SignJWT(payload).setProtectedHeader({ alg: key.alg }).sign(key.key);
112
+ token = await signJwt(cryptoService, payload, signingMaterial, header);
77
113
  } catch (error) {
78
114
  throw new ConfigurationError(`Signing assertion failed: ${error.message}`, error);
79
115
  }
@@ -107,36 +143,54 @@ export function isAssertionConfig(obj: unknown): obj is AssertionConfig {
107
143
  /**
108
144
  * Verifies the signature of the assertion using the provided key.
109
145
  *
110
- * @param {AssertionKey} key - The key used for verification.
111
- * @returns {Promise<[string, string]>} A promise that resolves to a tuple containing the assertion hash and signature.
112
- * @throws {Error} If the verification fails.
146
+ * @param thiz - The assertion to verify.
147
+ * @param aggregateHash - The aggregate hash for integrity checking.
148
+ * @param key - The key used for verification.
149
+ * @param isLegacyTDF - Whether this is a legacy TDF format.
150
+ * @param cryptoService - The crypto service to use for verification.
151
+ * @throws {InvalidFileError} If the verification fails.
152
+ * @throws {IntegrityError} If the integrity check fails.
113
153
  */
114
154
  export async function verify(
115
155
  thiz: Assertion,
116
156
  aggregateHash: Uint8Array,
117
157
  key: AssertionKey,
118
- isLegacyTDF: boolean
158
+ isLegacyTDF: boolean,
159
+ cryptoService: CryptoService
119
160
  ): Promise<void> {
120
161
  let payload: AssertionPayload;
121
162
  try {
122
- const uj = await jwtVerify(thiz.binding.signature, async (header) => {
123
- if (header.jwk) {
124
- return await importJWK(header.jwk, header.alg);
125
- }
126
- if (header.x5c && header.x5c.length > 0) {
127
- const cert = `-----BEGIN CERTIFICATE-----\n${header.x5c[0]}\n-----END CERTIFICATE-----`;
128
- return await importX509(cert, header.alg);
129
- }
130
- return key.key;
163
+ // Parse JWT header to check for embedded keys (jwk or x5c)
164
+ const header = decodeProtectedHeader(thiz.binding.signature);
165
+
166
+ // Runtime check: ensure we have a verification key, not a signing key
167
+ if (typeof key.key === 'object' && '_brand' in key.key && key.key._brand === 'PrivateKey') {
168
+ throw new ConfigurationError(
169
+ 'Cannot verify assertion with PrivateKey. Use PublicKey or SymmetricKey for verification.'
170
+ );
171
+ }
172
+ let verificationKey: string | Uint8Array | PublicKey | SymmetricKey = key.key;
173
+
174
+ if (header.jwk) {
175
+ // Convert embedded JWK to PEM
176
+ verificationKey = await cryptoService.jwkToPublicKeyPem(header.jwk as JsonWebKey);
177
+ } else if (header.x5c && Array.isArray(header.x5c) && header.x5c.length > 0) {
178
+ // Extract public key from X.509 certificate
179
+ const cert = `-----BEGIN CERTIFICATE-----\n${header.x5c[0]}\n-----END CERTIFICATE-----`;
180
+ verificationKey = await cryptoService.extractPublicKeyPem(cert);
181
+ }
182
+
183
+ const result = await verifyJwt(cryptoService, thiz.binding.signature, verificationKey, {
184
+ algorithms: [key.alg],
131
185
  });
132
- payload = uj.payload as AssertionPayload;
186
+ payload = result.payload as AssertionPayload;
133
187
  } catch (error) {
134
188
  throw new InvalidFileError(`Verifying assertion failed: ${error.message}`, error);
135
189
  }
136
190
  const { assertionHash, assertionSig } = payload;
137
191
 
138
192
  // Get the hash of the assertion
139
- const hashOfAssertion = await hash(thiz);
193
+ const hashOfAssertion = await hash(thiz, cryptoService);
140
194
 
141
195
  // check if assertionHash is same as hashOfAssertion
142
196
  if (hashOfAssertion !== assertionHash) {
@@ -164,13 +218,17 @@ export async function verify(
164
218
 
165
219
  /**
166
220
  * Creates an Assertion object with the specified properties.
167
- */
168
- /**
169
- * Creates an Assertion object with the specified properties.
221
+ *
222
+ * @param aggregateHash - The aggregate hash for the assertion.
223
+ * @param assertionConfig - The configuration for the assertion.
224
+ * @param cryptoService - The crypto service to use for signing.
225
+ * @param targetVersion - The target TDF spec version.
226
+ * @returns The created assertion.
170
227
  */
171
228
  export async function CreateAssertion(
172
229
  aggregateHash: Uint8Array | string,
173
230
  assertionConfig: AssertionConfig,
231
+ cryptoService: CryptoService,
174
232
  targetVersion?: string
175
233
  ): Promise<Assertion> {
176
234
  if (!assertionConfig.signingKey) {
@@ -187,7 +245,7 @@ export async function CreateAssertion(
187
245
  binding: { method: '', signature: '' },
188
246
  };
189
247
 
190
- const assertionHash = await hash(a);
248
+ const assertionHash = await hash(a, cryptoService);
191
249
  let encodedHash: string;
192
250
  switch (targetVersion || '4.3.0') {
193
251
  case '4.2.2':
@@ -212,12 +270,23 @@ export async function CreateAssertion(
212
270
  throw new ConfigurationError(`Unsupported TDF spec version: [${targetVersion}]`);
213
271
  }
214
272
 
215
- return await sign(a, assertionHash, encodedHash, assertionConfig.signingKey);
273
+ return await sign(a, assertionHash, encodedHash, assertionConfig.signingKey, cryptoService);
216
274
  }
217
275
 
276
+ // TODO: Split AssertionKey into two separate types:
277
+ // - AssertionSigningKey: key restricted to PrivateKey | SymmetricKey (no strings, no raw bytes)
278
+ // - AssertionVerificationKey: key restricted to string | PublicKey | SymmetricKey
279
+ // This would make the signing/verification distinction type-safe rather than relying on runtime checks.
280
+ // AssertionConfig.signingKey would use AssertionSigningKey; verify() and AssertionVerificationKeys would use AssertionVerificationKey.
281
+
282
+ /**
283
+ * Key used for signing or verifying assertions.
284
+ * For asymmetric algorithms (RS256, ES256): PEM string, PrivateKey (for signing), or PublicKey (for verification).
285
+ * For symmetric algorithms (HS256): Uint8Array or SymmetricKey (opaque).
286
+ */
218
287
  export type AssertionKey = {
219
288
  alg: AssertionKeyAlg;
220
- key: CryptoKey | Uint8Array;
289
+ key: string | Uint8Array | PrivateKey | PublicKey | SymmetricKey;
221
290
  };
222
291
 
223
292
  // AssertionConfig is a shadow of Assertion with the addition of the signing key.
@@ -7,6 +7,7 @@ import {
7
7
  type CryptoService,
8
8
  type DecryptResult,
9
9
  type EncryptResult,
10
+ type SymmetricKey,
10
11
  } from '../crypto/declarations.js';
11
12
 
12
13
  const KEY_LENGTH = 32;
@@ -45,7 +46,7 @@ export class AesGcmCipher extends SymmetricCipher {
45
46
  * result from the crypto service and construct the payload automatically from
46
47
  * it's parts. There is no need to process the payload.
47
48
  */
48
- override async encrypt(payload: Binary, key: Binary, iv: Binary): Promise<EncryptResult> {
49
+ override async encrypt(payload: Binary, key: SymmetricKey, iv: Binary): Promise<EncryptResult> {
49
50
  const toConcat: Uint8Array[] = [];
50
51
  const result = await this.cryptoService.encrypt(payload, key, iv, Algorithms.AES_256_GCM);
51
52
  toConcat.push(new Uint8Array(iv.asArrayBuffer()));
@@ -62,7 +63,11 @@ export class AesGcmCipher extends SymmetricCipher {
62
63
  * @returns
63
64
  */
64
65
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
65
- override async decrypt(buffer: ArrayBuffer, key: Binary, iv?: Binary): Promise<DecryptResult> {
66
+ override async decrypt(
67
+ buffer: ArrayBuffer,
68
+ key: SymmetricKey,
69
+ iv?: Binary
70
+ ): Promise<DecryptResult> {
66
71
  const { payload, payloadIv, payloadAuthTag } = processGcmPayload(buffer);
67
72
 
68
73
  return this.cryptoService.decrypt(
@@ -3,7 +3,9 @@ import {
3
3
  type CryptoService,
4
4
  type DecryptResult,
5
5
  type EncryptResult,
6
+ type SymmetricKey,
6
7
  } from '../crypto/declarations.js';
8
+ import { encodeArrayBuffer as hexEncode } from '../../../src/encodings/hex.js';
7
9
 
8
10
  export abstract class SymmetricCipher {
9
11
  cryptoService: CryptoService;
@@ -22,17 +24,18 @@ export abstract class SymmetricCipher {
22
24
  if (!this.ivLength) {
23
25
  throw Error('No iv length');
24
26
  }
25
- return this.cryptoService.generateInitializationVector(this.ivLength);
27
+ const bytes = await this.cryptoService.randomBytes(this.ivLength);
28
+ return hexEncode(bytes.buffer);
26
29
  }
27
30
 
28
- async generateKey(): Promise<string> {
31
+ async generateKey(): Promise<SymmetricKey> {
29
32
  if (!this.keyLength) {
30
33
  throw Error('No key length');
31
34
  }
32
35
  return this.cryptoService.generateKey(this.keyLength);
33
36
  }
34
37
 
35
- abstract encrypt(payload: Binary, key: Binary, iv: Binary): Promise<EncryptResult>;
38
+ abstract encrypt(payload: Binary, key: SymmetricKey, iv: Binary): Promise<EncryptResult>;
36
39
 
37
- abstract decrypt(payload: Uint8Array, key: Binary, iv?: Binary): Promise<DecryptResult>;
40
+ abstract decrypt(payload: Uint8Array, key: SymmetricKey, iv?: Binary): Promise<DecryptResult>;
38
41
  }
@@ -4,7 +4,7 @@ import { type Metadata } from '../tdf.js';
4
4
  import { Binary } from '../binary.js';
5
5
 
6
6
  import { ConfigurationError } from '../../../src/errors.js';
7
- import { PemKeyPair } from '../crypto/declarations.js';
7
+ import { PemKeyPair, type SymmetricKey } from '../crypto/declarations.js';
8
8
  import { DecoratedReadableStream } from './DecoratedReadableStream.js';
9
9
  import { type Chunker } from '../../../src/seekable.js';
10
10
  import { AssertionConfig, AssertionVerificationKeys } from '../assertions.js';
@@ -516,7 +516,7 @@ class EncryptParamsBuilder {
516
516
  }
517
517
  }
518
518
 
519
- export type DecryptKeyMiddleware = (key: Binary) => Promise<Binary>;
519
+ export type DecryptKeyMiddleware = (key: SymmetricKey) => Promise<SymmetricKey>;
520
520
 
521
521
  export type DecryptStreamMiddleware = (
522
522
  stream: DecoratedReadableStream
@@ -19,12 +19,7 @@ import { OIDCRefreshTokenProvider } from '../../../src/auth/oidc-refreshtoken-pr
19
19
  import { OIDCExternalJwtProvider } from '../../../src/auth/oidc-externaljwt-provider.js';
20
20
  import { CryptoService } from '../crypto/declarations.js';
21
21
  import { type AuthProvider, HttpRequest, withHeaders } from '../../../src/auth/auth.js';
22
- import {
23
- getPlatformUrlFromKasEndpoint,
24
- pemToCryptoPublicKey,
25
- rstrip,
26
- validateSecureUrl,
27
- } from '../../../src/utils.js';
22
+ import { getPlatformUrlFromKasEndpoint, rstrip, validateSecureUrl } from '../../../src/utils.js';
28
23
 
29
24
  import {
30
25
  type DecryptParams,
@@ -42,13 +37,11 @@ import { DecoratedReadableStream } from './DecoratedReadableStream.js';
42
37
  import {
43
38
  fetchKeyAccessServers,
44
39
  type KasPublicKeyInfo,
45
- keyAlgorithmToPublicKeyAlgorithm,
46
40
  OriginAllowList,
47
41
  } from '../../../src/access.js';
48
42
  import { ConfigurationError } from '../../../src/errors.js';
49
- import { Binary } from '../binary.js';
50
43
  import { AesGcmCipher } from '../ciphers/aes-gcm-cipher.js';
51
- import { toCryptoKeyPair } from '../crypto/crypto-utils.js';
44
+ import { type KeyPair, type SymmetricKey } from '../crypto/declarations.js';
52
45
  import * as defaultCryptoService from '../crypto/index.js';
53
46
  import {
54
47
  type AttributeObject,
@@ -69,24 +62,23 @@ const defaultClientConfig = { oidcOrigin: '', cryptoService: defaultCryptoServic
69
62
 
70
63
  const getFirstTwoBytes = async (chunker: Chunker) => new TextDecoder().decode(await chunker(0, 2));
71
64
 
72
- async function algorithmFromPEM(pem: string) {
73
- const k: CryptoKey = await pemToCryptoPublicKey(pem);
74
- return keyAlgorithmToPublicKeyAlgorithm(k);
65
+ async function algorithmFromPEM(pem: string, cryptoService: CryptoService) {
66
+ const keyInfo = await cryptoService.parsePublicKeyPem(pem);
67
+ return keyInfo.algorithm;
75
68
  }
76
69
 
77
- // Convert a PEM string to a CryptoKey
70
+ // Convert a PEM string to KasPublicKeyInfo
78
71
  export const resolveKasInfo = async (
79
72
  pem: string,
80
73
  uri: string,
74
+ cryptoService: CryptoService,
81
75
  kid?: string
82
76
  ): Promise<KasPublicKeyInfo> => {
83
- const k: CryptoKey = await pemToCryptoPublicKey(pem);
84
- const algorithm = keyAlgorithmToPublicKeyAlgorithm(k);
77
+ const keyInfo = await cryptoService.parsePublicKeyPem(pem);
85
78
  return {
86
- key: Promise.resolve(k),
87
79
  publicKey: pem,
88
80
  url: uri,
89
- algorithm,
81
+ algorithm: keyInfo.algorithm,
90
82
  kid: kid,
91
83
  };
92
84
  };
@@ -132,7 +124,7 @@ export interface ClientConfig {
132
124
  /// oauth client id; used to generate oauth authProvider
133
125
  clientId?: string;
134
126
  dpopEnabled?: boolean;
135
- dpopKeys?: Promise<CryptoKeyPair>;
127
+ dpopKeys?: Promise<KeyPair>;
136
128
  kasEndpoint: string;
137
129
  /**
138
130
  * Service to use to look up ABAC. Used during autoconfigure. Defaults to
@@ -177,28 +169,23 @@ export interface ClientConfig {
177
169
  */
178
170
  export async function createSessionKeys({
179
171
  authProvider,
180
- // FIXME use cryptoservice to generate keys again
181
172
  cryptoService,
182
173
  dpopKeys,
183
174
  }: {
184
175
  authProvider?: AuthProvider;
185
176
  cryptoService: CryptoService;
186
- dpopKeys?: Promise<CryptoKeyPair>;
187
- }): Promise<CryptoKeyPair> {
188
- let signingKeys: CryptoKeyPair;
177
+ dpopKeys?: Promise<KeyPair>;
178
+ }): Promise<KeyPair> {
179
+ let signingKeys: KeyPair;
189
180
  if (dpopKeys) {
190
181
  signingKeys = await dpopKeys;
191
182
  } else {
192
- const keys = await cryptoService.generateSigningKeyPair();
193
- // signingKeys = await crypto.subtle.generateKey(rsaPkcs1Sha256(), true, ['sign']);
194
- signingKeys = await toCryptoKeyPair(keys);
183
+ // generateSigningKeyPair returns opaque KeyPair
184
+ signingKeys = await cryptoService.generateSigningKeyPair();
195
185
  }
196
186
 
197
187
  // This will contact the auth server and forcibly refresh the auth token claims,
198
188
  // binding the token and the (new) pubkey together.
199
- // Note that we base64 encode the PEM string here as a quick workaround, simply because
200
- // a formatted raw PEM string isn't a valid header value and sending it raw makes keycloak's
201
- // header parser barf. There are more subtle ways to solve this, but this works for now.
202
189
  if (authProvider) {
203
190
  await authProvider?.updateClientPublicKey(signingKeys);
204
191
  }
@@ -301,7 +288,8 @@ const putKasKeyIntoCache = (
301
288
  cache: KasKeyInfoCache,
302
289
  kasKey: Omit<SimpleKasKey, 'publicKey'> & {
303
290
  publicKey: Exclude<SimpleKasKey['publicKey'], undefined>;
304
- }
291
+ },
292
+ cryptoService: CryptoService
305
293
  ): ReturnType<typeof fetchKasPublicKey> => {
306
294
  const algorithmString = algorithmEnumValueToString(kasKey.publicKey.algorithm);
307
295
  const cachedEntry = findEntryInCache(cache, kasKey.kasUri, algorithmString, kasKey.publicKey.kid);
@@ -309,12 +297,9 @@ const putKasKeyIntoCache = (
309
297
  return cachedEntry;
310
298
  }
311
299
  const keyInfoPromise = (async function () {
312
- const keyPromise = pemToCryptoPublicKey(kasKey.publicKey.pem);
313
- const key = await keyPromise;
314
- const algorithm = keyAlgorithmToPublicKeyAlgorithm(key);
300
+ const keyInfo = await cryptoService.parsePublicKeyPem(kasKey.publicKey.pem);
315
301
  return {
316
- algorithm: algorithm,
317
- key: keyPromise,
302
+ algorithm: keyInfo.algorithm,
318
303
  kid: kasKey.publicKey.kid,
319
304
  publicKey: kasKey.publicKey.pem,
320
305
  url: kasKey.kasUri,
@@ -371,7 +356,7 @@ export class Client {
371
356
  /**
372
357
  * Session binding keys. Used for DPoP and signed request bodies.
373
358
  */
374
- readonly dpopKeys: Promise<CryptoKeyPair>;
359
+ readonly dpopKeys: Promise<KeyPair>;
375
360
 
376
361
  readonly dpopEnabled: boolean;
377
362
 
@@ -453,18 +438,24 @@ export class Client {
453
438
  //browser-based OIDC login and authentication process against the OIDC endpoint using their chosen method,
454
439
  //and provide us with a valid refresh token/clientId obtained from that process.
455
440
  if (clientConfig.refreshToken) {
456
- this.authProvider = new OIDCRefreshTokenProvider({
457
- clientId: clientConfig.clientId,
458
- refreshToken: clientConfig.refreshToken,
459
- oidcOrigin: clientConfig.oidcOrigin,
460
- });
441
+ this.authProvider = new OIDCRefreshTokenProvider(
442
+ {
443
+ clientId: clientConfig.clientId,
444
+ refreshToken: clientConfig.refreshToken,
445
+ oidcOrigin: clientConfig.oidcOrigin,
446
+ },
447
+ this.cryptoService
448
+ );
461
449
  } else if (clientConfig.externalJwt) {
462
450
  //Are we exchanging a JWT previously issued by a trusted external entity (e.g. Google) for a bearer token?
463
- this.authProvider = new OIDCExternalJwtProvider({
464
- clientId: clientConfig.clientId,
465
- externalJwt: clientConfig.externalJwt,
466
- oidcOrigin: clientConfig.oidcOrigin,
467
- });
451
+ this.authProvider = new OIDCExternalJwtProvider(
452
+ {
453
+ clientId: clientConfig.clientId,
454
+ externalJwt: clientConfig.externalJwt,
455
+ oidcOrigin: clientConfig.oidcOrigin,
456
+ },
457
+ this.cryptoService
458
+ );
468
459
  }
469
460
  }
470
461
  this.dpopKeys = createSessionKeys({
@@ -509,22 +500,27 @@ export class Client {
509
500
  metadata,
510
501
  mimeType = 'unknown',
511
502
  windowSize = DEFAULT_SEGMENT_SIZE,
512
- keyMiddleware = defaultKeyMiddleware,
503
+ keyMiddleware: keyMiddlewareOpt,
513
504
  splitPlan: preconfiguredSplitPlan,
514
505
  streamMiddleware = async (stream: DecoratedReadableStream) => stream,
515
506
  tdfSpecVersion,
516
507
  wrappingKeyAlgorithm,
517
508
  } = opts;
509
+ const keyMiddleware = keyMiddlewareOpt ?? (() => defaultKeyMiddleware(this.cryptoService));
518
510
  const scope = opts.scope ?? { attributes: [], dissem: [] };
519
511
 
520
512
  for (const attributeValue of scope.attributeValues || []) {
521
513
  for (const kasKey of attributeValue.kasKeys) {
522
514
  if (kasKey.publicKey !== undefined) {
523
- await putKasKeyIntoCache(this.kasKeyInfoCache, {
524
- // TypeScript is silly and cannot infer that publicKey is not undefined, without re-referencing it like this, even though we checked already.
525
- ...kasKey,
526
- publicKey: kasKey.publicKey,
527
- });
515
+ await putKasKeyIntoCache(
516
+ this.kasKeyInfoCache,
517
+ {
518
+ // TypeScript is silly and cannot infer that publicKey is not undefined, without re-referencing it like this, even though we checked already.
519
+ ...kasKey,
520
+ publicKey: kasKey.publicKey,
521
+ },
522
+ this.cryptoService
523
+ );
528
524
  }
529
525
  }
530
526
  }
@@ -594,11 +590,15 @@ export class Client {
594
590
  for (const attributeValue of attributeValues) {
595
591
  for (const kasKey of effectiveKasKeys(attributeValue)) {
596
592
  if (kasKey.publicKey !== undefined) {
597
- await putKasKeyIntoCache(this.kasKeyInfoCache, {
598
- // TypeScript is silly and cannot infer that publicKey is not undefined, without re-referencing it like this, even though we checked already.
599
- ...kasKey,
600
- publicKey: kasKey.publicKey,
601
- });
593
+ await putKasKeyIntoCache(
594
+ this.kasKeyInfoCache,
595
+ {
596
+ // TypeScript is silly and cannot infer that publicKey is not undefined, without re-referencing it like this, even though we checked already.
597
+ ...kasKey,
598
+ publicKey: kasKey.publicKey,
599
+ },
600
+ this.cryptoService
601
+ );
602
602
  }
603
603
  }
604
604
  }
@@ -606,7 +606,7 @@ export class Client {
606
606
  const detailedPlan = plan(attributeValues);
607
607
  for (const item of detailedPlan) {
608
608
  if ('kid' in item.kas) {
609
- const pemAlgorithm = await algorithmFromPEM(item.kas.pem);
609
+ const pemAlgorithm = await algorithmFromPEM(item.kas.pem, this.cryptoService);
610
610
  const kasPublicKeyInfo = await this._doFetchKasKeyWithCache(
611
611
  this.kasKeyInfoCache,
612
612
  item.kas.kasUri,
@@ -694,7 +694,7 @@ export class Client {
694
694
  }
695
695
  encryptionInformation.keyAccess = await Promise.all(
696
696
  splitPlan.map(async ({ kas, kid, pem, sid }) => {
697
- const algorithm = await algorithmFromPEM(pem);
697
+ const algorithm = await algorithmFromPEM(pem, this.cryptoService);
698
698
  if (algorithm !== wrappingKeyAlgorithm) {
699
699
  console.warn(
700
700
  `Mismatched wrapping key algorithm: [${algorithm}] is not requested type, [${wrappingKeyAlgorithm}]`
@@ -722,6 +722,7 @@ export class Client {
722
722
  publicKey: pem,
723
723
  metadata,
724
724
  sid,
725
+ cryptoService: this.cryptoService,
725
726
  });
726
727
  })
727
728
  );
@@ -765,7 +766,7 @@ export class Client {
765
766
  async decrypt({
766
767
  source,
767
768
  allowList,
768
- keyMiddleware = async (key: Binary) => key,
769
+ keyMiddleware = async (key: SymmetricKey) => key,
769
770
  streamMiddleware = async (stream: DecoratedReadableStream) => stream,
770
771
  assertionVerificationKeys,
771
772
  noVerifyAssertions,
@@ -1,5 +1,5 @@
1
1
  import { base64 } from '../../../src/encodings/index.js';
2
- import { type AnyKeyPair, type PemKeyPair } from './declarations.js';
2
+ import { type PemKeyPair } from './declarations.js';
3
3
  import { rsaPkcs1Sha256 } from './index.js';
4
4
 
5
5
  /**
@@ -73,7 +73,10 @@ export const removePemFormatting = (input: string): string => {
73
73
  const PEMRE =
74
74
  /-----BEGIN\s((?:RSA\s)?(?:PUBLIC\sKEY|PRIVATE\sKEY|CERTIFICATE))-----[\s0-9A-Za-z+/=]+-----END\s\1-----/;
75
75
 
76
- export const isPemKeyPair = (i: AnyKeyPair): i is PemKeyPair => {
76
+ /**
77
+ * Type guard to check if a key pair is a PemKeyPair.
78
+ */
79
+ export const isPemKeyPair = (i: PemKeyPair | CryptoKeyPair): i is PemKeyPair => {
77
80
  const { privateKey, publicKey } = i;
78
81
  if (typeof privateKey !== 'string' || typeof publicKey !== 'string') {
79
82
  return false;
@@ -89,7 +92,10 @@ export const isPemKeyPair = (i: AnyKeyPair): i is PemKeyPair => {
89
92
  return true;
90
93
  };
91
94
 
92
- export const isCryptoKeyPair = (i: AnyKeyPair): i is CryptoKeyPair => {
95
+ /**
96
+ * Type guard to check if a key pair is a CryptoKeyPair.
97
+ */
98
+ export const isCryptoKeyPair = (i: PemKeyPair | CryptoKeyPair): i is CryptoKeyPair => {
93
99
  const { privateKey, publicKey } = i;
94
100
  if (typeof privateKey !== 'object' || typeof publicKey !== 'object') {
95
101
  return false;
@@ -100,12 +106,13 @@ export const isCryptoKeyPair = (i: AnyKeyPair): i is CryptoKeyPair => {
100
106
  return privateKey.type === 'private' && publicKey.type === 'public';
101
107
  };
102
108
 
103
- export const toCryptoKeyPair = async (input: AnyKeyPair): Promise<CryptoKeyPair> => {
104
- if (isCryptoKeyPair(input)) {
105
- return input;
106
- }
109
+ /**
110
+ * Convert a PemKeyPair to CryptoKeyPair for internal use.
111
+ * This is needed when interfacing with APIs that still require CryptoKey objects.
112
+ */
113
+ export const toCryptoKeyPair = async (input: PemKeyPair): Promise<CryptoKeyPair> => {
107
114
  if (!isPemKeyPair(input)) {
108
- throw new Error('internal: generated invalid keypair');
115
+ throw new Error('internal: invalid PEM keypair');
109
116
  }
110
117
  const k = [input.publicKey, input.privateKey]
111
118
  .map(removePemFormatting)