@twin.org/crypto 0.0.1-next.9 → 0.0.2-next.3

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 (38) hide show
  1. package/dist/cjs/index.cjs +304 -1
  2. package/dist/esm/index.mjs +304 -3
  3. package/dist/types/address/bip44.d.ts +15 -0
  4. package/dist/types/ciphers/rsa.d.ts +63 -0
  5. package/dist/types/curves/ed25519.d.ts +12 -0
  6. package/dist/types/helpers/pemHelper.d.ts +19 -0
  7. package/dist/types/index.d.ts +2 -0
  8. package/docs/changelog.md +400 -1
  9. package/docs/reference/classes/Bech32.md +15 -7
  10. package/docs/reference/classes/Bip32Path.md +17 -9
  11. package/docs/reference/classes/Bip39.md +28 -12
  12. package/docs/reference/classes/Bip44.md +121 -21
  13. package/docs/reference/classes/Blake2b.md +35 -17
  14. package/docs/reference/classes/Blake3.md +28 -14
  15. package/docs/reference/classes/ChaCha20Poly1305.md +18 -8
  16. package/docs/reference/classes/Ed25519.md +65 -9
  17. package/docs/reference/classes/HmacSha1.md +17 -9
  18. package/docs/reference/classes/HmacSha256.md +26 -12
  19. package/docs/reference/classes/HmacSha512.md +38 -16
  20. package/docs/reference/classes/Hotp.md +9 -5
  21. package/docs/reference/classes/PasswordGenerator.md +6 -4
  22. package/docs/reference/classes/PasswordValidator.md +23 -11
  23. package/docs/reference/classes/Pbkdf2.md +27 -11
  24. package/docs/reference/classes/PemHelper.md +69 -0
  25. package/docs/reference/classes/RSA.md +213 -0
  26. package/docs/reference/classes/Secp256k1.md +21 -9
  27. package/docs/reference/classes/Sha1.md +11 -7
  28. package/docs/reference/classes/Sha256.md +17 -9
  29. package/docs/reference/classes/Sha3.md +23 -11
  30. package/docs/reference/classes/Sha512.md +23 -11
  31. package/docs/reference/classes/Slip0010.md +27 -11
  32. package/docs/reference/classes/Totp.md +42 -16
  33. package/docs/reference/classes/X25519.md +9 -5
  34. package/docs/reference/classes/Zip215.md +12 -6
  35. package/docs/reference/index.md +2 -0
  36. package/docs/reference/type-aliases/KeyType.md +1 -1
  37. package/locales/en.json +4 -0
  38. package/package.json +13 -13
@@ -8,6 +8,7 @@ var blake2b = require('@noble/hashes/blake2b');
8
8
  var bip32 = require('@scure/bip32');
9
9
  var slip10_js = require('micro-key-producer/slip10.js');
10
10
  var chacha = require('@noble/ciphers/chacha');
11
+ var node_crypto = require('node:crypto');
11
12
  var blake3 = require('@noble/hashes/blake3');
12
13
  var hmac = require('@noble/hashes/hmac');
13
14
  var sha1 = require('@noble/hashes/sha1');
@@ -188,6 +189,39 @@ class Ed25519 {
188
189
  return false;
189
190
  }
190
191
  }
192
+ /**
193
+ * Convert a private key in PKCS8 format.
194
+ * @param privateKey The private key to convert.
195
+ * @returns The private key in PKCS8 format.
196
+ */
197
+ static async privateKeyToPkcs8(privateKey) {
198
+ core.Guards.uint8Array(Ed25519._CLASS_NAME, "privateKey", privateKey);
199
+ if (privateKey.length !== Ed25519.PRIVATE_KEY_SIZE) {
200
+ throw new core.GeneralError(Ed25519._CLASS_NAME, "privateKeyLength", {
201
+ requiredSize: Ed25519.PRIVATE_KEY_SIZE,
202
+ actualSize: privateKey.length
203
+ });
204
+ }
205
+ // crypto.subtle.importKey does not support Ed25519 keys in raw format.
206
+ // We need to convert the key to PKCS8 format before importing.
207
+ // The PKCS8 format is the raw key prefixed with the ASN.1 sequence for an Ed25519 private key.
208
+ // The ASN.1 sequence is 48 46 02 01 00 30 05 06 03 2b 65 70 04 20 04 20 (0x302e020100300506032b657004220420)
209
+ const pkcs8Prefix = new Uint8Array([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32]);
210
+ const fullKey = core.Uint8ArrayHelper.concat([pkcs8Prefix, privateKey]);
211
+ return crypto.subtle.importKey("pkcs8", fullKey, "Ed25519", true, ["sign"]);
212
+ }
213
+ /**
214
+ * Convert a crypto key to raw private key.
215
+ * @param cryptoKey The crypto key to convert.
216
+ * @returns The raw private key.
217
+ */
218
+ static async pkcs8ToPrivateKey(cryptoKey) {
219
+ core.Guards.defined(Ed25519._CLASS_NAME, "cryptoKey", cryptoKey);
220
+ // crypto.subtle.exportKey does not support Ed25519 keys in raw format.
221
+ // so we export as PKCS8 and remove the ASN.1 sequence prefix.
222
+ const pkcs8Bytes = await crypto.subtle.exportKey("pkcs8", cryptoKey);
223
+ return new Uint8Array(pkcs8Bytes.slice(16));
224
+ }
191
225
  }
