Completed
Push — develop ( c28702...43dcba )
by Fabian
01:22
created

Encoder::urlSafeEncode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cakasim\Payone\Sdk\Redirect\Token\Format;
6
7
use Cakasim\Payone\Sdk\Config\ConfigExceptionInterface;
8
use Cakasim\Payone\Sdk\Config\ConfigInterface;
9
use Cakasim\Payone\Sdk\Redirect\Token\TokenInterface;
10
11
/**
12
 * @author Fabian Böttcher <[email protected]>
13
 * @since 0.1.0
14
 */
15
class Encoder implements EncoderInterface
16
{
17
    /**
18
     * @var ConfigInterface The SDK config.
19
     */
20
    protected $config;
21
22
    /**
23
     * @var SignerInterface The token signer.
24
     */
25
    protected $signer;
26
27
    /**
28
     * Constructs the token encoder.
29
     *
30
     * @param ConfigInterface $config The SDK config.
31
     * @param SignerInterface $signer The token signer.
32
     */
33
    public function __construct(ConfigInterface $config, SignerInterface $signer)
34
    {
35
        $this->config = $config;
36
        $this->signer = $signer;
37
    }
38
39
    /**
40
     * @inheritDoc
41
     */
42
    public function encode(TokenInterface $token): string
43
    {
44
        // JSON encode the token.
45
        $token = json_encode($token);
46
47
        if ($token === false) {
48
            throw new EncoderException("Failed token encoding, the token could not be JSON encoded.");
49
        }
50
51
        try {
52
            // Load token encryption config.
53
            $encryptionMethod = $this->config->get('redirect.token_encryption_method');
54
            $encryptionKey = $this->config->get('redirect.token_encryption_key');
55
        } catch (ConfigExceptionInterface $e) {
56
            throw new EncoderException("Failed token encoding, the token encryption config is incomplete.", 0, $e);
57
        }
58
59
        // Get binary SHA-256 hash of cleartext encryption key.
60
        $encryptionKey = hash('sha256', $encryptionKey, true);
61
62
        // Make initialization vector.
63
        $iv = $this->makeIv($encryptionMethod);
64
65
        // Encrypt the JSON encoded token.
66
        $token = $this->encrypt($token, $encryptionMethod, $encryptionKey, $iv);
67
68
        try {
69
            // Sign the encrypted token value.
70
            $signature = $this->signer->createSignature($token);
71
        } catch (SignerExceptionInterface $e) {
72
            throw new EncoderException("Failed token encoding, the token could not be signed.", 0, $e);
73
        }
74
75
        // Encode iv, token and signature.
76
        $iv = $this->urlSafeEncode($iv);
77
        $token = $this->urlSafeEncode($token);
78
        $signature = $this->urlSafeEncode($signature);
79
80
        return "{$iv}.{$token}.{$signature}";
81
    }
82
83
    /**
84
     * Makes an initialization vector with proper length according
85
     * to the provided cipher method.
86
     *
87
     * @param string $method The cipher method.
88
     * @return string The initialization vector.
89
     * @throws EncoderExceptionInterface If making the initialization vector fails.
90
     */
91
    protected function makeIv(string $method): string
92
    {
93
        $length = openssl_cipher_iv_length($method);
94
95
        if (!is_int($length)) {
0 ignored issues
show
introduced by
The condition is_int($length) is always true.
Loading history...
96
            throw new EncoderException("Failed token encoding, could not get length for initialization vector.");
97
        }
98
99
        try {
100
            $iv = $length > 0
101
                ? random_bytes($length)
102
                : '';
103
        } catch (\Exception $e) {
104
            throw new EncoderException("Failed token encoding, could not generate random initialization vector.");
105
        }
106
107
108
        if (!is_string($iv)) {
0 ignored issues
show
introduced by
The condition is_string($iv) is always true.
Loading history...
109
            throw new EncoderException("Failed token encoding, could not make the initialization vector.");
110
        }
111
112
        return $iv;
113
    }
114
115
    /**
116
     * Encrypts the provided data.
117
     *
118
     * @param string $data The data to encrypt.
119
     * @param string $method The cipher method.
120
     * @param string $key The encryption key.
121
     * @param string $iv The initialization vector.
122
     * @return string The encrypted data.
123
     * @throws EncoderExceptionInterface If the encryption fails.
124
     */
125
    protected function encrypt(string $data, string $method, string $key, string $iv): string
126
    {
127
        $data = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
128
129
        if (!is_string($data)) {
0 ignored issues
show
introduced by
The condition is_string($data) is always true.
Loading history...
130
            throw new EncoderException("Failed token encoding, the token could not be encrypted.");
131
        }
132
133
        return $data;
134
    }
135
136
    /**
137
     * Encodes the provided data URL-safe.
138
     *
139
     * @param string $data The data to encode.
140
     * @return string The encoded data
141
     */
142
    protected function urlSafeEncode(string $data): string
143
    {
144
        $data = base64_encode($data);
145
        $data =strtr($data, '+/', '-_');
146
        return rtrim($data, '=');
147
    }
148
}
149