EncryptedElementReader   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 222
Duplicated Lines 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 28
eloc 75
c 2
b 1
f 0
dl 0
loc 222
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
A decryptSymmetricKey() 0 23 4
A loadSymmetricKey() 0 8 2
A getSymmetricKeyInfo() 0 3 1
A decrypt() 0 16 2
A serialize() 0 3 1
A loadSymmetricKeyInfo() 0 8 2
A decryptMulti() 0 25 6
A deserialize() 0 19 3
A buildXmlElement() 0 25 4
A getSymmetricKey() 0 3 1
A decryptCipher() 0 8 2
1
<?php
2
3
/*
4
 * This file is part of the LightSAML-Core package.
5
 *
6
 * (c) Milos Tomic <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace LightSaml\Model\Assertion;
13
14
use LightSaml\Credential\CredentialInterface;
15
use LightSaml\Error\LightSamlSecurityException;
16
use LightSaml\Error\LightSamlXmlException;
17
use LightSaml\Model\Context\DeserializationContext;
18
use LightSaml\Model\Context\SerializationContext;
19
use RobRichards\XMLSecLibs\XMLSecEnc;
20
use RobRichards\XMLSecLibs\XMLSecurityKey;
21
22
class EncryptedElementReader extends EncryptedElement
23
{
24
    /** @var XMLSecEnc */
25
    protected $xmlEnc;
26
27
    /** @var XMLSecurityKey */
28
    protected $symmetricKey;
29
30
    /** @var XMLSecurityKey */
31
    protected $symmetricKeyInfo;
32
33
    /**
34
     * @return XMLSecurityKey
35
     */
36
    public function getSymmetricKey()
37
    {
38
        return $this->symmetricKey;
39
    }
40
41
    /**
42
     * @return XMLSecurityKey
43
     */
44
    public function getSymmetricKeyInfo()
45
    {
46
        return $this->symmetricKeyInfo;
47
    }
48
49
    /**
50
     * @throws \LogicException
51
     *
52
     * @return void
53
     */
54
    public function serialize(\DOMNode $parent, SerializationContext $context)
55
    {
56
        throw new \LogicException('EncryptedElementReader can not be used for serialization');
57
    }
58
59
    public function deserialize(\DOMNode $node, DeserializationContext $context)
60
    {
61
        $list = $context->getXpath()->query('xenc:EncryptedData', $node);
62
        if (0 == $list->length) {
63
            throw new LightSamlXmlException('Missing encrypted data in <saml:EncryptedAssertion>');
64
        }
65
        if (1 != $list->length) {
66
            throw new LightSamlXmlException('More than one encrypted data element in <saml:EncryptedAssertion>');
67
        }
68
69
        /** @var \DOMElement $encryptedData */
70
        $encryptedData = $list->item(0);
71
        $this->xmlEnc = new XMLSecEnc();
72
        $this->xmlEnc->setNode($encryptedData);
73
        $this->xmlEnc->type = $encryptedData->getAttribute('Type');
74
75
        $this->symmetricKey = $this->loadSymmetricKey();
76
77
        $this->symmetricKeyInfo = $this->loadSymmetricKeyInfo($this->symmetricKey);
78
    }
79
80
    /**
81
     * @param XMLSecurityKey[] $inputKeys
82
     *
83
     * @throws \LogicException
84
     * @throws \LightSaml\Error\LightSamlXmlException
85
     * @throws \LightSaml\Error\LightSamlSecurityException
86
     *
87
     * @return \DOMElement
88
     */
89
    public function decryptMulti(array $inputKeys)
90
    {
91
        /** @var \LogicException $lastException */
92
        $lastException = null;
93
94
        foreach ($inputKeys as $key) {
95
            if ($key instanceof CredentialInterface) {
96
                $key = $key->getPrivateKey();
97
            }
98
            if (false == $key instanceof XMLSecurityKey) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
99
                throw new \InvalidArgumentException('Expected XMLSecurityKey');
100
            }
101
102
            try {
103
                return $this->decrypt($key);
104
            } catch (\Exception $ex) {
105
                $lastException = $ex;
106
            }
107
        }
108
109
        if ($lastException) {
110
            throw $lastException;
111
        }
112
113
        throw new LightSamlSecurityException('No key provided for decryption');
114
    }
115
116
    /**
117
     * @throws \LogicException
118
     * @throws \LightSaml\Error\LightSamlXmlException
119
     * @throws \LightSaml\Error\LightSamlSecurityException
120
     *
121
     * @return \DOMElement
122
     */
123
    public function decrypt(XMLSecurityKey $inputKey)
