99use SimpleSAML \{Configuration , Error , Logger };
1010use SimpleSAML \HTTP \RunnableResponse ;
1111use SimpleSAML \Metadata \MetaDataStorageHandler ;
12- use SimpleSAML \Module \saml \Message ;;
1312use SimpleSAML \SAML2 \Binding ;
1413use SimpleSAML \SAML2 \Binding \HTTPPost ;
1514use SimpleSAML \SAML2 \Constants as C ;
16- use SimpleSAML \SAML2 \Utils ;
15+ use SimpleSAML \SAML2 \Utils as SAML2_Utils ;
1716use SimpleSAML \SAML2 \XML \saml \{
1817 Assertion ,
1918 Attribute ,
2322 AudienceRestriction ,
2423 Conditions ,
2524 Issuer ,
26- Status ,
27- StatusCode ,
2825 Subject ,
2926 SubjectConfirmation ,
3027 SubjectConfirmationData ,
3128};
32- use SimpleSAML \SAML2 \XML \samlp \{AttributeQuery , Response };
29+ use SimpleSAML \SAML2 \XML \samlp \{AttributeQuery , Response , Status , StatusCode };
30+ use SimpleSAML \Utils ;
3331use SimpleSAML \XML \Utils \Random ;
32+ use SimpleSAML \XMLSecurity \Alg \Signature \SignatureAlgorithmFactory ;
33+ use SimpleSAML \XMLSecurity \CryptoEncoding \PEM ;
34+ use SimpleSAML \XMLSecurity \Key \PrivateKey ;
35+ use SimpleSAML \XMLSecurity \XML \ds \{KeyInfo , X509Certificate , X509Data };
36+ use SimpleSAML \XMLSecurity \XML \SignableElementInterface ;
3437use Symfony \Bridge \PsrHttpMessage \Factory \{HttpFoundationFactory , PsrHttpFactory };
3538use Symfony \Component \HttpFoundation \Request ;
3639
40+ use function array_filter ;
41+
3742/**
3843 * Controller class for the exampleattributeserver module.
3944 *
@@ -118,7 +123,7 @@ public function main(/** @scrutinizer ignore-unused */ Request $request): Runnab
118123 new AttributeValue ('value1 ' ),
119124 new AttributeValue ('value2 ' ),
120125 new AttributeValue ('value3 ' ),
121- ]
126+ ],
122127 ),
123128 new Attribute (
124129 'test ' ,
@@ -131,21 +136,27 @@ public function main(/** @scrutinizer ignore-unused */ Request $request): Runnab
131136 ];
132137
133138 // Determine which attributes we will return
134- $ returnAttributes = [];
135-
136- if (count ($ returnAttributes ) === 0 ) {
139+ if (count ($ attributes ) === 0 ) {
137140 Logger::debug ('No attributes requested - return all attributes. ' );
138- $ returnAttributes = $ attributes ;
141+ $ attributeStatement = null ;
139142 } else {
143+ $ returnAttributes = [];
140144 foreach ($ message ->getAttributes () as $ reqAttr ) {
141145 foreach ($ attributes as $ attr ) {
142- if ($ attr ->getName () === $ reqAttr ->getName () && $ attr ->getNameFormat () === $ reqAttr ->getNameFormat ()) {
146+ if (
147+ $ attr ->getName () === $ reqAttr ->getName ()
148+ && $ attr ->getNameFormat () === $ reqAttr ->getNameFormat ()
149+ ) {
143150 // The requested attribute is available
144151 if ($ reqAttr ->getAttributeValues () === []) {
145152 // If no specific values are requested, return all
146153 $ returnAttributes [] = $ attr ;
147154 } else {
148- $ returnValues = $ this ->filterAttributeValues ($ reqAttr ->getAttributeValues (), $ attr ->getAttributeValues ());
155+ $ returnValues = $ this ->filterAttributeValues (
156+ $ reqAttr ->getAttributeValues (),
157+ $ attr ->getAttributeValues (),
158+ );
159+
149160 $ returnAttributes [] = new Attribute (
150161 $ attr ->getName (),
151162 $ attr ->getNameFormat (),
@@ -156,11 +167,14 @@ public function main(/** @scrutinizer ignore-unused */ Request $request): Runnab
156167 }
157168 }
158169 }
170+
171+ $ attributeStatement = $ returnAttributes ? (new AttributeStatement ($ returnAttributes )) : null ;
159172 }
160173
161174 // $returnAttributes contains the attributes we should return. Send them
162- $ clock = Utils ::getContainer ()->getClock ();
175+ $ clock = SAML2_Utils ::getContainer ()->getClock ();
163176
177+ $ statements = array_filter ([$ attributeStatement ]);
164178 $ assertion = new Assertion (
165179 issuer: new Issuer ($ idpEntityId ),
166180 issueInstant: $ clock ->now (),
@@ -187,29 +201,25 @@ public function main(/** @scrutinizer ignore-unused */ Request $request): Runnab
187201 ]),
188202 ],
189203 ),
190- statements: [
191- new AttributeStatement ($ returnAttributes ),
192- ],
204+ statements: $ statements ,
193205 );
194206
195- // TODO: Fix signing; should use xml-security lib
196- Message::addSign ($ idpMetadata , $ spMetadata , $ assertion );
207+ self ::addSign ($ idpMetadata , $ spMetadata , $ assertion );
197208
198209 $ response = new Response (
199210 status: new Status (
200211 new StatusCode (C::STATUS_SUCCESS ),
201212 ),
202213 issueInstant: $ clock ->now (),
203- issuer: new Issuer ( $ issuer) ,
214+ issuer: $ issuer ,
204215 id: (new Random ())->generateID (),
205216 version: '2.0 ' ,
206217 inResponseTo: $ message ->getId (),
207218 destination: $ endpoint ,
208219 assertions: [$ assertion ],
209220 );
210221
211- // TODO: Fix signing; should use xml-security lib
212- Message::addSign ($ idpMetadata , $ spMetadata , $ response );
222+ self ::addSign ($ idpMetadata , $ spMetadata , $ response );
213223
214224 $ httpPost = new HTTPPost ();
215225 $ httpPost ->setRelayState ($ binding ->getRelayState ());
@@ -238,4 +248,60 @@ private function filterAttributeValues(array $reqValues, array $values): array
238248
239249 return $ result ;
240250 }
251+
252+
253+ /**
254+ * @deprecated This method is a modified version of \SimpleSAML\Module\saml\Message::addSign and
255+ * should be replaced with a call to a future ServiceProvider-class in the saml2-library
256+ *
257+ * Add signature key and sender certificate to an element (Message or Assertion).
258+ *
259+ * @param \SimpleSAML\Configuration $srcMetadata The metadata of the sender.
260+ * @param \SimpleSAML\Configuration $dstMetadata The metadata of the recipient.
261+ * @param \SimpleSAML\XMLSecurity\XML\SignableElementInterface $element The element we should add the data to.
262+ */
263+ private static function addSign (
264+ Configuration $ srcMetadata ,
265+ Configuration $ dstMetadata ,
266+ SignableElementInterface &$ element ,
267+ ): void {
268+ $ dstPrivateKey = $ dstMetadata ->getOptionalString ('signature.privatekey ' , null );
269+ $ cryptoUtils = new Utils \Crypto ();
270+
271+ if ($ dstPrivateKey !== null ) {
272+ /** @var array $keyArray */
273+ $ keyArray = $ cryptoUtils ->loadPrivateKey ($ dstMetadata , true , 'signature. ' );
274+ $ certArray = $ cryptoUtils ->loadPublicKey ($ dstMetadata , false , 'signature. ' );
275+ } else {
276+ /** @var array $keyArray */
277+ $ keyArray = $ cryptoUtils ->loadPrivateKey ($ srcMetadata , true );
278+ $ certArray = $ cryptoUtils ->loadPublicKey ($ srcMetadata , false );
279+ }
280+
281+ $ algo = $ dstMetadata ->getOptionalString ('signature.algorithm ' , null );
282+ if ($ algo === null ) {
283+ $ algo = $ srcMetadata ->getOptionalString ('signature.algorithm ' , C::SIG_RSA_SHA256 );
284+ }
285+
286+ $ privateKey = PrivateKey::fromFile ($ keyArray ['PEM ' ], $ keyArray ['password ' ]);
287+
288+ $ keyInfo = null ;
289+ if ($ certArray !== null ) {
290+ $ certificate = new X509Certificate (PEM ::fromString ($ keyArray ['PEM ' ]));
291+ $ keyInfo = new KeyInfo ([
292+ new X509Data (
293+ [
294+ new X509Certificate ($ certArray ['PEM ' ]),
295+ ],
296+ ),
297+ ]);
298+ }
299+
300+ $ signer = (new SignatureAlgorithmFactory ())->getAlgorithm (
301+ $ algo ,
302+ $ privateKey ,
303+ );
304+
305+ $ element ->sign ($ signer , C::C14N_EXCLUSIVE_WITHOUT_COMMENTS , $ keyInfo );
306+ }
241307}
0 commit comments