Request::__construct()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 13
rs 10
cc 3
nc 3
nop 2
1
<?php declare(strict_types=1);
2
3
namespace ncryptf;
4
5
use InvalidArgumentException;
6
use SodiumException;
7
use ncryptf\Keypair;
8
use ncryptf\exceptions\EncryptionFailedException;
9
10
final class Request
11
{
12
    /**
13
     * @var string
14
     */
15
    private $secretKey;
16
17
    /**
18
     * @var string
19
     */
20
    private $signatureSecretKey;
21
22
    /**
23
     * 24 byte nonce
24
     *
25
     * @var string
26
     */
27
    private $nonce;
28
29
    /**
30
     * Constructor
31
     *
32
     * @param string $secretKey The 32 byte secret key
33
     * @param string $signatureSecretKey The 64 byte public keyy
34
     *
35
     * @throws InvalidArgumentException
36
     */
37
    public function __construct(string $secretKey, string $signatureSecretKey)
38
    {
39
        if (\strlen($secretKey) !== SODIUM_CRYPTO_BOX_SECRETKEYBYTES) {
40
            throw new InvalidArgumentException(sprintf("Secret key should be %d bytes.", SODIUM_CRYPTO_BOX_SECRETKEYBYTES));
41
        }
42
43
        $this->secretKey = $secretKey;
44
45
        if (\strlen($signatureSecretKey) !== SODIUM_CRYPTO_SIGN_SECRETKEYBYTES) {
46
            throw new InvalidArgumentException(sprintf("Signing key should be %d bytes.", SODIUM_CRYPTO_SIGN_SECRETKEYBYTES));
47
        }
48
49
        $this->signatureSecretKey = $signatureSecretKey;
50
    }
51
52
    /**
53
     * Encrypts a request body
54
     *
55
     * @param string $request           The raw HTTP request as a string
56
     * @param string $publicKey   32 byte public key
57
     * @param int    $version           Version to generate, defaults to 2
58
     * @param string $nonce             Optional nonce. If not provided, a 24 byte nonce will be generated
59
     * @return string
60
     *
61
     * @throws InvalidArgumentException
62
     */
63
    public function encrypt(string $request, string $publicKey, int $version = 2, string $nonce = null) : string
64
    {
65
        $this->nonce = $nonce ?? \random_bytes(SODIUM_CRYPTO_BOX_NONCEBYTES);
66
67
        if (\strlen($publicKey) !== SODIUM_CRYPTO_BOX_PUBLICKEYBYTES) {
68
            throw new InvalidArgumentException(sprintf("Remote public key should be %d bytes.", SODIUM_CRYPTO_BOX_PUBLICKEYBYTES));
69
        }
70
71
        if ($version === 2) {
72
            $version = \pack('H*', 'DE259002');
73
            $body = $this->encryptBody($request, $publicKey, $this->nonce);
74
            if (!$body) {
75
                throw new EncryptionFailedException('An unexpected error occured when encrypting the message.');
76
            }
77
78
            $iPublicKey = \sodium_crypto_box_publickey_from_secretkey($this->secretKey);
79
            $sigPubKey = \sodium_crypto_sign_publickey_from_secretkey($this->signatureSecretKey);
80
            $payload = $version . $this->nonce . $iPublicKey . $body . $sigPubKey . $this->sign($request);
81
            $checksum = sodium_crypto_generichash($payload, $this->nonce, 64);
82
83
            return $payload . $checksum;
84
        }
85
86
87
        // Version 1 payload is just a single sodium crypto box
88
        return $this->encryptBody($request, $publicKey, $this->nonce);
89
    }
90
91
    /**
92
     * Encrypts a request
93
     *
94
     * @param string $request   The raw HTTP request as a string
95
     * @param string $nonce     Optional nonce. If not provided, a 24 byte nonce will be generated
96
     * @return string
97
     *
98
     * @throws InvalidArguementException
99
     */
100
    private function encryptBody(string $request, string $publicKey, string $nonce) : string
101
    {
102
        if (\strlen($publicKey) !== SODIUM_CRYPTO_BOX_PUBLICKEYBYTES) {
103
            throw new InvalidArgumentException(sprintf("Public key should be %d bytes.", SODIUM_CRYPTO_BOX_PUBLICKEYBYTES));
104
        }
105
106
        if (\strlen($nonce) !== SODIUM_CRYPTO_BOX_NONCEBYTES) {
107
            throw new InvalidArgumentException(sprintf("Nonce should be %d bytes.", SODIUM_CRYPTO_BOX_NONCEBYTES));
108
        }
109
110
        try {
111
            $keypair = new Keypair(
112
                $this->secretKey,
113
                $publicKey
114
            );
115
            return \sodium_crypto_box(
116
                $request,
117
                $nonce,
118
                $keypair->getSodiumKeypair()
119
            );
120
        } catch (SodiumException $e) {
121
            throw new InvalidArgumentException($e->getMessage());
122
        }
123
    }
124
125
    /**
126
     * Creates a detached signature for the keypair
127
     *
128
     * @param string $request
129
     * @param string $secretKey
130
     * @return string
131
     *
132
     * @throws InvalidArguementException
133
     */
134
    public function sign(string $request) : string
135
    {
136
        try {
137
            return \sodium_crypto_sign_detached(
138
                $request,
139
                $this->signatureSecretKey
140
            );
141
        } catch (SodiumException $e) {
142
            throw new InvalidArgumentException($e->getMessage());
143
        }
144
    }
145
146
    /**
147
     * Returns the nonce used
148
     *
149
     * @return string
150
     */
151
    public function getNonce() : string
152
    {
153
        return $this->nonce;
154
    }
155
}
156