Passed
Branch master (c86cc6)
by Tim
01:57
created

EncryptedCustom::decryptWithSessionKey()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 33
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 13
nc 3
nop 1
dl 0
loc 33
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\Test\XML;
6
7
use SimpleSAML\XML\AbstractElement;
8
use SimpleSAML\XML\DOMDocumentFactory;
9
use SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmFactory;
10
use SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmInterface;
11
use SimpleSAML\XMLSecurity\Backend\EncryptionBackend;
12
use SimpleSAML\XMLSecurity\Backend\OpenSSL;
13
use SimpleSAML\XMLSecurity\Constants as C;
14
use SimpleSAML\XMLSecurity\Exception\RuntimeException;
15
use SimpleSAML\XMLSecurity\Key\SymmetricKey;
16
use SimpleSAML\XMLSecurity\XML\EncryptedElementInterface;
17
use SimpleSAML\XMLSecurity\XML\EncryptedElementTrait;
18
use SimpleSAML\XMLSecurity\XML\xenc\EncryptedData;
19
20
/**
21
 * This is an example class demonstrating how an encrypted object can be implemented with this library.
22
 *
23
 * Please have a look at \SimpleSAML\XMLSecurity\XML\EncryptedElementInterface, and read carefully the comments in this
24
 * file. The implementation in \SimpleSAML\XMLSecurity\XML\EncryptedElementTrait may also serve as a reference.
25
 *
26
 * @package simplesamlphp/xml-security
27
 */
