Skip to content

Commit

Permalink
First work
Browse files Browse the repository at this point in the history
  • Loading branch information
tvdijen committed May 11, 2024
1 parent fee7a6c commit f85522e
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 12 deletions.
60 changes: 60 additions & 0 deletions src/SAML2/Artifact.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML2;

/**
* Class for SAML artifacts.
*
* @package simplesamlphp/saml2
*/
final class Artifact
{
/**
* Initialize an artifact.
*
* @param string $artifact
* @param int $endpointIndex
* @param string $sourceId
*/
public function __construct(
protected string $artifact,
protected int $endpointIndex,
protected string $sourceId,
) {
}


/**
* Collect the value of the artifact-property
*
* @return string
*/
public function getArtifact: string
{
return $this->artifact;
}


/**
* Collect the value of the endpointIndex-property
*
* @return int
*/
public function getEndpointIndex(): int
{
return $this->endpointIndex;
}


/**
* Collect the value of the sourceId-property
*
* @return string
*/
public function getSourceId(): string
{
return $this->sourceId;
}
}
149 changes: 149 additions & 0 deletions src/SAML2/Entity/ServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML2\Entity;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use SimpleSAML\SAML2\Constants as C;
use SimpleSAML\SAML2\Exception\MetadataNotFoundException;
use SimpleSAML\SAML2\Exception\Protocol\ResourceNotRecognizedException;
use SimpleSAML\SAML2\XML\samlp\Response;
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
use SimpleSAML\XMLSecurity\CryptoEncoding\PEM;
use SimpleSAML\XMLSecurity\Key\PublicKey;

/**
* Class representing a SAML 2 Service Provider.
*
* @package simplesamlphp/saml2
*/
abstract class ServiceProvider
{
/**
* @param \SimpleSAML\SAML2\XML\md\EntityDescriptor $idpMetadata
* @param bool $encryptedAssertions Whether assertions must be encrypted
* @param bool $disableScoping Wheter to send the samlp:Scoping element in requests
* @param bool $enableUnsolicited Wheter to process unsolicited responses
* @param bool $encryptNameId Whether to encrypt the NameID sent
* @param bool $signAuthnRequest Whether to sign the AuthnRequest sent
* @param bool $signLogout Whether to sign the LogoutRequest/LogoutResponse sent
* @param bool $validateLogout Whether to validate the signature of LogoutRequest/LogoutResponse received
* @param string $signatureAlgorithm The algorithm to use for signing
* @param string[] $blacklistedAlgorithms The algorithms that are disallowed
*/
public function __construct(
protected readonly EntityDescriptor $idpMetadata,
protected readonly bool $encryptedAssertions = false,
protected readonly bool $disableScoping = false,
protected readonly bool $enableUnsolicited = false,
protected readonly bool $encryptNameId = false,
protected readonly bool $signAuthnRequest = false,
protected readonly bool $signLogout = false,
protected readonly bool $validateLogout = true,
protected readonly string $signatureAlgorithm = C::SIG_RSA_SHA256,
protected readonly array $blacklistedAlgorithms,
) {
}


/**
* Receive a validated response.
*
* @param \Psr\Http\Message\ServerRequestInterface $request
* @return \SimpleSAML\SAML2\XML\samlp\Response The validated response.
*
* @throws \SimpleSAML\SAML2\Exception\Protocol\UnsupportedBindingException
*/
public function receiveValidatedResponse(ServerRequestInterface $request): Response
{
$b = Binding::getCurrentBinding($request);

if ($b instanceof HTTPArtifact) {
$artifact = $b->receiveArtifact($request);
$idpMetadata = $this->getMetadataForSha1($artifact->getSourceId());

if ($idpMetadata === null) {
throw new MetadataNotFoundException(sprintf(
'No metadata found for remote provider with SHA1 ID: %s',
$artifact->getSourceId(),
));
}

$b->setIdpMetadata($idpMetadata);
$b->setSPMetadata($this->spMetadata);
}

$response = $b->receive($request);
Assert::isInstanceOf($response, Response::class, ResourceNotRecognizedException::class);

// Validate the signature (if any)
$validatedResponse = $this->validateResponseSignature($response);

// Validate that the destination matches the appropriate endpoint from the SP-metadata
// TODO

// Validate that the status is 'success'
// TODO
}


/**
* Validate the signature of a given response.
*
* @param \SimpleSAML\SAML2\XML\samlp\Response $response
* @return \SimpleSAML\SAML2\XML\samlp\Response The validated response.
*
* @throws \SimpleSAML\XMLSecurity\Exception\SignatureVerificationFailedException
*/
public function validateResponse(Response $response): Response
{
// Validate the signature on the response, if any
if (!$response->isSigned()) {
return $response;
}

$keyDescriptors = $this->spMetadata->getRoleDescriptor()[0]->getKeyDescriptor();
$validatingKeys = [];
foreach ($keyDescriptors as $keyDescriptor) {
$use = $keyDescriptor->getUse();
if ($use === null || $use === 'signing') {
$validatingKeys[] = new PublicKey(new PEM(
PEM::TYPE_PUBLIC_KEY,
$keyDescriptor->getKeyInfo()->getInfo()[0]->getData()[0]->getContent(),
));
}
}

$signatureAlgorithm = $response->getSignature()->getSignedInfo()->getSignatureMethod()->getAlgorithm();
for ($i = 0; $i < count($validadingKeys); $i = $i + 1) {
$verifier = (new SignatureAlgorithmFactory())->getAlgorithm($signatureAlgorithm, $validatingKeys[$i]);
try {
return $response->verify($verifier);
} catch (SignatureVerificationFailedException $e) {
continue;
}
}

throw new SignatureVerificationFailedException();
}


/**
* Find IdP-metadata based on a SHA-1 hash of the entityID. Return `null` if not found.
*/
abstract protected function getIdPMetadataForSha1(string $sourceId): ?EntityDescriptor;


/**
* Find IdP-metadata based on an entityID. Return `null` if not found.
*/
abstract protected function getIdPMetadata(string $entityId): ?EntityDescriptor;


/**
* Find SP-metadata based on an entityID. Return `null` if not found.
*/
abstract protected function getSPMetadata(string $entityId): ?EntityDescriptor;
}
12 changes: 12 additions & 0 deletions src/SAML2/Exception/MetadataNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\SAML2\Exception;

