@@ -9,12 +9,14 @@ import { algorithms, wording, namespace } from './urn';
9
9
import { select } from 'xpath' ;
10
10
import { MetadataInterface } from './metadata' ;
11
11
import nrsa , { SigningSchemeHash } from 'node-rsa' ;
12
- import { SignedXml , FileKeyInfo } from 'xml-crypto' ;
12
+ import { SignedXml } from 'xml-crypto' ;
13
13
import * as xmlenc from '@authenio/xml-encryption' ;
14
14
import { extract } from './extractor' ;
15
15
import camelCase from 'camelcase' ;
16
16
import { getContext } from './api' ;
17
17
import xmlEscape from 'xml-escape' ;
18
+ import * as fs from 'fs' ;
19
+ import { DOMParser } from '@xmldom/xmldom' ;
18
20
19
21
const signatureAlgorithms = algorithms . signature ;
20
22
const digestAlgorithms = algorithms . digest ;
@@ -95,6 +97,7 @@ export interface LibSamlInterface {
95
97
verifySignature : ( xml : string , opts : SignatureVerifierOptions ) => [ boolean , any ] ;
96
98
createKeySection : ( use : KeyUse , cert : string | Buffer ) => { } ;
97
99
constructMessageSignature : ( octetString : string , key : string , passphrase ?: string , isBase64 ?: boolean , signingAlgorithm ?: string ) => string ;
100
+
98
101
verifyMessageSignature : ( metadata , octetString : string , signature : string | Buffer , verifyAlgorithm ?: string ) => boolean ;
99
102
getKeyInfo : ( x509Certificate : string , signatureConfig ?: any ) => void ;
100
103
encryptAssertion : ( sourceEntity , targetEntity , entireXML : string ) => Promise < string > ;
@@ -326,28 +329,28 @@ const libSaml = () => {
326
329
} = opts ;
327
330
const sig = new SignedXml ( ) ;
328
331
// Add assertion sections as reference
332
+ const digestAlgorithm = getDigestMethod ( signatureAlgorithm ) ;
329
333
if ( referenceTagXPath ) {
330
- sig . addReference (
331
- referenceTagXPath ,
332
- transformationAlgorithms ,
333
- getDigestMethod ( signatureAlgorithm )
334
- ) ;
334
+ sig . addReference ( {
335
+ xpath : referenceTagXPath ,
336
+ transforms : transformationAlgorithms ,
337
+ digestAlgorithm : digestAlgorithm
338
+ } ) ;
335
339
}
336
340
if ( isMessageSigned ) {
337
- sig . addReference (
341
+ sig . addReference ( {
338
342
// reference to the root node
339
- '/*' ,
340
- transformationAlgorithms ,
341
- getDigestMethod ( signatureAlgorithm ) ,
342
- '' ,
343
- '' ,
344
- '' ,
345
- false ,
346
- ) ;
343
+ xpath : '/*' ,
344
+ transforms : transformationAlgorithms ,
345
+ digestAlgorithm
346
+ } ) ;
347
347
}
348
348
sig . signatureAlgorithm = signatureAlgorithm ;
349
- sig . keyInfoProvider = new this . getKeyInfo ( signingCert , signatureConfig ) ;
350
- sig . signingKey = utility . readPrivateKey ( privateKey , privateKeyPass , true ) ;
349
+ sig . publicCert = this . getKeyInfo ( signingCert , signatureConfig ) . getKey ( ) ;
350
+ sig . getKeyInfoContent = this . getKeyInfo ( signingCert , signatureConfig ) . getKeyInfo ;
351
+ sig . privateKey = utility . readPrivateKey ( privateKey , privateKeyPass , true ) ;
352
+ sig . canonicalizationAlgorithm = 'http://www.w3.org/2001/10/xml-exc-c14n#' ;
353
+
351
354
if ( signatureConfig ) {
352
355
sig . computeSignature ( rawSamlMessage , signatureConfig ) ;
353
356
} else {
@@ -359,11 +362,15 @@ const libSaml = () => {
359
362
* @desc Verify the XML signature
360
363
* @param {string } xml xml
361
364
* @param {SignatureVerifierOptions } opts cert declares the X509 certificate
362
- * @return {boolean } verification result
363
- */
365
+ * @return {[boolean, string | null] } - A tuple where:
366
+ * - The first element is `true` if the signature is valid, `false` otherwise.
367
+ * - The second element is the cryptographically authenticated assertion node as a string, or `null` if not found.
368
+ */
364
369
verifySignature ( xml : string , opts : SignatureVerifierOptions ) {
365
370
const { dom } = getContext ( ) ;
366
371
const doc = dom . parseFromString ( xml ) ;
372
+
373
+ const docParser = new DOMParser ( ) ;
367
374
// In order to avoid the wrapping attack, we have changed to use absolute xpath instead of naively fetching the signature element
368
375
// message signature (logout response / saml response)
369
376
const messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']" ;
@@ -374,7 +381,6 @@ const libSaml = () => {
374
381
375
382
// select the signature node
376
383
let selection : any = [ ] ;
377
- let assertionNode : string | null = null ;
378
384
const messageSignatureNode = select ( messageSignatureXpath , doc ) ;
379
385
const assertionSignatureNode = select ( assertionSignatureXpath , doc ) ;
380
386
const wrappingElementNode = select ( wrappingElementsXPath , doc ) ;
@@ -392,10 +398,11 @@ const libSaml = () => {
392
398
throw new Error ( 'ERR_ZERO_SIGNATURE' ) ;
393
399
}
394
400
395
- const sig = new SignedXml ( ) ;
396
- let verified = true ;
401
+
397
402
// need to refactor later on
398
- selection . forEach ( signatureNode => {
403
+ for ( const signatureNode of selection ) {
404
+ const sig = new SignedXml ( ) ;
405
+ let verified = false ;
399
406
400
407
sig . signatureAlgorithm = opts . signatureAlgorithm ! ;
401
408
@@ -404,7 +411,7 @@ const libSaml = () => {
404
411
}
405
412
406
413
if ( opts . keyFile ) {
407
- sig . keyInfoProvider = new FileKeyInfo ( opts . keyFile ) ;
414
+ sig . publicCert = fs . readFileSync ( opts . keyFile )
408
415
}
409
416
410
417
if ( opts . metadata ) {
@@ -440,28 +447,56 @@ const libSaml = () => {
440
447
throw new Error ( 'ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA' ) ;
441
448
}
442
449
443
- sig . keyInfoProvider = new this . getKeyInfo ( x509Certificate ) ;
450
+ sig . publicCert = this . getKeyInfo ( x509Certificate ) . getKey ( ) ;
444
451
445
452
} else {
446
453
// Select first one from metadata
447
- sig . keyInfoProvider = new this . getKeyInfo ( metadataCert [ 0 ] ) ;
454
+ sig . publicCert = this . getKeyInfo ( metadataCert [ 0 ] ) . getKey ( ) ;
448
455
}
449
-
450
456
}
451
457
452
458
sig . loadSignature ( signatureNode ) ;
453
459
454
460
doc . removeChild ( signatureNode ) ;
455
461
456
- verified = verified && sig . checkSignature ( doc . toString ( ) ) ;
462
+ verified = sig . checkSignature ( doc . toString ( ) ) ;
457
463
458
464
// immediately throw error when any one of the signature is failed to get verified
459
465
if ( ! verified ) {
460
466
throw new Error ( 'ERR_FAILED_TO_VERIFY_SIGNATURE' ) ;
461
467
}
468
+ // attempt is made to get the signed Reference as a string();
469
+ // note, we don't have access to the actual signedReferences API unfortunately
470
+ // mainly a sanity check here for SAML. (Although ours would still be secure, if multiple references are used)
471
+ if ( ! ( sig . getReferences ( ) . length >= 1 ) ) {
472
+ throw new Error ( 'NO_SIGNATURE_REFERENCES' )
473
+ }
474
+ const signedVerifiedXML = sig . getSignedReferences ( ) [ 0 ] ;
475
+ const rootNode = docParser . parseFromString ( signedVerifiedXML , 'text/xml' ) . documentElement ;
476
+ // process the verified signature:
477
+ // case 1, rootSignedDoc is a response:
478
+ if ( rootNode . localName === 'Response' ) {
479
+
480
+ // try getting the Xml from the first assertion
481
+ const assertions = select (
482
+ "./*[local-name()='Assertion']" ,
483
+ rootNode
484
+ ) ;
485
+ // now we can process the assertion as an assertion
486
+ if ( assertions . length === 1 ) {
487
+ return [ true , assertions [ 0 ] . toString ( ) ] ;
488
+ }
489
+ } else if ( rootNode . localName === 'Assertion' ) {
490
+ return [ true , rootNode . toString ( ) ] ;
491
+ } else {
492
+ return [ true , null ] ; // signature is valid. But there is no assertion node here. It could be metadata node, hence return null
493
+ }
494
+ } ;
462
495
463
- } ) ;
496
+ // something has gone seriously wrong if we are still here
497
+ throw new Error ( 'ERR_ZERO_SIGNATURE' ) ;
464
498
499
+ /*
465
500
// response must be signed, either entire document or assertion
466
501
// default we will take the assertion section under root
467
502
if (messageSignatureNode.length === 1) {
@@ -503,7 +538,7 @@ const libSaml = () => {
503
538
assertionNode = verifiedDoc.assertion.toString();
504
539
}
505
540
506
- return [ verified , assertionNode ] ;
541
+ return [verified, assertionNode];*/
507
542
} ,
508
543
/**
509
544
* @desc Helper function to create the key section in metadata (abstraction for signing and encrypt use)
@@ -586,12 +621,14 @@ const libSaml = () => {
586
621
* @return {string } public key
587
622
*/
588
623
getKeyInfo ( x509Certificate : string , signatureConfig : any = { } ) {
589
- this . getKeyInfo = key => {
590
- const prefix = signatureConfig . prefix ? `${ signatureConfig . prefix } :` : '' ;
591
- return `<${ prefix } X509Data><${ prefix } X509Certificate>${ x509Certificate } </${ prefix } X509Certificate></${ prefix } X509Data>` ;
592
- } ;
593
- this . getKey = keyInfo => {
594
- return utility . getPublicKeyPemFromCertificate ( x509Certificate ) . toString ( ) ;
624
+ const prefix = signatureConfig . prefix ? `${ signatureConfig . prefix } :` : '' ;
625
+ return {
626
+ getKeyInfo : ( ) => {
627
+ return `<${ prefix } X509Data><${ prefix } X509Certificate>${ x509Certificate } </${ prefix } X509Certificate></${ prefix } X509Data>` ;
628
+ } ,
629
+ getKey : ( ) => {
630
+ return utility . getPublicKeyPemFromCertificate ( x509Certificate ) . toString ( ) ;
631
+ } ,
595
632
} ;
596
633
} ,
597
634
/**
0 commit comments