@brightchain/brightchain-lib 0.29.26 → 0.30.1

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 (290) hide show
  1. package/package.json +10 -4
  2. package/src/index.d.ts +1 -1
  3. package/src/index.d.ts.map +1 -1
  4. package/src/index.js +2 -1
  5. package/src/index.js.map +1 -1
  6. package/src/lib/constants.d.ts +3 -26
  7. package/src/lib/constants.d.ts.map +1 -1
  8. package/src/lib/constants.js +2 -1
  9. package/src/lib/constants.js.map +1 -1
  10. package/src/lib/db/collection.d.ts +1 -1
  11. package/src/lib/db/collection.d.ts.map +1 -1
  12. package/src/lib/db/collection.js +2 -1
  13. package/src/lib/db/collection.js.map +1 -1
  14. package/src/lib/enumeration-translations/index.d.ts +2 -0
  15. package/src/lib/enumeration-translations/index.d.ts.map +1 -1
  16. package/src/lib/enumeration-translations/index.js +1 -0
  17. package/src/lib/enumeration-translations/index.js.map +1 -1
  18. package/src/lib/enumeration-translations/memberStatusType.d.ts +5 -0
  19. package/src/lib/enumeration-translations/memberStatusType.d.ts.map +1 -0
  20. package/src/lib/enumeration-translations/memberStatusType.js +58 -0
  21. package/src/lib/enumeration-translations/memberStatusType.js.map +1 -0
  22. package/src/lib/enumerations/brightChainStrings.d.ts +61 -0
  23. package/src/lib/enumerations/brightChainStrings.d.ts.map +1 -1
  24. package/src/lib/enumerations/brightChainStrings.js +64 -0
  25. package/src/lib/enumerations/brightChainStrings.js.map +1 -1
  26. package/src/lib/enumerations/brightchainFeatures.d.ts +1 -0
  27. package/src/lib/enumerations/brightchainFeatures.d.ts.map +1 -1
  28. package/src/lib/enumerations/brightchainFeatures.js +1 -0
  29. package/src/lib/enumerations/brightchainFeatures.js.map +1 -1
  30. package/src/lib/enumerations/communication.d.ts +7 -1
  31. package/src/lib/enumerations/communication.d.ts.map +1 -1
  32. package/src/lib/enumerations/communication.js +6 -0
  33. package/src/lib/enumerations/communication.js.map +1 -1
  34. package/src/lib/enumerations/friendRequestStatus.d.ts +7 -0
  35. package/src/lib/enumerations/friendRequestStatus.d.ts.map +1 -0
  36. package/src/lib/enumerations/friendRequestStatus.js +11 -0
  37. package/src/lib/enumerations/friendRequestStatus.js.map +1 -0
  38. package/src/lib/enumerations/friendsErrorCode.d.ts +10 -0
  39. package/src/lib/enumerations/friendsErrorCode.d.ts.map +1 -0
  40. package/src/lib/enumerations/friendsErrorCode.js +14 -0
  41. package/src/lib/enumerations/friendsErrorCode.js.map +1 -0
  42. package/src/lib/enumerations/friendshipStatus.d.ts +7 -0
  43. package/src/lib/enumerations/friendshipStatus.d.ts.map +1 -0
  44. package/src/lib/enumerations/friendshipStatus.js +11 -0
  45. package/src/lib/enumerations/friendshipStatus.js.map +1 -0
  46. package/src/lib/enumerations/index.d.ts +3 -0
  47. package/src/lib/enumerations/index.d.ts.map +1 -1
  48. package/src/lib/enumerations/index.js +4 -0
  49. package/src/lib/enumerations/index.js.map +1 -1
  50. package/src/lib/enumerations/messaging/emailErrorType.d.ts +15 -2
  51. package/src/lib/enumerations/messaging/emailErrorType.d.ts.map +1 -1
  52. package/src/lib/enumerations/messaging/emailErrorType.js +17 -1
  53. package/src/lib/enumerations/messaging/emailErrorType.js.map +1 -1
  54. package/src/lib/enumerations/messaging/messageEncryptionScheme.d.ts +4 -2
  55. package/src/lib/enumerations/messaging/messageEncryptionScheme.d.ts.map +1 -1
  56. package/src/lib/enumerations/messaging/messageEncryptionScheme.js +3 -1
  57. package/src/lib/enumerations/messaging/messageEncryptionScheme.js.map +1 -1
  58. package/src/lib/errors/encryptionErrors.d.ts +71 -0
  59. package/src/lib/errors/encryptionErrors.d.ts.map +1 -0
  60. package/src/lib/errors/encryptionErrors.js +112 -0
  61. package/src/lib/errors/encryptionErrors.js.map +1 -0
  62. package/src/lib/errors/friendsServiceError.d.ts +20 -0
  63. package/src/lib/errors/friendsServiceError.d.ts.map +1 -0
  64. package/src/lib/errors/friendsServiceError.js +48 -0
  65. package/src/lib/errors/friendsServiceError.js.map +1 -0
  66. package/src/lib/errors/index.d.ts +15 -0
  67. package/src/lib/errors/index.d.ts.map +1 -1
  68. package/src/lib/errors/index.js +21 -0
  69. package/src/lib/errors/index.js.map +1 -1
  70. package/src/lib/i18n/i18n-setup.d.ts +2 -2
  71. package/src/lib/i18n/i18n-setup.d.ts.map +1 -1
  72. package/src/lib/i18n/strings/englishUK.d.ts +2 -2
  73. package/src/lib/i18n/strings/englishUK.d.ts.map +1 -1
  74. package/src/lib/i18n/strings/englishUK.js.map +1 -1
  75. package/src/lib/i18n/strings/englishUs.d.ts +2 -2
  76. package/src/lib/i18n/strings/englishUs.d.ts.map +1 -1
  77. package/src/lib/i18n/strings/englishUs.js +65 -1
  78. package/src/lib/i18n/strings/englishUs.js.map +1 -1
  79. package/src/lib/i18n/strings/french.d.ts +2 -2
  80. package/src/lib/i18n/strings/french.d.ts.map +1 -1
  81. package/src/lib/i18n/strings/french.js +78 -13
  82. package/src/lib/i18n/strings/french.js.map +1 -1
  83. package/src/lib/i18n/strings/german.d.ts +2 -2
  84. package/src/lib/i18n/strings/german.d.ts.map +1 -1
  85. package/src/lib/i18n/strings/german.js +77 -12
  86. package/src/lib/i18n/strings/german.js.map +1 -1
  87. package/src/lib/i18n/strings/japanese.d.ts +2 -2
  88. package/src/lib/i18n/strings/japanese.d.ts.map +1 -1
  89. package/src/lib/i18n/strings/japanese.js +77 -12
  90. package/src/lib/i18n/strings/japanese.js.map +1 -1
  91. package/src/lib/i18n/strings/mandarin.d.ts +2 -2
  92. package/src/lib/i18n/strings/mandarin.d.ts.map +1 -1
  93. package/src/lib/i18n/strings/mandarin.js +77 -12
  94. package/src/lib/i18n/strings/mandarin.js.map +1 -1
  95. package/src/lib/i18n/strings/spanish.d.ts +2 -2
  96. package/src/lib/i18n/strings/spanish.d.ts.map +1 -1
  97. package/src/lib/i18n/strings/spanish.js +77 -12
  98. package/src/lib/i18n/strings/spanish.js.map +1 -1
  99. package/src/lib/i18n/strings/ukrainian.d.ts +2 -2
  100. package/src/lib/i18n/strings/ukrainian.d.ts.map +1 -1
  101. package/src/lib/i18n/strings/ukrainian.js +77 -12
  102. package/src/lib/i18n/strings/ukrainian.js.map +1 -1
  103. package/src/lib/index.d.ts +31 -0
  104. package/src/lib/index.d.ts.map +1 -1
  105. package/src/lib/index.js +29 -1
  106. package/src/lib/index.js.map +1 -1
  107. package/src/lib/interfaces/appSubsystemPlugin.d.ts +69 -0
  108. package/src/lib/interfaces/appSubsystemPlugin.d.ts.map +1 -0
  109. package/src/lib/interfaces/appSubsystemPlugin.js +3 -0
  110. package/src/lib/interfaces/appSubsystemPlugin.js.map +1 -0
  111. package/src/lib/interfaces/auth/writeProof.d.ts +2 -0
  112. package/src/lib/interfaces/auth/writeProof.d.ts.map +1 -1
  113. package/src/lib/interfaces/auth/writeProofUtils.d.ts +1 -1
  114. package/src/lib/interfaces/auth/writeProofUtils.d.ts.map +1 -1
  115. package/src/lib/interfaces/auth/writeProofUtils.js +2 -2
  116. package/src/lib/interfaces/auth/writeProofUtils.js.map +1 -1
  117. package/src/lib/interfaces/availability/gossipService.d.ts +99 -1
  118. package/src/lib/interfaces/availability/gossipService.d.ts.map +1 -1
  119. package/src/lib/interfaces/availability/gossipService.js +4 -0
  120. package/src/lib/interfaces/availability/gossipService.js.map +1 -1
  121. package/src/lib/interfaces/communication/blockContentStore.d.ts +57 -0
  122. package/src/lib/interfaces/communication/blockContentStore.d.ts.map +1 -0
  123. package/src/lib/interfaces/communication/blockContentStore.js +21 -0
  124. package/src/lib/interfaces/communication/blockContentStore.js.map +1 -0
  125. package/src/lib/interfaces/communication/chatStorageProvider.d.ts +77 -0
  126. package/src/lib/interfaces/communication/chatStorageProvider.d.ts.map +1 -0
  127. package/src/lib/interfaces/communication/chatStorageProvider.js +25 -0
  128. package/src/lib/interfaces/communication/chatStorageProvider.js.map +1 -0
  129. package/src/lib/interfaces/communication/index.d.ts +4 -0
  130. package/src/lib/interfaces/communication/index.d.ts.map +1 -0
  131. package/src/lib/interfaces/communication/index.js +3 -0
  132. package/src/lib/interfaces/communication/index.js.map +1 -0
  133. package/src/lib/interfaces/communication/server.d.ts +57 -0
  134. package/src/lib/interfaces/communication/server.d.ts.map +1 -0
  135. package/src/lib/interfaces/communication/server.js +14 -0
  136. package/src/lib/interfaces/communication/server.js.map +1 -0
  137. package/src/lib/interfaces/communication.d.ts +62 -5
  138. package/src/lib/interfaces/communication.d.ts.map +1 -1
  139. package/src/lib/interfaces/communication.js +8 -0
  140. package/src/lib/interfaces/communication.js.map +1 -1
  141. package/src/lib/interfaces/communicationEvents.d.ts +59 -1
  142. package/src/lib/interfaces/communicationEvents.d.ts.map +1 -1
  143. package/src/lib/interfaces/constants.d.ts +2 -0
  144. package/src/lib/interfaces/constants.d.ts.map +1 -1
  145. package/src/lib/interfaces/events/communicationEventEmitter.d.ts +9 -0
  146. package/src/lib/interfaces/events/communicationEventEmitter.d.ts.map +1 -1
  147. package/src/lib/interfaces/events/communicationEventEmitter.js +9 -0
  148. package/src/lib/interfaces/events/communicationEventEmitter.js.map +1 -1
  149. package/src/lib/interfaces/friends/baseFriendRequest.d.ts +13 -0
  150. package/src/lib/interfaces/friends/baseFriendRequest.d.ts.map +1 -0
  151. package/src/lib/interfaces/friends/baseFriendRequest.js +3 -0
  152. package/src/lib/interfaces/friends/baseFriendRequest.js.map +1 -0
  153. package/src/lib/interfaces/friends/baseFriendship.d.ts +11 -0
  154. package/src/lib/interfaces/friends/baseFriendship.d.ts.map +1 -0
  155. package/src/lib/interfaces/friends/baseFriendship.js +3 -0
  156. package/src/lib/interfaces/friends/baseFriendship.js.map +1 -0
  157. package/src/lib/interfaces/friends/friendsService.d.ts +32 -0
  158. package/src/lib/interfaces/friends/friendsService.d.ts.map +1 -0
  159. package/src/lib/interfaces/friends/friendsService.js +3 -0
  160. package/src/lib/interfaces/friends/friendsService.js.map +1 -0
  161. package/src/lib/interfaces/friends/friendsSuggestionProvider.d.ts +17 -0
  162. package/src/lib/interfaces/friends/friendsSuggestionProvider.d.ts.map +1 -0
  163. package/src/lib/interfaces/friends/friendsSuggestionProvider.js +3 -0
  164. package/src/lib/interfaces/friends/friendsSuggestionProvider.js.map +1 -0
  165. package/src/lib/interfaces/friends/index.d.ts +6 -0
  166. package/src/lib/interfaces/friends/index.d.ts.map +1 -0
  167. package/src/lib/interfaces/friends/index.js +3 -0
  168. package/src/lib/interfaces/friends/index.js.map +1 -0
  169. package/src/lib/interfaces/friends/pagination.d.ts +11 -0
  170. package/src/lib/interfaces/friends/pagination.d.ts.map +1 -0
  171. package/src/lib/interfaces/friends/pagination.js +3 -0
  172. package/src/lib/interfaces/friends/pagination.js.map +1 -0
  173. package/src/lib/interfaces/index.d.ts +6 -1
  174. package/src/lib/interfaces/index.d.ts.map +1 -1
  175. package/src/lib/interfaces/index.js +3 -0
  176. package/src/lib/interfaces/index.js.map +1 -1
  177. package/src/lib/interfaces/messaging/gpgKey.d.ts +93 -0
  178. package/src/lib/interfaces/messaging/gpgKey.d.ts.map +1 -0
  179. package/src/lib/interfaces/messaging/gpgKey.js +12 -0
  180. package/src/lib/interfaces/messaging/gpgKey.js.map +1 -0
  181. package/src/lib/interfaces/messaging/index.d.ts +4 -0
  182. package/src/lib/interfaces/messaging/index.d.ts.map +1 -1
  183. package/src/lib/interfaces/messaging/index.js +4 -0
  184. package/src/lib/interfaces/messaging/index.js.map +1 -1
  185. package/src/lib/interfaces/messaging/keyStore.d.ts +100 -0
  186. package/src/lib/interfaces/messaging/keyStore.d.ts.map +1 -0
  187. package/src/lib/interfaces/messaging/keyStore.js +13 -0
  188. package/src/lib/interfaces/messaging/keyStore.js.map +1 -0
  189. package/src/lib/interfaces/messaging/recipientKeyResolver.d.ts +92 -0
  190. package/src/lib/interfaces/messaging/recipientKeyResolver.d.ts.map +1 -0
  191. package/src/lib/interfaces/messaging/recipientKeyResolver.js +13 -0
  192. package/src/lib/interfaces/messaging/recipientKeyResolver.js.map +1 -0
  193. package/src/lib/interfaces/messaging/smimeCertificate.d.ts +99 -0
  194. package/src/lib/interfaces/messaging/smimeCertificate.d.ts.map +1 -0
  195. package/src/lib/interfaces/messaging/smimeCertificate.js +12 -0
  196. package/src/lib/interfaces/messaging/smimeCertificate.js.map +1 -0
  197. package/src/lib/interfaces/responses/adminDashboardResponse.d.ts +8 -0
  198. package/src/lib/interfaces/responses/adminDashboardResponse.d.ts.map +1 -1
  199. package/src/lib/interfaces/responses/communicationResponses.d.ts +25 -0
  200. package/src/lib/interfaces/responses/communicationResponses.d.ts.map +1 -1
  201. package/src/lib/interfaces/storage/documentTypes.d.ts +4 -0
  202. package/src/lib/interfaces/storage/documentTypes.d.ts.map +1 -1
  203. package/src/lib/services/blockService.d.ts +11 -3
  204. package/src/lib/services/blockService.d.ts.map +1 -1
  205. package/src/lib/services/blockService.js +22 -2
  206. package/src/lib/services/blockService.js.map +1 -1
  207. package/src/lib/services/communication/__tests__/mockChatStorageProvider.d.ts +108 -0
  208. package/src/lib/services/communication/__tests__/mockChatStorageProvider.d.ts.map +1 -0
  209. package/src/lib/services/communication/__tests__/mockChatStorageProvider.js +284 -0
  210. package/src/lib/services/communication/__tests__/mockChatStorageProvider.js.map +1 -0
  211. package/src/lib/services/communication/attachmentUtils.d.ts +20 -0
  212. package/src/lib/services/communication/attachmentUtils.d.ts.map +1 -0
  213. package/src/lib/services/communication/attachmentUtils.js +43 -0
  214. package/src/lib/services/communication/attachmentUtils.js.map +1 -0
  215. package/src/lib/services/communication/channelService.d.ts +143 -14
  216. package/src/lib/services/communication/channelService.d.ts.map +1 -1
  217. package/src/lib/services/communication/channelService.js +562 -41
  218. package/src/lib/services/communication/channelService.js.map +1 -1
  219. package/src/lib/services/communication/conversationService.d.ts +91 -4
  220. package/src/lib/services/communication/conversationService.d.ts.map +1 -1
  221. package/src/lib/services/communication/conversationService.js +269 -7
  222. package/src/lib/services/communication/conversationService.js.map +1 -1
  223. package/src/lib/services/communication/eciesKeyEncryptionHandler.d.ts +36 -0
  224. package/src/lib/services/communication/eciesKeyEncryptionHandler.d.ts.map +1 -0
  225. package/src/lib/services/communication/eciesKeyEncryptionHandler.js +30 -0
  226. package/src/lib/services/communication/eciesKeyEncryptionHandler.js.map +1 -0
  227. package/src/lib/services/communication/groupService.d.ts +99 -21
  228. package/src/lib/services/communication/groupService.d.ts.map +1 -1
  229. package/src/lib/services/communication/groupService.js +387 -41
  230. package/src/lib/services/communication/groupService.js.map +1 -1
  231. package/src/lib/services/communication/index.d.ts +6 -1
  232. package/src/lib/services/communication/index.d.ts.map +1 -1
  233. package/src/lib/services/communication/index.js +20 -1
  234. package/src/lib/services/communication/index.js.map +1 -1
  235. package/src/lib/services/communication/keyEpochManager.d.ts +41 -0
  236. package/src/lib/services/communication/keyEpochManager.d.ts.map +1 -0
  237. package/src/lib/services/communication/keyEpochManager.js +59 -0
  238. package/src/lib/services/communication/keyEpochManager.js.map +1 -0
  239. package/src/lib/services/communication/rehydrationHelpers.d.ts +32 -0
  240. package/src/lib/services/communication/rehydrationHelpers.d.ts.map +1 -0
  241. package/src/lib/services/communication/rehydrationHelpers.js +58 -0
  242. package/src/lib/services/communication/rehydrationHelpers.js.map +1 -0
  243. package/src/lib/services/communication/serverService.d.ts +193 -0
  244. package/src/lib/services/communication/serverService.d.ts.map +1 -0
  245. package/src/lib/services/communication/serverService.js +495 -0
  246. package/src/lib/services/communication/serverService.js.map +1 -0
  247. package/src/lib/services/copyOnWrite.service.d.ts +110 -0
  248. package/src/lib/services/copyOnWrite.service.d.ts.map +1 -0
  249. package/src/lib/services/copyOnWrite.service.js +256 -0
  250. package/src/lib/services/copyOnWrite.service.js.map +1 -0
  251. package/src/lib/services/index.d.ts +1 -0
  252. package/src/lib/services/index.d.ts.map +1 -1
  253. package/src/lib/services/index.js +1 -0
  254. package/src/lib/services/index.js.map +1 -1
  255. package/src/lib/services/memberStore.d.ts +17 -1
  256. package/src/lib/services/memberStore.d.ts.map +1 -1
  257. package/src/lib/services/memberStore.js +98 -17
  258. package/src/lib/services/memberStore.js.map +1 -1
  259. package/src/lib/services/messaging/emailEncryptionService.d.ts +162 -0
  260. package/src/lib/services/messaging/emailEncryptionService.d.ts.map +1 -1
  261. package/src/lib/services/messaging/emailEncryptionService.js +293 -0
  262. package/src/lib/services/messaging/emailEncryptionService.js.map +1 -1
  263. package/src/lib/services/messaging/emailMessageService.d.ts +64 -0
  264. package/src/lib/services/messaging/emailMessageService.d.ts.map +1 -1
  265. package/src/lib/services/messaging/emailMessageService.js +142 -13
  266. package/src/lib/services/messaging/emailMessageService.js.map +1 -1
  267. package/src/lib/services/messaging/gpgKeyManager.d.ts +130 -0
  268. package/src/lib/services/messaging/gpgKeyManager.d.ts.map +1 -0
  269. package/src/lib/services/messaging/gpgKeyManager.js +381 -0
  270. package/src/lib/services/messaging/gpgKeyManager.js.map +1 -0
  271. package/src/lib/services/messaging/index.d.ts +3 -0
  272. package/src/lib/services/messaging/index.d.ts.map +1 -1
  273. package/src/lib/services/messaging/index.js +3 -0
  274. package/src/lib/services/messaging/index.js.map +1 -1
  275. package/src/lib/services/messaging/recipientKeyResolver.d.ts +47 -0
  276. package/src/lib/services/messaging/recipientKeyResolver.d.ts.map +1 -0
  277. package/src/lib/services/messaging/recipientKeyResolver.js +132 -0
  278. package/src/lib/services/messaging/recipientKeyResolver.js.map +1 -0
  279. package/src/lib/services/messaging/smimeCertificateManager.d.ts +207 -0
  280. package/src/lib/services/messaging/smimeCertificateManager.d.ts.map +1 -0
  281. package/src/lib/services/messaging/smimeCertificateManager.js +696 -0
  282. package/src/lib/services/messaging/smimeCertificateManager.js.map +1 -0
  283. package/src/lib/utils/index.d.ts +6 -0
  284. package/src/lib/utils/index.d.ts.map +1 -1
  285. package/src/lib/utils/index.js +9 -0
  286. package/src/lib/utils/index.js.map +1 -1
  287. package/src/lib/utils/sortPair.d.ts +6 -0
  288. package/src/lib/utils/sortPair.d.ts.map +1 -0
  289. package/src/lib/utils/sortPair.js +11 -0
  290. package/src/lib/utils/sortPair.js.map +1 -0
