samlesa 2.12.3 → 2.12.5

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.

Potentially problematic release.


This version of samlesa might be problematic. Click here for more details.

Files changed (60) hide show
  1. package/build/.idea/workspace.xml +13 -1
  2. package/build/index.js +54 -64
  3. package/build/index.js.map +1 -1
  4. package/build/src/api.js +24 -23
  5. package/build/src/api.js.map +1 -1
  6. package/build/src/binding-post.js +358 -368
  7. package/build/src/binding-post.js.map +1 -1
  8. package/build/src/binding-redirect.js +333 -332
  9. package/build/src/binding-redirect.js.map +1 -1
  10. package/build/src/binding-simplesign.js +222 -232
  11. package/build/src/binding-simplesign.js.map +1 -1
  12. package/build/src/entity-idp.js +130 -130
  13. package/build/src/entity-idp.js.map +1 -1
  14. package/build/src/entity-sp.js +96 -96
  15. package/build/src/entity-sp.js.map +1 -1
  16. package/build/src/entity.js +225 -235
  17. package/build/src/entity.js.map +1 -1
  18. package/build/src/extractor.js +385 -369
  19. package/build/src/extractor.js.map +1 -1
  20. package/build/src/flow.js +320 -319
  21. package/build/src/flow.js.map +1 -1
  22. package/build/src/libsaml.js +665 -641
  23. package/build/src/libsaml.js.map +1 -1
  24. package/build/src/metadata-idp.js +127 -127
  25. package/build/src/metadata-idp.js.map +1 -1
  26. package/build/src/metadata-sp.js +231 -231
  27. package/build/src/metadata-sp.js.map +1 -1
  28. package/build/src/metadata.js +166 -176
  29. package/build/src/metadata.js.map +1 -1
  30. package/build/src/types.js +11 -11
  31. package/build/src/urn.js +212 -212
  32. package/build/src/urn.js.map +1 -1
  33. package/build/src/utility.js +292 -248
  34. package/build/src/utility.js.map +1 -1
  35. package/build/src/validator.js +27 -26
  36. package/build/src/validator.js.map +1 -1
  37. package/package.json +8 -10
  38. package/src/api.ts +1 -1
  39. package/src/binding-redirect.ts +83 -64
  40. package/src/extractor.ts +23 -5
  41. package/src/libsaml.ts +95 -62
  42. package/src/utility.ts +147 -76
  43. package/types/index.d.ts +10 -10
  44. package/types/src/api.d.ts +13 -13
  45. package/types/src/binding-post.d.ts +46 -46
  46. package/types/src/binding-redirect.d.ts +52 -52
  47. package/types/src/binding-simplesign.d.ts +39 -39
  48. package/types/src/entity-idp.d.ts +42 -42
  49. package/types/src/entity-sp.d.ts +36 -36
  50. package/types/src/entity.d.ts +101 -99
  51. package/types/src/extractor.d.ts +25 -25
  52. package/types/src/flow.d.ts +6 -6
  53. package/types/src/libsaml.d.ts +200 -210
  54. package/types/src/metadata-idp.d.ts +24 -24
  55. package/types/src/metadata-sp.d.ts +36 -36
  56. package/types/src/metadata.d.ts +59 -57
  57. package/types/src/types.d.ts +129 -127
  58. package/types/src/urn.d.ts +194 -194
  59. package/types/src/utility.d.ts +134 -134
  60. package/types/src/validator.d.ts +3 -3
package/src/libsaml.ts CHANGED
@@ -3,12 +3,12 @@
3
3
  * @author tngan
4
4
  * @desc A simple library including some common functions
5
5
  */
6
-
6
+ import { createSign, createPrivateKey,createVerify } from 'node:crypto';
7
7
  import utility, { flattenDeep, isString } from './utility.js';
8
8
  import { algorithms, wording, namespace } from './urn.js';
9
9
  import { select } from 'xpath';
10
10
  import { MetadataInterface } from './metadata.js';
11
- import nrsa, { SigningSchemeHash } from 'node-rsa';
11
+
12
12
  import { SignedXml } from 'xml-crypto';
13
13
  import * as xmlenc from 'xml-encryption';
14
14
  import { extract } from './extractor.js';
@@ -22,7 +22,22 @@ const signatureAlgorithms = algorithms.signature;
22
22
  const digestAlgorithms = algorithms.digest;
23
23
  const certUse = wording.certUse;