/**
* Exception to be raised when no metadata was found for a specific entityID
*/
class MetadataNotFound extends RuntimeException
{
}
32 changes: 20 additions & 12 deletions src/SAML2/HTTPArtifact.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
use function array_key_exists;
use function base64_decode;
use function base64_encode;
use function bindec;
use function bin2hex;
use function hexdec;
use function openssl_random_pseudo_bytes;
use function pack;
use function sha1;
Expand Down Expand Up @@ -109,6 +109,21 @@ public function send(AbstractMessage $message): ResponseInterface
}


public function receiveArtifact(ServerRequestInterface $request): Artifact
{
$query = $request->getQueryParams();
if (array_key_exists('SAMLart', $query)) {
$artifact = base64_decode($query['SAMLart'], true);
$endpointIndex = bindec(substr($artifact, 2, 2));
$sourceId = bin2hex(substr($artifact, 4, 20));

return new Artifact($artifact, $endpointIndex, $sourceId);
}

throw new Exception('Missing SAMLart parameter.');
}


/**
* Receive a SAML 2 message sent using the HTTP-Artifact binding.
*
Expand All @@ -122,27 +137,20 @@ public function send(AbstractMessage $message): ResponseInterface
*/
public function receive(ServerRequestInterface $request): AbstractMessage
{
$query = $request->getQueryParams();
if (array_key_exists('SAMLart', $query)) {
$artifact = base64_decode($query['SAMLart'], true);
$endpointIndex = bin2hex(substr($artifact, 2, 2));
$sourceId = bin2hex(substr($artifact, 4, 20));
} else {
throw new Exception('Missing SAMLart parameter.');
}
$artifact = $this->receiveArtifact($request);

/** @psalm-suppress UndefinedClass */
$metadataHandler = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance());

$idpMetadata = $metadataHandler->getMetaDataConfigForSha1($sourceId, 'saml20-idp-remote');
$idpMetadata = $metadataHandler->getMetaDataConfigForSha1($artifact->getSourceId(), 'saml20-idp-remote');

if ($idpMetadata === null) {
throw new Exception('No metadata found for remote provider with SHA1 ID: ' . var_export($sourceId, true));
throw new Exception('No metadata found for remote provider with SHA1 ID: ' . var_export($artifact->getSourceId(), true));
}

$endpoint = null;
foreach ($idpMetadata->getEndpoints('ArtifactResolutionService') as $ep) {
if ($ep['index'] === hexdec($endpointIndex)) {
if ($ep['index'] === $artifact->getEndpointIndex()) {
$endpoint = $ep;
break;
}
Expand Down

0 comments on commit f85522e

Please sign in to comment.