@@ -0,0 +1,696 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SmimeCertificateManager = void 0;
4
+ const emailErrorType_1 = require("../../enumerations/messaging/emailErrorType");
5
+ const emailError_1 = require("../../errors/messaging/emailError");
6
+ /**
7
+ * Manages S/MIME X.509 certificate lifecycle: import, export, validation,
8
+ * and CMS encryption/signing operations.
9
+ *
10
+ * Wraps @peculiar/x509 for certificate parsing and pkijs for CMS operations.
11
+ *
12
+ * @see Requirements 6.1, 6.2, 6.4, 6.5, 6.7, 7.1, 8.1, 9.2
13
+ */
14
+ class SmimeCertificateManager {
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ _x509;
17
+ async x509() {
18
+ if (!this._x509) {
19
+ this._x509 = await import('@peculiar/x509');
20
+ }
21
+ return this._x509;
22
+ }
23
+ /**
24
+ * Import a PEM or DER X.509 certificate, validate structure, and extract metadata.
25
+ *
26
+ * @param content - PEM string or DER Uint8Array certificate data
27
+ * @param format - Certificate format: 'pem' or 'der'
28
+ * @returns Metadata extracted from the imported certificate
29
+ * @throws EmailError with SMIME_INVALID_CERT if the certificate is malformed
30
+ *
31
+ * @see Requirement 6.1 — Validate well-formed X.509 certificate in PEM or DER format
32
+ * @see Requirement 6.7 — Import S/MIME certificate from another user or CA
33
+ */
34
+ async importCertificate(content, format) {
35
+ try {
36
+ let cert;
37
+ if (format === 'pem') {
38
+ if (typeof content !== 'string') {
39
+ throw new Error('PEM format requires string content');
40
+ }
41
+ cert = new (await this.x509()).X509Certificate(content);
42
+ }
43
+ else {
44
+ if (!(content instanceof Uint8Array)) {
45
+ throw new Error('DER format requires Uint8Array content');
46
+ }
47
+ // @peculiar/x509 accepts ArrayBuffer for DER data
48
+ cert = new (await this.x509()).X509Certificate(content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength));
49
+ }
50
+ return this.extractMetadata(cert);
51
+ }
52
+ catch (error) {
53
+ if (error instanceof emailError_1.EmailError) {
54
+ throw error;
55
+ }
56
+ throw new emailError_1.EmailError(emailErrorType_1.EmailErrorType.SMIME_INVALID_CERT, `Failed to import S/MIME certificate: ${error instanceof Error ? error.message : String(error)}`);
57
+ }
58
+ }
59
+ /**
60
+ * Import a PKCS#12 bundle (.p12/.pfx), extract certificate and private key.
61
+ *
62
+ * @param data - Raw PKCS#12 binary data
63
+ * @param password - Password to decrypt the PKCS#12 bundle
64
+ * @returns Certificate bundle with PEM certificate, optional PEM private key, and metadata
65
+ * @throws EmailError with SMIME_PKCS12_FAILED on extraction failure
66
+ *
67
+ * @see Requirement 6.2 — Extract and store certificate and private key from PKCS#12
68
+ */
69
+ async importPkcs12(data, password) {
70
+ try {
71
+ const pkijs = await import('pkijs');
72
+ const dataBuffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
73
+ // PFX.fromBER parses the ASN.1 structure directly
74
+ const pfx = pkijs.PFX.fromBER(dataBuffer);
75
+ // Convert password string to ArrayBuffer for pkijs
76
+ const passwordBuffer = SmimeCertificateManager.stringToArrayBuffer(password);
77
+ // Parse the PFX to extract bags
78
+ await pfx.parseInternalValues({
79
+ password: passwordBuffer,
80
+ checkIntegrity: true,
81
+ });
82
+ if (!pfx.parsedValue) {
83
+ throw new Error('Failed to parse PKCS#12 internal values');
84
+ }
85
+ let certificatePem;
86
+ let privateKeyPem;
87
+ // Iterate through authenticated safe contents to find certs and keys
88
+ for (const safeContent of pfx.parsedValue.authenticatedSafe?.parsedValue
89
+ ?.safeContents ?? []) {
90
+ for (const safeBag of safeContent.value?.safeBags ?? []) {
91
+ // Certificate bag (OID 1.2.840.113549.1.12.10.1.3)
92
+ if (safeBag.bagId === '1.2.840.113549.1.12.10.1.3') {
93
+ const certBag = safeBag.bagValue;
94
+ if (certBag &&
95
+ 'parsedValue' in certBag &&
96
+ certBag.parsedValue &&
97
+ typeof certBag
98
+ .parsedValue.toSchema === 'function') {
99
+ const parsedValue = certBag.parsedValue;
100
+ const certDer = parsedValue.toSchema().toBER(false);
101
+ const certX509 = new (await this.x509()).X509Certificate(certDer);
102
+ certificatePem = certX509.toString('pem');
103
+ }
104
+ }
105
+ // PKCS8 shrouded key bag (OID 1.2.840.113549.1.12.10.1.2)
106
+ if (safeBag.bagId === '1.2.840.113549.1.12.10.1.2') {
107
+ const shroudedBag = safeBag.bagValue;
108
+ if (shroudedBag && 'parseInternalValues' in shroudedBag) {
109
+ const keyInfo = await shroudedBag.parseInternalValues({
110
+ password: passwordBuffer,
111
+ });
112
+ const keyDer = keyInfo.toSchema().toBER(false);
113
+ privateKeyPem = SmimeCertificateManager.derToPem(new Uint8Array(keyDer), 'PRIVATE KEY');
114
+ }
115
+ }
116
+ // Plain key bag (OID 1.2.840.113549.1.12.10.1.1)
117
+ if (safeBag.bagId === '1.2.840.113549.1.12.10.1.1') {
118
+ const keyBag = safeBag.bagValue;
119
+ if (keyBag && 'toSchema' in keyBag) {
120
+ const keyDer = keyBag
121
+ .toSchema()
122
+ .toBER(false);
123
+ privateKeyPem = SmimeCertificateManager.derToPem(new Uint8Array(keyDer), 'PRIVATE KEY');
124
+ }
125
+ }
126
+ }
127
+ }
128
+ if (!certificatePem) {
129
+ throw new Error('No certificate found in PKCS#12 bundle');
130
+ }
131
+ const cert = new (await this.x509()).X509Certificate(certificatePem);
132
+ const metadata = await this.extractMetadata(cert);
133
+ return {
134
+ certificatePem,
135
+ privateKeyPem,
136
+ metadata,
137
+ };
138
+ }
139
+ catch (error) {
140
+ if (error instanceof emailError_1.EmailError) {
141
+ throw error;
142
+ }
143
+ throw new emailError_1.EmailError(emailErrorType_1.EmailErrorType.SMIME_PKCS12_FAILED, `Failed to import PKCS#12 bundle: ${error instanceof Error ? error.message : String(error)}`);
144
+ }
145
+ }
146
+ /**
147
+ * Export a certificate as PEM, validating it first.
148
+ *
149
+ * @param certificatePem - PEM-encoded X.509 certificate to export
150
+ * @returns The validated PEM-encoded certificate string
151
+ * @throws EmailError with SMIME_INVALID_CERT if the certificate is malformed
152
+ *
153
+ * @see Requirement 15.2 — PEM round-trip property
154
+ */
155
+ async exportCertificatePem(certificatePem) {
156
+ try {
157
+ const cert = new (await this.x509()).X509Certificate(certificatePem);
158
+ return cert.toString('pem');
159
+ }
160
+ catch (error) {
161
+ if (error instanceof emailError_1.EmailError) {
162
+ throw error;
163
+ }
164
+ throw new emailError_1.EmailError(emailErrorType_1.EmailErrorType.SMIME_INVALID_CERT, `Failed to export S/MIME certificate: ${error instanceof Error ? error.message : String(error)}`);
165
+ }
166
+ }
167
+ /**
168
+ * Validate that content is a well-formed X.509 certificate.
169
+ *
170
+ * @param content - PEM string or DER Uint8Array certificate data
171
+ * @param format - Certificate format: 'pem' or 'der'
172
+ * @returns true if the content is a valid X.509 certificate
173
+ *
174
+ * @see Requirement 6.5 — Reject invalid X.509 certificates
175
+ */
176
+ async validateCertificate(content, format) {
177
+ try {
178
+ if (format === 'pem') {
179
+ if (typeof content !== 'string') {
180
+ return false;
181
+ }
182
+ const trimmed = content.trim();
183
+ if (!trimmed.includes('-----BEGIN CERTIFICATE-----') ||
184
+ !trimmed.includes('-----END CERTIFICATE-----')) {
185
+ return false;
186
+ }
187
+ // Attempt to parse — will throw if malformed
188
+ new (await this.x509()).X509Certificate(content);
189
+ return true;
190
+ }
191
+ else {
192
+ if (!(content instanceof Uint8Array)) {
193
+ return false;
194
+ }
195
+ if (content.length === 0) {
196
+ return false;
197
+ }
198
+ // Attempt to parse — will throw if malformed
199
+ new (await this.x509()).X509Certificate(content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength));
200
+ return true;
201
+ }
202
+ }
203
+ catch {
204
+ return false;
205
+ }
206
+ }
207
+ /**
208
+ * Encrypt content using CMS/PKCS#7 enveloped-data for one or more recipients.
209
+ *
210
+ * Uses pkijs EnvelopedData with AES-256-CBC content encryption and
211
+ * RSA-OAEP (SHA-256) key transport per RFC 5751.
212
+ *
213
+ * @param content - Plaintext content to encrypt
214
+ * @param recipientCertificatesPem - PEM-encoded X.509 certificates of recipients
215
+ * @returns CMS enveloped-data result with DER-encoded content and MIME content type
216
+ * @throws EmailError with SMIME_ENCRYPT_FAILED on any failure
217
+ *
218
+ * @see Requirement 7.1 — CMS/PKCS#7 encryption per RFC 5751
219
+ * @see Requirement 7.3 — S/MIME encrypted message output (application/pkcs7-mime)
220
+ * @see Requirement 7.4 — Error on invalid/expired certificate
221
+ */
222
+ async encrypt(content, recipientCertificatesPem) {
223
+ try {
224
+ const pkijs = await import('pkijs');
225
+ const asn1js = await import('asn1js');
226
+ // Set up the pkijs crypto engine
227
+ SmimeCertificateManager.ensureCryptoEngine(pkijs);
228
+ if (recipientCertificatesPem.length === 0) {
229
+ throw new Error('At least one recipient certificate is required');
230
+ }
231
+ // Parse each recipient PEM certificate into a pkijs Certificate
232
+ const recipientCerts = recipientCertificatesPem.map((pem, index) => {
233
+ try {
234
+ return SmimeCertificateManager.pemToPkijsCertificate(pem, asn1js, pkijs);
235
+ }
236
+ catch (err) {
237
+ throw new Error(`Failed to parse recipient certificate at index ${index}: ${err instanceof Error ? err.message : String(err)}`);
238
+ }
239
+ });
240
+ // Create the EnvelopedData structure
241
+ const envelopedData = new pkijs.EnvelopedData();
242
+ // Add each recipient
243
+ for (const cert of recipientCerts) {
244
+ envelopedData.addRecipientByCertificate(cert, {
245
+ oaepHashAlgorithm: 'SHA-256',
246
+ });
247
+ }
248
+ // Encrypt the content with AES-256-CBC
249
+ await envelopedData.encrypt({ name: 'AES-CBC', length: 256 }, content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength));
250
+ // Wrap in ContentInfo (OID 1.2.840.113549.1.7.3 = enveloped-data)
251
+ const contentInfo = new pkijs.ContentInfo({
252
+ contentType: '1.2.840.113549.1.7.3',
253
+ content: envelopedData.toSchema(),
254
+ });
255
+ // Serialize to DER
256
+ const derBuffer = contentInfo.toSchema().toBER(false);
257
+ const encryptedContent = new Uint8Array(derBuffer);
258
+ return {
259
+ encryptedContent,
260
+ contentType: 'application/pkcs7-mime; smime-type=enveloped-data',
261
+ };
262
+ }
263
+ catch (error) {
264
+ if (error instanceof emailError_1.EmailError) {
265
+ throw error;
266
+ }
267
+ throw new emailError_1.EmailError(emailErrorType_1.EmailErrorType.SMIME_ENCRYPT_FAILED, `S/MIME CMS encryption failed: ${error instanceof Error ? error.message : String(error)}`);
268
+ }
269
+ }
270
+ /**
271
+ * Decrypt CMS/PKCS#7 enveloped-data content using a recipient's certificate and private key.
272
+ *
273
+ * Parses the DER-encoded ContentInfo, extracts the EnvelopedData, and decrypts
274
+ * using the provided certificate and private key.
275
+ *
276
+ * @param encryptedContent - DER-encoded CMS/PKCS#7 enveloped-data
277
+ * @param certificatePem - PEM-encoded X.509 certificate of the recipient
278
+ * @param privateKeyPem - PEM-encoded PKCS#8 private key of the recipient
279
+ * @returns Decrypted plaintext content
280
+ * @throws EmailError with SMIME_DECRYPT_FAILED on any failure
281
+ *
282
+ * @see Requirement 9.1 — S/MIME decryption with private key
283
+ * @see Requirement 9.5 — Error when private key is missing/invalid
284
+ */
285
+ async decrypt(encryptedContent, certificatePem, privateKeyPem) {
286
+ try {
287
+ const pkijs = await import('pkijs');
288
+ const asn1js = await import('asn1js');
289
+ // Set up the pkijs crypto engine
290
+ SmimeCertificateManager.ensureCryptoEngine(pkijs);
291
+ // Parse the DER content into ContentInfo
292
+ const contentBuffer = encryptedContent.buffer.slice(encryptedContent.byteOffset, encryptedContent.byteOffset + encryptedContent.byteLength);
293
+ const asn1Result = asn1js.fromBER(contentBuffer);
294
+ if (asn1Result.offset === -1) {
295
+ throw new Error('Failed to parse ASN.1 structure from encrypted content');
296
+ }
297
+ const contentInfo = new pkijs.ContentInfo({ schema: asn1Result.result });
298
+ const envelopedData = new pkijs.EnvelopedData({
299
+ schema: contentInfo.content,
300
+ });
301
+ // Parse the recipient's certificate
302
+ const recipientCert = SmimeCertificateManager.pemToPkijsCertificate(certificatePem, asn1js, pkijs);
303
+ // Import the recipient's private key
304
+ const privateKey = await SmimeCertificateManager.pemToPrivateKey(privateKeyPem);
305
+ // Find the matching recipient index
306
+ const recipientIndex = SmimeCertificateManager.findRecipientIndex(envelopedData, recipientCert);
307
+ // Decrypt the content
308
+ const decryptedBuffer = await envelopedData.decrypt(recipientIndex, {
309
+ recipientCertificate: recipientCert,
310
+ recipientPrivateKey: privateKey,
311
+ });
312
+ return new Uint8Array(decryptedBuffer);
313
+ }
314
+ catch (error) {
315
+ if (error instanceof emailError_1.EmailError) {
316
+ throw error;
317
+ }
318
+ throw new emailError_1.EmailError(emailErrorType_1.EmailErrorType.SMIME_DECRYPT_FAILED, `S/MIME CMS decryption failed: ${error instanceof Error ? error.message : String(error)}`);
319
+ }
320
+ }
321
+ /**
322
+ * Create a CMS detached signature over the given content.
323
+ *
324
+ * Uses pkijs SignedData with RSASSA-PKCS1-v1_5 / SHA-256 to produce
325
+ * a DER-encoded CMS detached signature conforming to RFC 5751.
326
+ *
327
+ * @param content - The content to sign
328
+ * @param certificatePem - PEM-encoded X.509 certificate of the signer
329
+ * @param privateKeyPem - PEM-encoded PKCS#8 private key of the signer
330
+ * @returns Signature result with DER-encoded CMS signature and signer subject
331
+ * @throws EmailError with SMIME_VERIFY_FAILED on any failure
332
+ *
333
+ * @see Requirement 8.1 — CMS detached signature production
334
+ * @see Requirement 8.2 — Sign before encrypt ordering
335
+ */
336
+ async sign(content, certificatePem, privateKeyPem) {
337
+ try {
338
+ const pkijs = await import('pkijs');
339
+ const asn1js = await import('asn1js');
340
+ // Set up the pkijs crypto engine
341
+ SmimeCertificateManager.ensureCryptoEngine(pkijs);
342
+ // Parse the signer's PEM certificate into a pkijs Certificate
343
+ const signerCert = SmimeCertificateManager.pemToPkijsCertificate(certificatePem, asn1js, pkijs);
344
+ // Import the private key for signing (RSASSA-PKCS1-v1_5 with SHA-256)
345
+ const privateKey = await SmimeCertificateManager.pemToSigningKey(privateKeyPem);
346
+ // Create the SignedData structure with detached content
347
+ const signedData = new pkijs.SignedData({
348
+ encapContentInfo: new pkijs.EncapsulatedContentInfo({
349
+ eContentType: '1.2.840.113549.1.7.1', // id-data
350
+ }),
351
+ certificates: [signerCert],
352
+ signerInfos: [
353
+ new pkijs.SignerInfo({
354
+ sid: new pkijs.IssuerAndSerialNumber({
355
+ issuer: signerCert.issuer,
356
+ serialNumber: signerCert.serialNumber,
357
+ }),
358
+ }),
359
+ ],
360
+ });
361
+ // Sign the content (detached — content is passed as data parameter, not embedded)
362
+ const contentBuffer = content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength);
363
+ await signedData.sign(privateKey, 0, 'SHA-256', contentBuffer);
364
+ // Wrap in ContentInfo (OID 1.2.840.113549.1.7.2 = signed-data)
365
+ const contentInfo = new pkijs.ContentInfo({
366
+ contentType: '1.2.840.113549.1.7.2',
367
+ content: signedData.toSchema(true),
368
+ });
369
+ // Serialize to DER
370
+ const derBuffer = contentInfo.toSchema().toBER(false);
371
+ const signature = new Uint8Array(derBuffer);
372
+ // Extract the signer's subject from the certificate
373
+ const signerCertSubject = SmimeCertificateManager.extractPkijsCertSubject(signerCert);
374
+ return {
375
+ signature,
376
+ signerCertSubject,
377
+ };
378
+ }
379
+ catch (error) {
380
+ if (error instanceof emailError_1.EmailError) {
381
+ throw error;
382
+ }
383
+ throw new emailError_1.EmailError(emailErrorType_1.EmailErrorType.SMIME_VERIFY_FAILED, `S/MIME CMS signing failed: ${error instanceof Error ? error.message : String(error)}`);
384
+ }
385
+ }
386
+ /**
387
+ * Verify a CMS detached signature against the given content and signer certificate.
388
+ *
389
+ * Parses the DER-encoded CMS signature, extracts the SignedData, and verifies
390
+ * using the provided signer certificate as a trusted certificate.
391
+ *
392
+ * @param content - The original content that was signed
393
+ * @param signature - DER-encoded CMS detached signature
394
+ * @param signerCertificatePem - PEM-encoded X.509 certificate of the expected signer
395
+ * @returns Verification result with valid status, signer subject, and optional reason
396
+ *
397
+ * @see Requirement 9.2 — S/MIME signature verification
398
+ * @see Requirement 9.3 — Verification result with signer subject and valid status
399
+ * @see Requirement 9.4 — Verification failure with reason
400
+ */
401
+ async verify(content, signature, signerCertificatePem) {
402
+ try {
403
+ const pkijs = await import('pkijs');
404
+ const asn1js = await import('asn1js');
405
+ // Set up the pkijs crypto engine
406
+ SmimeCertificateManager.ensureCryptoEngine(pkijs);
407
+ // Parse the DER signature into ContentInfo
408
+ const sigBuffer = signature.buffer.slice(signature.byteOffset, signature.byteOffset + signature.byteLength);
409
+ const asn1Result = asn1js.fromBER(sigBuffer);
410
+ if (asn1Result.offset === -1) {
411
+ return {
412
+ valid: false,
413
+ reason: 'Failed to parse ASN.1 structure from signature',
414
+ };
415
+ }
416
+ const contentInfo = new pkijs.ContentInfo({ schema: asn1Result.result });
417
+ const signedData = new pkijs.SignedData({ schema: contentInfo.content });
418
+ // Parse the signer's PEM certificate
419
+ const signerCert = SmimeCertificateManager.pemToPkijsCertificate(signerCertificatePem, asn1js, pkijs);
420
+ // Prepare the content buffer for verification
421
+ const contentBuffer = content.buffer.slice(content.byteOffset, content.byteOffset + content.byteLength);
422
+ // Verify the signature
423
+ const verifyResult = await signedData.verify({
424
+ signer: 0,
425
+ data: contentBuffer,
426
+ trustedCerts: [signerCert],
427
+ });
428
+ const signerSubject = SmimeCertificateManager.extractPkijsCertSubject(signerCert);
429
+ if (verifyResult) {
430
+ return {
431
+ valid: true,
432
+ signerSubject,
433
+ };
434
+ }
435
+ else {
436
+ return {
437
+ valid: false,
438
+ signerSubject,
439
+ reason: 'CMS signature verification returned false',
440
+ };
441
+ }
442
+ }
443
+ catch (error) {
444
+ // Verification failures should return a result, not throw
445
+ const reason = error instanceof Error ? error.message : String(error);
446
+ return {
447
+ valid: false,
448
+ reason: `CMS signature verification failed: ${reason}`,
449
+ };
450
+ }
451
+ }
452
+ /**
453
+ * Extract metadata from an X.509 certificate.
454
+ *
455
+ * @param cert - The parsed X.509 certificate
456
+ * @returns Certificate metadata
457
+ */
458
+ async extractMetadata(cert) {
459
+ const subject = cert.subject;
460
+ const issuer = cert.issuer;
461
+ const serialNumber = cert.serialNumber;
462
+ const validFrom = cert.notBefore;
463
+ const validTo = cert.notAfter;
464
+ const isExpired = validTo < new Date();
465
+ // Extract email addresses from Subject Alternative Name extension
466
+ const emailAddresses = await this.extractEmailAddresses(cert);
467
+ // Compute SHA-256 fingerprint
468
+ const fingerprint = await this.computeFingerprint(cert);
469
+ return {
470
+ subject,
471
+ issuer,
472
+ serialNumber,
473
+ validFrom,
474
+ validTo,
475
+ emailAddresses,
476
+ fingerprint,
477
+ isExpired,
478
+ };
479
+ }
480
+ /**
481
+ * Extract email addresses from the Subject Alternative Name extension.
482
+ *
483
+ * @param cert - The parsed X.509 certificate
484
+ * @returns Array of email addresses found in the SAN extension
485
+ */
486
+ async extractEmailAddresses(cert) {
487
+ const emails = [];
488
+ try {
489
+ const x509Mod = await this.x509();
490
+ const sanExt = cert.getExtension(x509Mod.SubjectAlternativeNameExtension);
491
+ if (sanExt) {
492
+ for (const name of sanExt.names.items) {
493
+ if (name.type === 'email') {
494
+ emails.push(name.value);
495
+ }
496
+ }
497
+ }
498
+ }
499
+ catch {
500
+ // SAN extension not present or malformed — return empty array
501
+ }
502
+ return emails;
503
+ }
504
+ /**
505
+ * Compute SHA-256 fingerprint of a certificate.
506
+ *
507
+ * @param cert - The parsed X.509 certificate
508
+ * @returns Hex-encoded SHA-256 fingerprint
509
+ */
510
+ async computeFingerprint(cert) {
511
+ const thumbprint = await cert.getThumbprint('SHA-256');
512
+ return Array.from(new Uint8Array(thumbprint))
513
+ .map((b) => b.toString(16).padStart(2, '0'))
514
+ .join('');
515
+ }
516
+ /**
517
+ * Convert a string to ArrayBuffer (UTF-16 encoding as expected by pkijs).
518
+ *
519
+ * @param str - The string to convert
520
+ * @returns ArrayBuffer with UTF-16 encoded string
521
+ */
522
+ static stringToArrayBuffer(str) {
523
+ const buf = new ArrayBuffer(str.length * 2);
524
+ const view = new Uint16Array(buf);
525
+ for (let i = 0; i < str.length; i++) {
526
+ view[i] = str.charCodeAt(i);
527
+ }
528
+ return buf;
529
+ }
530
+ /**
531
+ * Convert DER-encoded data to PEM format.
532
+ *
533
+ * @param der - DER-encoded binary data
534
+ * @param tag - PEM tag (e.g., 'CERTIFICATE', 'PRIVATE KEY')
535
+ * @returns PEM-encoded string
536
+ */
537
+ static derToPem(der, tag) {
538
+ const base64 = Buffer.from(der).toString('base64');
539
+ const lines = [];
540
+ for (let i = 0; i < base64.length; i += 64) {
541
+ lines.push(base64.substring(i, i + 64));
542
+ }
543
+ return `-----BEGIN ${tag}-----\n${lines.join('\n')}\n-----END ${tag}-----`;
544
+ }
545
+ /**
546
+ * Ensure the pkijs crypto engine is initialized with WebCrypto.
547
+ *
548
+ * @param pkijs - The pkijs module
549
+ */
550
+ static ensureCryptoEngine(pkijs) {
551
+ const crypto = globalThis.crypto;
552
+ pkijs.setEngine('webcrypto', new pkijs.CryptoEngine({ name: 'webcrypto', crypto }));
553
+ }
554
+ /**
555
+ * Parse a PEM-encoded X.509 certificate into a pkijs Certificate object.
556
+ *
557
+ * @param pem - PEM-encoded certificate string
558
+ * @param asn1js - The asn1js module
559
+ * @param pkijs - The pkijs module
560
+ * @returns pkijs Certificate instance
561
+ */
562
+ static pemToPkijsCertificate(pem, asn1js, pkijs) {
563
+ // Strip PEM headers and decode base64
564
+ const b64 = pem
565
+ .replace(/-----BEGIN CERTIFICATE-----/g, '')
566
+ .replace(/-----END CERTIFICATE-----/g, '')
567
+ .replace(/\s/g, '');
568
+ const derBuffer = Buffer.from(b64, 'base64');
569
+ const arrayBuffer = derBuffer.buffer.slice(derBuffer.byteOffset, derBuffer.byteOffset + derBuffer.byteLength);
570
+ const asn1Result = asn1js.fromBER(arrayBuffer);
571
+ if (asn1Result.offset === -1) {
572
+ throw new Error('Failed to parse certificate ASN.1 structure');
573
+ }
574
+ return new pkijs.Certificate({ schema: asn1Result.result });
575
+ }
576
+ /**
577
+ * Import a PEM-encoded PKCS#8 private key as a CryptoKey for signing.
578
+ *
579
+ * Uses RSASSA-PKCS1-v1_5 with SHA-256, which is the standard algorithm
580
+ * for CMS/S/MIME digital signatures.
581
+ *
582
+ * @param pem - PEM-encoded PKCS#8 private key
583
+ * @returns CryptoKey suitable for signing
584
+ */
585
+ static async pemToSigningKey(pem) {
586
+ const b64 = pem
587
+ .replace(/-----BEGIN PRIVATE KEY-----/g, '')
588
+ .replace(/-----END PRIVATE KEY-----/g, '')
589
+ .replace(/\s/g, '');
590
+ const derBuffer = Buffer.from(b64, 'base64');
591
+ const arrayBuffer = derBuffer.buffer.slice(derBuffer.byteOffset, derBuffer.byteOffset + derBuffer.byteLength);
592
+ return globalThis.crypto.subtle.importKey('pkcs8', arrayBuffer, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, true, ['sign']);
593
+ }
594
+ /**
595
+ * Extract the subject distinguished name string from a pkijs Certificate.
596
+ *
597
+ * @param cert - pkijs Certificate instance
598
+ * @returns Subject DN string (e.g., "CN=John Doe, O=Example Corp")
599
+ */
600
+ static extractPkijsCertSubject(cert) {
601
+ try {
602
+ return cert.subject.typesAndValues
603
+ .map((tv) => {
604
+ const oid = tv.type;
605
+ const value = tv.value.valueBlock.value;
606
+ // Map common OIDs to readable names
607
+ const oidMap = {
608
+ '2.5.4.3': 'CN',
609
+ '2.5.4.6': 'C',
610
+ '2.5.4.7': 'L',
611
+ '2.5.4.8': 'ST',
612
+ '2.5.4.10': 'O',
613
+ '2.5.4.11': 'OU',
614
+ '1.2.840.113549.1.9.1': 'E',
615
+ };
616
+ const name = oidMap[oid] || oid;
617
+ return `${name}=${value}`;
618
+ })
619
+ .join(', ');
620
+ }
621
+ catch {
622
+ return 'Unknown Subject';
623
+ }
624
+ }
625
+ /**
626
+ * Import a PEM-encoded PKCS#8 private key as a CryptoKey for decryption.
627
+ *
628
+ * Tries RSA-OAEP first, then falls back to ECDH if RSA import fails.
629
+ *
630
+ * @param pem - PEM-encoded PKCS#8 private key
631
+ * @returns CryptoKey suitable for decryption
632
+ */
633
+ static async pemToPrivateKey(pem) {
634
+ const b64 = pem
635
+ .replace(/-----BEGIN PRIVATE KEY-----/g, '')
636
+ .replace(/-----END PRIVATE KEY-----/g, '')
637
+ .replace(/\s/g, '');
638
+ const derBuffer = Buffer.from(b64, 'base64');
639
+ const arrayBuffer = derBuffer.buffer.slice(derBuffer.byteOffset, derBuffer.byteOffset + derBuffer.byteLength);
640
+ // Try RSA-OAEP first (most common for S/MIME)
641
+ try {
642
+ return await globalThis.crypto.subtle.importKey('pkcs8', arrayBuffer, { name: 'RSA-OAEP', hash: 'SHA-256' }, true, ['decrypt']);
643
+ }
644
+ catch {
645
+ // Fall back to RSA-OAEP with SHA-1 (legacy)
646
+ try {
647
+ return await globalThis.crypto.subtle.importKey('pkcs8', arrayBuffer, { name: 'RSA-OAEP', hash: 'SHA-1' }, true, ['decrypt']);
648
+ }
649
+ catch {
650
+ throw new Error('Failed to import private key: unsupported key algorithm');
651
+ }
652
+ }
653
+ }
654
+ /**
655
+ * Find the recipient index in an EnvelopedData structure that matches
656
+ * the given certificate.
657
+ *
658
+ * @param envelopedData - The pkijs EnvelopedData structure
659
+ * @param recipientCert - The pkijs Certificate to match
660
+ * @returns The zero-based index of the matching recipient
661
+ */
662
+ static findRecipientIndex(envelopedData, recipientCert) {
663
+ // Try each recipient index — pkijs will match internally
664
+ // For KeyTransRecipientInfo, match by issuer+serialNumber
665
+ const recipientInfos = envelopedData.recipientInfos;
666
+ if (recipientInfos.length === 0) {
667
+ throw new Error('No recipient info found in enveloped data');
668
+ }
669
+ // Get the recipient cert's serial number for matching
670
+ const certSerial = recipientCert.serialNumber.valueBlock.toString();
671
+ const certIssuer = recipientCert.issuer.typesAndValues
672
+ .map((tv) => `${tv.type}=${tv.value.valueBlock.value}`)
673
+ .join(',');
674
+ for (let i = 0; i < recipientInfos.length; i++) {
675
+ const ri = recipientInfos[i];
676
+ // KeyTransRecipientInfo has rid (RecipientIdentifier)
677
+ if (ri.value && 'rid' in ri.value) {
678
+ const rid = ri.value.rid;
679
+ if (rid.issuer && rid.serialNumber) {
680
+ const riSerial = rid.serialNumber.valueBlock.toString();
681
+ const riIssuer = rid.issuer.typesAndValues
682
+ .map((tv) => `${tv.type}=${tv.value.valueBlock.value}`)
683
+ .join(',');
684
+ if (riSerial === certSerial && riIssuer === certIssuer) {
685
+ return i;
686
+ }
687
+ }
688
+ }
689
+ }
690
+ // If no exact match found, default to index 0 (single recipient case)
691
+ // pkijs decrypt will validate the match internally
692
+ return 0;
693
+ }
694
+ }
695
+ exports.SmimeCertificateManager = SmimeCertificateManager;
696
+ //# sourceMappingURL=smimeCertificateManager.js.map