192
226
 
193
227
  // Copyright 2024 IOTA Stiftung.
@@ -592,6 +626,24 @@ class Bip44 {
592
626
  static basePath(coinType) {
593
627
  return `m/44'/${coinType}'`;
594
628
  }
629
+ /**
630
+ * Generate an address from the seed and parts.
631
+ * @param seed The account seed.
632
+ * @param keyType The key type.
633
+ * @param coinType The coin type.
634
+ * @param accountIndex The account index.
635
+ * @param isInternal Is this an internal address.
636
+ * @param addressIndex The address index.
637
+ * @returns The generated path and the associated keypair.
638
+ */
639
+ static address(seed, keyType, coinType, accountIndex, isInternal, addressIndex) {
640
+ const keyPair = Bip44.keyPair(seed, keyType, coinType, accountIndex, isInternal, addressIndex);
641
+ const addressData = Blake2b.sum256(keyPair.publicKey);
642
+ return {
643
+ address: core.Converter.bytesToHex(addressData, true),
644
+ ...keyPair
645
+ };
646
+ }
595
647
  /**
596
648
  * Generate a bech32 address from the seed and parts.
597
649
  * @param seed The account seed.
@@ -666,8 +718,215 @@ class ChaCha20Poly1305 {
666
718
  // Copyright 2024 IOTA Stiftung.
667
719
  // SPDX-License-Identifier: Apache-2.0.
668
720
  /**
669
- * This is a TypeScript port of https://github.com/katzenpost/core/blob/master/crypto/extra25519/extra25519.go.
721
+ * Implementation of the RSA cipher.
670
722
  */
723
+ class RSA {
724
+ /**
725
+ * Runtime name for the class.
726
+ * @internal
727
+ */
728
+ static _CLASS_NAME = "RSA";
729
+ /**
730
+ * The public key for encryption.
731
+ * @internal
732
+ */
733
+ _publicKey;
734
+ /**
735
+ * The private key for decryption.
736
+ * @internal
737
+ */
738
+ _privateKey;
739
+ /**
740
+ * The block size for encryption.
741
+ * @internal
742
+ */
743
+ _blockSize;
744
+ /**
745
+ * The key size for decryption.
746
+ * @internal
747
+ */
748
+ _keySize;
749
+ /**
750
+ * Create a new instance of RSA.
751
+ * @param publicKey The public key for encryption (DER format as Uint8Array).
752
+ * @param privateKey The private key for decryption (DER format as Uint8Array).
753
+ */
754
+ constructor(publicKey, privateKey) {
755
+ core.Guards.uint8Array(RSA._CLASS_NAME, "publicKey", publicKey);
756
+ this._publicKey = node_crypto.createPublicKey({
757
+ key: Buffer.from(publicKey),
758
+ format: "der",
759
+ type: "spki"
760
+ });
761
+ if (!core.Is.empty(privateKey)) {
762
+ this._privateKey = node_crypto.createPrivateKey({
763
+ key: Buffer.from(privateKey),
764
+ format: "der",
765
+ type: "pkcs8"
766
+ });
767
+ }
768
+ // Get modulus length in bits from key details
769
+ const modulusLengthBits = this._publicKey.asymmetricKeyDetails?.modulusLength;
770
+ if (core.Is.empty(modulusLengthBits)) {
771
+ throw new core.GeneralError(RSA._CLASS_NAME, "invalidKeySize");
772
+ }
773
+ // Convert bits to bytes
774
+ this._keySize = Math.ceil(modulusLengthBits / 8);
775
+ // Calculate block size for OAEP with SHA-256
776
+ // Formula: keySize - 2 * hashLength - 2
777
+ const hashLength = 32; // SHA-256 = 32 bytes
778
+ // eslint-disable-next-line no-mixed-operators
779
+ this._blockSize = this._keySize - 2 * hashLength - 2;
780
+ }
781
+ /**
782
+ * Generate a new RSA key pair in PKCS8 format.
783
+ * @param modulusLength The key size in bits (default: 2048).
784
+ * @returns The public and private keys as Uint8Array.
785
+ */
786
+ static generateKeyPair(modulusLength = 2048) {
787
+ const { publicKey, privateKey } = node_crypto.generateKeyPairSync("rsa", {
788
+ modulusLength,
789
+ publicKeyEncoding: {
790
+ type: "spki",
791
+ format: "der"
792
+ },
793
+ privateKeyEncoding: {
794
+ type: "pkcs8",
795
+ format: "der"
796
+ }
797
+ });
798
+ return {
799
+ publicKey: new Uint8Array(publicKey),
800
+ privateKey: new Uint8Array(privateKey)
801
+ };
802
+ }
803
+ /**
804
+ * Convert a PKCS1 key to a PKCS8 key.
805
+ * @param pkcs1Key The PKCS1 key as Uint8Array.
806
+ * @returns The PKCS8 key as Uint8Array.
807
+ */
808
+ static convertPkcs1ToPkcs8(pkcs1Key) {
809
+ core.Guards.uint8Array(RSA._CLASS_NAME, "pkcs1Key", pkcs1Key);
810
+ const privateKey = node_crypto.createPrivateKey({
811
+ key: Buffer.from(pkcs1Key),
812
+ format: "der",
813
+ type: "pkcs1"
814
+ });
815
+ return new Uint8Array(privateKey.export({
816
+ format: "der",
817
+ type: "pkcs8"
818
+ }));
819
+ }
820
+ /**
821
+ * Break the private key down in to its components.
822
+ * @param pkcs8Key The PKCS8 key as Uint8Array.
823
+ * @returns The key components.
824
+ */
825
+ static getPrivateKeyComponents(pkcs8Key) {
826
+ core.Guards.uint8Array(RSA._CLASS_NAME, "pkcs8Key", pkcs8Key);
827
+ const privateKey = node_crypto.createPrivateKey({
828
+ key: Buffer.from(pkcs8Key),
829
+ format: "der",
830
+ type: "pkcs8"
831
+ });
832
+ const jwk = privateKey.export({ format: "jwk" });
833
+ return {
834
+ n: this.base64UrlToBigInt(jwk.n),
835
+ e: this.base64UrlToBigInt(jwk.e),
836
+ d: this.base64UrlToBigInt(jwk.d),
837
+ p: this.base64UrlToBigInt(jwk.p),
838
+ q: this.base64UrlToBigInt(jwk.q),
839
+ dp: this.base64UrlToBigInt(jwk.dp),
840
+ dq: this.base64UrlToBigInt(jwk.dq),
841
+ qi: this.base64UrlToBigInt(jwk.qi)
842
+ };
843
+ }
844
+ /**
845
+ * Break the public key down in to its components.
846
+ * @param spkiKey The SPKI key as Uint8Array.
847
+ * @returns The key components.
848
+ */
849
+ static getPublicKeyComponents(spkiKey) {
850
+ core.Guards.uint8Array(RSA._CLASS_NAME, "spkiKey", spkiKey);
851
+ const publicKey = node_crypto.createPublicKey({
852
+ key: Buffer.from(spkiKey),
853
+ format: "der",
854
+ type: "spki"
855
+ });
856
+ const jwk = publicKey.export({ format: "jwk" });
857
+ return {
858
+ n: this.base64UrlToBigInt(jwk.n),
859
+ e: this.base64UrlToBigInt(jwk.e)
860
+ };
861
+ }
862
+ /**
863
+ * Convert base64 encoded data to a big int.
864
+ * @param bytes The bytes to convert.
865
+ * @returns The bigint representation of the bytes.
866
+ * @internal
867
+ */
868
+ static base64UrlToBigInt(base64Url) {
869
+ if (core.Is.empty(base64Url)) {
870
+ return BigInt(0);
871
+ }
872
+ const bytes = core.Converter.base64UrlToBytes(base64Url);
873
+ const hexString = Array.from(bytes)
874
+ .map(byte => byte.toString(16).padStart(2, "0"))
875
+ .join("");
876
+ return BigInt(`0x${hexString}`);
877
+ }
878
+ /**
879
+ * Encrypt the data.
880
+ * @param data The data to encrypt.
881
+ * @returns The data encrypted.
882
+ */
883
+ encrypt(data) {
884
+ core.Guards.uint8Array(RSA._CLASS_NAME, "data", data);
885
+ if (data.length === 0) {
886
+ return new Uint8Array(0);
887
+ }
888
+ const blocks = [];
889
+ // Split data into blocks of block size
890
+ for (let i = 0; i < data.length; i += this._blockSize) {
891
+ const block = data.slice(i, i + this._blockSize);
892
+ const encryptedBlock = node_crypto.publicEncrypt({
893
+ key: this._publicKey,
894
+ padding: node_crypto.constants.RSA_PKCS1_OAEP_PADDING
895
+ }, block);
896
+ blocks.push(encryptedBlock);
897
+ }
898
+ return core.Uint8ArrayHelper.concat(blocks);
899
+ }
900
+ /**
901
+ * Decrypt the data.
902
+ * @param data The data to decrypt.
903
+ * @returns The data decrypted.
904
+ * @throws GeneralError If no private key is provided.
905
+ */
906
+ decrypt(data) {
907
+ core.Guards.uint8Array(RSA._CLASS_NAME, "data", data);
908
+ if (core.Is.empty(this._privateKey)) {
909
+ throw new core.GeneralError(RSA._CLASS_NAME, "noPrivateKey");
910
+ }
911
+ if (data.length === 0) {
912
+ return new Uint8Array(0);
913
+ }
914
+ const blocks = [];
915
+ // Split encrypted data into blocks of key size
916
+ for (let i = 0; i < data.length; i += this._keySize) {
917
+ const block = data.slice(i, i + this._keySize);
918
+ const decryptedBlock = node_crypto.privateDecrypt({
919
+ key: this._privateKey,
920
+ padding: node_crypto.constants.RSA_PKCS1_OAEP_PADDING
921
+ }, block);
922
+ blocks.push(decryptedBlock);
923
+ }
924
+ return core.Uint8ArrayHelper.concat(blocks);
925
+ }
926
+ }
927
+
928
+ // Copyright 2024 IOTA Stiftung.
929
+ // SPDX-License-Identifier: Apache-2.0.
671
930
  /**
672
931
  * Implementation of X25519.
673
932
  */
@@ -1475,6 +1734,48 @@ class Sha512 {
1475
1734
  }
1476
1735
  }
