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 \Key \PrivateKey ;
34+ use SimpleSAML \XMLSecurity \XML \ds \{KeyInfo , X509Certificate , X509Data };
35+ use SimpleSAML \XMLSecurity \XML \SignableElementInterface ;
3436use Symfony \Bridge \PsrHttpMessage \Factory \{HttpFoundationFactory , PsrHttpFactory };
3537use Symfony \Component \HttpFoundation \Request ;
3638
39+ use function array_filter ;
40+
3741/**
3842 * Controller class for the exampleattributeserver module.
3943 *
@@ -118,7 +122,7 @@ public function main(/** @scrutinizer ignore-unused */ Request $request): Runnab
118122 new AttributeValue ('value1 ' ),
119123 new AttributeValue ('value2 ' ),
120124 new AttributeValue ('value3 ' ),
121- ]
125+ ],
122126 ),
123127 new Attribute (
124128 'test ' ,
@@ -131,36 +135,47 @@ public function main(/** @scrutinizer ignore-unused */ Request $request): Runnab
131135 ];
132136
133137 // Determine which attributes we will return
134- $ returnAttributes = [];
135-
136- if (count ($ returnAttributes ) === 0 ) {
138+ // @phpstan-ignore identical.alwaysFalse
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 (),
163+ null ,
152164 $ returnValues ,
153165 $ attr ->getAttributesNS (),
154166 );
155167 }
156168 }
157169 }
158170 }
171+
172+ $ attributeStatement = $ returnAttributes ? (new AttributeStatement ($ returnAttributes )) : null ;
159173 }
160174
161175 // $returnAttributes contains the attributes we should return. Send them
162- $ clock = Utils ::getContainer ()->getClock ();
176+ $ clock = SAML2_Utils ::getContainer ()->getClock ();
163177
178+ $ statements = array_filter ([$ attributeStatement ]);
164179 $ assertion = new Assertion (
165180 issuer: new Issuer ($ idpEntityId ),
166181 issueInstant: $ clock ->now (),
@@ -187,30 +202,27 @@ public function main(/** @scrutinizer ignore-unused */ Request $request): Runnab
187202 ]),
188203 ],
189204 ),
190- statements: [
191- new AttributeStatement ($ returnAttributes ),
192- ],
205+ statements: $ statements ,
193206 );
194207
195- // TODO: Fix signing; should use xml-security lib
196- Message::addSign ($ idpMetadata , $ spMetadata , $ assertion );
208+ self ::addSign ($ idpMetadata , $ spMetadata , $ assertion );
197209
198210 $ response = new Response (
199211 status: new Status (
200212 new StatusCode (C::STATUS_SUCCESS ),
201213 ),
202214 issueInstant: $ clock ->now (),
203- issuer: new Issuer ( $ issuer) ,
215+ issuer: $ issuer ,
204216 id: (new Random ())->generateID (),
205217 version: '2.0 ' ,
206218 inResponseTo: $ message ->getId (),
207219 destination: $ endpoint ,
208220 assertions: [$ assertion ],
209221 );
210222
211- // TODO: Fix signing; should use xml-security lib
212- Message::addSign ($ idpMetadata , $ spMetadata , $ response );
223+ self ::addSign ($ idpMetadata , $ spMetadata , $ response );
213224
225+ /** @var \SimpleSAML\SAML2\Binding\HTTPPost $httpPost */
214226 $ httpPost = new HTTPPost ();
215227 $ httpPost ->setRelayState ($ binding ->getRelayState ());
216228
@@ -238,4 +250,59 @@ private function filterAttributeValues(array $reqValues, array $values): array
238250
239251 return $ result ;
240252 }
253+
254+
255+ /**
256+ * @deprecated This method is a modified version of \SimpleSAML\Module\saml\Message::addSign and
257+ * should be replaced with a call to a future ServiceProvider-class in the saml2-library
258+ *
259+ * Add signature key and sender certificate to an element (Message or Assertion).
260+ *
261+ * @param \SimpleSAML\Configuration $srcMetadata The metadata of the sender.
262+ * @param \SimpleSAML\Configuration $dstMetadata The metadata of the recipient.
263+ * @param \SimpleSAML\XMLSecurity\XML\SignableElementInterface $element The element we should add the data to.
264+ */
265+ private static function addSign (
266+ Configuration $ srcMetadata ,
267+ Configuration $ dstMetadata ,
268+ SignableElementInterface &$ element ,
269+ ): void {
270+ $ dstPrivateKey = $ dstMetadata ->getOptionalString ('signature.privatekey ' , null );
271+ $ cryptoUtils = new Utils \Crypto ();
272+
273+ if ($ dstPrivateKey !== null ) {
274+ /** @var string[] $keyArray */
275+ $ keyArray = $ cryptoUtils ->loadPrivateKey ($ dstMetadata , true , 'signature. ' );
276+ $ certArray = $ cryptoUtils ->loadPublicKey ($ dstMetadata , false , 'signature. ' );
277+ } else {
278+ /** @var string[] $keyArray */
279+ $ keyArray = $ cryptoUtils ->loadPrivateKey ($ srcMetadata , true );
280+ $ certArray = $ cryptoUtils ->loadPublicKey ($ srcMetadata , false );
281+ }
282+
283+ $ algo = $ dstMetadata ->getOptionalString ('signature.algorithm ' , null );
284+ if ($ algo === null ) {
285+ $ algo = $ srcMetadata ->getOptionalString ('signature.algorithm ' , C::SIG_RSA_SHA256 );
286+ }
287+
288+ $ privateKey = PrivateKey::fromFile ($ keyArray ['PEM ' ], $ keyArray ['password ' ]);
289+
290+ $ keyInfo = null ;
291+ if ($ certArray !== null ) {
292+ $ keyInfo = new KeyInfo ([
293+ new X509Data (
294+ [
295+ new X509Certificate ($ certArray ['PEM ' ]),
296+ ],
297+ ),
298+ ]);
299+ }
300+
301+ $ signer = (new SignatureAlgorithmFactory ())->getAlgorithm (
302+ $ algo ,
303+ $ privateKey ,
304+ );
305+
306+ $ element ->sign ($ signer , C::C14N_EXCLUSIVE_WITHOUT_COMMENTS , $ keyInfo );
307+ }
241308}
0 commit comments