Issues (25)

src/Signature/RsaSha.php (2 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\AuthClient\Signature;
6
7
use Yiisoft\Yii\AuthClient\Exception\InvalidConfigException;
8
use Yiisoft\Yii\AuthClient\Exception\NotSupportedException;
9
10
use function function_exists;
11
use function is_int;
12
13
/**
14
 * RsaSha1 represents 'SHAwithRSA' (also known as RSASSA-PKCS1-V1_5-SIGN with the SHA hash) signature method.
15
 *
16
 * > **Note:** This class requires PHP "OpenSSL" extension({@link https://php.net/manual/en/book.openssl.php}).
17
 */
18
final class RsaSha extends Signature
19
{
20
    /**
21
     * @var string path to the file, which holds private key certificate.
22
     */
23
    private string $privateCertificateFile;
24
    /**
25
     * @var string path to the file, which holds public key certificate.
26
     */
27
    private string $publicCertificateFile;
28
    /**
29
     * @var int|string signature hash algorithm, e.g. `OPENSSL_ALGO_SHA1`, `OPENSSL_ALGO_SHA256` and so on.
30
     *
31
     * @link https://php.net/manual/en/openssl.signature-algos.php
32
     */
33
    private $algorithm;
34
35
    /**
36
     * @var string|null OpenSSL private key certificate content.
37
     * This value can be fetched from file specified by {@see privateCertificateFile}.
38
     */
39
    private ?string $privateCertificate = null;
40
    /**
41
     * @var string|null OpenSSL public key certificate content.
42
     * This value can be fetched from file specified by {@see publicCertificateFile}.
43
     */
44
    private ?string $publicCertificate = null;
45
46 7
    public function __construct($algorithm = null)
47
    {
48 7
        $this->algorithm = $algorithm;
49
50 7
        if (!function_exists('openssl_sign')) {
51
            throw new NotSupportedException('PHP "OpenSSL" extension is required.');
52
        }
53
    }
54
55
    /**
56
     * @param string $publicCertificateFile public key certificate file.
57
     */
58 3
    public function setPublicCertificateFile(string $publicCertificateFile): void
59
    {
60 3
        $this->publicCertificateFile = $publicCertificateFile;
61
    }
62
63
    /**
64
     * @param string $privateCertificateFile private key certificate file.
65
     */
66 3
    public function setPrivateCertificateFile(string $privateCertificateFile): void
67
    {
68 3
        $this->privateCertificateFile = $privateCertificateFile;
69
    }
70
71 3
    public function getName(): string
72
    {
73 3
        if (is_int($this->algorithm)) {
74 2
            $constants = get_defined_constants(true);
75 2
            if (isset($constants['openssl'])) {
76 2
                foreach ($constants['openssl'] as $name => $value) {
77 2
                    if (strpos($name, 'OPENSSL_ALGO_') !== 0) {
78 2
                        continue;
79
                    }
80 2
                    if ($value === $this->algorithm) {
81 2
                        $algorithmName = substr($name, strlen('OPENSSL_ALGO_'));
82 2
                        break;
83
                    }
84
                }
85
            }
86
87 2
            if (!isset($algorithmName)) {
88 2
                throw new InvalidConfigException("Unable to determine name of algorithm '{$this->algorithm}'");
89
            }
90
        } else {
91 1
            $algorithmName = strtoupper($this->algorithm);
92
        }
93 3
        return 'RSA-' . $algorithmName;
94
    }
95
96 2
    public function generateSignature(string $baseString, string $key): string
97
    {
98 2
        $privateCertificateContent = $this->getPrivateCertificate();
99
        // Pull the private key ID from the certificate
100 2
        $privateKeyId = openssl_pkey_get_private($privateCertificateContent);
101
        // Sign using the key
102 2
        openssl_sign($baseString, $signature, $privateKeyId, $this->algorithm);
103
        // Release the key resource
104 2
        if (PHP_MAJOR_VERSION < 8) {
105
            openssl_pkey_free($privateKeyId);
106
        }
107
108 2
        return base64_encode($signature);
109
    }
110
111
    /**
112
     * @return string private key certificate content.
113
     */
114 3
    public function getPrivateCertificate(): string
115
    {
116 3
        if ($this->privateCertificate === null) {
117 3
            $this->privateCertificate = $this->initPrivateCertificate();
118
        }
119
120 3
        return $this->privateCertificate;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->privateCertificate could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
121
    }
122
123
    /**
124
     * Creates initial value for {@see privateCertificate}.
125
     * This method will attempt to fetch the certificate value from {@see privateCertificateFile} file.
126
     *
127
     * @throws InvalidConfigException on failure.
128
     *
129
     * @return string private certificate content.
130
     */
131 3
    protected function initPrivateCertificate(): string
132
    {
133 3
        if (!empty($this->privateCertificateFile)) {
134 3
            if (!file_exists($this->privateCertificateFile)) {
135
                throw new InvalidConfigException(
136
                    "Private certificate file '{$this->privateCertificateFile}' does not exist!"
137
                );
138
            }
139 3
            return file_get_contents($this->privateCertificateFile);
140
        }
141
        return '';
142
    }
143
144 1
    public function verify(string $signature, string $baseString, string $key): bool
145
    {
146 1
        $decodedSignature = base64_decode($signature);
147
        // Fetch the public key cert based on the request
148 1
        $publicCertificate = $this->getPublicCertificate();
149
        // Pull the public key ID from the certificate
150 1
        $publicKeyId = openssl_pkey_get_public($publicCertificate);
151
        // Check the computed signature against the one passed in the query
152 1
        $verificationResult = openssl_verify($baseString, $decodedSignature, $publicKeyId, $this->algorithm);
153
        // Release the key resource
154 1
        if (PHP_MAJOR_VERSION < 8) {
155
            openssl_pkey_free($publicKeyId);
156
        }
157
158 1
        return $verificationResult === 1;
159
    }
160
161
    /**
162
     * @return string public key certificate content.
163
     */
164 2
    public function getPublicCertificate(): string
165
    {
166 2
        if ($this->publicCertificate === null) {
167 2
            $this->publicCertificate = $this->initPublicCertificate();
168
        }
169
170 2
        return $this->publicCertificate;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->publicCertificate could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
171
    }
172
173
    /**
174
     * Creates initial value for {@see publicCertificate}.
175
     * This method will attempt to fetch the certificate value from {@see publicCertificateFile} file.
176
     *
177
     * @throws InvalidConfigException on failure.
178
     *
179
     * @return string public certificate content.
180
     */
181 2
    protected function initPublicCertificate(): string
182
    {
183 2
        $content = '';
184 2
        if (!empty($this->publicCertificateFile)) {
185 2
            if (!file_exists($this->publicCertificateFile)) {
186
                throw new InvalidConfigException(
187
                    "Public certificate file '{$this->publicCertificateFile}' does not exist!"
188
                );
189
            }
190 2
            $fp = fopen($this->publicCertificateFile, 'rb');
191
192 2
            while (!feof($fp)) {
193 2
                $content .= fgets($fp);
194
            }
195 2
            fclose($fp);
196
        }
197 2
        return $content;
198
    }
199
}
200