Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@
*/
public class CertificateHelpers
{
public enum PEMContentsType {PRIVATE_KEY, LEGACY_OPENSSL_PRIVATE_KEY,
public enum PEMContentsType {PRIVATE_KEY, LEGACY_OPENSSL_PRIVATE_KEY,
CERTIFICATE, CSR, CRL, UNKNOWN};

private static final byte[] TEST = new byte[] {1, 2, 3, 4, 100};

/**
* Assumes that the input is the contents of the PEM identification line,
* after '-----BEGIN ' prefix.
*
*
* @param name PEM first line to be checked.
* @return the type
*/
Expand All @@ -71,7 +71,7 @@ public static PEMContentsType getPEMType(String name)
return PEMContentsType.UNKNOWN;
}


public static Collection<? extends Certificate> readDERCertificates(InputStream input) throws IOException
{
CertificateFactory factory = getFactory();
Expand Down Expand Up @@ -109,7 +109,7 @@ public static Certificate readDERCertificate(InputStream input) throws IOExcepti
input.close();
}
}

private static CertificateFactory getFactory()
{
try
Expand All @@ -125,9 +125,9 @@ private static CertificateFactory getFactory()
"no BouncyCastle provider, it is a BUG!", e);
}
}

/**
* Creates a chain of certificates, where the top-most certificate (the one without
* Creates a chain of certificates, where the top-most certificate (the one without
* issuing certificate) is the last in the returned array.
* @param certificates unsorted certificates of one chain
* @return sorted certificate chain
Expand All @@ -137,11 +137,11 @@ public static X509Certificate[] sortChain(List<X509Certificate> certificates) th
{
if (certificates.size() == 0)
return new X509Certificate[0];

Map<X500Principal, X509Certificate> certsMapBySubject = new HashMap<X500Principal, X509Certificate>();
//in this map root CA cert is not stored (as it has the same Issuer as its direct child)
Map<X500Principal, X509Certificate> certsMapByIssuer = new HashMap<X500Principal, X509Certificate>();
for (X509Certificate c: certificates)
for (X509Certificate c: certificates)
{
certsMapBySubject.put(c.getSubjectX500Principal(), c);
if (!c.getIssuerX500Principal().equals(c.getSubjectX500Principal()))
Expand All @@ -167,7 +167,7 @@ public static X509Certificate[] sortChain(List<X509Certificate> certificates) th
} else
break;
}

//build path from the first on the list down to the user's certificate
current = certsList.get(0);
while (true)
Expand All @@ -180,13 +180,13 @@ public static X509Certificate[] sortChain(List<X509Certificate> certificates) th
} else
break;
}

if (certsMapByIssuer.size() > 0)
throw new IOException("The keystore is inconsistent as it contains certificates from different chains");

return certsList.toArray(new X509Certificate[certsList.size()]);
}

/**
* Converts certificates array to {@link CertPath}
* @param in array
Expand All @@ -206,7 +206,7 @@ public static CertPath toCertPath(X509Certificate[] in) throws CertificateExcept
}
return certFactory.generateCertPath(Arrays.asList(in));
}

/**
* Converts {@link X500Principal} to {@link X500Name} with the {@link JavaAndBCStyle}
* style.
Expand All @@ -223,7 +223,7 @@ public static X500Name toX500Name(X500Principal srcDn)
/**
* Gets the certificate extension identified by the oid and returns the
* value bytes unwrapped by the ASN1OctetString.
*
*
* @param cert
* The certificate to inspect.
* @param oid
Expand All @@ -243,7 +243,7 @@ public static byte[] getExtensionBytes(X509Certificate cert, String oid)
.fromByteArray(bytes);
return valueOctets.getOctets();
}

/**
* Throws an exception if the private key is not matching the public key.
* The check is done only for known types of keys - RSA and DSA currently.
Expand All @@ -253,37 +253,42 @@ public static byte[] getExtensionBytes(X509Certificate cert, String oid)
*/
public static void checkKeysMatching(PrivateKey privKey, PublicKey pubKey) throws InvalidKeyException
{
String algorithm = pubKey.getAlgorithm();
String privAlg = privKey.getAlgorithm();
String pubAlg = pubKey.getAlgorithm();

// "EC" and "ECDSA" are both used for elliptic-curve keys depending on the
// provider and BouncyCastle version — treat them as equivalent.
boolean privIsEC = privAlg.equals("EC") || privAlg.equals("ECDSA");
boolean pubIsEC = pubAlg.equals("EC") || pubAlg.equals("ECDSA");

// REVISIT: BouncyCastle uses "ECDSA" as the private key algorithm and "EC" as the public key algorithm names for elliptic curve keys.
if (!privKey.getAlgorithm().equals(algorithm) && !(privKey.getAlgorithm().equals("ECDSA") && algorithm.equals("EC")))
if (!privAlg.equals(pubAlg) && !(privIsEC && pubIsEC))
throw new InvalidKeyException("Private and public keys are not matching: different algorithms");
if (algorithm.equals("DSA"))

if (pubAlg.equals("DSA"))
{
if (!checkKeysViaSignature("SHA1withDSA", privKey, pubKey))
throw new InvalidKeyException("Private and public keys are not matching: DSA");
} else if (algorithm.equals("RSA"))
} else if (pubAlg.equals("RSA"))
{
RSAPublicKey rpub = (RSAPublicKey)pubKey;
RSAPrivateKey rpriv = (RSAPrivateKey)privKey;
if (!rpub.getModulus().equals(rpriv.getModulus()))
throw new InvalidKeyException("Private and public keys are not matching: RSA parameters");
} else if (algorithm.equals("GOST3410"))
} else if (pubAlg.equals("GOST3410"))
{
if (!checkKeysViaSignature("GOST3411withGOST3410", privKey, pubKey))
throw new InvalidKeyException("Private and public keys are not matching: GOST 34.10");
} else if (algorithm.equals("ECGOST3410"))
} else if (pubAlg.equals("ECGOST3410"))
{
if (!checkKeysViaSignature("GOST3411withECGOST3410", privKey, pubKey))
throw new InvalidKeyException("Private and public keys are not matching: EC GOST 34.10");
} else if (algorithm.equals("ECDSA"))
} else if (privIsEC && pubIsEC)
{
if (!checkKeysViaSignature("SHA1withECDSA", privKey, pubKey))
if (!checkKeysViaSignature("SHA256withECDSA", privKey, pubKey))
throw new InvalidKeyException("Private and public keys are not matching: EC DSA");
}
}

