Skip to content

Commit b393625

Browse files
committed
Rewrite signed elements structure
1 parent 86de607 commit b393625

5 files changed

Lines changed: 349 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\XML;
6+
7+
use DOMElement;
8+
use SimpleSAML\XML\DOMDocumentFactory;
9+
use SimpleSAML\XML\Exception\MissingAttributeException;
10+
use Serializable;
11+
use SimpleSAML\Assert\Assert;
12+
13+
/**
14+
* Abstract class to be implemented by all signed classes
15+
*
16+
* @package simplesamlphp/xml-security
17+
*/
18+
abstract class AbstractSignedXMLElement implements SignedElementInterface
19+
{
20+
use SignedElementTrait;
21+
22+
/**
23+
* Create a document structure for this element
24+
*
25+
* @param \DOMElement|null $parent The element we should append to.
26+
* @return \DOMElement
27+
*/
28+
/**
29+
* The signed DOM structure.
30+
*
31+
* @var \DOMElement
32+
*/
33+
protected DOMElement $structure;
34+
35+
/**
36+
* The unsigned elelement.
37+
*
38+
* @var \SimpleSAML\XML\AbstractXMLElement
39+
*/
40+
protected AbstractXMLElement $elt;
41+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace SimpleSAML\SAML2\XML;
4+
5+
use SimpleSAML\XMLSecurity\XMLSecurityKey;
6+
7+
/**
8+
* An interface describing signable elements.
9+
*
10+
* @package simplesamlphp/xml-security
11+
*/
12+
interface SignableElementInterface
13+
{
14+
/**
15+
* Retrieve the certificates that are included in the message.
16+
*
17+
* @return string[] An array of certificates
18+
*/
19+
public function getCertificates(): array;
20+
21+
22+
/**
23+
* Set the certificates that should be included in the element.
24+
* The certificates should be strings with the PEM encoded data.
25+
*
26+
* @param string[] $certificates An array of certificates.
27+
*/
28+
public function setCertificates(array $certificates): void;
29+
30+
31+
/**
32+
* Get the private key we should use to sign the message.
33+
*
34+
* If the key is null, the message will be sent unsigned.
35+
*
36+
* @return \SimpleSAML\XMLSecurity\XMLSecurityKey|null
37+
*/
38+
public function getSigningKey(): ?XMLSecurityKey;
39+
40+
41+
/**
42+
* Set the private key we should use to sign the message.
43+
*
44+
* If the key is null, the message will be sent unsigned.
45+
*
46+
* @param \SimpleSAML\XMLSecurity\XMLSecurityKey|null $signingKey
47+
*/
48+
public function setSigningKey(XMLSecurityKey $signingKey = null): void;
49+
}

src/XML/SignableElementTrait.php

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\XMLSecurity\XML;
6+
7+
//use DOMElement;
8+
//use DOMNode;
9+
//use Exception;
10+
use SimpleSAML\Assert\Assert;
11+
//use SimpleSAML\XMLSecurity\Utils\Security as XMLSecurityUtils;
12+
use SimpleSAML\XMLSecurity\XML\ds\Signature;
13+
use SimpleSAML\XMLSecurity\XMLSecurityKey;
14+
use SimpleSAML\XML\Utils as XMLUtils;
15+
16+
/**
17+
* Helper trait for processing signed elements.
18+
*
19+
* @package simplesamlphp/xml-security
20+
*/
21+
trait SignableElementTrait
22+
{
23+
/**
24+
* List of certificates that should be included in the message.
25+
*
26+
* @var string[]
27+
*/
28+
protected array $certificates = [];
29+
30+
/**
31+
* The private key we should use to sign an unsigned message.
32+
*
33+
* The private key can be null, in which case we can only validate an already signed message.
34+
*
35+
* @var \SimpleSAML\XMLSecurity\XMLSecurityKey|null
36+
*/
37+
protected ?XMLSecurityKey $signingKey = null;
38+
39+
40+
/**
41+
* Retrieve the certificates that are included in the message.
42+
*
43+
* @return string[] An array of certificates
44+
*/
45+
public function getCertificates(): array
46+
{
47+
return $this->certificates;
48+
}
49+
50+
51+
/**
52+
* Set the certificates that should be included in the element.
53+
* The certificates should be strings with the PEM encoded data.
54+
*
55+
* @param string[] $certificates An array of certificates.
56+
*/
57+
public function setCertificates(array $certificates): void
58+
{
59+
Assert::allStringNotEmpty($certificates);
60+
61+
$this->certificates = $certificates;
62+
}
63+
64+
65+
/**
66+
* Get the private key we should use to sign the message.
67+
*
68+
* If the key is null, the message will be sent unsigned.
69+
*
70+
* @return \SimpleSAML\XMLSecurity\XMLSecurityKey|null
71+
*/
72+
public function getSigningKey(): ?XMLSecurityKey
73+
{
74+
return $this->signingKey;
75+
}
76+
77+
78+
/**
79+
* Set the private key we should use to sign the message.
80+
*
81+
* If the key is null, the message will be sent unsigned.
82+
*
83+
* @param \SimpleSAML\XMLSecurity\XMLSecurityKey|null $signingKey
84+
*/
85+
public function setSigningKey(XMLSecurityKey $signingKey = null): void
86+
{
87+
$this->signingKey = $signingKey;
88+
}
89+
90+
91+
/**
92+
* Sign the given XML element.
93+
*
94+
* @param \DOMElement $root The element we should sign.
95+
* @return \DOMElement The signed element.
96+
* @throws \Exception If an error occurs while trying to sign.
97+
protected function signElement(DOMElement $root, DOMNode $insertBefore = null): DOMElement
98+
{
99+
if ($this->signingKey instanceof XMLSecurityKey) {
100+
if ($insertBefore !== null) {
101+
XMLSecurityUtils::insertSignature($this->signingKey, $this->certificates, $root, $insertBefore);
102+
103+
$doc = clone $root->ownerDocument;
104+
$this->signature = Signature::fromXML(XMLUtils::xpQuery($doc->documentElement, './ds:Signature')[0]);
105+
} else {
106+
$this->signature = new Signature($this->signingKey->getAlgorithm(), $this->certificates, $this->signingKey);
107+
$this->signature->toXML($root);
108+
}
109+
}
110+
return $root;
111+
}
112+
*/
113+
}

src/XML/SignedElementInterface.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace SimpleSAML\SAML2\XML;
4+
5+
use SimpleSAML\XMLSecurity\XMLSecurityKey;
6+
7+
/**
8+
* An interface describing signed elements.
9+
*
10+
* @package simplesamlphp/xml-security
11+
*/
12+
interface SignedElementInterface
13+
{
14+
/**
15+
* Retrieve certificates that sign this element.
16+
*
17+
* @return array Array with certificates.
18+
* @throws \Exception if an error occurs while trying to extract the public key from a certificate.
19+
*/
20+
public function getValidatingCertificates(): array;
21+
22+
23+
/**
24+
* Validate this element against a public key.
25+
*
26+
* If no signature is present, false is returned. If a signature is present,
27+
* but cannot be verified, an exception will be thrown.
28+
*
29+
* @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key we should check against.
30+
* @return bool True if successful, false if we don't have a signature that can be verified.
31+
*/
32+
public function validate(XMLSecurityKey $key): bool;
33+
}

src/XML/SignedElementTrait.php

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\XMLSecurity\XML;
6+
7+
use Exception;
8+
use SimpleSAML\Assert\Assert;
9+
use SimpleSAML\XMLSecurity\XML\ds\Signature;
10+
use SimpleSAML\XMLSecurity\XMLSecurityKey;
11+
12+
/**
13+
* Helper trait for processing signed elements.
14+
*
15+
* @package simplesamlphp/xml-security
16+
*/
17+
trait SignableElementTrait
18+
{
19+
/**
20+
* The signature of this element.
21+
*
22+
* @var \SimpleSAML\XMLSecurity\XML\ds\Signature $signature
23+
*/
24+
protected Signature $signature;
25+
26+
27+
/**
28+
* Get the signature element of this object.
29+
*
30+
* @return \SimpleSAML\XMLSecurity\XML\ds\Signature|null
31+
*/
32+
public function getSignature(): ?Signature
33+
{
34+
return $this->signature;
35+
}
36+
37+
38+
/**
39+
* Initialize a signed element from XML.
40+
*
41+
* @param \SimpleSAML\XMLSecurity\XML\ds\Signature|null $signature The ds:Signature object
42+
*/
43+
protected function setSignature(?Signature $signature): void
44+
{
45+
if ($signature) {
46+
$this->signature = $signature;
47+
}
48+
}
49+
50+
51+
/**
52+
* Validate this element against a public key.
53+
*
54+
* true is returned on success, false is returned if we don't have any
55+
* signature we can validate. An exception is thrown if the signature
56+
* validation fails.
57+
*
58+
* @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key we should check against.
59+
* @return bool True on success, false when we don't have a signature.
60+
* @throws \Exception
61+
*/
62+
public function validate(XMLSecurityKey $key): bool
63+
{
64+
if ($this->signature === null) {
65+
return false;
66+
}
67+
68+
$signer = $this->signature->getSigner();
69+
Assert::eq(
70+
$key->getAlgorithm(),
71+
$this->signature->getAlgorithm(),
72+
'Algorithm provided in key does not match algorithm used in signature.'
73+
);
74+
75+
// check the signature
76+
if ($signer->verify($key) === 1) {
77+
return true;
78+
}
79+
80+
throw new Exception("Unable to validate Signature");
81+
}
82+
83+
84+
/**
85+
* Retrieve certificates that sign this element.
86+
*
87+
* @return array Array with certificates.
88+
* @throws \Exception if an error occurs while trying to extract the public key from a certificate.
89+
*/
90+
public function getValidatingCertificates(): array
91+
{
92+
if ($this->signature === null) {
93+
return [];
94+
}
95+
$ret = [];
96+
foreach ($this->signature->getCertificates() as $cert) {
97+
// extract the public key from the certificate for validation.
98+
$key = new XMLSecurityKey($this->signature->getAlgorithm(), ['type' => 'public']);
99+
$key->loadKey($cert);
100+
101+
try {
102+
// check the signature.
103+
if ($this->validate($key)) {
104+
$ret[] = $cert;
105+
}
106+
} catch (Exception $e) {
107+
// this certificate does not sign this element.
108+
}
109+
}
110+
111+
return $ret;
112+
}
113+
}

0 commit comments

Comments
 (0)