Skip to content

Commit cb38485

Browse files
sign and verify data
1 parent c222a57 commit cb38485

File tree

9 files changed

+447
-319
lines changed

9 files changed

+447
-319
lines changed

composer.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
{
22
"name": "pderas/azure-key-vault",
3-
"version": "0.0.1",
3+
"version": "0.0.2",
44
"description": "A package to interact with Azure Key Vault",
5+
"keywords": [
6+
"azure",
7+
"key-vault",
8+
"vault",
9+
"secrets",
10+
"laravel"
11+
],
12+
"homepage": "https://github.com/PDERAS/azure-key-vault",
513
"type": "library",
614
"license": "MIT",
715
"autoload": {

composer.lock

Lines changed: 171 additions & 163 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/azure_vault.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
*/
1212
'key_name' => env('AZURE_KEY_VAULT_KEY_NAME'),
1313

14+
/**
15+
* Azure Key Vault hashing algorithm to use for signing.
16+
* Supported algorithms: RS256, RS384, RS512
17+
*/
18+
'algorithm' => env('AZURE_KEY_VAULT_ALGORITHM', 'RS256'),
19+
1420
/**
1521
* Whether to use the Azure CLI to generate the access token
1622
*/

src/AzureKeyVault.php

Lines changed: 35 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -2,179 +2,71 @@
22

33
namespace Pderas\AzureKeyVault;
44

5-
use Carbon\Carbon;
6-
use GuzzleHttp\Client;
7-
use GuzzleHttp\Exception\ClientException;
85
use Illuminate\Support\Arr;
9-
use Illuminate\Support\Facades\Log;
10-
use Illuminate\Support\Facades\Process;
11-
use Illuminate\Support\Facades\URL;
126
use Pderas\AzureKeyVault\Enums\AzureEndpoint;
7+
use Pderas\AzureKeyVault\Services\AzureClient;
8+
use Pderas\AzureKeyVault\Services\AzureHasher;
139
use Str;
1410

1511
class AzureKeyVault
1612
{
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+
) {}
6417

6518
/**
6619
* Sign the data using the Azure Key Vault
6720
*/
6821
public function sign(string $data)
6922
{
70-
$this->getAccessToken();
71-
7223
// Reference: https://learn.microsoft.com/en-us/rest/api/keyvault/keys/sign/sign?view=rest-keyvault-keys-7.4
7324
$payload = json_encode([
74-
'alg' => 'RS512',
75-
'value' => $this->hash($data),
25+
'alg' => $this->hasher->getHashingAlgorithm(),
26+
'value' => $this->hasher->hash($data),
7627
]);
7728

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+
);
9633

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+
];
11538
}
11639

11740
/**
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
11942
*/
120-
protected function hash(string $data): string
43+
public function verify(string $value, string $signature, string $key_version)
12144
{
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+
]);
12450

125-
return base64_encode($hashed_value);
51+
return $this->client->request(
52+
AzureEndpoint::VERIFY,
53+
['body' => $payload],
54+
$key_version
55+
);
12656
}
12757

12858
/**
12959
* Get the current key version
13060
*/
131-
protected function getCurrentKeyVersion(): ?string
61+
public function currentKeyVersion(): ?string
13262
{
133-
$url = $this->getKeyVersionUrl();
63+
$response = $this->client->request(
64+
AzureEndpoint::CURRENT,
65+
);
13466

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');
15368

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, '/');
17971
}
18072
}

src/AzureKeyVaultServiceProvider.php

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,9 @@
22

33
namespace Pderas\AzureKeyVault;
44

5-
use AzureOss\FlysystemAzureBlobStorage\AzureBlobStorageAdapter;
6-
use AzureOss\Storage\Blob\BlobContainerClient;
7-
use AzureOss\Storage\Blob\BlobServiceClient;
8-
use Illuminate\Contracts\Container\Container;
9-
use Illuminate\Filesystem\FilesystemAdapter;
10-
use Illuminate\Support\Facades\Storage;
115
use Illuminate\Support\ServiceProvider;
12-
use League\Flysystem\Filesystem;
13-
// use MicrosoftAzure\Storage\Blob\BlobRestProxy;
6+
use Pderas\AzureKeyVault\Services\AzureClient;
7+
use Pderas\AzureKeyVault\Services\AzureHasher;
148

