Completed
Push — master ( 02d10d...0ebe5c )
by BENOIT
07:36
created

Shh::freeResource()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace BenTools\Shh;
4
5
final class Shh
6
{
7
    private const DEFAULT_OPENSSL_GENERATION_CONFIGURATION = [
8
        'digest_alg'       => 'sha512',
9
        'private_key_bits' => 4096,
10
        'private_key_type' => \OPENSSL_KEYTYPE_RSA,
11
    ];
12
13
    /**
14
     * @var string
15
     */
16
    private $publicKey;
17
18
    /**
19
     * @var string|null
20
     */
21
    private $privateKey;
22
23
    /**
24
     * @var string|null
25
     */
26
    private $passphrase;
27
28
    /**
29
     * @var resource
30
     */
31
    private $resource;
32
33
    /**
34
     * Shh constructor.
35
     */
36
    public function __construct(string $publicKey, ?string $privateKey = null, ?string $passphrase = null)
37
    {
38
        $this->publicKey = self::normalize($publicKey);
39
        $this->privateKey = null === $privateKey ? null : self::normalize($privateKey);
40
        $this->passphrase = $passphrase;
41
    }
42
43
    /**
44
     * @return resource
45
     */
46
    private function getPublicKeyAsResource()
47
    {
48
        if (null === $this->resource) {
49
            $this->resource = \openssl_pkey_get_public($this->publicKey)
50
                or ShhException::throwFromLastOpenSSLError('Unable to open resource.');
51
        }
52
53
        return $this->resource;
54
    }
55
56
    private function freeResource(): void
57
    {
58
        if (null === $this->resource) {
59
            return;
60
        }
61
62
        \openssl_free_key($this->resource);
63
        $this->resource = null;
64
    }
65
66
    /**
67
     * @param string $payload
68
     * @return string
69
     */
70
    public function encrypt(string $payload): string
71
    {
72
        $resource = $this->getPublicKeyAsResource();
73
        $success = \openssl_public_encrypt($payload, $encryptedData, $resource, \OPENSSL_PKCS1_OAEP_PADDING);
74
        $this->freeResource();
75
76
        if (!$success) {
77
            throw new ShhException("Encryption failed. Ensure you are using a PUBLIC key.");
78
        }
79
80
        return \base64_encode($encryptedData);
81
    }
82
83
    /**
84
     * @param string $base64EncodedPayload
85
     * @return string
86
     */
87
    public function decrypt(string $base64EncodedPayload): string
88
    {
89
        if (null === $this->privateKey) {
90
            throw new ShhException('Unable to decrypt payload: no private key provided.');
91
        }
92
93
        $payload = \base64_decode($base64EncodedPayload);
94
95
        if (false === $payload) {
96
            throw new ShhException('Encrypted payload was not provided as Base64.');
97
        }
98
99
        $resource = \openssl_pkey_get_private($this->privateKey, $this->passphrase)
100
            or ShhException::throwFromLastOpenSSLError('Private key seems corrupted.');
101
102
        $success = \openssl_private_decrypt($payload, $decryptedData, $resource, \OPENSSL_PKCS1_OAEP_PADDING);
103
        \openssl_free_key($resource);
104
105
        if (!$success) {
106
            throw new ShhException("Decryption failed. Ensure you are using (1) A PRIVATE key, and (2) the correct one.");
107
        }
108
109
        return $decryptedData;
110
    }
111
112
    /**
113
     * Generate a new private/public key pair.
114
     *
115
     * @param string|null $passphrase
116
     * @param array       $config
117
     * @return array - [privateKey, publicKey]
118
     */
119
    public static function generateKeyPair(?string $passphrase = null, array $config = self::DEFAULT_OPENSSL_GENERATION_CONFIGURATION): array
120
    {
121
        $resource = \openssl_pkey_new($config)
122
            or ShhException::throwFromLastOpenSSLError('Unable to open resource.');
123
124
        $success = \openssl_pkey_export($resource, $privateKey, $passphrase);
125
126
        if (false === $success) {
127
            ShhException::throwFromLastOpenSSLError('Private key generation failed.');
128
        }
129
130
        $publicKey = \openssl_pkey_get_details($resource)['key'];
131
132
        return [$publicKey, $privateKey];
133
    }
134
135
    /**
136
     * Change passphrase and return a new private key.
137
     *
138
     * @param string      $privateKey
139
     * @param string|null $oldPassphrase
140
     * @param string|null $newPassphrase
141
     * @return string
142
     */
143
    public static function changePassphrase(string $privateKey, ?string $oldPassphrase, ?string $newPassphrase): string
144
    {
145
        $resource = \openssl_pkey_get_private(self::normalize($privateKey), $oldPassphrase);
146
        $success = @\openssl_pkey_export($resource, $newPrivateKey, $newPassphrase);
147
148
        if (false === $success) {
149
            throw new ShhException('Wrong passphrase or inexistent private key.');
150
        }
151
152
        return $newPrivateKey;
153
    }
154
155
    /**
156
     * @param string $key
157
     * @return string
158
     */
159
    private static function normalize(string $key): string
160
    {
161
        return (0 === \strpos($key, '/')) ? 'file://'.$key : $key;
162
    }
163
}
164