EncryptedElementTrait::fromXML()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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