1477
1736
 
1737
+ // Copyright 2024 IOTA Stiftung.
1738
+ // SPDX-License-Identifier: Apache-2.0.
1739
+ /**
1740
+ * Helper class for working with PEM (Privacy-Enhanced Mail) formatted data.
1741
+ */
1742
+ class PemHelper {
1743
+ /**
1744
+ * Runtime name for the class.
1745
+ * @internal
1746
+ */
1747
+ static _CLASS_NAME = "PemHelper";
1748
+ /**
1749
+ * Strip the PEM content of its headers, footers, and newlines.
1750
+ * @param pemContent The PEM content to strip.
1751
+ * @returns The stripped PEM content in bas64 format.
1752
+ */
1753
+ static stripPemMarkers(pemContent) {
1754
+ core.Guards.string(PemHelper._CLASS_NAME, "pemContent", pemContent);
1755
+ return pemContent
1756
+ .replace(/-----BEGIN.*-----/, "")
1757
+ .replace(/-----END.*-----/, "")
1758
+ .replace(/\n/g, "")
1759
+ .trim();
1760
+ }
1761
+ /**
1762
+ * Format the PEM content to have a specific line length.
1763
+ * @param marker The marker for the PEM content, e.g. RSA PRIVATE KEY
1764
+ * @param base64Content The base64 content to format.
1765
+ * @param lineLength The length of each line in the PEM content, default is 64 characters.
1766
+ * @returns The formatted PEM content.
1767
+ */
1768
+ static formatPem(marker, base64Content, lineLength = 64) {
1769
+ core.Guards.stringValue(PemHelper._CLASS_NAME, "marker", marker);
1770
+ core.Guards.stringBase64(PemHelper._CLASS_NAME, "base64Content", base64Content);
1771
+ const lines = [];
1772
+ for (let i = 0; i < base64Content.length; i += lineLength) {
1773
+ lines.push(base64Content.slice(i, i + lineLength));
1774
+ }
1775
+ return [`-----BEGIN ${marker}-----`, ...lines, `-----END ${marker}-----`].join("\n");
1776
+ }
1777
+ }
1778
+
1478
1779
  // Copyright 2024 IOTA Stiftung.
