Passed
Push — master ( 6745d5...76374b )
by
unknown
05:09
created

AccountStoreV1Encoder   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 80
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 33
c 1
b 0
f 0
dl 0
loc 80
rs 10
wmc 18

3 Methods

Rating   Name   Duplication   Size   Complexity  
A encode() 0 5 1
A decode() 0 20 6
B getSecretKey() 0 27 11
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Files\Backend\Seafile\Model;
6
7
/**
8
 * Encoder for Account Store V1 encoding.
9
 */
10
class AccountStoreV1Encoder {
11
	/**
12
	 * Resolve the secret key from configuration.
13
	 *
14
	 * Source priority:
15
	 * - constant FILES_ACCOUNTSTORE_V1_SECRET_KEY (hex-encoded)
16
	 * - env var FILES_ACCOUNTSTORE_V1_SECRET_KEY (hex-encoded)
17
	 *
18
	 * @return string binary key with length SODIUM_CRYPTO_SECRETBOX_KEYBYTES
19
	 */
20
	private static function getSecretKey(): string {
21
		$keyHex = null;
22
		if (\defined('FILES_ACCOUNTSTORE_V1_SECRET_KEY')) {
23
			$keyHex = (string) \constant('FILES_ACCOUNTSTORE_V1_SECRET_KEY');
24
		}
25
		elseif (is_string(getenv('FILES_ACCOUNTSTORE_V1_SECRET_KEY')) && getenv('FILES_ACCOUNTSTORE_V1_SECRET_KEY') !== false) {
26
			$keyHex = (string) getenv('FILES_ACCOUNTSTORE_V1_SECRET_KEY');
27
		}
28
29
		if (!is_string($keyHex) || $keyHex === '') {
30
			throw new \RuntimeException('FILES_ACCOUNTSTORE_V1_SECRET_KEY not configured. Define it as a hex-encoded key.');
31
		}
32
33
		if (!ctype_xdigit($keyHex) || (strlen($keyHex) % 2) !== 0) {
34
			throw new \UnexpectedValueException('FILES_ACCOUNTSTORE_V1_SECRET_KEY must be hex-encoded.');
35
		}
36
37
		$key = hex2bin($keyHex);
38
		if (!is_string($key) || strlen($key) !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
0 ignored issues
show
introduced by
The condition is_string($key) is always true.
Loading history...
39
			throw new \UnexpectedValueException(sprintf(
40
				'FILES_ACCOUNTSTORE_V1_SECRET_KEY must decode to %d bytes; got %d.',
41
				SODIUM_CRYPTO_SECRETBOX_KEYBYTES,
42
				is_string($key) ? strlen($key) : -1
43
			));
44
		}
45
46
		return $key;
47
	}
48
49
	/**
50
	 * Encode an account setting value.
51
	 *
52
	 * {@see AccountStore::encryptBackendConfigProperty()}
53
	 *
54
	 * @return string encoded
55
	 */
56
	public static function encode(string $value): string {
57
		$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
58
		$encrypted = sodium_crypto_secretbox($value, $nonce, self::getSecretKey());
59
60
		return bin2hex($nonce) . bin2hex($encrypted);
61
	}
62
63
	/**
64
	 * Decode an encoded account setting value.
65
	 *
66
	 * {@see AccountStore::decryptBackendConfigProperty()}
67
	 *
68
	 * @return string decoded
69
	 */
70
	public static function decode(string $valueInHex): string {
71
		$value = hex2bin($valueInHex);
72
		if (!is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
73
			throw new \UnexpectedValueException(sprintf('Not an envelope of an encrypted value. Raw binary length of envelope is %d.', strlen($valueInHex)));
74
		}
75
		$nonce = substr($value, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
76
		if (!is_string($nonce) || strlen($nonce) !== SODIUM_CRYPTO_SECRETBOX_NONCEBYTES) {
0 ignored issues
show
introduced by
The condition is_string($nonce) is always true.
Loading history...
77
			throw new \UnexpectedValueException(sprintf('Not an encrypted value. Raw binary length is %d which is below %d.', strlen($value), SODIUM_CRYPTO_SECRETBOX_NONCEBYTES));
78
		}
79
		$encrypted = substr($value, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, strlen($value));
80
		if (!is_string($encrypted)) {
0 ignored issues
show
introduced by
The condition is_string($encrypted) is always true.
Loading history...
81
			throw new \UnexpectedValueException(sprintf('Not an encrypted value. Raw binary length is %d.', strlen($value)));
82
		}
83
		$result = sodium_crypto_secretbox_open($encrypted, $nonce, self::getSecretKey());
84
		// Decryption failed, password might have changed
85
		if ($result === false) {
86
			throw new \UnexpectedValueException("invalid password");
87
		}
88
89
		return $result;
90
	}
91
}
92