Skip to content

Commit

Permalink
fix: restore OCSP responses to work with sha1 cert hashes for backwar…
Browse files Browse the repository at this point in the history
…ds compatibility

Refs: XRDDEV-445
  • Loading branch information
andresrosenthal committed Dec 7, 2023
1 parent f12cc09 commit 3438edd
Show file tree
Hide file tree
Showing 17 changed files with 189 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,8 @@ public static ConfigurationPartMetadata getMetadata(Path fileName) throws IOExce
}

public static boolean isCurrentVersion(Path filePath) {
Integer confVersion = getVersion(filePath);
return confVersion != null && confVersion == CURRENT_GLOBAL_CONFIGURATION_VERSION;
Integer confVersion = getVersion(filePath);
return confVersion != null && confVersion == CURRENT_GLOBAL_CONFIGURATION_VERSION;
}

public static Integer getVersion(Path filePath) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,23 @@

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.URIBuilder;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.parser.AbstractContentHandler;
import org.apache.james.mime4j.parser.MimeStreamParser;
import org.apache.james.mime4j.stream.BodyDescriptor;
import org.apache.james.mime4j.stream.MimeConfig;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.operator.OperatorCreationException;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -54,7 +57,8 @@
public final class CertHashBasedOcspResponderClient {

private static final String METHOD = "GET";
private static final String CERT_PARAM = "cert";
private static final String SHA_1_CERT_PARAM = "cert";
private static final String SHA_256_CERT_PARAM = "cert_hash";

private static final List<Integer> VALID_RESPONSE_CODES = Arrays.asList(
200, 201, 202, 203, 204, 205, 206, 207, 208, 226);
Expand All @@ -66,17 +70,13 @@ private CertHashBasedOcspResponderClient() {
* Creates an GET request to the internal cert hash based OCSP responder and expects an OCSP responses.
*
* @param providerAddress URL of the OCSP response provider
* @param hashes certificate hashes for which to get the responses
* @param certificates certificates for which to get the responses
* @return list of OCSP response objects
* @throws IOException if I/O errors occurred
* @throws OCSPException if the response could not be parsed
* @throws Exception if I/O errors occurred
*/
public static List<OCSPResp> getOcspResponsesFromServer(String providerAddress, String[] hashes)
throws IOException, OCSPException {
URL url = createUrl(providerAddress, hashes);

log.debug("Getting OCSP responses for hashes ({}) from: {}", Arrays.toString(hashes), url.getHost());

public static List<OCSPResp> getOcspResponsesFromServer(String providerAddress, List<X509Certificate> certificates)
throws CertificateEncodingException, URISyntaxException, IOException, OperatorCreationException, OCSPException {
URL url = createUrl(providerAddress, certificates);
return getOcspResponsesFromServer(url);
}

Expand Down Expand Up @@ -132,8 +132,19 @@ public void body(BodyDescriptor bd, InputStream is) throws MimeException, IOExce
return responses;
}

private static URL createUrl(String providerAddress, String[] hashes) throws MalformedURLException {
return new URL("http", providerAddress, SystemProperties.getOcspResponderPort(), "/?" + CERT_PARAM + "="
+ StringUtils.join(hashes, "&" + CERT_PARAM + "="));
private static URL createUrl(String providerAddress, List<X509Certificate> certificates)
throws URISyntaxException, IOException, CertificateEncodingException, OperatorCreationException {
String[] sha1Hashes = CertUtils.getSha1Hashes(certificates);
String[] sha256Hashes = CertUtils.getHashes(certificates);

var uriBuilder = new URIBuilder().setScheme("http").setHost(providerAddress).setPort(SystemProperties.getOcspResponderPort());
// TODO sha1 hashes should not be added to the request once 7.3.x is no longer supported
Arrays.stream(sha1Hashes).forEach(hash -> uriBuilder.addParameter(SHA_1_CERT_PARAM, hash));
Arrays.stream(sha256Hashes).forEach(hash -> uriBuilder.addParameter(SHA_256_CERT_PARAM, hash));
var uri = uriBuilder.build();

log.debug("Getting OCSP responses for hashes ({}) from: {}", Arrays.toString(sha1Hashes), uri.getHost());

return uri.toURL();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
Expand All @@ -90,6 +91,7 @@

import static ee.ria.xroad.common.ErrorCodes.X_INTERNAL_ERROR;
import static ee.ria.xroad.common.util.CryptoUtils.calculateCertHexHash;
import static ee.ria.xroad.common.util.CryptoUtils.calculateCertSha1HexHash;
import static ee.ria.xroad.common.util.CryptoUtils.toDERObject;

/**
Expand Down Expand Up @@ -329,11 +331,35 @@ public static String getOcspResponderUriFromCert(X509Certificate subject) throws
}

/**
* @deprecated This method should be applicable until 7.3.x is no longer supported
* <p> From that point onward its usages should be replaced with {@link #getHashes(List)} instead.
* @param certs list of certificates
* @return list of certificate hashes for given list of certificates.
* @throws Exception in case of any errors
* @return list of certificate SHA-1 hashes for given list of certificates.
* @throws CertificateEncodingException if a certificate encoding error occurs
* @throws OperatorCreationException if digest calculator cannot be created
* @throws IOException if an I/O error occurred
*/
@Deprecated
public static String[] getSha1Hashes(List<X509Certificate> certs)
throws CertificateEncodingException, IOException, OperatorCreationException {
String[] certHashes = new String[certs.size()];

for (int i = 0; i < certs.size(); i++) {
certHashes[i] = calculateCertSha1HexHash(certs.get(i));
}

return certHashes;
}

/**
* @param certs list of certificates
* @return list of certificate SHA-256 hashes for given list of certificates.
* @throws CertificateEncodingException if a certificate encoding error occurs
* @throws OperatorCreationException if digest calculator cannot be created
* @throws IOException if an I/O error occurred
*/
public static String[] getCertHashes(List<X509Certificate> certs) throws Exception {
public static String[] getHashes(List<X509Certificate> certs)
throws CertificateEncodingException, IOException, OperatorCreationException {
String[] certHashes = new String[certs.size()];

for (int i = 0; i < certs.size(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,10 +597,12 @@ public static Collection<X509Certificate> readCertificates(
* Calculates digest of the certificate and encodes it as lowercase hex.
* @param cert the certificate
* @return calculated certificate hex hash String
* @throws Exception if any errors occur
* @throws CertificateEncodingException if a certificate encoding error occurs
* @throws OperatorCreationException if digest calculator cannot be created
* @throws IOException if an I/O error occurred
*/
public static String calculateCertHexHash(X509Certificate cert)
throws Exception {
throws CertificateEncodingException, IOException, OperatorCreationException {
return calculateCertHexHash(cert.getEncoded());
}

Expand All @@ -609,9 +611,12 @@ public static String calculateCertHexHash(X509Certificate cert)
* @param cert the certificate
* @param delimiter the delimiter to use
* @return calculated certificate hex hash String
* @throws Exception if any errors occur
* @throws CertificateEncodingException if a certificate encoding error occurs
* @throws OperatorCreationException if digest calculator cannot be created
* @throws IOException if an I/O error occurred
*/
public static String calculateDelimitedCertHexHash(X509Certificate cert, String delimiter) throws Exception {
public static String calculateDelimitedCertHexHash(X509Certificate cert, String delimiter)
throws CertificateEncodingException, IOException, OperatorCreationException {
return String.join(delimiter, Splitter.fixedLength(2).split(calculateCertHexHash(cert).toUpperCase()));
}

Expand All @@ -620,13 +625,45 @@ public static String calculateDelimitedCertHexHash(X509Certificate cert, String
* as lowercase hex.
* @return calculated certificate hex hash String
* @param bytes the bytes
* @throws Exception if any errors occur
* @throws OperatorCreationException if digest calculator cannot be created
* @throws IOException if an I/O error occurred
*/
public static String calculateCertHexHash(byte[] bytes)
throws Exception {
public static String calculateCertHexHash(byte[] bytes) throws IOException, OperatorCreationException {
return hexDigest(DEFAULT_CERT_HASH_ALGORITHM_ID, bytes);
}

/**
* Calculates a sha-1 digest of the given bytes and encodes it
* as lowercase hex.
* @deprecated This method should be applicable until 7.3.x is no longer supported
* <p> From that point onward its usages should be replaced with {@link #calculateCertHexHash(X509Certificate)} instead.
* @return calculated certificate hex hash String
* @param cert the certificate
* @throws OperatorCreationException if digest calculator cannot be created
* @throws CertificateEncodingException if a certificate encoding error occurs
* @throws IOException if an I/O error occurred
*/
@Deprecated
public static String calculateCertSha1HexHash(X509Certificate cert)
throws IOException, OperatorCreationException, CertificateEncodingException {
return calculateCertSha1HexHash(cert.getEncoded());
}

/**
* Calculates a sha-1 digest of the given bytes and encodes it
* as lowercase hex.
* @deprecated This method should be applicable until 7.3.x is no longer supported
* <p> From that point onward its usages should be replaced with {@link #calculateCertHexHash(byte[])} instead.
* @return calculated certificate hex hash String
* @param bytes the bytes
* @throws OperatorCreationException if digest calculator cannot be created
* @throws IOException if an I/O error occurred
*/
@Deprecated
public static String calculateCertSha1HexHash(byte[] bytes) throws IOException, OperatorCreationException {
return hexDigest(SHA1_ID, bytes);
}

/**
* Calculates a sha-256 digest of the given bytes and encodes it in
* format 92:62:34:C5:39:1B:95:1F:BF:AF:8D:D6:23:24:AE:56:83:DC...
Expand Down Expand Up @@ -663,7 +700,9 @@ public static String calculateAnchorHashDelimited(byte[] bytes) {
* Calculates a digest of the given certificate.
* @param cert the certificate
* @return digest byte array of the certificate
* @throws Exception if any errors occur
* @throws CertificateEncodingException if a certificate encoding error occurs
* @throws OperatorCreationException if digest calculator cannot be created
* @throws IOException if an I/O error occurred
*/
public static byte[] certHash(X509Certificate cert) throws CertificateEncodingException, IOException, OperatorCreationException {
return certHash(cert.getEncoded());
Expand All @@ -683,10 +722,13 @@ public static byte[] certHash(byte[] bytes) throws IOException, OperatorCreation
/**
* Calculates sha-1 digest of the given certificate bytes.
* @param bytes the bytes
* @deprecated This method should be applicable until 7.3.x is no longer supported
* <p> From that point onward its usages should be replaced with {@link #certHash(byte[])} instead.
* @return digest byte array of the certificate
* @throws OperatorCreationException if digest calculator cannot be created
* @throws IOException if an I/O error occurred
*/
@Deprecated
public static byte[] certSha1Hash(byte[] bytes) throws IOException, OperatorCreationException {
return calculateDigest(SHA1_ID, bytes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import ee.ria.xroad.common.cert.CertHelper;
import ee.ria.xroad.common.identifier.ClientId;
import ee.ria.xroad.common.identifier.ServiceId;
import ee.ria.xroad.common.util.CertUtils;
import ee.ria.xroad.proxy.conf.KeyConf;

import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -160,29 +159,26 @@ private static List<OCSPResp> getOcspResponses(
/**
* Sends the GET request with all cert hashes which need OCSP responses.
*/
private static List<OCSPResp> getAndCacheOcspResponses(
List<X509Certificate> hashes,
String address) throws Exception {
private static List<OCSPResp> getAndCacheOcspResponses(List<X509Certificate> certs, String address) throws Exception {
List<OCSPResp> receivedResponses;
try {
log.trace("get ocsp responses from server {}", address);
receivedResponses = getOcspResponsesFromServer(address,
CertUtils.getCertHashes(hashes));
receivedResponses = getOcspResponsesFromServer(address, certs);
} catch (Exception e) {
throw new CodedException(X_INTERNAL_ERROR, e);
}

// Did we get OCSP response for each cert hash?
if (receivedResponses.size() != hashes.size()) {
// Did we get OCSP response for each cert?
if (receivedResponses.size() != certs.size()) {
throw new CodedException(X_INTERNAL_ERROR,
"Could not get all OCSP responses from server "
+ "(expected %s, but got %s)",
hashes.size(), receivedResponses.size());
certs.size(), receivedResponses.size());
}

// Cache the responses locally
log.trace("got ocsp responses, setting them to key conf");
KeyConf.setOcspResponses(hashes, receivedResponses);
KeyConf.setOcspResponses(certs, receivedResponses);

return receivedResponses;
}
Expand Down
10 changes: 5 additions & 5 deletions src/proxy/src/main/java/ee/ria/xroad/proxy/conf/KeyConfImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
import java.util.List;

import static ee.ria.xroad.common.ErrorCodes.X_CANNOT_CREATE_SIGNATURE;
import static ee.ria.xroad.common.util.CertUtils.getCertHashes;
import static ee.ria.xroad.common.util.CryptoUtils.calculateCertHexHash;
import static ee.ria.xroad.common.util.CertUtils.getSha1Hashes;
import static ee.ria.xroad.common.util.CryptoUtils.calculateCertSha1HexHash;
import static ee.ria.xroad.common.util.CryptoUtils.decodeBase64;
import static ee.ria.xroad.common.util.CryptoUtils.encodeBase64;
import static ee.ria.xroad.common.util.CryptoUtils.loadPkcs12KeyStore;
Expand Down Expand Up @@ -108,7 +108,7 @@ public AuthKey getAuthKey() {

@Override
public OCSPResp getOcspResponse(X509Certificate cert) throws Exception {
return getOcspResponse(calculateCertHexHash(cert));
return getOcspResponse(calculateCertSha1HexHash(cert));
}

@Override
Expand All @@ -126,7 +126,7 @@ public OCSPResp getOcspResponse(String certHash) throws Exception {
@Override
public List<OCSPResp> getOcspResponses(List<X509Certificate> certs)
throws Exception {
String[] responses = SignerProxy.getOcspResponses(getCertHashes(certs));
String[] responses = SignerProxy.getOcspResponses(getSha1Hashes(certs));

List<OCSPResp> ocspResponses = new ArrayList<>();
for (String base64Encoded : responses) {
Expand All @@ -150,7 +150,7 @@ public void setOcspResponses(List<X509Certificate> certs,
encodeBase64(responses.get(i).getEncoded());
}

SignerProxy.setOcspResponses(getCertHashes(certs), base64EncodedResponses);
SignerProxy.setOcspResponses(getSha1Hashes(certs), base64EncodedResponses);
}

static SigningCtx createSigningCtx(ClientId subject, String keyId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public void join() throws InterruptedException {
}

private void doHandleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
String[] hashes = getCertHashes(request);
String[] hashes = getCertSha1Hashes(request);
List<OCSPResp> ocspResponses = getOcspResponses(hashes);

log.debug("Returning OCSP responses for cert hashes: " + Arrays.toString(hashes));
Expand Down Expand Up @@ -196,10 +196,10 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
}
}

private static List<OCSPResp> getOcspResponses(String[] hashes) throws Exception {
List<OCSPResp> ocspResponses = new ArrayList<>(hashes.length);
private static List<OCSPResp> getOcspResponses(String[] certHashes) throws Exception {
List<OCSPResp> ocspResponses = new ArrayList<>(certHashes.length);

for (String certHash : hashes) {
for (String certHash : certHashes) {
ocspResponses.add(getOcspResponse(certHash));
}

Expand All @@ -216,7 +216,8 @@ private static OCSPResp getOcspResponse(String certHash) throws Exception {
return ocsp;
}

private static String[] getCertHashes(HttpServletRequest request) throws Exception {
private static String[] getCertSha1Hashes(HttpServletRequest request) throws Exception {
// TODO sha256 cert hashes should be read from "cert_hash" param instead once 7.3.x is no longer supported
String[] paramValues = request.getParameterValues(CERT_PARAM);

if (paramValues.length < 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public List<ApprovedCaDto> getCertificateAuthorities(KeyUsageInfo keyUsageInfo,

String[] base64EncodedOcspResponses;
try {
String[] certHashes = CertUtils.getCertHashes(new ArrayList<>(filteredCerts));
String[] certHashes = CertUtils.getSha1Hashes(new ArrayList<>(filteredCerts));
base64EncodedOcspResponses = signerProxyFacade.getOcspResponses(certHashes);
} catch (Exception e) {
throw new InconsistentCaDataException("failed to get read CA OCSP responses", e);
Expand Down
Loading

0 comments on commit 3438edd

Please sign in to comment.