1479
1780
  // SPDX-License-Identifier: Apache-2.0.
1480
1781
  /**
@@ -1761,6 +2062,8 @@ exports.KeyType = KeyType;
1761
2062
  exports.PasswordGenerator = PasswordGenerator;
1762
2063
  exports.PasswordValidator = PasswordValidator;
1763
2064
  exports.Pbkdf2 = Pbkdf2;
2065
+ exports.PemHelper = PemHelper;
2066
+ exports.RSA = RSA;
1764
2067
  exports.Secp256k1 = Secp256k1;
1765
2068
  exports.Sha1 = Sha1;
1766
2069
  exports.Sha256 = Sha256;
@@ -1,11 +1,12 @@
1
1
  import { bech32 } from '@scure/base';
2
- import { Guards, BaseError, GeneralError, Is, Converter, GuardError, Base32, RandomHelper, Validation } from '@twin.org/core';
2
+ import { Guards, BaseError, GeneralError, Is, Uint8ArrayHelper, Converter, GuardError, Base32, RandomHelper, Validation } from '@twin.org/core';
3
3
  import { ed25519, edwardsToMontgomeryPriv, edwardsToMontgomeryPub } from '@noble/curves/ed25519';
4
4
  import { secp256k1 } from '@noble/curves/secp256k1';
5
5
  import { blake2b } from '@noble/hashes/blake2b';
6
6
  import { HDKey as HDKey$1 } from '@scure/bip32';
7
7
  import { HDKey } from 'micro-key-producer/slip10.js';
8
8
  import { chacha20poly1305 } from '@noble/ciphers/chacha';
9
+ import { createPublicKey, createPrivateKey, generateKeyPairSync, publicEncrypt, constants, privateDecrypt } from 'node:crypto';
9
10
  import { blake3 } from '@noble/hashes/blake3';
10
11
  import { hmac } from '@noble/hashes/hmac';
11
12
  import { sha1 } from '@noble/hashes/sha1';
@@ -166,6 +167,39 @@ class Ed25519 {
166
167
  return false;
167
168
  }
168
169
  }
170
+ /**
171
+ * Convert a private key in PKCS8 format.
172
+ * @param privateKey The private key to convert.
173
+ * @returns The private key in PKCS8 format.
174
+ */
175
+ static async privateKeyToPkcs8(privateKey) {
176
+ Guards.uint8Array(Ed25519._CLASS_NAME, "privateKey", privateKey);
177
+ if (privateKey.length !== Ed25519.PRIVATE_KEY_SIZE) {
178
+ throw new GeneralError(Ed25519._CLASS_NAME, "privateKeyLength", {
179
+ requiredSize: Ed25519.PRIVATE_KEY_SIZE,
180
+ actualSize: privateKey.length
181
+ });
182
+ }
183
+ // crypto.subtle.importKey does not support Ed25519 keys in raw format.
184
+ // We need to convert the key to PKCS8 format before importing.
185
+ // The PKCS8 format is the raw key prefixed with the ASN.1 sequence for an Ed25519 private key.
186
+ // The ASN.1 sequence is 48 46 02 01 00 30 05 06 03 2b 65 70 04 20 04 20 (0x302e020100300506032b657004220420)
187
+ const pkcs8Prefix = new Uint8Array([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32]);
188
+ const fullKey = Uint8ArrayHelper.concat([pkcs8Prefix, privateKey]);
189
+ return crypto.subtle.importKey("pkcs8", fullKey, "Ed25519", true, ["sign"]);
190
+ }
191
+ /**
192
+ * Convert a crypto key to raw private key.
193
+ * @param cryptoKey The crypto key to convert.
194
+ * @returns The raw private key.
195
+ */
196
+ static async pkcs8ToPrivateKey(cryptoKey) {
197
+ Guards.defined(Ed25519._CLASS_NAME, "cryptoKey", cryptoKey);
198
+ // crypto.subtle.exportKey does not support Ed25519 keys in raw format.
199
+ // so we export as PKCS8 and remove the ASN.1 sequence prefix.
200
+ const pkcs8Bytes = await crypto.subtle.exportKey("pkcs8", cryptoKey);
201
+ return new Uint8Array(pkcs8Bytes.slice(16));
202
+ }
169
203
  }