159
class AzureKeyVaultServiceProvider extends ServiceProvider
1610
{
@@ -24,8 +18,11 @@ public function register(): void
2418
'azure_vault'
2519
);
2620

27-
$this->app->singleton('azure-key-vault', function () {
28-
return new AzureKeyVault();
21+
$this->app->singleton('azure-key-vault', function ($app) {
22+
return new AzureKeyVault(
23+
$app->make(AzureClient::class),
24+
$app->make(AzureHasher::class)
25+
);
2926
});
3027

3128
}

src/Enums/AzureEndpoint.php

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,41 @@
55
enum AzureEndpoint: string {
66
case SIGN = 'sign';
77
case VERIFY = 'verify';
8-
case ENCRYPT = 'encrypt';
9-
case DECRYPT = 'decrypt';
108
case VERSIONS = 'versions';
9+
case CURRENT = '';
10+
11+
/**
12+
* Get the method name for the endpoint.
13+
*/
14+
public function method(): string
15+
{
16+
return match ($this) {
17+
self::SIGN, self::VERIFY => 'POST',
18+
self::VERSIONS, self::CURRENT => 'GET',
19+
default => 'GET',
20+
};
21+
}
22+
23+
/**
24+
* Get the full path for the Azure Key Vault endpoint.
25+
*/
26+
public function path(string $base_url, string $key_name, ?string $version = null): string
27+
{
28+
$url = "{$base_url}/keys/{$key_name}";
29+
30+
// Verify requires a version
31+
if ($this->requiresVersion()) {
32+
$url .= "/{$version}";
33+
}
34+
35+
return "{$url}/{$this->value}";
36+
}
37+
38+
/**
39+
* Check if the endpoint requires a key version.
40+
*/
41+
private function requiresVersion(): bool
42+
{
43+
return $this === self::VERIFY;
44+
}
1145
}

src/Services/AzureClient.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
namespace Pderas\AzureKeyVault\Services;
4+
5+
use GuzzleHttp\Exception\ClientException;
6+
use Illuminate\Support\Facades\Http;
7+
use Illuminate\Support\Facades\Log;
8+
use Pderas\AzureKeyVault\Enums\AzureEndpoint;
9+
10+
class AzureClient
11+
{
12+
/**
13+
* The base URL for the Azure Key Vault.
14+
*/
15+
protected string $vault_base_url;
16+
17+
/**
18+
* The name of the key in Azure Key Vault.
19+
*/
20+
protected string $key_name;
21+
/**
22+
* The API version to use for Azure Key Vault requests.
23+
*/
24+
protected static string $api_version = '7.4';
25+
26+
public function __construct(
27+
protected AzureTokenProvider $token_provider,
28+
)
29+
{
30+
$this->vault_base_url = config('azure_vault.vault_base_url');
31+
$this->key_name = config('azure_vault.key_name');
32+
}
33+
34+
/**
35+
* Make a request to the Azure Key Vault
36+
*/
37+
public function request(AzureEndpoint $endpoint, array $options = [], ?string $key_version = null): ?array
38+
{
39+
try {
40+
$token = $this->token_provider->getAccessToken();
41+
42+
$options['headers'] = [
43+
'Authorization' => "Bearer {$token}",
44+
'Content-Type' => 'application/json',
45+
];
46+
$options['query'] = [
47+
'api-version' => self::$api_version,
48+
];
49+
50+
$response = Http::send(
51+
$endpoint->method(),
52+
$endpoint->path(
53+
$this->vault_base_url,
54+
$this->key_name,
55+
$key_version
56+
),
57+
$options,
58+
);
59+
60+
return json_decode($response->getBody(), true);
61+
} catch (ClientException $e) {
62+
Log::error($e, [
63+
'response' => (string) $e->getResponse()->getBody()
64+
]);
65+
throw $e;
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)