Skip to content

Commit

Permalink
Add element xenc:AgreementMethod
Browse files Browse the repository at this point in the history
  • Loading branch information
tvdijen committed Nov 27, 2024
1 parent 26d0811 commit 0c27170
Show file tree
Hide file tree
Showing 5 changed files with 403 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,7 @@ class Constants extends \SimpleSAML\XML\Constants
public const XMLENC_ELEMENT = 'http://www.w3.org/2001/04/xmlenc#Element';
public const XMLENC_ENCRYPTEDKEY = 'http://www.w3.org/2001/04/xmlenc#EncryptedKey';
public const XMLENC_EXI = 'http://www.w3.org/2009/xmlenc11#EXI';

// The namespace for the Elliptic Curve Diffie-Hellman Ephemeral Static (ECDH-ES) algorithm
public const XMLENC11_ECDH_ES = 'http://www.w3.org/2009/xmlenc11#ECDH-ES';
}
159 changes: 159 additions & 0 deletions src/XML/xenc/AbstractAgreementMethodType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\XMLSecurity\XML\xenc;

use DOMElement;
use SimpleSAML\Assert\Assert;
use SimpleSAML\XML\Exception\InvalidDOMElementException;
use SimpleSAML\XML\Exception\SchemaViolationException;
use SimpleSAML\XML\Exception\TooManyElementsException;
use SimpleSAML\XML\ExtendableElementTrait;
use SimpleSAML\XML\XsNamespace as NS;

use function array_pop;

/**
* A class implementing the xenc:AbstractAgreementMethodType element.
*
* @package simplesamlphp/xml-security
*/
abstract class AbstractAgreementMethodType extends AbstractXencElement
{
use ExtendableElementTrait;

/** The namespace-attribute for the xs:any element */
public const XS_ANY_ELT_NAMESPACE = NS::OTHER;


/**
* AgreementMethodType constructor.
*
* @param string $algorithm
* @param \SimpleSAML\XMLSecurity\XML\xenc\KANonce|null $kaNonce
* @param \SimpleSAML\XMLSecurity\XML\xenc\OriginatorKeyInfo|null $originatorKeyInfo
* @param \SimpleSAML\XMLSecurity\XML\xenc\RecipientKeyInfo|null $recipientKeyInfo
* @param list<\SimpleSAML\XML\SerializableElementInterface> $children
*/
final public function __construct(
protected string $algorithm,
protected ?KANonce $kaNonce = null,
protected ?OriginatorKeyInfo $originatorKeyInfo = null,
protected ?RecipientKeyInfo $recipientKeyInfo = null,
protected array $children = [],
) {
Assert::validURI($algorithm, SchemaViolationException::class); // Covers the empty string

$this->setElements($children);
}


/**
* Get the URI identifying the algorithm used by this agreement method.
*
* @return string
*/
public function getAlgorithm(): string
{
return $this->algorithm;
}


/**
* Get the KA-Nonce.
*
* @return \SimpleSAML\XMLSecurity\XML\xenc\KANonce|null
*/
public function getKANonce(): ?KANonce
{
return $this->kaNonce;
}


/**
* Get the Originator KeyInfo.
*
* @return \SimpleSAML\XMLSecurity\XML\xenc\OriginatorKeyInfo|null
*/
public function getOriginatorKeyInfo(): ?OriginatorKeyInfo
{
return $this->originatorKeyInfo;
}


/**
* Get the Recipient KeyInfo.
*
* @return \SimpleSAML\XMLSecurity\XML\xenc\RecipientKeyInfo|null
*/
public function getRecipientKeyInfo(): ?RecipientKeyInfo
{
return $this->recipientKeyInfo;
}


/**
* Initialize an AgreementMethod object from an existing XML.
*
* @param \DOMElement $xml
* @return static
*
* @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
* if the qualified name of the supplied element is wrong
* @throws \SimpleSAML\XML\Exception\MissingAttributeException
* if the supplied element is missing one of the mandatory attributes
* @throws \SimpleSAML\XML\Exception\TooManyElementsException
* if too many child-elements of a type are specified
*/
public static function fromXML(DOMElement $xml): static
{
Assert::same($xml->localName, 'AgreementMethod', InvalidDOMElementException::class);
Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class);

$algorithm = self::getAttribute($xml, 'Algorithm');

$kaNonce = KANonce::getChildrenOfClass($xml);
Assert::maxCount($kaNonce, 1, TooManyElementsException::class);

$originatorKeyInfo = OriginatorKeyInfo::getChildrenOfClass($xml);
Assert::maxCount($originatorKeyInfo, 1, TooManyElementsException::class);

$recipientKeyInfo = RecipientKeyInfo::getChildrenOfClass($xml);
Assert::maxCount($recipientKeyInfo, 1, TooManyElementsException::class);

$children = self::getChildElementsFromXML($xml);

return new static(
$algorithm,
array_pop($kaNonce),
array_pop($originatorKeyInfo),
array_pop($recipientKeyInfo),
$children,
);
}


