Passed
Push — master ( a343df...d20b40 )
by Tim
10:40
created

EncryptedElementTrait::setEncryptedData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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