170
204
 
171
205
  // Copyright 2024 IOTA Stiftung.
@@ -570,6 +604,24 @@ class Bip44 {
570
604
  static basePath(coinType) {
571
605
  return `m/44'/${coinType}'`;
572
606
  }
607
+ /**
608
+ * Generate an address from the seed and parts.
609
+ * @param seed The account seed.
610
+ * @param keyType The key type.
611
+ * @param coinType The coin type.
612
+ * @param accountIndex The account index.
613
+ * @param isInternal Is this an internal address.
614
+ * @param addressIndex The address index.
615
+ * @returns The generated path and the associated keypair.
616
+ */
617
+ static address(seed, keyType, coinType, accountIndex, isInternal, addressIndex) {
618
+ const keyPair = Bip44.keyPair(seed, keyType, coinType, accountIndex, isInternal, addressIndex);
619
+ const addressData = Blake2b.sum256(keyPair.publicKey);
620
+ return {
621
+ address: Converter.bytesToHex(addressData, true),
622
+ ...keyPair
623
+ };
624
+ }
573
625
  /**
574
626
  * Generate a bech32 address from the seed and parts.
575
627
  * @param seed The account seed.
@@ -644,8 +696,215 @@ class ChaCha20Poly1305 {
644
696
  // Copyright 2024 IOTA Stiftung.
645
697
  // SPDX-License-Identifier: Apache-2.0.
646
698
  /**
647
- * This is a TypeScript port of https://github.com/katzenpost/core/blob/master/crypto/extra25519/extra25519.go.
699
+ * Implementation of the RSA cipher.
648
700
  */
701
+ class RSA {
702
+ /**
703
+ * Runtime name for the class.
704
+ * @internal
705
+ */
706
+ static _CLASS_NAME = "RSA";
707
+ /**
708
+ * The public key for encryption.
709
+ * @internal
710
+ */
711
+ _publicKey;
712
+ /**
713
+ * The private key for decryption.
714
+ * @internal
715
+ */
716
+ _privateKey;
717
+ /**
718
+ * The block size for encryption.
719
+ * @internal
720
+ */
721
+ _blockSize;
722
+ /**
723
+ * The key size for decryption.
724
+ * @internal
725
+ */
726
+ _keySize;
727
+ /**
728
+ * Create a new instance of RSA.
729
+ * @param publicKey The public key for encryption (DER format as Uint8Array).
730
+ * @param privateKey The private key for decryption (DER format as Uint8Array).
731
+ */
732
+ constructor(publicKey, privateKey) {
733
+ Guards.uint8Array(RSA._CLASS_NAME, "publicKey", publicKey);
734
+ this._publicKey = createPublicKey({
735
+ key: Buffer.from(publicKey),
736
+ format: "der",
737
+ type: "spki"
738
+ });
739
+ if (!Is.empty(privateKey)) {
740
+ this._privateKey = createPrivateKey({
741
+ key: Buffer.from(privateKey),
742
+ format: "der",
743
+ type: "pkcs8"
744
+ });
745
+ }
746
+ // Get modulus length in bits from key details
747
+ const modulusLengthBits = this._publicKey.asymmetricKeyDetails?.modulusLength;
748
+ if (Is.empty(modulusLengthBits)) {
749
+ throw new GeneralError(RSA._CLASS_NAME, "invalidKeySize");
750
+ }
751
+ // Convert bits to bytes
752
+ this._keySize = Math.ceil(modulusLengthBits / 8);
753
+ // Calculate block size for OAEP with SHA-256
754
+ // Formula: keySize - 2 * hashLength - 2
755
+ const hashLength = 32; // SHA-256 = 32 bytes
756
+ // eslint-disable-next-line no-mixed-operators
757
+ this._blockSize = this._keySize - 2 * hashLength - 2;
758
+ }
759
+ /**
760
+ * Generate a new RSA key pair in PKCS8 format.
761
+ * @param modulusLength The key size in bits (default: 2048).
762
+ * @returns The public and private keys as Uint8Array.
763
+ */
764
+ static generateKeyPair(modulusLength = 2048) {
765
+ const { publicKey, privateKey } = generateKeyPairSync("rsa", {
766
+ modulusLength,
767
+ publicKeyEncoding: {
768
+ type: "spki",
769
+ format: "der"
770
+ },
771
+ privateKeyEncoding: {
772
+ type: "pkcs8",
773
+ format: "der"
774
+ }
775
+ });
776
+ return {
777
+ publicKey: new Uint8Array(publicKey),
778
+ privateKey: new Uint8Array(privateKey)
779
+ };
780
+ }
781
+ /**
782
+ * Convert a PKCS1 key to a PKCS8 key.
783
+ * @param pkcs1Key The PKCS1 key as Uint8Array.
784
+ * @returns The PKCS8 key as Uint8Array.
785
+ */
786
+ static convertPkcs1ToPkcs8(pkcs1Key) {
787
+ Guards.uint8Array(RSA._CLASS_NAME, "pkcs1Key", pkcs1Key);
788
+ const privateKey = createPrivateKey({
789
+ key: Buffer.from(pkcs1Key),
790
+ format: "der",
791
+ type: "pkcs1"
792
+ });
793
+ return new Uint8Array(privateKey.export({
794
+ format: "der",
795
+ type: "pkcs8"
796
+ }));
797
+ }
798
+ /**
799
+ * Break the private key down in to its components.
800
+ * @param pkcs8Key The PKCS8 key as Uint8Array.
801
+ * @returns The key components.
802
+ */
803
+ static getPrivateKeyComponents(pkcs8Key) {
804
+ Guards.uint8Array(RSA._CLASS_NAME, "pkcs8Key", pkcs8Key);
805
+ const privateKey = createPrivateKey({
806
+ key: Buffer.from(pkcs8Key),
807
+ format: "der",
808
+ type: "pkcs8"
809
+ });
810
+ const jwk = privateKey.export({ format: "jwk" });
811
+ return {
812
+ n: this.base64UrlToBigInt(jwk.n),
813
+ e: this.base64UrlToBigInt(jwk.e),
814
+ d: this.base64UrlToBigInt(jwk.d),
815
+ p: this.base64UrlToBigInt(jwk.p),
816
+ q: this.base64UrlToBigInt(jwk.q),
817
+ dp: this.base64UrlToBigInt(jwk.dp),
818
+ dq: this.base64UrlToBigInt(jwk.dq),
819
+ qi: this.base64UrlToBigInt(jwk.qi)
820
+ };
821
+ }
822
+ /**
823
+ * Break the public key down in to its components.
824
+ * @param spkiKey The SPKI key as Uint8Array.
825
+ * @returns The key components.
826
+ */
827
+ static getPublicKeyComponents(spkiKey) {
828
+ Guards.uint8Array(RSA._CLASS_NAME, "spkiKey", spkiKey);
829
+ const publicKey = createPublicKey({
830
+ key: Buffer.from(spkiKey),
831
+ format: "der",
832
+ type: "spki"
833
+ });
834
+ const jwk = publicKey.export({ format: "jwk" });
835
+ return {
836
+ n: this.base64UrlToBigInt(jwk.n),
837
+ e: this.base64UrlToBigInt(jwk.e)
838
+ };
839
+ }
840
+ /**
841
+ * Convert base64 encoded data to a big int.
842
+ * @param bytes The bytes to convert.
843
+ * @returns The bigint representation of the bytes.
844
+ * @internal
845
+ */
846
+ static base64UrlToBigInt(base64Url) {
847
+ if (Is.empty(base64Url)) {
848
+ return BigInt(0);
849
+ }
850
+ const bytes = Converter.base64UrlToBytes(base64Url);
851
+ const hexString = Array.from(bytes)
852
+ .map(byte => byte.toString(16).padStart(2, "0"))
853
+ .join("");
854
+ return BigInt(`0x${hexString}`);
855
+ }
856
+ /**
857
+ * Encrypt the data.
858
+ * @param data The data to encrypt.
859
+ * @returns The data encrypted.
860
+ */
861
+ encrypt(data) {
862
+ Guards.uint8Array(RSA._CLASS_NAME, "data", data);
863
+ if (data.length === 0) {
864
+ return new Uint8Array(0);
865
+ }
866
+ const blocks = [];
867
+ // Split data into blocks of block size
868
+ for (let i = 0; i < data.length; i += this._blockSize) {
869
+ const block = data.slice(i, i + this._blockSize);
870
+ const encryptedBlock = publicEncrypt({
871
+ key: this._publicKey,
872
+ padding: constants.RSA_PKCS1_OAEP_PADDING
873
+ }, block);
874
+ blocks.push(encryptedBlock);
875
+ }
876
+ return Uint8ArrayHelper.concat(blocks);
877
+ }
878
+ /**
879
+ * Decrypt the data.
880
+ * @param data The data to decrypt.
881
+ * @returns The data decrypted.
882
+ * @throws GeneralError If no private key is provided.
883
+ */
884
+ decrypt(data) {
885
+ Guards.uint8Array(RSA._CLASS_NAME, "data", data);
886
+ if (Is.empty(this._privateKey)) {
887
+ throw new GeneralError(RSA._CLASS_NAME, "noPrivateKey");
888
+ }
889
+ if (data.length === 0) {
890
+ return new Uint8Array(0);
891
+ }
892
+ const blocks = [];
893
+ // Split encrypted data into blocks of key size
894
+ for (let i = 0; i < data.length; i += this._keySize) {
895
+ const block = data.slice(i, i + this._keySize);
896
+ const decryptedBlock = privateDecrypt({
897
+ key: this._privateKey,
898
+ padding: constants.RSA_PKCS1_OAEP_PADDING
899
+ }, block);
900
+ blocks.push(decryptedBlock);
901
+ }
902
+ return Uint8ArrayHelper.concat(blocks);
903
+ }
904
+ }
905
+
906
+ // Copyright 2024 IOTA Stiftung.
907
+ // SPDX-License-Identifier: Apache-2.0.
649
908
  /**
650
909
  * Implementation of X25519.
651
910
  */
@@ -1453,6 +1712,48 @@ class Sha512 {
1453
1712
  }
1454
1713
  }