28
final class EncryptedCustom extends AbstractElement implements EncryptedElementInterface
29
{
30
    /*
31
     * By using this trait, we get a constructor out of the box that processes the EncryptedData and any possible
32
     * EncryptedKey elements inside. If you need your own constructor, make sure to rename the one from the trait
33
     * here so that you can call it later.
34
     */
35
    use EncryptedElementTrait {
0 ignored issues
show
introduced by
The trait SimpleSAML\XMLSecurity\XML\EncryptedElementTrait requires some properties which are not provided by SimpleSAML\XMLSecurity\Test\XML\EncryptedCustom: $localName, $namespaceURI
Loading history...
36
        __construct as constructor;
37
    }
38
39
    /** @var string */
40
    public const NS = 'urn:ssp:custom';
41
42
    /** @var string */
43
    public const NS_PREFIX = 'ssp';
44
45
    /** @var EncryptionBackend|null $backend */
46
    private ?EncryptionBackend $backend = null;
47
48
    /** @var string[] $blacklistedAlgs */
49
    private array $blacklistedAlgs = [];
50
51
52
    /**
53
     * Construct an encrypted object.
54
     *
55
     * @param \SimpleSAML\XMLSecurity\XML\xenc\EncryptedData $encryptedData
56
     */
57
    public function __construct(EncryptedData $encryptedData)
58
    {
59
        $this->constructor($encryptedData);
60
        $this->backend = new OpenSSL();
61
    }
62
63
64
    /**
65
     * Implement a method like this if your encrypted object needs to instantiate a new decryptor, for example, to
66
     * decrypt a session key. This method is required by \SimpleSAML\XMLSecurity\XML\EncryptedElementTrait.
67
     *
68
     * @return \SimpleSAML\XMLSecurity\Backend\EncryptionBackend|null The encryption backend to use, or null if we want
69
     * to use the default.
70
     */
71
    public function getEncryptionBackend(): ?EncryptionBackend
72
    {
73
        return $this->backend;
74
    }
75
76
77
    /**
78
     * Implement a method like this if your encrypted object needs to instantiate a new decryptor, for example, to
79
     * decrypt a session key. This method is required by \SimpleSAML\XMLSecurity\XML\EncryptedElementTrait.
80
     *
81
     * @param \SimpleSAML\XMLSecurity\Backend\EncryptionBackend|null $backend The encryption backend we want to use, or
82
     * null if we want to use the defaults.
83
     */
84
    public function setEncryptionBackend(?EncryptionBackend $backend): void
85
    {
86
        $this->backend = $backend;
87
    }
88
89
90
    /**
91
     * Implement a method like this if your encrypted object needs to instantiate a new decryptor, for example, to
92
     * decrypt a session key. This method is required by \SimpleSAML\XMLSecurity\XML\EncryptedElementTrait.
93
     *
94
     * @return string[]|null An array with all algorithm identifiers that we want to blacklist, or null if we want to
95
     * use the defaults.
96
     */
97
    public function getBlacklistedAlgorithms(): ?array
98
    {
99
        return $this->blacklistedAlgs;
100
    }
101
102
103
    /**
104
     * Implement a method like this if your encrypted object needs to instantiate a new decryptor, for example, to
105
     * decrypt a session key. This method is required by \SimpleSAML\XMLSecurity\XML\EncryptedElementTrait.
106
     *
107
     * @param string[]|null $algIds An array with the identifiers of the algorithms we want to blacklist, or null if we
108
     * want to use the defaults.
109
     */
110
    public function setBlacklistedAlgorithms(?array $algIds): void
111
    {
112
        $this->blacklistedAlgs = $algIds;
113
    }
114
115
116
    /**
117
     * Decrypt this encrypted element.
118
     *
119
     * This method needs to be implemented by any object implementing EncryptedElementInterface. Depending on the
120
     * encryption mechanism used by your XML elements, this might be as simple as instantiating a decryptor with
121
     * the algorithm specified in the EncryptionMethod and giving it the right key, or you might need to first obtain
122
     * a decryption key by decrypting it with a KeyTransport algorithm or by resolving a reference.
123
     *
124
     * The \SimpleSAML\XMLSecurity\XML\EncryptedElementTrait trait implements this method, supporting objects encrypted
125
     * with and without a session key. Depending on the decryptor passed as an argument, if it implements a key
126
     * transport algorithm and the EncryptedData has a KeyInfo object with an EncryptedKey inside, then that key will
127
     * be decrypted with the given decryptor, and later used to build a decryptor that can decrypt the object itself.
128
     * If, on the contrary, the decryptor implements a block cipher encryption algorithm, the method in the trait will
129
     * attempt to decrypt the object directly.
130
     *
131
     * @param \SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmInterface $decryptor The decryptor able to
132
     * decrypt this object.
133
     */
134
    public function decrypt(EncryptionAlgorithmInterface $decryptor): CustomSignable
135
    {
136
        return CustomSignable::fromXML(
137
            DOMDocumentFactory::fromString(
138
                $this->decryptData($decryptor),
139
            )->documentElement,
140
        );
141
    }
142
143
144
    /**
145
     * Custom implementation of the decrypt() method.
146
     *
147
     * Here we implement this method manually to serve as a guide for those needing to implement it on their own. This
148
     * method implements an example where the EncryptedData includes a KeyInfo with an EncryptedKey. We then use the
149
     * given decryptor to decrypt that key, which will in turn be used to decrypt the element itself. Note that if you
150
     * plan to support encrypted objects that include their own EncryptedKey, your object will have to build a
151
     * decryptor on its own. This means the end user will have no way to specify the backend to use or what algorithms
152
     * should be blacklisted, so your encrypted object implementation should cater for this.
153
     *
154
     * @param \SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmInterface $decryptor The decryptor able to
155
     * decrypt this object. In this particular example, this decryptor will be used to decrypt the session key inside
156
     * the encrypted object, and therefore must implement a key transport algorithm.
157
     * @return CustomSignable A CustomSignable object created from the decrypted element.
158
     */
159
    public function decryptWithSessionKey(EncryptionAlgorithmInterface $decryptor): CustomSignable
160
    {
161
        if (!$this->hasDecryptionKey()) {
162
            throw new RuntimeException('EncryptedCustom without encryption key.');
163
        }
164
165
        /*
166
         * Get the encryption algorithm, and check if we know it. In this case, we assume it must be a block cipher,
167
         * since this object can only be encrypted with them (which is the common scenario). Always remember to check
168
         * the supported algorithms.
169
         */
170
        $algId = $this->getEncryptedData()->getEncryptionMethod()->getAlgorithm();
171
        if (!isset(C::$BLOCK_CIPHER_ALGORITHMS[$algId])) {
172
            throw new RuntimeException('Unknown or unsupported encryption algorithm.');
173
        }
174
175
        // decrypt the encryption key with the decryptor we were provided
176
        $encryptedKey = $this->getEncryptedKey();
177
        $decryptionKey = $encryptedKey->decrypt($decryptor);
178
179
        /*
180
         * Instantiate a new decryptor with the blacklisted algorithms and encryption backend given. This decryptor
181
         * will be the one implementing the block cipher used to encrypt the object itself.
182
         */
183
        $factory = new EncryptionAlgorithmFactory($this->getBlacklistedAlgorithms());
184
        $alg = $factory->getAlgorithm($algId, new SymmetricKey($decryptionKey));
185
        $alg->setBackend($this->getEncryptionBackend());
186
187
        // finally, decrypt the element, create an XML document from it and then use that to create an object
188
        $xml = DOMDocumentFactory::fromString(
189
            $alg->decrypt($this->getEncryptedData()->getCipherData()->getCipherValue()->getContent()),
190
        );
191
        return CustomSignable::fromXML($xml->documentElement);
192
    }
193
}
194