Passed
Push — master ( 6db269...70aff8 )
by Jaime Pérez
02:49
created

SignedElementTrait::getSignatureFromXML()   A

Complexity

Conditions 3
Paths 5

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 11
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
A SignedElementTrait::getSignature() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SAML2;
6
7
use DOMElement;
8
use Exception;
9
use RobRichards\XMLSecLibs\XMLSecurityKey;
10
use SAML2\XML\ds\Signature;
11
use Webmozart\Assert\Assert;
12
13
/**
14
 * Helper trait for processing signed elements.
15
 *
16
 * @package simplesamlphp/saml2
17
 */
18
trait SignedElementTrait
19
{
20
    /**
21
     * List of certificates that should be included in the message.
22
     *
23
     * @var string[]
24
     */
25
    protected $certificates = [];
26
27
    /**
28
     * The signature of this element.
29
     *
30
     * @var \SAML2\XML\ds\Signature|null $signature
31
     */
32
    protected $signature;
33
34
35
    /**
36
     * The private key we should use to sign an unsigned message.
37
     *
38
     * The private key can be null, in which case we can only validate an already signed message.
39
     *
40
     * @var \RobRichards\XMLSecLibs\XMLSecurityKey|null
41
     */
42
    protected $signingKey;
43
44
45
    /**
46
     * Get the signature element of this object.
47
     *
48
     * @return \SAML2\XML\ds\Signature|null
49
     */
50
    public function getSignature(): ?Signature
51
    {
52
        return $this->signature;
53
    }
54
55
56
    /**
57
     * Initialize a signed element from XML.
58
     *
59
     * @param \SAML2\XML\ds\Signature|null $signature The ds:Signature object
60
     */
61
    protected function setSignature(?Signature $signature): void
62
    {
63
        if ($signature) {
64
            $this->signature = $signature;
65
        }
66
    }
67
68
69
    /**
70
     * Retrieve the certificates that are included in the message.
71
     *
72
     * @return string[] An array of certificates
73
     */
74
    public function getCertificates(): array
75
    {
76
        return $this->certificates;
77
    }
78
79
80
    /**
81
     * Set the certificates that should be included in the element.
82
     * The certificates should be strings with the PEM encoded data.
83
     *
84
     * @param string[] $certificates An array of certificates.
85
     * @return void
86
     */
87
    public function setCertificates(array $certificates): void
88
    {
89
        $this->certificates = $certificates;
90
    }
91
92
93
    /**
94
     * Set the private key we should use to sign the message.
95
     *
96
     * If the key is null, the message will be sent unsigned.
97
     *
98
     * @param \RobRichards\XMLSecLibs\XMLSecurityKey|null $signingKey
99
     * @return void
100
     */
101
    public function setSigningKey(XMLSecurityKey $signingKey = null): void
102
    {
103
        $this->signingKey = $signingKey;
104
    }
105
106
107
    /**
108
     * Validate this element against a public key.
109
     *
110
     * true is returned on success, false is returned if we don't have any
111
     * signature we can validate. An exception is thrown if the signature
112
     * validation fails.
113
     *
114
     * @param  \RobRichards\XMLSecLibs\XMLSecurityKey $key The key we should check against.
115
     * @return bool True on success, false when we don't have a signature.
116
     * @throws \Exception
117
     */
118
    public function validate(XMLSecurityKey $key): bool
119
    {
120
        if ($this->signature === null) {
121
            return false;
122
        }
123
124
        $signer = $this->signature->getSigner();
125
        Assert::eq(
126
            $key->getAlgorithm(),
127
            $this->signature->getAlgorithm(),
128
            'Algorithm provided in key does not match algorithm used in signature.'
129
        );
130
131
        // check the signature
132
        if ($signer->verify($key) === 1) {
133
            return true;
134
        }
135
136
        throw new Exception("Unable to validate Signature");
137
    }
138
139
140
    /**
141
     * Retrieve certificates that sign this element.
142
     *
143
     * @return array Array with certificates.
144
     * @throws \Exception if an error occurs while trying to extract the public key from a certificate.
145
     */
146
    public function getValidatingCertificates(): array
147
    {
148
        if ($this->signature === null) {
149
            return [];
150
        }
151
        $ret = [];
152
        foreach ($this->signature->getCertificates() as $cert) {
153
            // extract the public key from the certificate for validation.
154
            $key = new XMLSecurityKey($this->signature->getAlgorithm(), ['type' => 'public']);
155
            $key->loadKey($cert);
156
157
            try {
158
                // check the signature.
159
                if ($this->validate($key)) {
160
                    $ret[] = $cert;
161
                }
162
            } catch (Exception $e) {
163
                // this certificate does not sign this element.
164
            }
165
        }
166
167
        return $ret;
168
    }
169
170
171
    /**
172
     * Sign the given XML element.
173
     *
174
     * @param \DOMElement $root The element we should sign.
175
     * @return \DOMElement|null The signed element.
176
     * @throws \Exception If an error occurs while trying to sign.
177
     */
178
    protected function signElement(DOMElement $root): ?DOMElement
179
    {
180
        if ($this->signingKey instanceof XMLSecurityKey) {
181
            $this->signature = new Signature($this->signingKey->getAlgorithm(), $this->certificates, $this->signingKey);
182
            $this->signature->toXML($root);
183
        }
184
        return $root;
185
    }
186
}
187