/**
* Convert this AgreementMethod object to XML.
*
* @param \DOMElement|null $parent The element we should append this AgreementMethod to.
* @return \DOMElement
*/
public function toXML(DOMElement $parent = null): DOMElement
{
$e = $this->instantiateParentElement($parent);
$e->setAttribute('Algorithm', $this->getAlgorithm());

$this->getKANonce()?->toXML($e);

foreach ($this->getElements() as $child) {
$child->toXML($e);
}

$this->getOriginatorKeyInfo()?->toXML($e);
$this->getRecipientKeyInfo()?->toXML($e);

return $e;
}
}
14 changes: 14 additions & 0 deletions src/XML/xenc/AgreementMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\XMLSecurity\XML\xenc;

/**
* A class implementing the xenc:AgreementMethod element.
*
* @package simplesamlphp/xml-security
*/
final class AgreementMethod extends AbstractAgreementMethodType
{
}
207 changes: 207 additions & 0 deletions tests/XML/xenc/AgreementMethodTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\XMLSecurity\Test\XML\xenc;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use SimpleSAML\XML\Chunk;
use SimpleSAML\XML\DOMDocumentFactory;
use SimpleSAML\XML\TestUtils\SerializableElementTestTrait;
use SimpleSAML\XMLSecurity\Constants as C;
use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock;
use SimpleSAML\XMLSecurity\Utils\XPath;
use SimpleSAML\XMLSecurity\XML\ds\{KeyName, X509Certificate, X509Data, X509SubjectName};
use SimpleSAML\XMLSecurity\XML\xenc\{AbstractAgreementMethodType, AbstractXencElement};
use SimpleSAML\XMLSecurity\XML\xenc\{AgreementMethod, KANonce, OriginatorKeyInfo, RecipientKeyInfo};

use function dirname;
use function openssl_x509_parse;
use function str_replace;
use function strval;