1455
1714
 
1715
+ // Copyright 2024 IOTA Stiftung.
1716
+ // SPDX-License-Identifier: Apache-2.0.
1717
+ /**
1718
+ * Helper class for working with PEM (Privacy-Enhanced Mail) formatted data.
1719
+ */
1720
+ class PemHelper {
1721
+ /**
1722
+ * Runtime name for the class.
1723
+ * @internal
1724
+ */
1725
+ static _CLASS_NAME = "PemHelper";
1726
+ /**
1727
+ * Strip the PEM content of its headers, footers, and newlines.
1728
+ * @param pemContent The PEM content to strip.
1729
+ * @returns The stripped PEM content in bas64 format.
1730
+ */
1731
+ static stripPemMarkers(pemContent) {
1732
+ Guards.string(PemHelper._CLASS_NAME, "pemContent", pemContent);
1733
+ return pemContent
1734
+ .replace(/-----BEGIN.*-----/, "")
1735
+ .replace(/-----END.*-----/, "")
1736
+ .replace(/\n/g, "")
1737
+ .trim();
1738
+ }
1739
+ /**
1740
+ * Format the PEM content to have a specific line length.
1741
+ * @param marker The marker for the PEM content, e.g. RSA PRIVATE KEY
1742
+ * @param base64Content The base64 content to format.
1743
+ * @param lineLength The length of each line in the PEM content, default is 64 characters.
1744
+ * @returns The formatted PEM content.
1745
+ */
1746
+ static formatPem(marker, base64Content, lineLength = 64) {
1747
+ Guards.stringValue(PemHelper._CLASS_NAME, "marker", marker);
1748
+ Guards.stringBase64(PemHelper._CLASS_NAME, "base64Content", base64Content);
1749
+ const lines = [];
1750
+ for (let i = 0; i < base64Content.length; i += lineLength) {
1751
+ lines.push(base64Content.slice(i, i + lineLength));
1752
+ }
1753
+ return [`-----BEGIN ${marker}-----`, ...lines, `-----END ${marker}-----`].join("\n");
1754
+ }
1755
+ }
1756
+
1456
1757
  // Copyright 2024 IOTA Stiftung.
1457
1758
  // SPDX-License-Identifier: Apache-2.0.
1458
1759
  /**
@@ -1723,4 +2024,4 @@ class PasswordValidator {
1723
2024
  }
1724
2025
  }
1725
2026
 
1726
- export { Bech32, Bip32Path, Bip39, Bip44, Blake2b, Blake3, ChaCha20Poly1305, Ed25519, HmacSha1, HmacSha256, HmacSha512, Hotp, KeyType, PasswordGenerator, PasswordValidator, Pbkdf2, Secp256k1, Sha1, Sha256, Sha3, Sha512, Slip0010, Totp, X25519, Zip215 };
2027
+ export { Bech32, Bip32Path, Bip39, Bip44, Blake2b, Blake3, ChaCha20Poly1305, Ed25519, HmacSha1, HmacSha256, HmacSha512, Hotp, KeyType, PasswordGenerator, PasswordValidator, Pbkdf2, PemHelper, RSA, Secp256k1, Sha1, Sha256, Sha3, Sha512, Slip0010, Totp, X25519, Zip215 };