Passed
Push — master ( d47dbe...fd7e80 )
by Tim
02:00
created

EncryptedElementTrait::getEncryptedKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\XML;
6
7
use DOMElement;
8
use SimpleSAML\Assert\Assert;
9
use SimpleSAML\XML\AbstractXMLElement;
10
use SimpleSAML\XML\Exception\InvalidDOMElementException;
11
use SimpleSAML\XML\Exception\TooManyElementsException;
12
use SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmFactory;
13
use SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmInterface;
14
use SimpleSAML\XMLSecurity\Backend\EncryptionBackend;
15
use SimpleSAML\XMLSecurity\Constants as C;
16
use SimpleSAML\XMLSecurity\Exception\InvalidArgumentException;
17
use SimpleSAML\XMLSecurity\Exception\NoEncryptedDataException;
18
use SimpleSAML\XMLSecurity\Exception\RuntimeException;
19
use SimpleSAML\XMLSecurity\Key\SymmetricKey;
20
use SimpleSAML\XMLSecurity\XML\xenc\EncryptedData;
21
use SimpleSAML\XMLSecurity\XML\xenc\EncryptedKey;
22
23
/**
24
 * Trait aggregating functionality for encrypted elements.
25
 *
26
 * @package simplesamlphp/xml-security
27
 */