/**
* Class \SimpleSAML\XMLSecurity\Test\XML\xenc\AgreementMethodTest
*
* @package simplesamlphp/xml-security
*/
#[CoversClass(AbstractXencElement::class)]
#[CoversClass(AbstractAgreementMethodType::class)]
#[CoversClass(AgreementMethod::class)]
final class AgreementMethodTest extends TestCase
{
use SerializableElementTestTrait;

/** @var string */
private static string $certificate;

/** @var string[] */
private static array $certData;


/**
*/
public function setUp(): void
{
self::$testedClass = AgreementMethod::class;

self::$xmlRepresentation = DOMDocumentFactory::fromFile(
dirname(__FILE__, 3) . '/resources/xml/xenc_AgreementMethod.xml',
);

self::$certificate = str_replace(
[
'-----BEGIN CERTIFICATE-----',
'-----END CERTIFICATE-----',
'-----BEGIN RSA PUBLIC KEY-----',
'-----END RSA PUBLIC KEY-----',
"\r\n",
"\n",
],
[
'',
'',
'',
'',
"\n",
'',
],
PEMCertificatesMock::getPlainCertificate(PEMCertificatesMock::SELFSIGNED_CERTIFICATE),
);

self::$certData = openssl_x509_parse(
PEMCertificatesMock::getPlainCertificate(PEMCertificatesMock::SELFSIGNED_CERTIFICATE),
);
}


/**
*/
public function testMarshalling(): void
{
$kaNonce = new KANonce('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=');

$someChunk = new Chunk(DOMDocumentFactory::fromString(
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">some</ssp:Chunk>',
)->documentElement);

$originatorKeyInfo = new OriginatorKeyInfo(
[
new KeyName('testkey'),
new X509Data(
[
new X509Certificate(self::$certificate),
new X509SubjectName(self::$certData['name']),
],
),
new Chunk(DOMDocumentFactory::fromString(
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">originator</ssp:Chunk>',
)->documentElement),
],
'fed123',
);

$recipientKeyInfo = new RecipientKeyInfo(
[
new KeyName('testkey'),
new X509Data(
[
new X509Certificate(self::$certificate),
new X509SubjectName(self::$certData['name']),
],
),
new Chunk(DOMDocumentFactory::fromString(
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">recipient</ssp:Chunk>',
)->documentElement),
],
'fed654',
);

$agreementMethod = new AgreementMethod(
C::XMLENC11_ECDH_ES,
$kaNonce,
$originatorKeyInfo,
$recipientKeyInfo,
[$someChunk],
);

$this->assertEquals(
self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
strval($agreementMethod),
);
}


public function testMarshallingElementOrdering(): void
{
$kaNonce = new KANonce('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=');

$someChunk = new Chunk(DOMDocumentFactory::fromString(
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">some</ssp:Chunk>',
)->documentElement);

$originatorKeyInfo = new OriginatorKeyInfo(
[
new KeyName('testkey'),
new X509Data(
[
new X509Certificate(self::$certificate),
new X509SubjectName(self::$certData['name']),
],
),
new Chunk(DOMDocumentFactory::fromString(
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">originator</ssp:Chunk>',
)->documentElement),
],
'fed321',
);

$recipientKeyInfo = new RecipientKeyInfo(
[
new KeyName('testkey'),
new X509Data(
[
new X509Certificate(self::$certificate),
new X509SubjectName(self::$certData['name']),
],
),
new Chunk(DOMDocumentFactory::fromString(
'<ssp:Chunk xmlns:ssp="urn:x-simplesamlphp:namespace">recipient</ssp:Chunk>',
)->documentElement),
],
'fed654',
);

$agreementMethod = new AgreementMethod(
C::XMLENC11_ECDH_ES,
$kaNonce,
$originatorKeyInfo,
$recipientKeyInfo,
[$someChunk],
);

// Marshall it to a \DOMElement
$agreementMethodElement = $agreementMethod->toXML();

$xpCache = XPath::getXPath($agreementMethodElement);

// Test for an KA-Nonce
/** @var \DOMElement[] $kaNonceElements */
$kaNonceElements = XPath::xpQuery($agreementMethodElement, './xenc:KA-Nonce', $xpCache);
$this->assertCount(1, $kaNonceElements);

// Test ordering of AgreementMethod contents
/** @var \DOMElement[] $agreementMethodElements */
$agreementMethodElements = XPath::xpQuery(
$agreementMethodElement,
'./xenc:KA-Nonce/following-sibling::*',
$xpCache,
);

$this->assertCount(3, $agreementMethodElements);
$this->assertEquals('ssp:Chunk', $agreementMethodElements[0]->tagName);
$this->assertEquals('xenc:OriginatorKeyInfo', $agreementMethodElements[1]->tagName);
$this->assertEquals('xenc:RecipientKeyInfo', $agreementMethodElements[2]->tagName);
}
}
Loading

0 comments on commit 0c27170

Please sign in to comment.