Skip to content

Commit b088168

Browse files
authored
Migrated Key Vault JCA library to use azure-json serialization/deserialization (#41508)
* Removed Jackson dependency from POM. * Updated all models to implement `JsonSerializable`. * Updated and added util classes to handle serialization/deserialization. * Applied PR feedback. * Fixed compilation warning. * Removed reflection-related classes taken from azure-core. * Fixed SecurityManager issue with shaded uber-jar. * Applied PR feedback. * More PR feedback. * Fixed merge isues. * Applied Spotless. * Updated azure-json version. * Made azure-json an optional dependency.
1 parent c95f9fb commit b088168

14 files changed

Lines changed: 632 additions & 132 deletions

File tree

sdk/keyvault/azure-security-keyvault-jca/pom.xml

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@
4646
<scope>provided</scope>
4747
<version>2.5.2</version> <!-- {x-version-update;org.conscrypt:conscrypt-openjdk-uber;external_dependency} -->
4848
</dependency>
49-
<!-- Jackson Databind -->
49+
<!-- Azure JSON -->
5050
<dependency>
51-
<groupId>com.fasterxml.jackson.core</groupId>
52-
<artifactId>jackson-databind</artifactId>
53-
<version>2.17.2</version> <!-- {x-version-update;com.fasterxml.jackson.core:jackson-databind;external_dependency} -->
51+
<groupId>com.azure</groupId>
52+
<artifactId>azure-json</artifactId>
53+
<version>1.3.0</version> <!-- {x-version-update;com.azure:azure-json;dependency} -->
5454
<optional>true</optional>
5555
</dependency>
5656
<!-- SLF4j -->
@@ -149,18 +149,18 @@
149149
<exclude>META-INF/services/java.security.Provider</exclude>
150150
</excludes>
151151
</filter>
152-
</filters>
153-
<relocations>
154-
<relocation>
155-
<pattern>com.fasterxml.jackson</pattern>
156-
<shadedPattern>com.azure.security.keyvault.jca.implementation.shaded.com.fasterxml.jackson</shadedPattern>
152+
<filter>
153+
<artifact>com.azure:azure-json</artifact>
157154
<excludes>
158-
<exclude>com/fasterxml/jackson/databind/jsonFormatVisitors/**</exclude> <!-- Avoid upper case in package name-->
155+
<exclude>META-INF/*.SF</exclude>
156+
<exclude>META-INF/*.RSA</exclude>
159157
</excludes>
160-
</relocation>
158+
</filter>
159+
</filters>
160+
<relocations>
161161
<relocation>
162-
<pattern>com.fasterxml.jackson.databind.jsonFormatVisitors</pattern>
163-
<shadedPattern>com.azure.security.keyvault.jca.implementation.shaded.com.fasterxml.jackson.databind.json.format.visitors</shadedPattern> <!-- Avoid upper case in package name-->
162+
<pattern>com.azure.json</pattern>
163+
<shadedPattern>com.azure.security.keyvault.jca.implementation.shaded.com.azure.json</shadedPattern>
164164
</relocation>
165165
<relocation>
166166
<pattern>org.apache.commons</pattern>
@@ -241,7 +241,6 @@
241241
<bannedDependencies>
242242
<includes>
243243
<include>org.bouncycastle:bcpkix-lts8on:[2.73.6]</include> <!-- {x-include-update;org.bouncycastle:bcpkix-lts8on;external_dependency} -->
244-
<include>com.fasterxml.jackson.core:jackson-databind:[2.17.2]</include> <!-- {x-include-update;com.fasterxml.jackson.core:jackson-databind;external_dependency} -->
245244
<include>org.conscrypt:conscrypt-openjdk-uber:[2.5.2]</include> <!-- {x-include-update;org.conscrypt:conscrypt-openjdk-uber;external_dependency} -->
246245
<include>org.apache.httpcomponents:httpclient:[4.5.14]</include> <!-- {x-include-update;org.apache.httpcomponents:httpclient;external_dependency} -->
247246
<include>org.slf4j:slf4j-nop:[1.7.36]</include> <!-- {x-include-update;org.slf4j:slf4j-nop;external_dependency} -->

sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/KeyVaultClient.java

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.ByteArrayInputStream;
2020
import java.io.IOException;
2121
import java.io.StringReader;
22+
import java.io.UnsupportedEncodingException;
2223
import java.net.URLEncoder;
2324
import java.security.Key;
2425
import java.security.KeyFactory;
@@ -202,8 +203,8 @@ private AccessToken getAccessTokenByHttpRequest() {
202203
} else {
203204
accessToken = AccessTokenUtil.getAccessToken(resource, managedIdentity);
204205
}
205-
} catch (Throwable t) {
206-
LOGGER.log(WARNING, "Could not obtain access token to authenticate with.", t);
206+
} catch (UnsupportedEncodingException e) {
207+
LOGGER.log(WARNING, "Could not obtain access token to authenticate with.", e);
207208
}
208209

209210
LOGGER.exiting("KeyVaultClient", "getAccessTokenByHttpRequest", accessToken);
@@ -217,6 +218,8 @@ private AccessToken getAccessTokenByHttpRequest() {
217218
* @return The list of aliases.
218219
*/
219220
public List<String> getAliases() {
221+
LOGGER.entering("KeyVaultClient", "getAliases");
222+
220223
ArrayList<String> result = new ArrayList<>();
221224
HashMap<String, String> headers = new HashMap<>();
222225

@@ -229,8 +232,11 @@ public List<String> getAliases() {
229232
CertificateListResult certificateListResult = null;
230233

231234
if (response != null) {
232-
certificateListResult
233-
= (CertificateListResult) JsonConverterUtil.fromJson(response, CertificateListResult.class);
235+
try {
236+
certificateListResult = JsonConverterUtil.fromJson(CertificateListResult::fromJson, response);
237+
} catch (IOException e) {
238+
LOGGER.log(WARNING, "Failed to parse certificate list response", e);
239+
}
234240
}
235241

236242
if (certificateListResult != null) {
@@ -245,6 +251,8 @@ public List<String> getAliases() {
245251
}
246252
}
247253

254+
LOGGER.exiting("KeyVaultClient", "getAliases", result);
255+
248256
return result;
249257
}
250258

@@ -255,6 +263,8 @@ public List<String> getAliases() {
255263
* @return The certificate bundle.
256264
*/
257265
private CertificateBundle getCertificateBundle(String alias) {
266+
LOGGER.entering("KeyVaultClient", "getCertificateBundle", alias);
267+
258268
CertificateBundle result = null;
259269
HashMap<String, String> headers = new HashMap<>();
260270

@@ -264,9 +274,15 @@ private CertificateBundle getCertificateBundle(String alias) {
264274
String response = HttpUtil.get(uri, headers);
265275

266276
if (response != null) {
267-
result = (CertificateBundle) JsonConverterUtil.fromJson(response, CertificateBundle.class);
277+
try {
278+
result = JsonConverterUtil.fromJson(CertificateBundle::fromJson, response);
279+
} catch (IOException e) {
280+
LOGGER.log(WARNING, "Failed to parse certificate bundle response", e);
281+
}
268282
}
269283

284+
LOGGER.exiting("KeyVaultClient", "getCertificateBundle", result);
285+
270286
return result;
271287
}
272288

@@ -315,22 +331,35 @@ public Certificate[] getCertificateChain(String alias) {
315331
LOGGER.log(INFO, "Getting certificate chain for alias: {0}", alias);
316332

317333
HashMap<String, String> headers = new HashMap<>();
334+
318335
headers.put("Authorization", "Bearer " + getAccessToken());
336+
319337
String uri = keyVaultUri + "secrets/" + alias + API_VERSION_POSTFIX;
320338
String response = HttpUtil.get(uri, headers);
339+
321340
if (response == null) {
322341
throw new NullPointerException();
323342
}
324-
SecretBundle secretBundle = (SecretBundle) JsonConverterUtil.fromJson(response, SecretBundle.class);
343+
344+
SecretBundle secretBundle = null;
345+
346+
try {
347+
secretBundle = JsonConverterUtil.fromJson(SecretBundle::fromJson, response);
348+
} catch (IOException e) {
349+
LOGGER.log(WARNING, "Failed to parse secret bundle response", e);
350+
}
325351

326352
Certificate[] certificates = new Certificate[0];
353+
327354
try {
328355
certificates = loadCertificatesFromSecretBundleValue(secretBundle.getValue());
329356
} catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException
330357
| NoSuchProviderException | PKCSException e) {
331358
LOGGER.log(WARNING, "Unable to decode certificate chain", e);
332359
}
360+
333361
LOGGER.exiting("KeyVaultClient", "getCertificate", alias);
362+
334363
return certificates;
335364
}
336365

@@ -362,14 +391,16 @@ public Key getKey(String alias, char[] password) {
362391
// Return KeyVaultPrivateKey if certificate is not exportable because if the service needs to obtain the
363392
// private key for authentication, and we can't access private key(which is not exportable), we will use
364393
// the Azure Key Vault Secrets API to obtain the private key (keyless).
365-
LOGGER.exiting("KeyVaultClient", "getKey", null);
366-
367394
String keyType2 = keyType.contains("-HSM") ? keyType.substring(0, keyType.indexOf("-HSM")) : keyType;
368395

369-
return Optional.ofNullable(certificateBundle)
396+
KeyVaultPrivateKey key = Optional.ofNullable(certificateBundle)
370397
.map(CertificateBundle::getKid)
371398
.map(kid -> new KeyVaultPrivateKey(keyType2, kid, this))
372399
.orElse(null);
400+
401+
LOGGER.exiting("KeyVaultClient", "getKey", key);
402+
403+
return key;
373404
}
374405

375406
String certificateSecretUri = certificateBundle.getSid();
@@ -394,8 +425,15 @@ public Key getKey(String alias, char[] password) {
394425
// If the certificate is exportable the private key is available, so we'll store the private key for
395426
// authentication instead of obtaining a digital signature through the API (without keyless).
396427
Key key = null;
397-
SecretBundle secretBundle = (SecretBundle) JsonConverterUtil.fromJson(body, SecretBundle.class);
398-
String contentType = secretBundle.getContentType();
428+
SecretBundle secretBundle = null;
429+
String contentType = null;
430+
431+
try {
432+
secretBundle = JsonConverterUtil.fromJson(SecretBundle::fromJson, body);
433+
contentType = secretBundle.getContentType();
434+
} catch (IOException e) {
435+
LOGGER.log(WARNING, "Failed to parse secret bundle response.", e);
436+
}
399437

400438
if ("application/x-pkcs12".equals(contentType)) {
401439
try {
@@ -414,14 +452,13 @@ public Key getKey(String alias, char[] password) {
414452
} else if ("application/x-pem-file".equals(contentType)) {
415453
try {
416454
key = createPrivateKeyFromPem(secretBundle.getValue(), keyType);
417-
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException | IllegalArgumentException ex) {
418-
LOGGER.log(WARNING, "Unable to decode key", ex);
455+
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException | IllegalArgumentException e) {
456+
LOGGER.log(WARNING, "Unable to decode key", e);
419457
}
420458
}
421459

422-
// If the private key is not available the certificate cannot be
423-
// used for server side certificates or mTLS. Then we do not know
424-
// the intent of the usage at this stage we skip this key.
460+
// If the private key is not available the certificate cannot be used for server side certificates or mTLS.
461+
// Then we do not know the intent of the usage at this stage we skip this key.
425462
LOGGER.exiting("KeyVaultClient", "getKey", key);
426463

427464
return key;
@@ -437,6 +474,8 @@ public Key getKey(String alias, char[] password) {
437474
* @return Signature.
438475
*/
439476
public byte[] getSignedWithPrivateKey(String digestName, String digestValue, String keyId) {
477+
LOGGER.entering("KeyVaultClient", "getSignedWithPrivateKey", new Object[] { digestName, digestValue, keyId });
478+
440479
SignResult result = null;
441480
String bodyString = String.format("{\"alg\": \"" + digestName + "\", \"value\": \"%s\"}", digestValue);
442481
Map<String, String> headers = new HashMap<>();
@@ -447,14 +486,24 @@ public byte[] getSignedWithPrivateKey(String digestName, String digestValue, Str
447486
String response = HttpUtil.post(uri, headers, bodyString, "application/json");
448487

449488
if (response != null) {
450-
result = (SignResult) JsonConverterUtil.fromJson(response, SignResult.class);
489+
try {
490+
result = JsonConverterUtil.fromJson(SignResult::fromJson, response);
491+
} catch (IOException e) {
492+
LOGGER.log(WARNING, "Failed to parse sign result response.", e);
493+
}
451494
}
452495

496+
byte[] signature;
497+
453498
if (result != null) {
454-
return Base64.getUrlDecoder().decode(result.getValue());
499+
signature = Base64.getUrlDecoder().decode(result.getValue());
500+
} else {
501+
signature = new byte[0];
455502
}
456503

457-
return new byte[0];
504+
LOGGER.exiting("KeyVaultClient", "getSignedWithPrivateKey", signature);
505+
506+
return signature;
458507
}
459508

460509
/**
@@ -472,6 +521,8 @@ public byte[] getSignedWithPrivateKey(String digestName, String digestValue, Str
472521
private PrivateKey createPrivateKeyFromPem(String pemString, String keyType)
473522
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
474523

524+
LOGGER.entering("KeyVaultClient", "createPrivateKeyFromPem", new Object[] { pemString, keyType });
525+
475526
StringBuilder builder = new StringBuilder();
476527

477528
try (BufferedReader reader = new BufferedReader(new StringReader(pemString))) {
@@ -496,7 +547,10 @@ private PrivateKey createPrivateKeyFromPem(String pemString, String keyType)
496547
byte[] bytes = Base64.getDecoder().decode(builder.toString());
497548
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
498549
KeyFactory factory = KeyFactory.getInstance(keyType);
550+
PrivateKey privateKey = factory.generatePrivate(spec);
551+
552+
LOGGER.exiting("KeyVaultClient", "createPrivateKeyFromPem", privateKey);
499553

500-
return factory.generatePrivate(spec);
554+
return privateKey;
501555
}
502556
}

0 commit comments

Comments
 (0)