1+ var path = require ( 'path' ) ;
2+ var utils = require ( './utils' ) ;
3+ var Parser = require ( 'xmldom' ) . DOMParser ;
4+ var xmlenc = require ( 'xml-encryption' ) ;
5+ var moment = require ( 'moment' ) ;
6+ var async = require ( 'async' ) ;
7+ var crypto = require ( 'crypto' ) ;
18
2- var utils = require ( './utils' ) ,
3- Parser = require ( 'xmldom' ) . DOMParser ,
4- SignedXml = require ( 'xml-crypto' ) . SignedXml ,
5- xmlenc = require ( 'xml-encryption' ) ,
6- moment = require ( 'moment' ) ;
7- async = require ( 'async' ) ;
8- crypto = require ( 'crypto' ) ;
9+ var EncryptXml = require ( './xml/encrypt' ) ;
10+ var SignXml = require ( './xml/sign' ) ;
911
10- var fs = require ( 'fs' ) ;
11- var path = require ( 'path' ) ;
12- var saml11 = fs . readFileSync ( path . join ( __dirname , 'saml11.template' ) ) . toString ( ) ;
12+ var newSaml11Document = utils . factoryForNode ( path . join ( __dirname , 'saml11.template' ) ) ;
1313
1414var NAMESPACE = 'urn:oasis:names:tc:SAML:1.0:assertion' ;
1515
16- var algorithms = {
17- signature : {
18- 'rsa-sha256' : 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' ,
19- 'rsa-sha1' : 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
20- } ,
21- digest : {
22- 'sha256' : 'http://www.w3.org/2001/04/xmlenc#sha256' ,
23- 'sha1' : 'http://www.w3.org/2000/09/xmldsig#sha1'
24- }
25- } ;
16+ function extractSaml11Options ( opts ) {
17+ return {
18+ uid : opts . uid ,
19+ issuer : opts . issuer ,
20+ lifetimeInSeconds : opts . lifetimeInSeconds ,
21+ audiences : opts . audiences ,
22+ attributes : opts . attributes ,
23+ nameIdentifier : opts . nameIdentifier ,
24+ nameIdentifierFormat : opts . nameIdentifierFormat ,
25+ subjectConfirmationMethod : opts . subjectConfirmationMethod ,
26+ holderOfKeyProofSecret : opts . holderOfKeyProofSecret
27+ } ;
28+ }
2629
30+ /**
31+ * Creates a signed SAML 1.1 assertion from the given options.
32+ *
33+ * @param options
34+ *
35+ * // SAML
36+ * @param [options.uid] {string}
37+ * @param [options.issuer] {string}
38+ * @param [options.lifetimeInSeconds] {number}
39+ * @param [options.audiences] {string|string[]}
40+ * @param [options.attributes]
41+ * @param [options.nameIdentifier] {string}
42+ * @param [options.nameIdentifierFormat] {string}
43+ *
44+ * // XML Dsig
45+ * @param options.key {Buffer}
46+ * @param options.cert {Buffer}
47+ * @param [options.signatureAlgorithm] {string}
48+ * @param [options.digestAlgorithm] {string}
49+ * @param [options.signatureNamespacePrefix] {string}
50+ * @param [options.xpathToNodeBeforeSignature] {string}
51+ * @param [options.subjectConfirmationMethod] {string}
52+ * @param [options.holderOfKeyProofSecret] {Buffer}
53+ *
54+ * // XML encryption
55+ * @param [options.encryptionCert] {Buffer}
56+ * @param [options.encryptionPublicKey] {Buffer}
57+ * @param [options.encryptionAlgorithm] {string}
58+ * @param [options.keyEncryptionAlgorighm] {string}
59+ *
60+ * @param {Function } [callback] required if encrypting
61+ * @return {String|* }
62+ */
2763exports . create = function ( options , callback ) {
28- if ( ! options . key )
29- throw new Error ( 'Expect a private key in pem format' ) ;
30-
31- if ( ! options . cert )
32- throw new Error ( 'Expect a public key cert in pem format' ) ;
33-
34- options . signatureAlgorithm = options . signatureAlgorithm || 'rsa-sha256' ;
35- options . digestAlgorithm = options . digestAlgorithm || 'sha256' ;
36-
37- var cert = utils . pemToCert ( options . cert ) ;
38-
39- var sig = new SignedXml ( null , { signatureAlgorithm : algorithms . signature [ options . signatureAlgorithm ] , idAttribute : 'AssertionID' } ) ;
40- sig . addReference ( "//*[local-name(.)='Assertion']" ,
41- [ "http://www.w3.org/2000/09/xmldsig#enveloped-signature" , "http://www.w3.org/2001/10/xml-exc-c14n#" ] ,
42- algorithms . digest [ options . digestAlgorithm ] ) ;
64+ return createAssertion ( extractSaml11Options ( options ) , {
65+ signXml : SignXml . fromSignXmlOptions ( Object . assign ( {
66+ xpathToNodeBeforeSignature : "//*[local-name(.)='AuthenticationStatement']" ,
67+ signatureIdAttribute : 'AssertionID'
68+ } , options ) ) ,
69+ encryptXml : EncryptXml . fromEncryptXmlOptions ( options )
70+ } , callback ) ;
71+ }
4372
44- sig . signingKey = options . key ;
45-
46- sig . keyInfoProvider = {
47- getKeyInfo : function ( ) {
48- return "<X509Data><X509Certificate>" + cert + "</X509Certificate></X509Data>" ;
49- }
50- } ;
73+ /**
74+ * Creates an **unsigned** SAML 1.1 assertion from the given options.
75+ *
76+ * @param options
77+ *
78+ * // SAML
79+ * @param [options.uid] {string}
80+ * @param [options.issuer] {string}
81+ * @param [options.lifetimeInSeconds] {number}
82+ * @param [options.audiences] {string|string[]}
83+ * @param [options.attributes]
84+ * @param [options.nameIdentifier] {string}
85+ * @param [options.nameIdentifierFormat] {string}
86+ *
87+ * // XML encryption
88+ * @param [options.encryptionCert] {Buffer}
89+ * @param [options.encryptionPublicKey] {Buffer}
90+ * @param [options.encryptionAlgorithm] {string}
91+ * @param [options.keyEncryptionAlgorighm] {string}
92+ *
93+ * @param {Function } [callback] required if encrypting
94+ * @return {String|* }
95+ */
96+ exports . createUnsignedAssertion = function ( options , callback ) {
97+ return createAssertion ( extractSaml11Options ( options ) , {
98+ signXml : SignXml . unsigned ,
99+ encryptXml : EncryptXml . fromEncryptXmlOptions ( options )
100+ } , callback ) ;
101+ }
51102
103+ function createAssertion ( options , strategies , callback ) {
52104 var doc ;
53105 try {
54- doc = new Parser ( ) . parseFromString ( saml11 . toString ( ) ) ;
106+ doc = newSaml11Document ( ) ;
55107 } catch ( err ) {
56108 return utils . reportError ( err , callback ) ;
57109 }
@@ -125,42 +177,38 @@ exports.create = function(options, callback) {
125177 nameIDs [ 1 ] . setAttribute ( 'Format' , options . nameIdentifierFormat ) ;
126178 }
127179
128- if ( ! options . encryptionCert ) return sign ( options , sig , doc , callback ) ;
180+ if ( strategies . encryptXml === EncryptXml . unencrypted ) {
181+ var signed = strategies . signXml ( doc ) ;
182+ return strategies . encryptXml ( signed , callback ) ;
183+ }
129184
130- // encryption is turned on,
185+ // encryption is turned on,
131186 var proofSecret ;
132187 async . waterfall ( [
133- function ( cb ) {
134- if ( ! options . subjectConfirmationMethod && options . subjectConfirmationMethod !== 'holder-of-key' )
188+ function ( cb ) {
189+ if ( ! options . subjectConfirmationMethod && options . subjectConfirmationMethod !== 'holder-of-key' )
135190 return cb ( ) ;
136-
191+
137192 crypto . randomBytes ( 32 , function ( err , randomBytes ) {
138193 proofSecret = randomBytes ;
139- addSubjectConfirmation ( options , doc , options . holderOfKeyProofSecret || randomBytes , cb ) ;
194+ addSubjectConfirmation ( strategies . encryptXml . encryptOptions , doc , options . holderOfKeyProofSecret || randomBytes , cb ) ;
140195 } ) ;
141-
142196 } ,
143197 function ( cb ) {
144- sign ( options , sig , doc , function ( err , signed ) {
145- if ( err ) return cb ( err ) ;
146- return encrypt ( options , signed , cb ) ;
147- } ) ;
198+ strategies . signXml ( doc , cb ) ;
199+ } ,
200+ function ( signed , cb ) {
201+ strategies . encryptXml ( signed , cb ) ;
148202 }
149- ] , function ( err , result ) {
203+ ] , function ( err , result ) {
150204 if ( err ) return callback ( err ) ;
151205 callback ( null , result , proofSecret ) ;
152206 } ) ;
153- } ;
154-
155- function addSubjectConfirmation ( options , doc , randomBytes , callback ) {
156- var encryptOptions = {
157- rsa_pub : options . encryptionPublicKey ,
158- pem : options . encryptionCert ,
159- keyEncryptionAlgorighm : options . keyEncryptionAlgorighm || 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
160- } ;
207+ }
161208
162- xmlenc . encryptKeyInfo ( randomBytes , encryptOptions , function ( err , keyinfo ) {
163- if ( err ) return cb ( err ) ;
209+ function addSubjectConfirmation ( encryptOptions , doc , randomBytes , callback ) {
210+ xmlenc . encryptKeyInfo ( randomBytes , encryptOptions , function ( err , keyinfo ) {
211+ if ( err ) return callback ( err ) ;
164212 var subjectConfirmationNodes = doc . documentElement . getElementsByTagNameNS ( NAMESPACE , 'SubjectConfirmation' ) ;
165213
166214 for ( var i = 0 ; i < subjectConfirmationNodes . length ; i ++ ) {
@@ -179,40 +227,3 @@ function addSubjectConfirmation(options, doc, randomBytes, callback) {
179227 callback ( ) ;
180228 } ) ;
181229}
182-
183- function sign ( options , sig , doc , callback ) {
184- var token = utils . removeWhitespace ( doc . toString ( ) ) ;
185- var signed ;
186-
187- try {
188- var opts = options . xpathToNodeBeforeSignature ? {
189- location : {
190- reference : options . xpathToNodeBeforeSignature ,
191- action : 'after'
192- }
193- } : { } ;
194-
195- sig . computeSignature ( token , opts ) ;
196- signed = sig . getSignedXml ( ) ;
197- } catch ( err ) {
198- return utils . reportError ( err , callback ) ;
199- }
200-
201- if ( ! callback ) return signed ;
202-
203- return callback ( null , signed ) ;
204- }
205-
206- function encrypt ( options , signed , callback ) {
207- var encryptOptions = {
208- rsa_pub : options . encryptionPublicKey ,
209- pem : options . encryptionCert ,
210- encryptionAlgorithm : options . encryptionAlgorithm || 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' ,
211- keyEncryptionAlgorighm : options . keyEncryptionAlgorighm || 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
212- } ;
213-
214- xmlenc . encrypt ( signed , encryptOptions , function ( err , encrypted ) {
215- if ( err ) return callback ( err ) ;
216- callback ( null , utils . removeWhitespace ( encrypted ) ) ;
217- } ) ;
218- }
0 commit comments