Skip to content

Commit 6af5646

Browse files
committed
Use a local copy of Message:addSign that uses the xmlsecurity-library for signing
1 parent 21daf7e commit 6af5646

File tree

3 files changed

+93
-24
lines changed

3 files changed

+93
-24
lines changed

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@
3939
"nyholm/psr7": "~1.8.2",
4040
"simplesamlphp/saml2": "~5.0.2",
4141
"simplesamlphp/simplesamlphp": "~2.4.0",
42+
"simplesamlphp/xml-common": "~1.25.0",
43+
"simplesamlphp/xml-security": "~1.13.0",
4244
"symfony/http-foundation": "~6.4.0",
4345
"symfony/psr-http-message-bridge": "~6.4.0"
4446
},
4547
"require-dev": {
4648
"beste/clock": "~3.0.0",
47-
"simplesamlphp/simplesamlphp-test-framework": "~1.9.3",
48-
"simplesamlphp/xml-security": "~1.13.0"
49+
"simplesamlphp/simplesamlphp-test-framework": "~1.9.3"
4950
},
5051
"support": {
5152
"issues": "https://github.com/simplesamlphp/simplesamlphp-module-exampleattributeserver/issues",

src/Controller/AttributeServer.php

Lines changed: 89 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
use SimpleSAML\{Configuration, Error, Logger};
1010
use SimpleSAML\HTTP\RunnableResponse;
1111
use SimpleSAML\Metadata\MetaDataStorageHandler;
12-
use SimpleSAML\Module\saml\Message;;
1312
use SimpleSAML\SAML2\Binding;
1413
use SimpleSAML\SAML2\Binding\HTTPPost;
1514
use SimpleSAML\SAML2\Constants as C;
16-
use SimpleSAML\SAML2\Utils;
15+
use SimpleSAML\SAML2\Utils as SAML2_Utils;
1716
use SimpleSAML\SAML2\XML\saml\{
1817
Assertion,
1918
Attribute,
@@ -23,17 +22,23 @@
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;
3331
use 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;
3437
use Symfony\Bridge\PsrHttpMessage\Factory\{HttpFoundationFactory, PsrHttpFactory};
3538
use 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
}

tests/src/Controller/AttributeServerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public function testMain(): void
6060
$mdh->method('getMetaDataCurrentEntityID')->willReturn('https://example.org/');
6161
$mdh->method('getMetaDataConfig')->willReturn(Configuration::loadFromArray([
6262
'EntityID' => 'auth_source_id',
63-
'testAttributeEndpoint' => 'test',
63+
'testAttributeEndpoint' => 'https://example.org/testAttributeEndpoint',
6464
'privatekey' => PEMCertificatesMock::buildKeysPath(PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY),
6565
'privatekey_pass' => '1234',
6666
]));

0 commit comments

Comments
 (0)