FormSecrets   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 188
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 72
c 1
b 0
f 1
dl 0
loc 188
rs 10
wmc 25

16 Methods

Rating   Name   Duplication   Size   Complexity  
A get() 0 3 1
A setPublicKey() 0 3 1
A setData() 0 12 3
A verifyRS256JWT() 0 24 3
A loadPrivateKey() 0 7 2
A encodeJSON() 0 7 2
A __construct() 0 3 1
A setPrivateKey() 0 3 1
A getData() 0 9 2
A enableEncoding() 0 3 1
A add() 0 3 1
A encodeRS256JWT() 0 22 1
A loadPublicKey() 0 7 2
A decodeBase64Url() 0 3 1
A encodeBase64Url() 0 3 1
A decodeJSON() 0 7 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SKien\Formgenerator;
6
7
/**
8
 *
9
 * @author Stefanius <[email protected]>
10
 * @copyright MIT License - see the LICENSE file for details
11
 */
12
class FormSecrets extends FormInput
13
{
14
    protected string $strPrivateKey;
15
    protected string $strPublicKey;
16
    protected bool $bUseJWT = false;
17
    protected array $aSecrets = [];
18
19
    public function __construct(string $strName = '')
20
    {
21
        parent::__construct($strName, -1, FormFlags::HIDDEN);
22
    }
23
24
    public function setData(string $strData) : bool
25
    {
26
        $aSecrets = false;
27
        if ($this->bUseJWT) {
28
            $aSecrets = $this->verifyRS256JWT($strData);
29
        } else {
30
            $aSecrets = $this->decodeJSON($strData);
31
        }
32
        if ($aSecrets !== false) {
33
            $this->aSecrets = $aSecrets;
34
        }
35
        return $aSecrets !== false;
36
    }
37
38
    public function getData() : string
39
    {
40
        $strData = '';
41
        if ($this->bUseJWT) {
42
            $strData = $this->encodeRS256JWT($this->aSecrets);
43
        } else {
44
            $strData = $this->encodeJSON($this->aSecrets);
45
        }
46
        return $strData;
47
    }
48
49
    public function add(string $strName, mixed $value) : void
50
    {
51
        $this->aSecrets[$strName] = $value;
52
    }
53
54
    public function get(string $strName) : mixed
55
    {
56
        return $this->aSecrets[$strName] ?? null;
57
    }
58
59
    public function loadPrivateKey(string $strPrivateKeyFile) : void
60
    {
61
        if (is_readable($strPrivateKeyFile)) {
62
            $this->strPrivateKey = file_get_contents($strPrivateKeyFile);
63
            $this->bUseJWT = true;
64
        } else {
65
            trigger_error("The file [$strPrivateKeyFile] doesn't exist or is not readable!", E_USER_ERROR);
66
        }
67
    }
68
69
    public function loadPublicKey(string $strPublicKeyFile) : void
70
    {
71
        if (is_readable($strPublicKeyFile)) {
72
            $this->strPublicKey = file_get_contents($strPublicKeyFile);
73
            $this->bUseJWT = true;
74
        } else {
75
            trigger_error("The file [$strPublicKeyFile] doesn't exist or is not readable!", E_USER_ERROR);
76
        }
77
    }
78
79
    public function setPrivateKey(string $strPrivateKey) : void
80
    {
81
        $this->strPrivateKey = $strPrivateKey;
82
    }
83
84
    public function setPublicKey(string $strPublicKey) : void
85
    {
86
        $this->strPublicKey = $strPublicKey;
87
    }
88
89
    public function enableEncoding(bool $bEncode) : void
90
    {
91
        $this->bUseJWT = $bEncode;
92
    }
93
94
    /**
95
     * Builds the RS256 encoded JWT with given payload and private key.
96
     * @return string
97
     */
98
    public function encodeRS256JWT(array $aPayload) : string
99
    {
100
        // 1. build and encode Header
101
        $aJwtHeader = ["typ" => "JWT", "alg" => "RS256"];
102
        $strJwtHeader = $this->encodeJSON($aJwtHeader);
103
        $strJwtHeader64 = $this->encodeBase64Url($strJwtHeader);
104
105
        // 2. encode the payload / claim
106
        $strJwtPayload = $this->encodeJSON($aPayload);
107
        $strJwtPayload64 = $this->encodeBase64Url($strJwtPayload);
108
109
        // 3. sign both values for the signature
110
        $strJwtSignature = '';
111
        openssl_sign(
112
            $strJwtHeader64 . '.' . $strJwtPayload64,
113
            $strJwtSignature,
114
            $this->strPrivateKey,
115
            OPENSSL_ALGO_SHA256
116
            );
117
        $strJwtSignature64 = $this->encodeBase64Url($strJwtSignature);
118
119
        return implode('.', [$strJwtHeader64, $strJwtPayload64, $strJwtSignature64]);
120
    }
121
122
    /**
123
     * Verifies the given JWT and returns the extracted payload in case of success.
124
     * @param string $strJWT
125
     * @param string $strPrivateKey
126
     * @return array|false
127
     */
128
    public function verifyRS256JWT(string $strJWT) : array|false
129
    {
130
        // 1. split JWT into header, payload and signature
131
        list($strJwtHeader64, $strJwtPayload64, $strJwtSignature64) = explode('.', $strJWT);
132
133
        // 2. decode the signature
134
        $strJwtSignature = $this->decodeBase64Url($strJwtSignature64);
135
136
        // 3. verify the data and signature with the public key
137
        $iVerfied = openssl_verify(
138
            $strJwtHeader64 . '.' . $strJwtPayload64,
139
            $strJwtSignature,
140
            $this->strPublicKey,
141
            OPENSSL_ALGO_SHA256
142
            );
143
        $aPayload = false;
144
        if ($iVerfied === -1) {
145
            trigger_error('Error verifying the JWT: ' . openssl_error_string(), E_USER_ERROR);
146
        } else if ($iVerfied === 1) {
147
            // 4. JWT is verified - decode the payload
148
            $strJwtPayload = $this->decodeBase64Url($strJwtPayload64);
149
            $aPayload = $this->decodeJSON($strJwtPayload);
150
        }
151
        return $aPayload;
152
    }
153
154
    /**
155
     * Returns a string containing the JSON representation of a value.
156
     * @param mixed $value
157
     * @return string
158
     */
159
    protected function encodeJSON($value) : string
160
    {
161
        $strEncoded = json_encode($value, JSON_UNESCAPED_SLASHES);
162
        if ($strEncoded === false) {
163
            throw new \RuntimeException('Error encoding value to JSO: ' . json_last_error_msg());
164
        }
165
        return $strEncoded;
166
    }
167
168
    /**
169
     * Encodes string to save base64URL
170
     * @param string $strInput
171
     * @return string
172
     */
173
    protected function encodeBase64Url(string $strInput) : string
174
    {
175
        return str_replace('=', '', strtr(base64_encode($strInput), '+/', '-_'));
176
    }
177
178
    /**
179
     * Returns a decoded JSON string as array .
180
     * @param string $strJSON
181
     * @return array|false
182
     */
183
    protected function decodeJSON(string $strJSON) : array|false
184
    {
185
        $decoded = json_decode($strJSON, true, 64, JSON_INVALID_UTF8_IGNORE);
186
        if ($decoded === null) {
187
            throw new \RuntimeException('Error decoding JSON: ' . json_last_error_msg());
188
        }
189
        return $decoded;
190
    }
191
192
    /**
193
     * Decodes save base64URL to string.
194
     * @param string $strInput
195
     * @return string
196
     */
197
    protected function decodeBase64Url(string $strInput) : string
198
    {
199
        return base64_decode(strtr($strInput, '-_,', '+/='));
200
    }
201
}