24
24
  const urlParams = wording.urlParams;
25
+ /**
26
+ * 算法名称映射表 (兼容 X.509 和 SAML 规范)
27
+ */
28
+ function mapSignAlgorithm(algorithm: string): string {
29
+ const algorithmMap = {
30
+ 'rsa-sha1': 'RSA-SHA1',
31
+ 'rsa-sha256': 'RSA-SHA256',
32
+ 'rsa-sha384': 'RSA-SHA384',
33
+ 'rsa-sha512': 'RSA-SHA512',
34
+ 'ecdsa-sha256': 'ECDSA-SHA256',
35
+ 'ecdsa-sha384': 'ECDSA-SHA384',
36
+ 'ecdsa-sha512': 'ECDSA-SHA512'
37
+ };
25
38
 
39
+ return algorithmMap[algorithm.toLowerCase()] || algorithm;
40
+ }
26
41
  export interface SignatureConstructor {
27
42
  rawSamlMessage: string;
28
43
  referenceTagXPath?: string;
@@ -139,6 +154,11 @@ const libSaml = () => {
139
154
  'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256': 'pkcs1-sha256',
140
155
  'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512': 'pkcs1-sha512',
141
156
  };
157
+ const nrsaAliasMappingForNode = {
158
+ 'http://www.w3.org/2000/09/xmldsig#rsa-sha1': 'RSA-SHA1',
159
+ 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256': 'RSA-SHA256',
160
+ 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512': 'RSA-SHA512',
161
+ };
142
162
  /**
143
163
  * @desc Default login request template
144
164
  * @type {LoginRequestTemplate}
@@ -189,20 +209,15 @@ const libSaml = () => {
189
209
  const defaultLogoutResponseTemplate = {
190
210
  context: '<samlp:LogoutResponse xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}" InResponseTo="{InResponseTo}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="{StatusCode}"/></samlp:Status></samlp:LogoutResponse>',
191
211
  };
192
- /**
193
- * @private
194
- * @desc Get the signing scheme alias by signature algorithms, used by the node-rsa module
195
- * @param {string} sigAlg signature algorithm
196
- * @return {string/null} signing algorithm short-hand for the module node-rsa
197
- */
198
- function getSigningScheme(sigAlg?: string): SigningSchemeHash {
212
+
213
+ function getSigningSchemeForNode(sigAlg?: string){
199
214
  if (sigAlg) {
200
- const algAlias = nrsaAliasMapping[sigAlg];
215
+ const algAlias = nrsaAliasMappingForNode[sigAlg];
201
216
  if (!(algAlias === undefined)) {
202
217
  return algAlias;
203
218
  }
204
219
  }
205
- return nrsaAliasMapping[signatureAlgorithms.RSA_SHA1];
220
+ return nrsaAliasMappingForNode[signatureAlgorithms.RSA_SHA1];
206
221
  }
207
222
  /**
208
223
  * @private
@@ -376,7 +391,7 @@ const libSaml = () => {
376
391
  */
377
392
  verifySignature(xml: string, opts: SignatureVerifierOptions) {
378
393
  const { dom } = getContext();
379
- const doc = dom.parseFromString(xml);
394
+ const doc = dom.parseFromString(xml,'application/xml') as unknown as Node;
380
395
 
381
396
  const docParser = new DOMParser();
382
397
  // In order to avoid the wrapping attack, we have changed to use absolute xpath instead of naively fetching the signature element
@@ -397,6 +412,7 @@ const libSaml = () => {
397
412
  selection = selection.concat(assertionSignatureNode);
398
413
 
399
414
  // try to catch potential wrapping attack
415
+ // @ts-expect-error misssing Node properties are not needed
400
416
  if (wrappingElementNode.length !== 0) {
401
417
  throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK');
402
418
  }
@@ -476,14 +492,14 @@ const libSaml = () => {
476
492
  // attempt is made to get the signed Reference as a string();
477
493
  // note, we don't have access to the actual signedReferences API unfortunately
478
494
  // mainly a sanity check here for SAML. (Although ours would still be secure, if multiple references are used)
479
- if (!(sig.getReferences().length >= 1)) {
495
+ if (!(sig.getSignedReferences().length >= 1)) {
480
496
  throw new Error('NO_SIGNATURE_REFERENCES')
481
497
  }
482
498
  const signedVerifiedXML = sig.getSignedReferences()[0];
483
- const rootNode = docParser.parseFromString(signedVerifiedXML, 'text/xml').documentElement;
499
+ const rootNode = docParser.parseFromString(signedVerifiedXML, 'text/xml').documentElement as unknown as Element;
484
500
  // process the verified signature:
485
501
  // case 1, rootSignedDoc is a response:
486
- if (rootNode.localName === 'Response') {
502
+ if (rootNode?.localName === 'Response') {
487
503
 
488
504
  // try getting the Xml from the first assertion
489
505
  const EncryptedAssertions = select(
@@ -496,17 +512,18 @@ const libSaml = () => {
496
512
  );
497
513
 
498
514
  // now we can process the assertion as an assertion
515
+ // @ts-expect-error misssing Node properties are not needed
499
516
  if (EncryptedAssertions.length === 1) {
500
-
517
+ // @ts-expect-error misssing Node properties are not needed
501
518
  return [true, EncryptedAssertions[0].toString()];
502
519
  }
503
-
520
+ // @ts-expect-error misssing Node properties are not needed
504
521
  if (assertions.length === 1) {
505
-
522
+ // @ts-expect-error misssing Node properties are not needed
506
523
  return [true, assertions[0].toString()];
507
524
  }
508
525
 
509
- } else if (rootNode.localName === 'Assertion') {
526
+ } else if (rootNode?.localName === 'Assertion') {
510
527
  return [true, rootNode.toString()];
511
528
  } else {
512
529
  return [true, null]; // signature is valid. But there is no assertion node here. It could be metadata node, hence return null
@@ -587,42 +604,51 @@ const libSaml = () => {
587
604
  }],
588
605
  };
589
606
  },
607
+
590
608
  /**
591
- * @desc Constructs SAML message
592
- * @param {string} octetString see "Bindings for the OASIS Security Assertion Markup Language (SAML V2.0)" P.17/46
593
- * @param {string} key declares the pem-formatted private key
594
- * @param {string} passphrase passphrase of private key [optional]
595
- * @param {string} signingAlgorithm signing algorithm
596
- * @return {string} message signature
597
- */
609
+ * SAML 消息签名 (符合 SAML V2.0 绑定规范)
610
+ * @param octetString - 要签名的原始数据 (OCTET STRING)
611
+ * @param key - PEM 格式私钥
612
+ * @param passphrase - 私钥密码 (如果有加密)
613
+ * @param isBase64 - 是否返回 base64 编码 (默认 true)
614
+ * @param signingAlgorithm - 签名算法 (默认 'rsa-sha256')
615
+ * @returns 消息签名
616
+ */
617
+
598
618
  constructMessageSignature(
599
- octetString: string,
600
- key: string,
601
- passphrase?: string,
602
- isBase64?: boolean,
603
- signingAlgorithm?: string
604
- ) {
605
- // Default returning base64 encoded signature
606
- // Embed with node-rsa module
607
- const decryptedKey = new nrsa(
608
- utility.readPrivateKey(key, passphrase),
609
- undefined,
610
- {
611
- signingScheme: getSigningScheme(signingAlgorithm),
612
- }
613
- );
614
- const signature = decryptedKey.sign(octetString);
615
- // Use private key to sign data
616
- return isBase64 !== false ? signature.toString('base64') : signature;
617
- },
618
- /**
619
- * @desc Verifies message signature
620
- * @param {Metadata} metadata metadata object of identity provider or service provider
621
- * @param {string} octetString see "Bindings for the OASIS Security Assertion Markup Language (SAML V2.0)" P.17/46
622
- * @param {string} signature context of XML signature
623
- * @param {string} verifyAlgorithm algorithm used to verify
624
- * @return {boolean} verification result
625
- */
619
+ octetString: string | Buffer,
620
+ key: string | Buffer,
621
+ passphrase?: string,
622
+ isBase64: boolean = true,
623
+ signingAlgorithm: string = nrsaAliasMappingForNode[signatureAlgorithms.RSA_SHA1]
624
+ ): string | Buffer {
625
+ try {
626
+ // 1. 标准化输入数据
627
+ const inputData = Buffer.isBuffer(octetString)
628
+ ? octetString
629
+ : Buffer.from(octetString, 'utf8');
630
+ // 2. 创建签名器并设置算
631
+ const signingAlgorithmValue = getSigningSchemeForNode(signingAlgorithm)
632
+ const signer = createSign(signingAlgorithmValue)
633
+
634
+ // 3. 加载私钥
635
+ const privateKey = createPrivateKey({
636
+ key: key,
637
+ format: 'pem',
638
+ passphrase: passphrase,
639
+ encoding: 'utf8'
640
+ });
641
+ signer.write(octetString);
642
+ signer.end();
643
+ const signature = signer.sign(privateKey, 'base64');
644
+ console.log(signature.toString());
645
+ console.log('dayingyixia')
646
+ // 5. 处理编码输出
647
+ return isBase64 ? signature.toString() : signature;
648
+ } catch (error) {
649
+ throw new Error(`SAML 签名失败: ${error.message}`);
650
+ }
651
+ },
626
652
  verifyMessageSignature(
627
653
  metadata,
628
654
  octetString: string,
@@ -630,10 +656,17 @@ const libSaml = () => {
630
656
  verifyAlgorithm?: string
631
657
  ) {
632
658
  const signCert = metadata.getX509Certificate(certUse.signing);
633
- const signingScheme = getSigningScheme(verifyAlgorithm);
634
- const key = new nrsa(utility.getPublicKeyPemFromCertificate(signCert), 'public', { signingScheme });
635
- return key.verify(Buffer.from(octetString), Buffer.from(signature));
659
+ const signingScheme = getSigningSchemeForNode(verifyAlgorithm);
660
+ const verifier = createVerify(signingScheme);
661
+ verifier.update(octetString);
662
+ const isValid = verifier.verify(utility.getPublicKeyPemFromCertificate(signCert), Buffer.isBuffer(signature) ? signature : Buffer.from(signature, 'base64'));
663
+ console.log(isValid);
664
+ console.log('验证结果-------------')
665
+ return isValid
666
+
636
667
  },
668
+
669
+
637
670
  /**
638
671
  * @desc Get the public key in string format
639
672
  * @param {string} x509Certificate certificate
@@ -668,7 +701,7 @@ const libSaml = () => {
668
701
  const sourceEntitySetting = sourceEntity.entitySetting;
669
702
  const targetEntityMetadata = targetEntity.entityMeta;
670
703
  const { dom } = getContext();
671
- const doc = dom.parseFromString(xml);
704
+ const doc = dom.parseFromString(xml,'application/xml') as unknown as Node;
672
705
  const assertions = select("//*[local-name(.)='Assertion']", doc) as Node[];
673
706
  if (!Array.isArray(assertions) || assertions.length === 0) {
674
707
  throw new Error('ERR_NO_ASSERTION');
@@ -701,8 +734,8 @@ const libSaml = () => {
701
734
  return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
702
735
  }
703
736
  const { encryptedAssertion: encAssertionPrefix } = sourceEntitySetting.tagPrefix;
704
- const encryptAssertionDoc = dom.parseFromString(`<${encAssertionPrefix}:EncryptedAssertion xmlns:${encAssertionPrefix}="${namespace.names.assertion}">${res}</${encAssertionPrefix}:EncryptedAssertion>`);
705
- doc.documentElement.replaceChild(encryptAssertionDoc.documentElement, rawAssertionNode);
737
+ const encryptAssertionDoc = dom.parseFromString(`<${encAssertionPrefix}:EncryptedAssertion xmlns:${encAssertionPrefix}="${namespace.names.assertion}">${res}</${encAssertionPrefix}:EncryptedAssertion>`,'application/xml');
738
+ (doc as Document)?.replaceChild(encryptAssertionDoc.documentElement as unknown as Node, rawAssertionNode) ;
706
739
  return resolve(utility.base64Encode(doc.toString()));
707
740
  });
708
741
  } else {
@@ -727,7 +760,7 @@ const libSaml = () => {
727
760
  // Perform encryption depends on the setting of where the message is sent, default is false
728
761
  const hereSetting = here.entitySetting;
729
762
  const { dom } = getContext();
730
- const doc = dom.parseFromString(entireXML);
763
+ const doc = dom.parseFromString(entireXML,'application/xml') as unknown as Node;
731
764
  const encryptedAssertions = select("/*[contains(local-name(), 'Response')]/*[local-name(.)='EncryptedAssertion']", doc) as Node[];
732
765
  if (!Array.isArray(encryptedAssertions) || encryptedAssertions.length === 0) {
733
766
  throw new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION');
@@ -746,8 +779,8 @@ const libSaml = () => {
746
779
  if (!res) {
747
780
  return reject(new Error('ERR_UNDEFINED_ENCRYPTED_ASSERTION'));
748
781
  }
749
- const rawAssertionDoc = dom.parseFromString(res);
750
- doc.documentElement.replaceChild(rawAssertionDoc.documentElement, encAssertionNode);
782
+ const rawAssertionDoc = dom.parseFromString(res,'application/xml') ;
783
+ (doc as Document)?.replaceChild(rawAssertionDoc?.documentElement as unknown as Node , encAssertionNode);
751
784
  return resolve([doc.toString(), res]);
752
785
  });
753
786
  });
package/src/utility.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
- * @file utility.ts
3
- * @author tngan
4
- * @desc Library for some common functions (e.g. de/inflation, en/decoding)
5
- */
6
- import { pki, util, asn1 } from 'node-forge';
7
- import { X509Certificate } from 'node:crypto';
2
+ * @file utility.ts
3
+ * @author tngan
4
+ * @desc Library for some common functions (e.g. de/inflation, en/decoding)
5
+ */
6
+
7
+ import {X509Certificate,createPrivateKey } from 'node:crypto';
8
8
 
9
9
 
10
- import { inflate, deflate } from 'pako';
10
+ import {inflate, deflate} from 'pako';
11
11
 
12
12
  const BASE64_STR = 'base64';
13
13
 
@@ -36,6 +36,7 @@ export function zipObject(arr1: string[], arr2: any[], skipDuplicated = true) {
36
36
 
37
37
  }, {});
38
38
  }
39
+
39
40
  /**
40
41
  * @desc Alternative to lodash.flattenDeep
41
42
  * @reference https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_flattendeep
@@ -43,9 +44,10 @@ export function zipObject(arr1: string[], arr2: any[], skipDuplicated = true) {
43
44
  */
44
45
  export function flattenDeep(input: any[]) {
45
46
  return Array.isArray(input)
46
- ? input.reduce( (a, b) => a.concat(flattenDeep(b)) , [])
47
- : [input];
47
+ ? input.reduce((a, b) => a.concat(flattenDeep(b)), [])
48
+ : [input];
48
49
  }
50
+
49
51
  /**
50
52
  * @desc Alternative to lodash.last
51
53
  * @reference https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_last
@@ -54,6 +56,7 @@ export function flattenDeep(input: any[]) {
54
56
  export function last(input: any[]) {
55
57
  return input.slice(-1)[0];
56
58
  }
59
+
57
60
  /**
58
61
  * @desc Alternative to lodash.uniq
59
62
  * @reference https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_uniq
@@ -61,8 +64,9 @@ export function last(input: any[]) {
61
64
  */
62
65
  export function uniq(input: string[]) {
63
66
  const set = new Set(input);
64
- return [... set];
67
+ return [...set];
65
68
  }
69
+
66
70
  /**
67
71
  * @desc Alternative to lodash.get
68
72
  * @reference https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_get
@@ -72,8 +76,9 @@ export function uniq(input: string[]) {
72
76
  */
73
77
  export function get(obj, path, defaultValue) {
74
78
  return path.split('.')
75
- .reduce((a, c) => (a && a[c] ? a[c] : (defaultValue || null)), obj);
79
+ .reduce((a, c) => (a && a[c] ? a[c] : (defaultValue || null)), obj);
76
80
  }
81
+
77
82
  /**
78
83
  * @desc Check if the input is string
79
84
  * @param {any} input
@@ -81,107 +86,123 @@ export function get(obj, path, defaultValue) {
81
86
  export function isString(input: any) {
82
87
  return typeof input === 'string';
83
88
  }
89
+
84
90
  /**
85
- * @desc Encode string with base64 format
86
- * @param {string} message plain-text message
87
- * @return {string} base64 encoded string
88
- */
91
+ * @desc Encode string with base64 format
92
+ * @param {string} message plain-text message
93
+ * @return {string} base64 encoded string
94
+ */
89
95
  function base64Encode(message: string | number[]) {
90
96
  return Buffer.from(message as string).toString(BASE64_STR);
91
97
  }
98
+
92
99
  /**
93
- * @desc Decode string from base64 format
94
- * @param {string} base64Message encoded string
95
- * @param {boolean} isBytes determine the return value type (True: bytes False: string)
96
- * @return {bytes/string} decoded bytes/string depends on isBytes, default is {string}
97
- */
100
+ * @desc Decode string from base64 format
101
+ * @param {string} base64Message encoded string
102
+ * @param {boolean} isBytes determine the return value type (True: bytes False: string)
103
+ * @return {bytes/string} decoded bytes/string depends on isBytes, default is {string}
104
+ */
98
105
  export function base64Decode(base64Message: string, isBytes?: boolean): string | Buffer {
99
106
  const bytes = Buffer.from(base64Message, BASE64_STR);
100
107
  return Boolean(isBytes) ? bytes : bytes.toString();
101
108
  }
109
+
102
110
  /**
103
- * @desc Compress the string
104
- * @param {string} message
105
- * @return {string} compressed string
106
- */
111
+ * @desc Compress the string
112
+ * @param {string} message
113
+ * @return {string} compressed string
114
+ */
107
115
  function deflateString(message: string): number[] {
108
116
  const input = Array.prototype.map.call(message, char => char.charCodeAt(0));
109
- return Array.from(deflate(input, { raw: true }));
117
+ return Array.from(deflate(input, {raw: true}));
110
118
  }
119
+
111
120
  /**
112
- * @desc Decompress the compressed string
113
- * @param {string} compressedString
114
- * @return {string} decompressed string
115
- */
121
+ * @desc Decompress the compressed string
122
+ * @param {string} compressedString
123
+ * @return {string} decompressed string
124
+ */
116
125
  export function inflateString(compressedString: string): string {
117
126
  const inputBuffer = Buffer.from(compressedString, BASE64_STR);
118
127
  const input = Array.prototype.map.call(inputBuffer.toString('binary'), char => char.charCodeAt(0));
119
- return Array.from(inflate(input, { raw: true }))
128
+ return Array.from(inflate(input, {raw: true}))
120
129
  .map((byte: number) => String.fromCharCode(byte))
121
130
  .join('');
122
131
  }
132
+
123
133
  /**
124
- * @desc Abstract the normalizeCerString and normalizePemString
125
- * @param {buffer} File stream or string
126
- * @param {string} String for header and tail
127
- * @return {string} A formatted certificate string
128
- */
134
+ * @desc Abstract the normalizeCerString and normalizePemString
135
+ * @param {buffer} File stream or string
136
+ * @param {string} String for header and tail
137
+ * @return {string} A formatted certificate string
138
+ */
129
139
  function _normalizeCerString(bin: string | Buffer, format: string) {
130
140
  return bin.toString().replace(/\n/g, '').replace(/\r/g, '').replace(`-----BEGIN ${format}-----`, '').replace(`-----END ${format}-----`, '').replace(/ /g, '').replace(/\t/g, '');
131
141
  }
142
+
132
143
  /**
133
- * @desc Parse the .cer to string format without line break, header and footer
134
- * @param {string} certString declares the certificate contents
135
- * @return {string} certificiate in string format
136
- */
144
+ * @desc Parse the .cer to string format without line break, header and footer
145
+ * @param {string} certString declares the certificate contents
146
+ * @return {string} certificiate in string format
147
+ */
137
148
  function normalizeCerString(certString: string | Buffer) {
138
149
  return _normalizeCerString(certString, 'CERTIFICATE');
139
150
  }
151
+
140
152
  /**
141
- * @desc Normalize the string in .pem format without line break, header and footer
142
- * @param {string} pemString
143
- * @return {string} private key in string format
144
- */
153
+ * @desc Normalize the string in .pem format without line break, header and footer
154
+ * @param {string} pemString
155
+ * @return {string} private key in string format
156
+ */
145
157
  function normalizePemString(pemString: string | Buffer) {
146
158
  return _normalizeCerString(pemString.toString(), 'RSA PRIVATE KEY');
147
159
  }
160
+
148
161
  /**
149
- * @desc Return the complete URL
150
- * @param {object} req HTTP request
151
- * @return {string} URL
152
- */
162
+ * @desc Return the complete URL
163
+ * @param {object} req HTTP request
164
+ * @return {string} URL
165
+ */
153
166
  function getFullURL(req) {
154
167
  return `${req.protocol}://${req.get('host')}${req.originalUrl}`;
155
168
  }
169
+
156
170
  /**
157
- * @desc Parse input string, return default value if it is undefined
158
- * @param {string/boolean}
159
- * @return {boolean}
160
- */
171
+ * @desc Parse input string, return default value if it is undefined
172
+ * @param {string/boolean}
173
+ * @return {boolean}
174
+ */
161
175
  function parseString(str, defaultValue = '') {
162
176
  return str || defaultValue;
163
177
  }
178
+
164
179
  /**
165
- * @desc Override the object by another object (rtl)
166
- * @param {object} default object
167
- * @param {object} object applied to the default object
168
- * @return {object} result object
169
- */
180
+ * @desc Override the object by another object (rtl)
181
+ * @param {object} default object
182
+ * @param {object} object applied to the default object
183
+ * @return {object} result object
184
+ */
170
185
  function applyDefault(obj1, obj2) {
171
186
  return Object.assign({}, obj1, obj2);
172
187
  }
188
+
173
189
  /**
174
- * @desc Get public key in pem format from the certificate included in the metadata
175
- * @param {string} x509 certificate
176
- * @return {string} public key fetched from the certificate
177
- */
190
+ * @desc Get public key in pem format from the certificate included in the metadata
191
+ * @param {string} x509 certificate
192
+ * @return {string} public key fetched from the certificate
193
+ */
178
194
  function getPublicKeyPemFromCertificate(x509CertificateString: string) {
179
- const certDerBytes = util.decode64(x509CertificateString);
180
- const obj = asn1.fromDer(certDerBytes);
181
- const cert = pki.certificateFromAsn1(obj);
182
- return pki.publicKeyToPem(cert.publicKey);
183
- }
195
+ const derBuffer = Buffer.from(x509CertificateString, 'base64');
196
+ // 解析 X.509 证书
197
+ const cert2 = new X509Certificate(derBuffer);
198
+ const publicKeyObject = cert2.publicKey
199
+ // 3. 导出为 PEM 格式
200
+ return publicKeyObject.export({
201
+ type: 'spki', // 使用 Subject Public Key Info 结构
202
+ format: 'pem' // 输出 PEM 格式
203
+ });
184
204
 
205
+ }
185
206
 
186
207
 
187
208
  /*function getPublicKeyPemFromCertificate(x509Certificate: string): string {
@@ -197,25 +218,75 @@ function getPublicKeyPemFromCertificate(x509CertificateString: string) {
197
218
  return cert.publicKey?.toString();
198
219
  }*/
199
220
  /**
200
- * @desc Read private key from pem-formatted string
201
- * @param {string | Buffer} keyString pem-formatted string
202
- * @param {string} protected passphrase of the key
203
- * @return {string} string in pem format
204
- * If passphrase is used to protect the .pem content (recommend)
205
- */
206
- export function readPrivateKey(keyString: string | Buffer, passphrase: string | undefined, isOutputString?: boolean) {
207
- return isString(passphrase) ? this.convertToString(pki.privateKeyToPem(pki.decryptRsaPrivateKey(String(keyString), passphrase)), isOutputString) : keyString;
221
+ * @desc Read private key from pem-formatted string
222
+ * @param {string | Buffer} keyString pem-formatted string
223
+ * @param {string} protected passphrase of the key
224
+ * @return {string} string in pem format
225
+ * If passphrase is used to protect the .pem content (recommend)
226
+ */
227
+
228
+ /**
229
+ * PEM 头尾格式校验与修复
230
+ */
231
+ function validatePEMHeaders(pem: string, keyType: string): string {
232
+ const expectedHeader = `-----BEGIN ${keyType}-----`;
233
+ const expectedFooter = `-----END ${keyType}-----`;
234
+
235
+ // 自动修复不规范的 PEM 头尾
236
+ return pem
237
+ .replace(/-{5}.*PRIVATE KEY-{5}/g, '') // 清除已有头尾
238
+ .replace(/(\r\n|\n|\r)/gm, '\n') // 统一换行符
239
+ .trim() + // 清理空白
240
+ `\n${expectedHeader}\n${pem}\n${expectedFooter}\n`;
208
241
  }
242
+ export function readPrivateKey(
243
+ keyString: string | Buffer,
244
+ passphrase?: string,
245
+ isOutputString: boolean = true
246
+ ): string | Buffer {
247
+ try {
248
+ // 统一转换为字符串格式处理
249
+ const pemKey = Buffer.isBuffer(keyString)
250
+ ? keyString.toString('utf8')
251
+ : keyString;
252
+
253
+ // 创建私钥对象 (自动处理加密)
254
+ const keyObject = createPrivateKey({
255
+ key: pemKey,
256
+ format: 'pem',
257
+ passphrase: isString(passphrase) ? passphrase : undefined,
258
+ encoding: 'utf8'
259
+ });
260
+
261
+ // 验证密钥类型为 RSA
262
+ if (keyObject.asymmetricKeyType !== 'rsa') {
263
+ throw new Error('仅支持 RSA 私钥类型');
264
+ }
265
+
266
+ // 强制转换为 PKCS#1 格式
267
+ const exported = keyObject.export({
268
+ type: 'pkcs1', // 明确指定 RSA 传统格式
269
+ format: 'pem' // 输出为 PEM 格式
270
+ }) as string;
271
+
272
+ return isOutputString ? String(exported) : Buffer.from(exported, 'utf8');
273
+ } catch (error) {
274
+ throw new Error(`私钥读取失败: ${error.message}`);
275
+ }
276
+ }
277
+
278
+
209
279
  /**
210
- * @desc Inline syntax sugar
211
- */
280
+ * @desc Inline syntax sugar
281
+ */
212
282
  function convertToString(input, isOutputString) {
213
283
  return Boolean(isOutputString) ? String(input) : input;
214
284
  }
285
+
215
286
  /**
216
287
  * @desc Check if the input is an array with non-zero size
217
288
  */
218
- export function isNonEmptyArray(a:any) {
289
+ export function isNonEmptyArray(a: any) {
219
290
  return Array.isArray(a) && a.length > 0;
220
291
  }
221
292
 
package/types/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import IdentityProvider, { IdentityProvider as IdentityProviderInstance } from './src/entity-idp.js';
2
- import ServiceProvider, { ServiceProvider as ServiceProviderInstance } from './src/entity-sp.js';
3
- export { default as IdPMetadata } from './src/metadata-idp.js';
4
- export { default as SPMetadata } from './src/metadata-sp.js';
5
- export { default as Utility } from './src/utility.js';
6
- export { default as SamlLib } from './src/libsaml.js';
7
- import * as Constants from './src/urn.js';
8
- import * as Extractor from './src/extractor.js';
9
- import { setSchemaValidator, setDOMParserOptions } from './src/api.js';
10
- export { Constants, Extractor, IdentityProvider, IdentityProviderInstance, ServiceProvider, ServiceProviderInstance, setSchemaValidator, setDOMParserOptions };
1
+ import IdentityProvider, { IdentityProvider as IdentityProviderInstance } from './src/entity-idp.js';
2
+ import ServiceProvider, { ServiceProvider as ServiceProviderInstance } from './src/entity-sp.js';
3
+ export { default as IdPMetadata } from './src/metadata-idp.js';
4
+ export { default as SPMetadata } from './src/metadata-sp.js';
5
+ export { default as Utility } from './src/utility.js';
6
+ export { default as SamlLib } from './src/libsaml.js';
7
+ import * as Constants from './src/urn.js';
8
+ import * as Extractor from './src/extractor.js';
9
+ import { setSchemaValidator, setDOMParserOptions } from './src/api.js';
10
+ export { Constants, Extractor, IdentityProvider, IdentityProviderInstance, ServiceProvider, ServiceProviderInstance, setSchemaValidator, setDOMParserOptions };
@@ -1,13 +1,13 @@
1
- import { DOMParser as dom, Options as DOMParserOptions } from '@xmldom/xmldom';
2
- interface Context extends ValidatorContext, DOMParserContext {
3
- }
4
- interface ValidatorContext {
5
- validate?: (xml: string) => Promise<any>;
6
- }
7
- interface DOMParserContext {
8
- dom: dom;
9
- }
10
- export declare function getContext(): Context;
11
- export declare function setSchemaValidator(params: ValidatorContext): void;
12
- export declare function setDOMParserOptions(options?: DOMParserOptions): void;
13
- export {};
1
+ import { DOMParser as dom, DOMParserOptions } from '@xmldom/xmldom';
2
+ interface Context extends ValidatorContext, DOMParserContext {
3
+ }
4
+ interface ValidatorContext {
5
+ validate?: (xml: string) => Promise<any>;
6
+ }
7
+ interface DOMParserContext {
8
+ dom: dom;
9
+ }
10
+ export declare function getContext(): Context;
11
+ export declare function setSchemaValidator(params: ValidatorContext): void;
12
+ export declare function setDOMParserOptions(options?: DOMParserOptions): void;
13
+ export {};