EncryptedElementTrait::decryptData()   B
last analyzed

Complexity

Conditions 10
Paths 15

Size

Total Lines 53
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 30
c 0
b 0
f 0
dl 0
loc 53
rs 7.6666
cc 10
nc 15
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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