Completed
Pull Request — master (#8)
by John
02:04
created

LEFunctions   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 218
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 82
c 2
b 1
f 0
dl 0
loc 218
rs 9.92
ccs 27
cts 27
cp 1
wmc 31

8 Methods

Rating   Name   Duplication   Size   Complexity  
A Base64UrlSafeEncode() 0 3 1
B RSAGenerateKeys() 0 37 9
B ECGenerateKeys() 0 35 7
A checkDNSChallenge() 0 19 6
A Base64UrlSafeDecode() 0 8 2
A log() 0 16 3
A createhtaccess() 0 9 1
A checkHTTPChallenge() 0 10 2
1
<?php
2
3
namespace LEClient;
4
5
use Exception;
6
use LEClient\Exceptions\LEFunctionsException;
7
8
/**
9
 * LetsEncrypt Functions class, supplying the LetsEncrypt Client with supportive functions.
10
 *
11
 * PHP version 5.2.0
12
 *
13
 * MIT License
14
 *
15
 * Copyright (c) 2018 Youri van Weegberg
16
 *
17
 * Permission is hereby granted, free of charge, to any person obtaining a copy
18
 * of this software and associated documentation files (the "Software"), to deal
19
 * in the Software without restriction, including without limitation the rights
20
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21
 * copies of the Software, and to permit persons to whom the Software is
22
 * furnished to do so, subject to the following conditions:
23 50
 *
24
 * The above copyright notice and this permission notice shall be included in all
25
 * copies or substantial portions of the Software.
26 50
 *
27 2
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 48
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 48
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 48
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33
 * SOFTWARE.
34
 *
35 48
 * @author     Youri van Weegberg <[email protected]>
36
 * @copyright  2018 Youri van Weegberg
37
 * @license    https://opensource.org/licenses/mit-license.php  MIT License
38
 * @link       https://github.com/yourivw/LEClient
39 48
 * @since      Class available since Release 1.0.0
40
 */
41 48
class LEFunctions
42
{
43 48
    /**
44
     * Generates a new RSA keypair and saves both keys to a new file.
45 48
     *
46
     * @param string	$directory		The directory in which to store the new keys. If set to null or empty string - privateKeyFile and publicKeyFile will be treated as absolute paths.
47
     * @param string	$privateKeyFile	The filename for the private key file.
48
     * @param string	$publicKeyFile  The filename for the public key file.
49
     * @param string	$keySize 		RSA key size, must be between 2048 and 4096 (default is 4096)
50
     */
51
	public static function RSAGenerateKeys($directory, $privateKeyFile = 'private.pem', $publicKeyFile = 'public.pem', $keySize = 4096)
52
	{
53
		if ($keySize < 2048 || $keySize > 4096) throw LEFunctionsException::InvalidArgumentException('RSA key size must be between 2048 and 4096.');
54
55
		$res = openssl_pkey_new(array(
56 34
			"private_key_type" => OPENSSL_KEYTYPE_RSA,
57
			"private_key_bits" => intval($keySize),
58 34
		));
59
60
		if ($res === false) {
61
			$error = "Could not generate key pair! Check your OpenSSL configuration. OpenSSL Error: ".PHP_EOL;
62 34
			while($message = openssl_error_string()){
63 30
				$error .= $message.PHP_EOL;
64 30
			}
65 30
			throw LEFunctionsException::GenerateKeypairException($error);
66
		}
67 4
68 2
		if(!openssl_pkey_export($res, $privateKey)) {
69 2
			$error = "RSA keypair export failed!! Error: ".PHP_EOL;
70 2
			while($message = openssl_error_string()){
71
				$error .= $message.PHP_EOL;
72
			}
73 2
			throw LEFunctionsException::GenerateKeypairException($error);
74
		}
75
76
		$details = openssl_pkey_get_details($res);
77 32
78
		if ($directory !== null && $directory !== '')
79
		{
80
			$privateKeyFile = $directory.$privateKeyFile;
81 32
			$publicKeyFile = $directory.$publicKeyFile;
82
		}
83 32
84
		file_put_contents($privateKeyFile, $privateKey);
85 32
		file_put_contents($publicKeyFile, $details['key']);
86
87 32
		openssl_pkey_free($res);
88
	}
89
90
    /**
91
     * Generates a new EC prime256v1 keypair and saves both keys to a new file.
92
     *
93
     * @param string	$directory		The directory in which to store the new keys. If set to null or empty string - privateKeyFile and publicKeyFile will be treated as absolute paths.
94
     * @param string	$privateKeyFile	The filename for the private key file.
95
     * @param string	$publicKeyFile  The filename for the public key file.
96
     * @param string	$keysize  		EC key size, possible values are 256 (prime256v1) or 384 (secp384r1), default is 256
97
     */
98 22
	public static function ECGenerateKeys($directory, $privateKeyFile = 'private.pem', $publicKeyFile = 'public.pem', $keySize = 256)
99
	{
100 22
		if (version_compare(PHP_VERSION, '7.1.0') == -1) throw LEFunctionsException::PHPVersionException();
101
102
		if ($keySize == 256)
103
		{
104
			$res = openssl_pkey_new(array(
105
					"private_key_type" => OPENSSL_KEYTYPE_EC,
106
					"curve_name" => "prime256v1",
107
			));
108
		}
109
		elseif ($keySize == 384)
110 2
		{
111
			$res = openssl_pkey_new(array(
112 2
					"private_key_type" => OPENSSL_KEYTYPE_EC,
113 2
					"curve_name" => "secp384r1",
114 2
			));
115 2
		}
116
		else throw LEFunctionsException::InvalidArgumentException('EC key size must be 256 or 384.');
117 2
118
119
		if(!openssl_pkey_export($res, $privateKey)) throw LEFunctionsException::GenerateKeypairException('EC keypair export failed!');
120
121
		$details = openssl_pkey_get_details($res);
122
123
		if ($directory !== null && $directory !== '')
124
		{
125
			$privateKeyFile = $directory.$privateKeyFile;
126
			$publicKeyFile = $directory.$publicKeyFile;
127
		}
128
129
		file_put_contents($privateKeyFile, $privateKey);
130
		file_put_contents($publicKeyFile, $details['key']);
131
132
		openssl_pkey_free($res);
133
	}
134
135
136
137
    /**
138
     * Encodes a string input to a base64 encoded string which is URL safe.
139
     *
140
     * @param string	$input 	The input string to encode.
141
     *
142
     * @return string	Returns a URL safe base64 encoded string.
143
     */
144
	public static function Base64UrlSafeEncode($input)
145
    {
146
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
147
    }
148
149
    /**
150
     * Decodes a string that is URL safe base64 encoded.
151
     *
152
     * @param string	$input	The encoded input string to decode.
153
     *
154
     * @return string	Returns the decoded input string.
155
     */
156
    public static function Base64UrlSafeDecode($input)
157
    {
158
        $remainder = strlen($input) % 4;
159
        if ($remainder) {
160
            $padlen = 4 - $remainder;
161
            $input .= str_repeat('=', $padlen);
162
        }
163
        return base64_decode(strtr($input, '-_', '+/'));
164
    }
165
166
167
168
    /**
169
     * Outputs a log message.
170
     *
171
     * @param object	$data		The data to print.
172
     * @param string	$function	The function name to print above. Defaults to the calling function's name from the stacktrace. (optional)
173
     */
174
	public static function log($data, $function = '')
175
	{
176
		$e = new Exception();
177
		$trace = $e->getTrace();
178
		$function = $function == '' ? 'function ' .  $trace[3]['function'] . ' (function ' . $trace[2]['function'] . ')' : $function;
179
		if (PHP_SAPI == "cli")
180
		{
181
			echo '[' . date('d-m-Y H:i:s') . '] ' . $function . ":\n";
182
			print_r($data);
183
			echo "\n\n";
184
		}
185
		else
186
		{
187
			echo '<b>' . date('d-m-Y H:i:s') . ', ' . $function . ':</b><br>';
188
			print_r($data);
189
			echo '<br><br>';
190
		}
191
	}
192
193
194
195
    /**
196
     * Makes a request to the HTTP challenge URL and checks whether the authorization is valid for the given $domain.
197
     *
198
     * @param string	$domain 			The domain to check the authorization for.
199
     * @param string 	$token 				The token (filename) to request.
200
     * @param string 	$keyAuthorization 	the keyAuthorization (file content) to compare.
201
     *
202
     * @return boolean	Returns true if the challenge is valid, false if not.
203
     */
204
	public static function checkHTTPChallenge($domain, $token, $keyAuthorization)
205
	{
206
		$requestURL = $domain . '/.well-known/acme-challenge/' . $token;
207
		$handle = curl_init();
208
        curl_setopt($handle, CURLOPT_URL, $requestURL);
209
        curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
210
        curl_setopt($handle, CURLOPT_FOLLOWLOCATION, true);
211
        $response = trim(curl_exec($handle));
0 ignored issues
show
Bug introduced by
It seems like curl_exec($handle) can also be of type true; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

211
        $response = trim(/** @scrutinizer ignore-type */ curl_exec($handle));
Loading history...
212
213
		return (!empty($response) && $response == $keyAuthorization);
214
	}
215
216
    /**
217
     * Checks whether the applicable DNS TXT record is a valid authorization for the given $domain.
218
     *
219
     * @param string	$domain 	The domain to check the authorization for.
220
     * @param string	$DNSDigest	The digest to compare the DNS record to.
221
     *
222
     * @return boolean	Returns true if the challenge is valid, false if not.
223
     */
224
	public static function checkDNSChallenge($domain, $DNSDigest)
225
	{
226
		$requestURL = 'https://dns.google.com/resolve?name=_acme-challenge.' . $domain . '&type=TXT';
227
		$handle = curl_init();
228
        curl_setopt($handle, CURLOPT_URL, $requestURL);
229
        curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
230
        curl_setopt($handle, CURLOPT_FOLLOWLOCATION, true);
231
        $response = json_decode(trim(curl_exec($handle)));
0 ignored issues
show
Bug introduced by
It seems like curl_exec($handle) can also be of type true; however, parameter $string of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

231
        $response = json_decode(trim(/** @scrutinizer ignore-type */ curl_exec($handle)));
Loading history...
232
		if($response->Status === 0 && isset($response->Answer))
233
		{
234
			foreach($response->Answer as $answer) 
235
			{
236
				if($answer->type === 16)
237
				{
238
					if($answer->data === ('"' . $DNSDigest . '"')) return true;
239
				}
240
			}
241
		}
242
		return false;
243
	}
244
245
    /**
246
     * Creates a simple .htaccess file in $directory which denies from all.
247
     *
248
     * @param string	$directory	The directory in which to put the .htaccess file.
249
     */
250
	public static function createhtaccess($directory)
251
	{
252
		$htaccess = '<ifModule mod_authz_core.c>' . "\n"
253
			. '    Require all denied' . "\n"
254
			. '</ifModule>' . "\n"
255
			. '<ifModule !mod_authz_core.c>' . "\n"
256
			. '    Deny from all' . "\n"
257
			. '</ifModule>' . "\n";
258
		file_put_contents($directory . '.htaccess', $htaccess);
259
	}
260
}
261