Passed
Branch feature/2.1-geodispersion-dev (1d61a8)
by Jonathan
61:21
created

UrlObfuscatorService   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 118
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 44
c 1
b 0
f 0
dl 0
loc 118
rs 10
wmc 17

4 Methods

Rating   Name   Duplication   Size   Complexity  
A encryptionKey() 0 14 4
A deobfuscate() 0 33 6
A obfuscate() 0 20 5
A tryDeobfuscate() 0 8 2
1
<?php
2
3
/**
4
 * webtrees-lib: MyArtJaub library for webtrees
5
 *
6
 * @package MyArtJaub\Webtrees
7
 * @subpackage Certificates
8
 * @author Jonathan Jaubart <[email protected]>
9
 * @copyright Copyright (c) 2021, Jonathan Jaubart
10
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 3
11
 */
12
13
declare(strict_types=1);
14
15
namespace MyArtJaub\Webtrees\Module\Certificates\Services;
16
17
use Psr\Http\Message\ServerRequestInterface;
18
use InvalidArgumentException;
19
20
/**
21
 * Service for obfuscating/deobfuscating strings for use in URLs.
22
 * Even though it is using encryption mechanism, the service is not designed to provide strong security.
23
 */
24
class UrlObfuscatorService
25
{
26
    /**
27
     * @var string|null $encryption_key
28
     */
29
    private $encryption_key;
30
31
32
    /**
33
     * Return (and generate) the key to be used for the encryption step
34
     *
35
     * @return string Encryption key
36
     */
37
    protected function encryptionKey(): string
38
    {
39
        if ($this->encryption_key === null) {
40
            /** @var ServerRequestInterface $request **/
41
            $request = app(ServerRequestInterface::class);
42
            $server_name = $request->getServerParams()['SERVER_NAME'] ?? '';
43
            $server_software = $request->getServerParams()['SERVER_SOFTWARE'] ?? '';
44
            $this->encryption_key = str_pad(md5(
45
                $server_name !== '' && $server_software !== '' ?
46
                $server_name . $server_software :
47
                'STANDARDKEYIFNOSERVER'
48
            ), SODIUM_CRYPTO_SECRETBOX_KEYBYTES, "1234567890ABCDEF");
49
        }
50
        return $this->encryption_key;
51
    }
52
53
    /**
54
     * Obfuscate a clear text, with a combination of encryption and base64 encoding.
55
     * The return string is URL-safe.
56
     *
57
     * @param string $cleartext Text to obfuscate
58
     * @param string $key
59
     * @param string $nonce
60
     * @return string
61
     */
62
    public function obfuscate(string $cleartext, string $key = '', string $nonce = ''): string
63
    {
64
        if ($nonce === '') {
65
            $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
66
        }
67
        if ($key === '') {
68
            $key = $this->encryptionKey();
69
        }
70
71
        if (strlen($nonce) !== SODIUM_CRYPTO_SECRETBOX_NONCEBYTES) {
72
            throw new InvalidArgumentException('The nonce needs to be SODIUM_CRYPTO_SECRETBOX_NONCEBYTES long');
73
        }
74
75
        if (strlen($key) !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
76
            throw new InvalidArgumentException('The key needs to be SODIUM_CRYPTO_SECRETBOX_KEYBYTES long');
77
        }
78
79
        \Fisharebest\Webtrees\DebugBar::addMessage($key);
80
        $encryted = sodium_crypto_secretbox($cleartext, $nonce, $key);
81
        return strtr(base64_encode($nonce . $encryted), '+/=', '._-');
82
    }
83
84
    /**
85
     * Deobfuscate a string from an URL to a clear text.
86
     *
87
     * @param string $obfuscated Text to deobfuscate
88
     * @param string $key
89
     * @throws InvalidArgumentException
90
     * @return string
91
     */
92
    public function deobfuscate(string $obfuscated, string $key = ''): string
93
    {
94
        $obfuscated = strtr($obfuscated, '._-', '+/=');
95
        if ($key === '') {
96
            $key = $this->encryptionKey();
97
        }
98
99
        if (strlen($key) !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
100
            throw new InvalidArgumentException('The key needs to be SODIUM_CRYPTO_SECRETBOX_KEYBYTES long');
101
        }
102
103
        $encrypted = base64_decode($obfuscated, true);
104
        if ($encrypted === false) {
105
            throw new InvalidArgumentException('The encrypted value is not in correct base64 format.');
106
        }
107
108
        if (mb_strlen($encrypted, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) {
109
            throw new InvalidArgumentException('The encrypted value does not contain enough characters for the key.');
110
        }
111
112
        $nonce = mb_substr($encrypted, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
113
        $ciphertext = mb_substr($encrypted, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
114
115
        $decrypted = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
116
117
        if ($decrypted === false) {
118
            throw new InvalidArgumentException('The message has been tampered with in transit.');
119
        }
120
121
        //sodium_memzero($ciphertext);    // sodium_compat cannot handle it, only through libsodium
122
123
        /** @var string $decrypted - Psalm detect as string|true otherwise */
124
        return $decrypted;
125
    }
126
127
    /**
128
     * Try to deobfuscate a string from an URL to a clear text, returning whether the operation is a success.
129
     *
130
     * @param string $obfuscated Text to deobfuscate
131
     * @param string $key
132
     * @return bool
133
     */
134
    public function tryDeobfuscate(string &$obfuscated, string $key = ''): bool
135
    {
136
        try {
137
            $obfuscated = $this->deobfuscate($obfuscated, $key);
138
            return true;
139
        } catch (InvalidArgumentException $ex) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
140
        }
141
        return false;
142
    }
143
}
144