Skip to content

Commit

Permalink
Support intermediate certs in keyvault jca (Azure#41303)
Browse files Browse the repository at this point in the history
  • Loading branch information
rujche authored and jairmyree committed Sep 23, 2024
1 parent 15f65c6 commit 906d510
Show file tree
Hide file tree
Showing 26 changed files with 665 additions and 74 deletions.
1 change: 1 addition & 0 deletions sdk/keyvault/azure-security-keyvault-jca/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!src/test/resources/certificate-util/**/*.pfx
2 changes: 2 additions & 0 deletions sdk/keyvault/azure-security-keyvault-jca/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Bugs Fixed

- Fixed bug about intermediate certificate not loaded. [#39715](https://github.com/Azure/azure-sdk-for-java/issues/39715).

### Other Changes

## 2.9.0-beta.2 (2024-07-09)
Expand Down
21 changes: 21 additions & 0 deletions sdk/keyvault/azure-security-keyvault-jca/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
</properties>

<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-lts8on</artifactId>
<version>2.73.6</version> <!-- {x-version-update;org.bouncycastle:bcpkix-lts8on;external_dependency} -->
</dependency>
<!-- Apache HttpClient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
Expand Down Expand Up @@ -135,6 +140,17 @@
<shadeTestJar>false</shadeTestJar>
<createSourcesJar>${createSourcesJar}</createSourcesJar>
<shadeSourcesContent>true</shadeSourcesContent>
<filters>
<filter>
<artifact>org.bouncycastle:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
<exclude>META-INF/services/java.security.Provider</exclude>
</excludes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>com.fasterxml.jackson</pattern>
Expand Down Expand Up @@ -163,6 +179,10 @@
<pattern>org.slf4j</pattern>
<shadedPattern>com.azure.security.keyvault.jca.implementation.shaded.org.slf4j</shadedPattern>
</relocation>
<relocation>
<pattern>org.bouncycastle</pattern>
<shadedPattern>com.azure.security.keyvault.jca.implementation.shaded.org.bouncycastle</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
Expand Down Expand Up @@ -221,6 +241,7 @@
<rules>
<bannedDependencies>
<includes>
<include>org.bouncycastle:bcpkix-lts8on:[2.73.6]</include> <!-- {x-include-update;org.bouncycastle:bcpkix-lts8on;external_dependency} -->
<include>com.fasterxml.jackson.core:jackson-databind:[2.17.2]</include> <!-- {x-include-update;com.fasterxml.jackson.core:jackson-databind;external_dependency} -->
<include>org.conscrypt:conscrypt-openjdk-uber:[2.5.2]</include> <!-- {x-include-update;org.conscrypt:conscrypt-openjdk-uber;external_dependency} -->
<include>org.apache.httpcomponents:httpclient:[4.5.14]</include> <!-- {x-include-update;org.apache.httpcomponents:httpclient;external_dependency} -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public final class KeyVaultKeyStore extends KeyStoreSpi {
private final boolean refreshCertificatesWhenHaveUnTrustCertificate;

/**
* Store the path where the well know certificate is placed
* Store the path where the well-known certificate is placed
*/
final String wellKnowPath = Optional.ofNullable(System.getProperty("azure.cert-path.well-known"))
.orElse("/etc/certs/well-known/");
Expand Down Expand Up @@ -279,16 +279,12 @@ public Certificate engineGetCertificate(String alias) {
@Override
public String engineGetCertificateAlias(Certificate cert) {
String alias = null;

if (cert != null) {
List<String> aliasList = getAllAliases();

for (String candidateAlias : aliasList) {
Certificate certificate = engineGetCertificate(candidateAlias);

if (certificate.equals(cert)) {
alias = candidateAlias;

break;
}
}
Expand All @@ -311,15 +307,18 @@ public String engineGetCertificateAlias(Certificate cert) {
*/
@Override
public Certificate[] engineGetCertificateChain(String alias) {
Certificate[] chain = null;
Certificate certificate = engineGetCertificate(alias);

if (certificate != null) {
chain = new Certificate[1];
chain[0] = certificate;
Certificate[] certificates = allCertificates.stream()
.map(AzureCertificates::getCertificateChains)
.filter(Objects::nonNull)
.filter(a -> a.containsKey(alias))
.findFirst()
.map(m -> m.get(alias))
.orElse(null);
if (refreshCertificatesWhenHaveUnTrustCertificate && certificates == null) {
keyVaultCertificates.refreshCertificates();
return keyVaultCertificates.getCertificateChains().get(alias);
}

return chain;
return certificates;
}

/**
Expand Down Expand Up @@ -450,10 +449,8 @@ private List<String> getAllAliases() {
public void engineSetCertificateEntry(String alias, Certificate certificate) {
if (getAllAliases().contains(alias)) {
LOGGER.log(WARNING, "Cannot overwrite own certificate");

return;
}

classpathCertificates.setCertificateEntry(alias, certificate);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.azure.security.keyvault.jca.implementation.utils.AccessTokenUtil;
import com.azure.security.keyvault.jca.implementation.utils.HttpUtil;
import com.azure.security.keyvault.jca.implementation.utils.JsonConverterUtil;
import org.bouncycastle.pkcs.PKCSException;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
Expand All @@ -24,6 +25,7 @@
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
Expand All @@ -41,6 +43,7 @@
import java.util.logging.Logger;

import static com.azure.security.keyvault.jca.implementation.utils.AccessTokenUtil.getLoginUri;
import static com.azure.security.keyvault.jca.implementation.utils.CertificateUtil.loadCertificatesFromSecretBundleValue;
import static com.azure.security.keyvault.jca.implementation.utils.HttpUtil.API_VERSION_POSTFIX;
import static com.azure.security.keyvault.jca.implementation.utils.HttpUtil.HTTPS_PREFIX;
import static com.azure.security.keyvault.jca.implementation.utils.HttpUtil.addTrailingSlashIfRequired;
Expand Down Expand Up @@ -302,6 +305,37 @@ public Certificate getCertificate(String alias) {
return certificate;
}

/**
* Get the certificate chain.
*
* @param alias The alias.
*
* @return The certificate chain, or null if not found.
*/
public Certificate[] getCertificateChain(String alias) {
LOGGER.entering("KeyVaultClient", "getCertificateChain", alias);
LOGGER.log(INFO, "Getting certificate chain for alias: {0}", alias);

HashMap<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + getAccessToken());
String uri = keyVaultUri + "secrets/" + alias + API_VERSION_POSTFIX;
String response = HttpUtil.get(uri, headers);
if (response == null) {
throw new NullPointerException();
}
SecretBundle secretBundle = (SecretBundle) JsonConverterUtil.fromJson(response, SecretBundle.class);

Certificate[] certificates = new Certificate[0];
try {
certificates = loadCertificatesFromSecretBundleValue(secretBundle.getValue());
} catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException
| NoSuchProviderException | PKCSException e) {
LOGGER.log(WARNING, "Unable to decode certificate chain", e);
}
LOGGER.exiting("KeyVaultClient", "getCertificate", alias);
return certificates;
}

/**
* Get the key.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ public interface AzureCertificates {
*/
Map<String, Certificate> getCertificates();

/**
* Get certificates.
* @return certificates
*/
Map<String, Certificate[]> getCertificateChains();

/**
* Get certificate keys.
* @return certificate keys
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,23 @@

package com.azure.security.keyvault.jca.implementation.certificates;

import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Logger;

import static com.azure.security.keyvault.jca.implementation.utils.CertificateUtil.loadX509CertificateFromFile;
import static com.azure.security.keyvault.jca.implementation.utils.CertificateUtil.loadX509CertificatesFromFile;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;

Expand All @@ -45,6 +43,11 @@ public final class ClasspathCertificates implements AzureCertificates {
*/
private final Map<String, Certificate> certificates = new HashMap<>();

/**
* Stores the certificate chains by alias.
*/
private final Map<String, Certificate[]> certificateChains = new HashMap<>();

/**
* Stores the certificate keys by alias.
*/
Expand All @@ -68,6 +71,15 @@ public Map<String, Certificate> getCertificates() {
return certificates;
}

/**
* Get certificate chains.
* @return certificate chains
*/
@Override
public Map<String, Certificate[]> getCertificateChains() {
return certificateChains;
}

/**
* Get certificate keys.
* @return certificate keys
Expand All @@ -86,6 +98,7 @@ public void setCertificateEntry(String alias, Certificate certificate) {
if (!aliases.contains(alias)) {
aliases.add(alias);
certificates.put(alias, certificate);
certificateChains.put(alias, new Certificate[]{certificate});
}
}

Expand All @@ -98,6 +111,7 @@ public void deleteEntry(String alias) {
aliases.remove(alias);
certificates.remove(alias);
certificateKeys.remove(alias);
certificateChains.remove(alias);
}

/**
Expand All @@ -113,12 +127,10 @@ public void loadCertificatesFromClasspath() {
if (alias.lastIndexOf('.') != -1) {
alias = alias.substring(0, alias.lastIndexOf('.'));
}
byte[] bytes = readAllBytes(inputStream);
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) cf.generateCertificate(
new ByteArrayInputStream(bytes));
Certificate certificate = loadX509CertificateFromFile(inputStream);
setCertificateEntry(alias, certificate);
certificateChains.put(alias, loadX509CertificatesFromFile(inputStream));
LOGGER.log(INFO, "Side loaded certificate: {0} from: {1}",
new Object[]{alias, filename});
} catch (CertificateException e) {
Expand All @@ -132,30 +144,6 @@ public void loadCertificatesFromClasspath() {
}
}


/**
* Read all the bytes for a given input stream.
*
* @param inputStream the input stream.
* @return the byte-array.
* @throws IOException when an I/O error occurs.
*/
private byte[] readAllBytes(InputStream inputStream) throws IOException {
byte[] bytes;
try (ByteArrayOutputStream byteOutput = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
while (true) {
int r = inputStream.read(buffer);
if (r == -1) {
break;
}
byteOutput.write(buffer, 0, r);
}
bytes = byteOutput.toByteArray();
}
return bytes;
}

/**
* Get the filenames.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
package com.azure.security.keyvault.jca.implementation.certificates;

import com.azure.security.keyvault.jca.implementation.JreKeyStoreFactory;

import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.HashMap;
import java.util.logging.Logger;

import static java.util.logging.Level.WARNING;

/**
Expand All @@ -35,6 +37,11 @@ public final class JreCertificates implements AzureCertificates {
*/
private final Map<String, Certificate> certs;

/**
* Stores the certificate chains by alias.
*/
private final Map<String, Certificate[]> certificateChains;

/**
* Stores the jre key store keys
*/
Expand Down Expand Up @@ -71,6 +78,17 @@ private JreCertificates() {
}
},
HashMap::putAll);
certificateChains = aliases.stream()
.collect(
HashMap::new,
(m, v) -> {
try {
m.put(v, jreKeyStore.getCertificateChain(v));
} catch (KeyStoreException e) {
LOGGER.log(WARNING, "Unable to get the jre key store certificate.", e);
}
},
HashMap::putAll);
keys = Collections.emptyMap();
}

Expand All @@ -93,6 +111,11 @@ public Map<String, Certificate> getCertificates() {
return certs;
}

@Override
public Map<String, Certificate[]> getCertificateChains() {
return certificateChains;
}

@Override
public Map<String, Key> getCertificateKeys() {
return keys;
Expand Down
Loading

0 comments on commit 906d510

Please sign in to comment.