private static boolean checkKeysViaSignature(String alg, PrivateKey privKey, PublicKey pubKey) throws InvalidKeyException
{
try
Expand All @@ -305,8 +310,3 @@ private static boolean checkKeysViaSignature(String alg, PrivateKey privKey, Pub
}
}
}





90 changes: 90 additions & 0 deletions src/test/java/ServerKeyPairValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import eu.emi.security.authn.x509.CrlCheckingMode;
import eu.emi.security.authn.x509.NamespaceCheckingMode;
import eu.emi.security.authn.x509.OCSPCheckingMode;
import eu.emi.security.authn.x509.OCSPParametes;
import eu.emi.security.authn.x509.ProxySupport;
import eu.emi.security.authn.x509.RevocationParameters;
import eu.emi.security.authn.x509.ValidationError;
import eu.emi.security.authn.x509.ValidationResult;
import eu.emi.security.authn.x509.X509CertChainValidator;
import eu.emi.security.authn.x509.impl.OpensslCertChainValidator;
import eu.emi.security.authn.x509.impl.PEMCredential;
import eu.emi.security.authn.x509.impl.ValidatorParams;

import java.security.cert.X509Certificate;

/**
* Validates that a server private key and certificate are:
* 1. Matching (key pair is consistent)
* 2. Trusted (chain validates against a given truststore)
*
* Usage:
* java ServerKeyPairValidator \
* /path/to/server.key \
* /path/to/server.crt \
* /path/to/custom/certifificates
*/
public class ServerKeyPairValidator {

public static void main(String[] args) throws Exception {

if (args.length < 2) {
System.err.println("Usage: ServerKeyPairValidator <keyPath> <certPath> [caDir]");
System.exit(1);
}

String keyPath = args[0];
String certPath = args[1];
String caDir = args.length >= 3 ? args[2] : "/etc/grid-security/certificates";

// 1. Load credential — also verifies key matches certificate internally
PEMCredential credential;
try {
credential = new PEMCredential(keyPath, certPath, (char[]) null);
} catch (Exception e) {
System.out.println("FAILED: Cannot load key/certificate pair.");
System.out.println(" Reason: " + e.getMessage());
System.exit(2);
return;
}

X509Certificate[] chain = credential.getCertificateChain();
System.out.println("Loaded certificate for: " + credential.getSubjectName());
System.out.println("Chain length: " + chain.length);

long trustAnchorRefreshInterval = 3_600_000;

NamespaceCheckingMode namespaceMode =
NamespaceCheckingMode.valueOf("IGNORE");
CrlCheckingMode crlCheckingMode =
CrlCheckingMode.valueOf("REQUIRE");
OCSPCheckingMode ocspCheckingMode =
OCSPCheckingMode.valueOf("REQUIRE");
ValidatorParams validatorParams =
new ValidatorParams(new RevocationParameters(crlCheckingMode,
new OCSPParametes(ocspCheckingMode)),
ProxySupport.ALLOW);
X509CertChainValidator validator = new OpensslCertChainValidator(caDir,
false,
namespaceMode,
trustAnchorRefreshInterval,
validatorParams,
false);


// 3. Validate certificate chain against truststore
ValidationResult result = validator.validate(chain);

if (result.isValid()) {
System.out.println("SUCCESS: Key/certificate pair is valid and trusted.");
} else {
System.out.println("FAILED: Certificate validation errors:");
for (ValidationError error : result.getErrors()) {
System.out.println(" [" + error.getErrorCode() + "] " + error.getMessage());
}
System.exit(3);
}

((OpensslCertChainValidator) validator).dispose();
}
}