28
trait EncryptedElementTrait
29
{
30
    /**
31
     * The current encrypted ID.
32
     *
33
     * @var \SimpleSAML\XMLSecurity\XML\xenc\EncryptedData
34
     * @psalm-suppress PropertyNotSetInConstructor
35
     */
36
    protected EncryptedData $encryptedData;
37
38
    /**
39
     * @var \SimpleSAML\XMLSecurity\XML\xenc\EncryptedKey
40
     */
41
    protected EncryptedKey $encryptedKey;
42
43
44
    /**
45
     * Constructor for encrypted elements.
46
     *
47
     * @param \SimpleSAML\XMLSecurity\XML\xenc\EncryptedData $encryptedData The EncryptedData object.
48
     */
49
    public function __construct(EncryptedData $encryptedData)
50
    {
51
        $this->setEncryptedData($encryptedData);
52
53
        if ($this->encryptedData === null) {
54
            return;
55
        }
56
57
        $keyInfo = $this->encryptedData->getKeyInfo();
58
        if ($keyInfo === null) {
59
            return;
60
        }
61
62
        foreach ($keyInfo->getInfo() as $info) {
63
            if ($info instanceof EncryptedKey) {
64
                $this->encryptedKey = $info;
65
            }
66
        }
67
    }
68
69
70
    /**
71
     * Whether the encrypted object is accompanied by the decryption key or not.
72
     *
73
     * @return bool
74
     */
75
    public function hasDecryptionKey(): bool
76
    {
77
        return $this->encryptedKey === null;
78
    }
79
80
81
    /**
82
     * Get the encrypted key used to encrypt the current element.
83
     *
84
     * @return \SimpleSAML\XMLSecurity\XML\xenc\EncryptedKey
85
     */
86
    public function getEncryptedKey(): EncryptedKey
87
    {
88
        return $this->encryptedKey;
89
90
    }
91
92
93
    /**
94
     * Get the EncryptedData object.
95
     *
96
     * @return \SimpleSAML\XMLSecurity\XML\xenc\EncryptedData
97
     */
98
    public function getEncryptedData(): EncryptedData
99
    {
100
        return $this->encryptedData;
101
    }
102
103
104
    /**
105
     * @param \SimpleSAML\XMLSecurity\XML\xenc\EncryptedData $encryptedData
106
     */
107
    protected function setEncryptedData(EncryptedData $encryptedData): void
108
    {
109
        $this->encryptedData = $encryptedData;
110
    }
111
112
113
    /**
114
     * Decrypt the data in any given element.
115
     *
116
     * Use this method to decrypt an EncryptedData XML elemento into a string. If the resulting plaintext represents
117
     * an XML document which has a corresponding implementation extending \SimpleSAML\XML\XMLElementInterface, you
118
     * can call this method to build an object from the resulting plaintext:
119
     *
120
     *     $data = $this->decryptData($decryptor);
121
     *     $xml = \SimpleSAML\XML\DOMDocumentFactory::fromString($data);
122
     *     $object = MyObject::fromXML($xml->documentElement);
123
     *
124
     * If the class using this trait implements \SimpleSAML\XMLSecurity\XML\EncryptedElementInterface, then the
125
     * decrypt() method will only need the proposed code and return the object.
126
     *
127
     * @param \SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmInterface $decryptor The decryptor to use to
128
     * decrypt the object.
129
     *
130
     * @return string The decrypted data.
131
     */
132
    protected function decryptData(EncryptionAlgorithmInterface $decryptor): string
133
    {
134
        $encData = $this->getEncryptedData();
135
        if (!$encData instanceof EncryptedData) {
0 ignored issues
show
introduced by
$encData is always a sub-type of SimpleSAML\XMLSecurity\XML\xenc\EncryptedData.
Loading history...
136
            throw new NoEncryptedDataException();
137
        }
138
139
        $algId = $decryptor->getAlgorithmId();
140
        $encMethod = $this->getEncryptedData()->getEncryptionMethod();
141
        if ($encMethod !== null) {
142
            $algId = $encMethod->getAlgorithm();
143
        }
144
145
        if (in_array($decryptor->getAlgorithmId(), C::$KEY_TRANSPORT_ALGORITHMS)) {
146
            // the decryptor uses a key transport algorithm, check if we have a session key
147
            if ($this->hasDecryptionKey() === null) {
0 ignored issues
show
introduced by
The condition $this->hasDecryptionKey() === null is always false.
Loading history...
148
                throw new RuntimeException('Cannot use a key transport algorithm to decrypt an object.');
149
            }
150
151
            if ($encMethod === null) {
152
                throw new RuntimeException('Cannot decrypt data with a session key and no EncryptionMethod.');
153
            }
154
155
            $encryptedKey = $this->getEncryptedKey();
156
            $decryptionKey = $encryptedKey->decrypt($decryptor);
157
158
            $factory = new EncryptionAlgorithmFactory($this->getBlacklistedAlgorithms());
159
            $decryptor = $factory->getAlgorithm($encMethod->getAlgorithm(), new SymmetricKey($decryptionKey));
160
            $decryptor->setBackend($this->getEncryptionBackend());
161
        }
162
163
        if ($algId !== $decryptor->getAlgorithmId()) {
164
            throw new InvalidArgumentException('Decryption algorithm does not match EncryptionMethod.');
165
        }
166
167
        return $decryptor->decrypt(base64_decode($encData->getCipherData()->getCipherValue()->getContent()));
168
    }
169
170
171
    /**
172
     * @inheritDoc
173
     * @return \SimpleSAML\XMLSecurity\XML\EncryptedElementInterface
174
     *
175
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
176
     *   If the qualified name of the supplied element is wrong
177
     */
178
    public static function fromXML(DOMElement $xml): self
179
    {
180
        Assert::same(
181
            $xml->localName,
182
            AbstractXMLElement::getClassName(static::class),
183
            InvalidDOMElementException::class,
184
        );
185
        Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class);
0 ignored issues
show
Bug introduced by
The constant SimpleSAML\XMLSecurity\X...cryptedElementTrait::NS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
186
187
        $ed = EncryptedData::getChildrenOfClass($xml);
188
        Assert::count(
189
            $ed,
190
            1,
191
            sprintf(
192
                'No more or less than one EncryptedData element allowed in %s.',
193
                AbstractXMLElement::getClassName(static::class),
194
            ),
195
            TooManyElementsException::class,
196
        );
197
198
        return new static($ed[0]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new static($ed[0]) returns the type SimpleSAML\XMLSecurity\XML\EncryptedElementTrait which is incompatible with the documented return type SimpleSAML\XMLSecurity\X...cryptedElementInterface.
Loading history...
199
    }
200
201
202
    /**
203
     * @inheritDoc
204
     */
205
    public function toXML(DOMElement $parent = null): DOMElement
206
    {
207
        /** @psalm-var \DOMDocument $e->ownerDocument */
208
        $e = $this->instantiateParentElement($parent);
209
        $this->encryptedData->toXML($e);
210
        return $e;
211
    }
212
213
214
    /**
215
     * Create a document structure for this element.
216
     *
217
     * The AbstractXMLElement class implements this method. If your object inherits from that class, you will already
218
     * have this method out of the box.
219
     *
220
     * @param \DOMElement|null $parent The element we should append to.
221
     * @return \DOMElement
222
     */
223
    abstract public function instantiateParentElement(DOMElement $parent = null): DOMElement;
224
225
226
    /**
227
     * Get the encryption backend to use for any encryption operation.
228
     *
229
     * @return \SimpleSAML\XMLSecurity\Backend\EncryptionBackend|null The encryption backend to use, or null if we
230
     * want to use the default.
231
     */
232
    abstract public function getEncryptionBackend(): ?EncryptionBackend;
233
234
235
    /**
236
     * Get the list of algorithms that are blacklisted for any encryption operation.
237
     *
238
     * @return string[]|null An array with all algorithm identifiers that are blacklisted, or null if we want to use the
239
     * defaults.
240
     */
241
    abstract public function getBlacklistedAlgorithms(): ?array;
242
}
243