@@ -9,12 +9,14 @@ import { algorithms, wording, namespace } from './urn';
99import { select } from 'xpath' ;
1010import { MetadataInterface } from './metadata' ;
1111import nrsa , { SigningSchemeHash } from 'node-rsa' ;
12- import { SignedXml , FileKeyInfo } from 'xml-crypto' ;
12+ import { SignedXml } from 'xml-crypto' ;
1313import * as xmlenc from '@authenio/xml-encryption' ;
1414import { extract } from './extractor' ;
1515import camelCase from 'camelcase' ;
1616import { getContext } from './api' ;
1717import xmlEscape from 'xml-escape' ;
18+ import * as fs from 'fs' ;
19+ import { DOMParser } from '@xmldom/xmldom' ;
1820
1921const signatureAlgorithms = algorithms . signature ;
2022const digestAlgorithms = algorithms . digest ;
@@ -95,6 +97,7 @@ export interface LibSamlInterface {
9597 verifySignature : ( xml : string , opts : SignatureVerifierOptions ) => [ boolean , any ] ;
9698 createKeySection : ( use : KeyUse , cert : string | Buffer ) => { } ;
9799 constructMessageSignature : ( octetString : string , key : string , passphrase ?: string , isBase64 ?: boolean , signingAlgorithm ?: string ) => string ;
100+
98101 verifyMessageSignature : ( metadata , octetString : string , signature : string | Buffer , verifyAlgorithm ?: string ) => boolean ;
99102 getKeyInfo : ( x509Certificate : string , signatureConfig ?: any ) => void ;
100103 encryptAssertion : ( sourceEntity , targetEntity , entireXML : string ) => Promise < string > ;
@@ -326,28 +329,28 @@ const libSaml = () => {
326329 } = opts ;
327330 const sig = new SignedXml ( ) ;
328331 // Add assertion sections as reference
332+ const digestAlgorithm = getDigestMethod ( signatureAlgorithm ) ;
329333 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+ } ) ;
335339 }
336340 if ( isMessageSigned ) {
337- sig . addReference (
341+ sig . addReference ( {
338342 // 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+ } ) ;
347347 }
348348 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+
351354 if ( signatureConfig ) {
352355 sig . computeSignature ( rawSamlMessage , signatureConfig ) ;
353356 } else {
@@ -359,11 +362,15 @@ const libSaml = () => {
359362 * @desc Verify the XML signature
360363 * @param {string } xml xml
361364 * @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+ */
364369 verifySignature ( xml : string , opts : SignatureVerifierOptions ) {
365370 const { dom } = getContext ( ) ;
366371 const doc = dom . parseFromString ( xml ) ;
372+
373+ const docParser = new DOMParser ( ) ;
367374 // In order to avoid the wrapping attack, we have changed to use absolute xpath instead of naively fetching the signature element
368375 // message signature (logout response / saml response)
369376 const messageSignatureXpath = "/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Signature']" ;
@@ -374,7 +381,6 @@ const libSaml = () => {
374381
375382 // select the signature node
376383 let selection : any = [ ] ;
377- let assertionNode : string | null = null ;
378384 const messageSignatureNode = select ( messageSignatureXpath , doc ) ;
379385 const assertionSignatureNode = select ( assertionSignatureXpath , doc ) ;
380386 const wrappingElementNode = select ( wrappingElementsXPath , doc ) ;
@@ -392,10 +398,11 @@ const libSaml = () => {
392398 throw new Error ( 'ERR_ZERO_SIGNATURE' ) ;
393399 }
394400
395- const sig = new SignedXml ( ) ;
396- let verified = true ;
401+
397402 // need to refactor later on
398- selection . forEach ( signatureNode => {
403+ for ( const signatureNode of selection ) {
404+ const sig = new SignedXml ( ) ;
405+ let verified = false ;
399406
400407 sig . signatureAlgorithm = opts . signatureAlgorithm ! ;
401408
@@ -404,7 +411,7 @@ const libSaml = () => {
404411 }
405412
406413 if ( opts . keyFile ) {
407- sig . keyInfoProvider = new FileKeyInfo ( opts . keyFile ) ;
414+ sig . publicCert = fs . readFileSync ( opts . keyFile )
408415 }
409416
410417 if ( opts . metadata ) {
@@ -440,28 +447,56 @@ const libSaml = () => {
440447 throw new Error ( 'ERROR_UNMATCH_CERTIFICATE_DECLARATION_IN_METADATA' ) ;
441448 }
442449
443- sig . keyInfoProvider = new this . getKeyInfo ( x509Certificate ) ;
450+ sig . publicCert = this . getKeyInfo ( x509Certificate ) . getKey ( ) ;
444451
445452 } else {
446453 // Select first one from metadata
447- sig . keyInfoProvider = new this . getKeyInfo ( metadataCert [ 0 ] ) ;
454+ sig . publicCert = this . getKeyInfo ( metadataCert [ 0 ] ) . getKey ( ) ;
448455 }
449-
450456 }
451457
452458 sig . loadSignature ( signatureNode ) ;
453459
454460 doc . removeChild ( signatureNode ) ;
455461
456- verified = verified && sig . checkSignature ( doc . toString ( ) ) ;
462+ verified = sig . checkSignature ( doc . toString ( ) ) ;
457463
458464 // immediately throw error when any one of the signature is failed to get verified
459465 if ( ! verified ) {
460466 throw new Error ( 'ERR_FAILED_TO_VERIFY_SIGNATURE' ) ;
461467 }
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+ } ;
462495
463- } ) ;
496+ // something has gone seriously wrong if we are still here
497+ throw new Error ( 'ERR_ZERO_SIGNATURE' ) ;
464498
499+ /*
465500 // response must be signed, either entire document or assertion
466501 // default we will take the assertion section under root
467502 if (messageSignatureNode.length === 1) {
@@ -503,7 +538,7 @@ const libSaml = () => {
503538 assertionNode = verifiedDoc.assertion.toString();
504539 }
505540
506- return [ verified , assertionNode ] ;
541+ return [verified, assertionNode];*/
507542 } ,
508543 /**
509544 * @desc Helper function to create the key section in metadata (abstraction for signing and encrypt use)
@@ -586,12 +621,14 @@ const libSaml = () => {
586621 * @return {string } public key
587622 */
588623 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+ } ,
595632 } ;
596633 } ,
597634 /**
0 commit comments