|
2 | 2 |
|
3 | 3 | namespace Pderas\AzureKeyVault;
|
4 | 4 |
|
5 |
| -use Carbon\Carbon; |
6 |
| -use GuzzleHttp\Client; |
7 |
| -use GuzzleHttp\Exception\ClientException; |
8 | 5 | use Illuminate\Support\Arr;
|
9 |
| -use Illuminate\Support\Facades\Log; |
10 |
| -use Illuminate\Support\Facades\Process; |
11 |
| -use Illuminate\Support\Facades\URL; |
12 | 6 | use Pderas\AzureKeyVault\Enums\AzureEndpoint;
|
| 7 | +use Pderas\AzureKeyVault\Services\AzureClient; |
| 8 | +use Pderas\AzureKeyVault\Services\AzureHasher; |
13 | 9 | use Str;
|
14 | 10 |
|
15 | 11 | class AzureKeyVault
|
16 | 12 | {
|
17 |
| - /** |
18 |
| - * Azure Key Vault API version |
19 |
| - * |
20 |
| - * @var string |
21 |
| - */ |
22 |
| - private const API_VERSION = '7.4'; |
23 |
| - |
24 |
| - /** |
25 |
| - * Azure Key Vault Base URL. Example: https://my-key-vault.vault.azure.net/ |
26 |
| - */ |
27 |
| - protected string $vault_base_url; |
28 |
| - |
29 |
| - /** |
30 |
| - * Name of the key in Azure Key Vault |
31 |
| - */ |
32 |
| - protected string $key_name; |
33 |
| - |
34 |
| - /** |
35 |
| - * The access token generated by Azure Key Vault |
36 |
| - */ |
37 |
| - protected ?string $token = null; |
38 |
| - |
39 |
| - /** |
40 |
| - * The expiry date of the Access Token |
41 |
| - */ |
42 |
| - protected Carbon $token_expiry; |
43 |
| - |
44 |
| - /** |
45 |
| - * Whether to use the Azure CLI to generate the access token |
46 |
| - */ |
47 |
| - protected bool $use_azure_cli = false; |
48 |
| - |
49 |
| - /** |
50 |
| - * Guzzle HTTP Client |
51 |
| - */ |
52 |
| - protected Client $client; |
53 |
| - |
54 |
| - public function __construct() |
55 |
| - { |
56 |
| - $config = config('azure_vault'); |
57 |
| - |
58 |
| - $this->vault_base_url = $config['vault_base_url']; |
59 |
| - $this->key_name = $config['key_name']; |
60 |
| - $this->use_azure_cli = $config['use_azure_cli']; |
61 |
| - |
62 |
| - $this->client = new Client(); |
63 |
| - } |
| 13 | + public function __construct( |
| 14 | + protected AzureClient $client, |
| 15 | + protected AzureHasher $hasher, |
| 16 | + ) {} |
64 | 17 |
|
65 | 18 | /**
|
66 | 19 | * Sign the data using the Azure Key Vault
|
67 | 20 | */
|
68 | 21 | public function sign(string $data)
|
69 | 22 | {
|
70 |
| - $this->getAccessToken(); |
71 |
| - |
72 | 23 | // Reference: https://learn.microsoft.com/en-us/rest/api/keyvault/keys/sign/sign?view=rest-keyvault-keys-7.4
|
73 | 24 | $payload = json_encode([
|
74 |
| - 'alg' => 'RS512', |
75 |
| - 'value' => $this->hash($data), |
| 25 | + 'alg' => $this->hasher->getHashingAlgorithm(), |
| 26 | + 'value' => $this->hasher->hash($data), |
76 | 27 | ]);
|
77 | 28 |
|
78 |
| - try { |
79 |
| - $response = $this->request( |
80 |
| - 'POST', |
81 |
| - $this->buildUrl(AzureEndpoint::SIGN), |
82 |
| - ['body' => $payload] |
83 |
| - ); |
84 |
| - |
85 |
| - return [ |
86 |
| - 'signature' => $response['value'], |
87 |
| - 'key_version' => $this->getCurrentKeyVersion(), |
88 |
| - ]; |
89 |
| - } catch (ClientException $e) { |
90 |
| - // Log a non-truncated error message |
91 |
| - Log::error($e, [ |
92 |
| - 'response' => (string) $e->getResponse()->getBody() |
93 |
| - ]); |
94 |
| - } |
95 |
| - } |
| 29 | + $response = $this->client->request( |
| 30 | + AzureEndpoint::SIGN, |
| 31 | + ['body' => $payload] |
| 32 | + ); |
96 | 33 |
|
97 |
| - /** |
98 |
| - * Retrieve an access token from Azure Key Vault |
99 |
| - */ |
100 |
| - protected function getAccessToken(): void |
101 |
| - { |
102 |
| - if ($this->token && $this->token_expiry->isFuture()) { |
103 |
| - return; |
104 |
| - } |
105 |
| - |
106 |
| - if ($this->use_azure_cli) { |
107 |
| - $azure_cli_result = Process::run('az account get-access-token --resource https://vault.azure.net'); |
108 |
| - $token_output = json_decode($azure_cli_result->output(), true); |
109 |
| - |
110 |
| - $this->token = Arr::get($token_output, 'accessToken'); |
111 |
| - $this->token_expiry = Carbon::parse(Arr::get($token_output, 'expiresOn')); |
112 |
| - } else { |
113 |
| - // TODO: Implement Managed Identity |
114 |
| - } |
| 34 | + return [ |
| 35 | + 'signature' => $response['value'], |
| 36 | + 'key_version' => Str::afterLast($response['kid'], '/'), |
| 37 | + ]; |
115 | 38 | }
|
116 | 39 |
|
117 | 40 | /**
|
118 |
| - * Hash the data using SHA-512 and return the base64 encoded value |
| 41 | + * Verify the signature of the data using the Azure Key Vault |
119 | 42 | */
|
120 |
| - protected function hash(string $data): string |
| 43 | + public function verify(string $value, string $signature, string $key_version) |
121 | 44 | {
|
122 |
| - // Use binary encoded hash |
123 |
| - $hashed_value = hash('sha512', $data, true); |
| 45 | + $payload = json_encode([ |
| 46 | + 'alg' => $this->hasher->getHashingAlgorithm(), |
| 47 | + 'digest' => $this->hasher->hash($value), |
| 48 | + 'value' => $signature, |
| 49 | + ]); |
124 | 50 |
|
125 |
| - return base64_encode($hashed_value); |
| 51 | + return $this->client->request( |
| 52 | + AzureEndpoint::VERIFY, |
| 53 | + ['body' => $payload], |
| 54 | + $key_version |
| 55 | + ); |
126 | 56 | }
|
127 | 57 |
|
128 | 58 | /**
|
129 | 59 | * Get the current key version
|
130 | 60 | */
|
131 |
| - protected function getCurrentKeyVersion(): ?string |
| 61 | + public function currentKeyVersion(): ?string |
132 | 62 | {
|
133 |
| - $url = $this->getKeyVersionUrl(); |
| 63 | + $response = $this->client->request( |
| 64 | + AzureEndpoint::CURRENT, |
| 65 | + ); |
134 | 66 |
|
135 |
| - // Return the last part of the URL which is the key version |
136 |
| - return Str::afterLast($url, '/'); |
137 |
| - } |
138 |
| - |
139 |
| - /** |
140 |
| - * Get the full key version URL |
141 |
| - */ |
142 |
| - protected function getKeyVersionUrl(): ?string |
143 |
| - { |
144 |
| - $url = $this->buildUrl(AzureEndpoint::VERSIONS, [ |
145 |
| - 'maxresults' => 1 |
146 |
| - ]); |
147 |
| - |
148 |
| - $response = $this->request('GET', $url); |
149 |
| - |
150 |
| - // Get the first key version (full URL) |
151 |
| - return Arr::get($response, 'value.0.kid'); |
152 |
| - } |
| 67 | + $response_key_url = Arr::get($response, 'key.kid'); |
153 | 68 |
|
154 |
| - /** |
155 |
| - * Make a request to the Azure Key Vault |
156 |
| - */ |
157 |
| - protected function request(string $method, string $url, array $options = []): ?array |
158 |
| - { |
159 |
| - $options['headers'] = [ |
160 |
| - 'Authorization' => "Bearer {$this->token}", |
161 |
| - 'Content-Type' => 'application/json', |
162 |
| - ]; |
163 |
| - |
164 |
| - $response = $this->client->request($method, $url, $options); |
165 |
| - |
166 |
| - return json_decode($response->getBody(), true); |
167 |
| - } |
168 |
| - |
169 |
| - /** |
170 |
| - * Build the URL for the Azure Key Vault |
171 |
| - */ |
172 |
| - protected function buildUrl(AzureEndpoint $endpoint, array $parameters = []): string |
173 |
| - { |
174 |
| - $url = "{$this->vault_base_url}/keys/{$this->key_name}/{$endpoint->value}"; |
175 |
| - |
176 |
| - $parameters['api-version'] = self::API_VERSION; |
177 |
| - |
178 |
| - return URL::query($url, $parameters); |
| 69 | + // Return the last part of the URL which is the key version |
| 70 | + return Str::afterLast($response_key_url, '/'); |
179 | 71 | }
|
180 | 72 | }
|
0 commit comments