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,36 +136,47 @@ 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+ // @phpstan-ignore identical.alwaysFalse
140+ if (count ($ attributes ) === 0 ) {
137141 Logger::debug ('No attributes requested - return all attributes. ' );
138- $ returnAttributes = $ attributes ;
142+ $ attributeStatement = null ;
139143 } else {
144+ $ returnAttributes = [];
140145 foreach ($ message ->getAttributes () as $ reqAttr ) {
141146 foreach ($ attributes as $ attr ) {
142- if ($ attr ->getName () === $ reqAttr ->getName () && $ attr ->getNameFormat () === $ reqAttr ->getNameFormat ()) {
147+ if (
148+ $ attr ->getName () === $ reqAttr ->getName ()
149+ && $ attr ->getNameFormat () === $ reqAttr ->getNameFormat ()
150+ ) {
143151 // The requested attribute is available
144152 if ($ reqAttr ->getAttributeValues () === []) {
145153 // If no specific values are requested, return all
146154 $ returnAttributes [] = $ attr ;
147155 } else {
148- $ returnValues = $ this ->filterAttributeValues ($ reqAttr ->getAttributeValues (), $ attr ->getAttributeValues ());
156+ $ returnValues = $ this ->filterAttributeValues (
157+ $ reqAttr ->getAttributeValues (),
158+ $ attr ->getAttributeValues (),
159+ );
160+
149161 $ returnAttributes [] = new Attribute (
150162 $ attr ->getName (),
151163 $ attr ->getNameFormat (),
164+ null ,
152165 $ returnValues ,
153166 $ attr ->getAttributesNS (),
154167 );
155168 }
156169 }
157170 }
158171 }
172+
173+ $ attributeStatement = $ returnAttributes ? (new AttributeStatement ($ returnAttributes )) : null ;
159174 }
160175
161176 // $returnAttributes contains the attributes we should return. Send them
162- $ clock = Utils ::getContainer ()->getClock ();
177+ $ clock = SAML2_Utils ::getContainer ()->getClock ();
163178
179+ $ statements = array_filter ([$ attributeStatement ]);
164180 $ assertion = new Assertion (
165181 issuer: new Issuer ($ idpEntityId ),
166182 issueInstant: $ clock ->now (),
@@ -187,30 +203,27 @@ public function main(/** @scrutinizer ignore-unused */ Request $request): Runnab
187203 ]),
188204 ],
189205 ),
190- statements: [
191- new AttributeStatement ($ returnAttributes ),
192- ],
206+ statements: $ statements ,
193207 );
194208
195- // TODO: Fix signing; should use xml-security lib
196- Message::addSign ($ idpMetadata , $ spMetadata , $ assertion );
209+ self ::addSign ($ idpMetadata , $ spMetadata , $ assertion );
197210
198211 $ response = new Response (
199212 status: new Status (
200213 new StatusCode (C::STATUS_SUCCESS ),
201214 ),
202215 issueInstant: $ clock ->now (),
203- issuer: new Issuer ( $ issuer) ,
216+ issuer: $ issuer ,
204217 id: (new Random ())->generateID (),
205218 version: '2.0 ' ,
206219 inResponseTo: $ message ->getId (),
207220 destination: $ endpoint ,
208221 assertions: [$ assertion ],
209222 );
210223
211- // TODO: Fix signing; should use xml-security lib
212- Message::addSign ($ idpMetadata , $ spMetadata , $ response );
224+ self ::addSign ($ idpMetadata , $ spMetadata , $ response );
213225
226+ /** @var \SimpleSAML\SAML2\Binding\HTTPPost $httpPost */
214227 $ httpPost = new HTTPPost ();
215228 $ httpPost ->setRelayState ($ binding ->getRelayState ());
216229
@@ -238,4 +251,59 @@ private function filterAttributeValues(array $reqValues, array $values): array
238251
239252 return $ result ;
240253 }
254+
255+
256+ /**
257+ * @deprecated This method is a modified version of \SimpleSAML\Module\saml\Message::addSign and
258+ * should be replaced with a call to a future ServiceProvider-class in the saml2-library
259+ *
260+ * Add signature key and sender certificate to an element (Message or Assertion).
261+ *
262+ * @param \SimpleSAML\Configuration $srcMetadata The metadata of the sender.
263+ * @param \SimpleSAML\Configuration $dstMetadata The metadata of the recipient.
264+ * @param \SimpleSAML\XMLSecurity\XML\SignableElementInterface $element The element we should add the data to.
265+ */
266+ private static function addSign (
267+ Configuration $ srcMetadata ,
268+ Configuration $ dstMetadata ,
269+ SignableElementInterface &$ element ,
270+ ): void {
271+ $ dstPrivateKey = $ dstMetadata ->getOptionalString ('signature.privatekey ' , null );
272+ $ cryptoUtils = new Utils \Crypto ();
273+
274+ if ($ dstPrivateKey !== null ) {
275+ /** @var string[] $keyArray */
276+ $ keyArray = $ cryptoUtils ->loadPrivateKey ($ dstMetadata , true , 'signature. ' );
277+ $ certArray = $ cryptoUtils ->loadPublicKey ($ dstMetadata , false , 'signature. ' );
278+ } else {
279+ /** @var string[] $keyArray */
280+ $ keyArray = $ cryptoUtils ->loadPrivateKey ($ srcMetadata , true );
281+ $ certArray = $ cryptoUtils ->loadPublicKey ($ srcMetadata , false );
282+ }
283+
284+ $ algo = $ dstMetadata ->getOptionalString ('signature.algorithm ' , null );
285+ if ($ algo === null ) {
286+ $ algo = $ srcMetadata ->getOptionalString ('signature.algorithm ' , C::SIG_RSA_SHA256 );
287+ }
288+
289+ $ privateKey = PrivateKey::fromFile ($ keyArray ['PEM ' ], $ keyArray ['password ' ]);
290+
291+ $ keyInfo = null ;
292+ if ($ certArray !== null ) {
293+ $ keyInfo = new KeyInfo ([
294+ new X509Data (
295+ [
296+ new X509Certificate ($ certArray ['PEM ' ]),
297+ ],
298+ ),
299+ ]);
300+ }
301+
302+ $ signer = (new SignatureAlgorithmFactory ())->getAlgorithm (
303+ $ algo ,
304+ $ privateKey ,
305+ );
306+
307+ $ element ->sign ($ signer , C::C14N_EXCLUSIVE_WITHOUT_COMMENTS , $ keyInfo );
308+ }
241309}
0 commit comments