124
    {
125
        $this->symmetricKey = $this->loadSymmetricKey();
126
        $this->symmetricKeyInfo = $this->loadSymmetricKeyInfo($this->symmetricKey);
127
128
        if ($this->symmetricKeyInfo->isEncrypted) {
129
            $this->decryptSymmetricKey($inputKey);
130
        } else {
131
            $this->symmetricKey = $inputKey;
132
        }
133
134
        $decrypted = $this->decryptCipher();
135
136
        $result = $this->buildXmlElement($decrypted);
137
138
        return $result;
139
    }
140
141
    /**
142
     * @param string $decrypted
143
     *
144
     * @return \DOMElement
145
     */
146
    protected function buildXmlElement($decrypted)
147
    {
148
        /*
149
         * This is a workaround for the case where only a subset of the XML
150
         * tree was serialized for encryption. In that case, we may miss the
151
         * namespaces needed to parse the XML.
152
         */
153
        $xml = sprintf(
154
            '<root xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">%s</root>',
155
            $decrypted
156
        );
157
        $newDoc = new \DOMDocument();
158
        if (false == @$newDoc->loadXML($xml)) {
159
            throw new LightSamlXmlException('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?');
160
        }
161
        $decryptedElement = $newDoc->firstChild->firstChild;
162
        if (null == $decryptedElement) {
163
            throw new LightSamlSecurityException('Missing encrypted element.');
164
        }
165
166
        if (false == $decryptedElement instanceof \DOMElement) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
167
            throw new LightSamlXmlException('Decrypted element was not actually a DOMElement.');
168
        }
169
170
        return $decryptedElement;
171
    }
172
173
    /**
174
     * @return string
175
     *
176
     * @throws \Exception
177
     */
178
    protected function decryptCipher()
179
    {
180
        $decrypted = $this->xmlEnc->decryptNode($this->symmetricKey, false);
181
        if (false == is_string($decrypted)) {
0 ignored issues
show
introduced by
The condition false == is_string($decrypted) is always false.
Loading history...
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
182
            throw new \LogicException('Expected decrypted string');
183
        }
184
185
        return $decrypted;
186
    }
187
188
    /**
189
     * @throws \Exception
190
     */
191
    protected function decryptSymmetricKey(XMLSecurityKey $inputKey)
192
    {
193
        /** @var XMLSecEnc $encKey */
194
        $encKey = $this->symmetricKeyInfo->encryptedCtx;
195
        $this->symmetricKeyInfo->key = $inputKey->key;
196
197
        $keySize = $this->symmetricKey->getSymmetricKeySize();
198
        if (null === $keySize) {
199
            // To protect against "key oracle" attacks, we need to be able to create a
200
            // symmetric key, and for that we need to know the key size.
201
            throw new LightSamlSecurityException(sprintf("Unknown key size for encryption algorithm: '%s'", $this->symmetricKey->type));
202
        }
203
204
        /** @var string $key */
205
        $key = $encKey->decryptKey($this->symmetricKeyInfo);
206
        if (false == is_string($key)) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
introduced by
The condition false == is_string($key) is always false.
Loading history...
207
            throw new \LogicException('Expected string');
208
        }
209
        if (strlen($key) != $keySize) {
210
            throw new LightSamlSecurityException(sprintf("Unexpected key size of '%s' bits for encryption algorithm '%s', expected '%s' bits size", strlen($key) * 8, $this->symmetricKey->type, $keySize));
211
        }
212
213
        $this->symmetricKey->loadkey($key);
214
    }
215
216
    /**
217
     * @return XMLSecurityKey
218
     *
219
     * @throws \LightSaml\Error\LightSamlXmlException
220
     */
221
    protected function loadSymmetricKey()
222
    {
223
        $symmetricKey = $this->xmlEnc->locateKey();
224
        if (false == $symmetricKey) {
225
            throw new LightSamlXmlException('Could not locate key algorithm in encrypted data');
226
        }
227
228
        return $symmetricKey;
229
    }
230
231
    /**
232
     * @throws \LightSaml\Error\LightSamlXmlException
233
     *
234
     * @return XMLSecurityKey
235
     */
236
    protected function loadSymmetricKeyInfo(XMLSecurityKey $symmetricKey)
237
    {
238
        $symmetricKeyInfo = $this->xmlEnc->locateKeyInfo($symmetricKey);
239
        if (false == $symmetricKeyInfo) {
240
            throw new LightSamlXmlException('Could not locate <dsig:KeyInfo> for the encrypted key');
241
        }
242
243
        return $symmetricKeyInfo;
244
    }
245
}
246