Passed
Pull Request — master (#36)
by Rogier
01:42
created

LocalFileAccount::generateNewKeys()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
c 0
b 0
f 0
dl 0
loc 27
rs 8.4444
cc 8
nc 5
nop 1
1
<?php
2
3
namespace Rogierw\RwAcme\Support;
4
5
use Rogierw\RwAcme\Exceptions\LetsEncryptClientException;
6
use Rogierw\RwAcme\Interfaces\AcmeAccountInterface;
7
8
class LocalFileAccount implements AcmeAccountInterface
9
{
10
    private string $accountName;
11
    private string $emailAddress;
12
13
    public function __construct(private string $accountKeysPath, string $emailAddress = null)
14
    {
15
        // Make sure the path ends with a slash.
16
        $this->accountKeysPath = rtrim($this->accountKeysPath, '/').'/';
17
18
        if ($emailAddress !== null) {
19
            $this->setEmailAddress($emailAddress);
20
        }
21
    }
22
23
    public function setEmailAddress(string $emailAddress): self
24
    {
25
        $alphaNumAccountName = preg_replace('/[^a-zA-Z0-9\-]/', '_', $emailAddress);
26
        // Prepend a hash to prevent collisions.
27
        $shortHash = substr(hash('sha256', $emailAddress), 0, 16);
28
29
        $this->emailAddress = $emailAddress;
30
        $this->accountName = $shortHash.'_'.$alphaNumAccountName;
31
32
        return $this;
33
    }
34
35
    public function getEmailAddress(): string
36
    {
37
        return $this->emailAddress;
38
    }
39
40
    public function getPrivateKey(): string
41
    {
42
        return $this->getKey('private');
43
    }
44
45
    public function getPublicKey(): string
46
    {
47
        return $this->getKey('public');
48
    }
49
50
    public function exists(): bool
51
    {
52
        if (is_dir($this->accountKeysPath)) {
53
            return is_file($this->accountKeysPath.$this->getKeyName('private'))
54
                && is_file($this->accountKeysPath.$this->getKeyName('public'));
55
        }
56
57
        return false;
58
    }
59
60
    public function generateNewKeys(string $keyType = 'RSA'): bool
61
    {
62
        if ($keyType !== 'RSA') {
63
            throw new LetsEncryptClientException('Key type is not supported.');
64
        }
65
66
        $concurrentDirectory = rtrim($this->accountKeysPath, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
67
68
        if (!is_dir($concurrentDirectory) && !mkdir($concurrentDirectory) && !is_dir($concurrentDirectory)) {
69
            throw new LetsEncryptClientException(sprintf('Directory "%s" was not created', $concurrentDirectory));
70
        }
71
72
        $keys = CryptRSA::generate();
73
74
        if (!isset($keys['privateKey'], $keys['publicKey'])) {
75
            throw new LetsEncryptClientException('Key generation failed.');
76
        }
77
78
        $privateKeyPath = $concurrentDirectory.$this->getKeyName('private');
79
        $publicKeyPath = $concurrentDirectory.$this->getKeyName('public');
80
81
        if (file_put_contents($privateKeyPath, $keys['privateKey']) === false ||
82
            file_put_contents($publicKeyPath, $keys['publicKey']) === false) {
83
            throw new LetsEncryptClientException('Failed to write keys to files.');
84
        }
85
86
        return true;
87
    }
88
89
    protected function getKey(string $type): string
90
    {
91
        $filePath = $this->accountKeysPath.$this->getKeyName($type);
92
93
        if (!file_exists($filePath)) {
94
            throw new LetsEncryptClientException(sprintf('[%s] File does not exist', $filePath));
95
        }
96
97
        $content = file_get_contents($filePath);
98
99
        if ($content === false) {
100
            throw new LetsEncryptClientException(sprintf('[%s] Failed to get contents of the file', $filePath));
101
        }
102
103
        return $content;
104
    }
105
106
    private function getKeyName(string $type): string
107
    {
108
        if (empty($this->accountName)) {
109
            throw new LetsEncryptClientException('Account name is not set.');
110
        }
111
112
        return sprintf('%s-%s.pem', $this->accountName, $type);
113
    }
114
}
115