Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support intermediate certs in keyvault jca #41303

Merged
merged 15 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
}
rujche marked this conversation as resolved.
Show resolved Hide resolved
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