Skip to content

Commit

Permalink
Move stuff used by RevocationChecker to separate class
Browse files Browse the repository at this point in the history
This makes RevocationChecker more friendly for shading/copying into other libraries,
because we avoid it depending on the entire "user-facing" facade
DigipostSecurity, which has more dependencies, checks that BouncyCastle is available in its initializer.

DigipostSecurity is still the public API, but delegates to
the internal JavaSecurityUtils for the moved operations (resolving
certificate factory, and describing certificates)
  • Loading branch information
runeflobakk committed Apr 28, 2023
1 parent f7091bd commit 11ac235
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 41 deletions.
38 changes: 5 additions & 33 deletions src/main/java/no/digipost/security/DigipostSecurity.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package no.digipost.security;

import no.digipost.security.cert.CertificateNotFound;
import no.digipost.security.cert.internal.JavaSecurityUtils;
import no.digipost.security.keystore.KeyStoreBuilder;
import no.digipost.security.keystore.KeyStoreType;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
Expand All @@ -29,7 +30,6 @@
import java.io.InputStream;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.security.cert.CertPath;
import java.security.cert.Certificate;
Expand All @@ -40,10 +40,8 @@
import java.util.stream.Stream;

import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.StreamSupport.stream;
import static javax.security.auth.x500.X500Principal.RFC1779;


public final class DigipostSecurity {
Expand All @@ -61,7 +59,7 @@ public final class DigipostSecurity {
/**
* String denoting the certificate type {@value #X509}.
*/
public static final String X509 = "X.509";
public static final String X509 = JavaSecurityUtils.X509;


private static final Logger LOG = LoggerFactory.getLogger(DigipostSecurity.class);
Expand All @@ -71,13 +69,7 @@ public final class DigipostSecurity {
* Retrieve a {@link CertificateFactory} for X.509 certificates.
*/
public static CertificateFactory getX509CertificateFactory() {
try {
return CertificateFactory.getInstance(X509);
} catch (CertificateException e) {
throw new RuntimeException(
"Could not create " + X509 + " certificate factory: '" + e.getMessage() + "'. " +
"Available providers: " + Stream.of(Security.getProviders()).map(Provider::getName).collect(joining(", ")), e);
}
return JavaSecurityUtils.getX509CertificateFactory();
}

/**
Expand Down Expand Up @@ -222,15 +214,7 @@ public static CertPath asCertPath(Stream<X509Certificate> certificates) {
* @return the multiline description.
*/
public static String describe(CertPath certPath) {
if (certPath == null) {
return "(null)";
}
List<? extends Certificate> certificates = certPath.getCertificates();
if (!certificates.isEmpty()) {
return certificates.stream().map(DigipostSecurity::describe).collect(joining("\n ^-- Issued by: ", "CertPath with the following certificates:\nCertificate: ", ""));
} else {
return "CertPath with no certificates";
}
return JavaSecurityUtils.describe(certPath);
}

/**
Expand All @@ -240,19 +224,7 @@ public static String describe(CertPath certPath) {
* @return the description
*/
public static String describe(Certificate certificate) {
if (certificate == null) {
return "(null)";
}
if (certificate instanceof X509Certificate) {
X509Certificate x509 = (X509Certificate) certificate;
String subjectDescription = x509.getSubjectX500Principal().getName(RFC1779);
String validityDescription = "valid from " + x509.getNotBefore().toInstant() + " to " + x509.getNotAfter().toInstant();
String serialNumberDescription = "serial-number: " + x509.getSerialNumber().toString(16);
String issuerDescription = x509.getSubjectX500Principal().equals(x509.getIssuerX500Principal()) ? "self-issued" : "issuer: " + x509.getIssuerX500Principal().getName(RFC1779);
return String.join(", ", subjectDescription, validityDescription, serialNumberDescription, issuerDescription);
} else {
return certificate.getType() + "-certificate";
}
return JavaSecurityUtils.describe(certificate);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (C) Posten Norge AS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package no.digipost.security.cert.internal;


import java.security.Provider;
import java.security.Security;
import java.security.cert.CertPath;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.stream.Stream;

import static java.util.stream.Collectors.joining;
import static javax.security.auth.x500.X500Principal.RFC1779;

public final class JavaSecurityUtils {

/**
* String denoting the certificate type {@value #X509}.
*/
public static final String X509 = "X.509";


public static CertificateFactory getX509CertificateFactory() {
try {
return CertificateFactory.getInstance(X509);
} catch (CertificateException e) {
throw new RuntimeException(
"Could not create " + X509 + " certificate factory: '" + e.getMessage() + "'. " +
"Available providers: " + Stream.of(Security.getProviders()).map(Provider::getName).collect(joining(", ")), e);
}
}


public static String describe(CertPath certPath) {
if (certPath == null) {
return "(null)";
}
List<? extends Certificate> certificates = certPath.getCertificates();
if (!certificates.isEmpty()) {
return certificates.stream().map(JavaSecurityUtils::describe).collect(joining("\n ^-- Issued by: ", "CertPath with the following certificates:\nCertificate: ", ""));
} else {
return "CertPath with no certificates";
}
}


public static String describe(Certificate certificate) {
if (certificate == null) {
return "(null)";
}
if (certificate instanceof X509Certificate) {
X509Certificate x509 = (X509Certificate) certificate;
String subjectDescription = x509.getSubjectX500Principal().getName(RFC1779);
String validityDescription = "valid from " + x509.getNotBefore().toInstant() + " to " + x509.getNotAfter().toInstant();
String serialNumberDescription = "serial-number: " + x509.getSerialNumber().toString(16);
String issuerDescription = x509.getSubjectX500Principal().equals(x509.getIssuerX500Principal()) ? "self-issued" : "issuer: " + x509.getIssuerX500Principal().getName(RFC1779);
return String.join(", ", subjectDescription, validityDescription, serialNumberDescription, issuerDescription);
} else {
return certificate.getType() + "-certificate";
}
}


private JavaSecurityUtils() {
}
}
14 changes: 6 additions & 8 deletions src/main/java/no/digipost/security/crl/RevocationChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
package no.digipost.security.crl;

import no.digipost.security.DigipostSecurity;
import no.digipost.security.cert.internal.JavaSecurityUtils;
import org.apache.http.ssl.TrustStrategy;

import java.io.IOException;
Expand All @@ -28,7 +28,6 @@
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import static java.lang.String.format;

/**
* Used for configuring a HTTP Client to check if the server's certificate is revoked.
Expand All @@ -51,22 +50,21 @@ public class RevocationChecker implements TrustStrategy {
private final CRL crl;

public RevocationChecker(Path crlPath) {
CertificateFactory cf = DigipostSecurity.getX509CertificateFactory();
CertificateFactory cf = JavaSecurityUtils.getX509CertificateFactory();
try (InputStream inputStream = Files.newInputStream(crlPath)) {
this.crl = cf.generateCRL(inputStream);
} catch (CRLException | IOException e) {
throw new RuntimeException(format("Could not load CRL from path '%s'.", crlPath));
throw new RuntimeException("Could not load CRL from path '" + crlPath + "'.", e);
}
}

@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for(X509Certificate certificate : chain) {
if (crl.isRevoked(certificate)) {
throw new CertificateException(format("Certificate with serial number %s is revoked: %s",
certificate.getSerialNumber().toString(16),
DigipostSecurity.describe(certificate))
);
throw new CertificateException(
"Certificate with serial number " + certificate.getSerialNumber().toString(16) +
" is revoked: " + JavaSecurityUtils.describe(certificate));
}
}
return false;
Expand Down

0 comments on commit 11ac235

Please sign in to comment.