Reputation: 513
In a application I have developed, I check the certificate sended by a client in an incoming message : in addition of basic certificate validity check (certificate signature, expiry date ...), I check if the client is trusted.
For this, I've created a keystore containing only trusted certificates : if a received certificate is not in the list, i reject the incoming message. A client certificate is in a certificate path, I check all the certification path.
For certification path validation, i'm using the following algo : (wikipedia)
1) Starting from the client certificate
2) While the current certificate is not the root :
3) Searching a parent certificate in the keystore : for this, i search a certificate in the keystore where SubjectDN = IssuerDN of the current certificate. If not found, the tested certificate is not valid
4) The signature of the current certificate is checked by using the public key of the parent certificate
So, the entire path is validated
Here the full code of the validator : (thanks to the author of this article)
Please note, that here, the revocation list (CRL) check is disabled.
public class CertificateChainValidator {
private static Logger logger = Logger.getLogger(CertificateChainValidator.class);
/**
* path of the keystore.
*/
private static final String TRUSTED_KEYSTORE = "/trusted.jks";
/**
* password for opening the keystore.
*/
private static final char[] TRUSTED_KEYSTORE_PWD = new char[] { '$' , '$' , '$','$','$','$','$'};
/**
* the keystore loaded.
*/
private static KeyStore trustedKeystore;
/**
* Validate keychain
*
* @param client
* is the client X509Certificate
* @param keyStore
* containing all trusted certificate
* @return true if validation until root certificate success, false
* otherwise
* @throws KeyStoreException
* @throws CertificateException
* @throws InvalidAlgorithmParameterException
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
*/
public static boolean validateKeyChain(final X509Certificate client, final KeyStore keyStore)
throws KeyStoreException, CertificateException, InvalidAlgorithmParameterException,
NoSuchAlgorithmException, NoSuchProviderException {
X509Certificate[] certs = new X509Certificate[keyStore.size()];
int i = 0;
Enumeration<String> alias = keyStore.aliases();
while (alias.hasMoreElements()) {
certs[i++] = (X509Certificate) keyStore.getCertificate(alias.nextElement());
}
return validateKeyChain(client, certs);
}
/**
* Validate keychain
*
* @param client
* is the client X509Certificate
* @param trustedCerts
* is Array containing all trusted X509Certificate
* @return true if validation until root certificate success, false
* otherwise
* @throws CertificateException
* @throws InvalidAlgorithmParameterException
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
*/
@SuppressWarnings("unchecked")
private static boolean validateKeyChain(final X509Certificate client, final X509Certificate... trustedCerts)
throws CertificateException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchProviderException {
boolean found = false;
int i = trustedCerts.length;
CertificateFactory cf = CertificateFactory.getInstance("X.509");
TrustAnchor anchor;
@SuppressWarnings("rawtypes")
Set anchors;
CertPath path;
@SuppressWarnings("rawtypes")
List list;
PKIXParameters params;
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
while (!found && i > 0) {
anchor = new TrustAnchor(trustedCerts[--i], null);
anchors = Collections.singleton(anchor);
list = Arrays.asList(new Certificate[] { client });
path = cf.generateCertPath(list);
params = new PKIXParameters(anchors);
params.setRevocationEnabled(false);
if (logger.isDebugEnabled()) {
logger.debug(String.format("Test certificate chain : client=%s IssuerDN=%s", client.getIssuerDN(),
trustedCerts[i].getSubjectDN()));
}
if (client.getIssuerDN().equals(trustedCerts[i].getSubjectDN())) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Validation certificate : IssuerDN=%s", trustedCerts[i].getSubjectDN()));
}
try {
validator.validate(path, params);
if (isSelfSigned(trustedCerts[i])) {
// found root ca
if (logger.isDebugEnabled()) {
logger.debug(String.format("certificate root validated : IssuerDN=%s",
trustedCerts[i].getSubjectDN()));
}
found = true;
} else if (!client.equals(trustedCerts[i])) {
// find parent ca
if (logger.isDebugEnabled()) {
logger.debug(String.format("certificate intermerdiart validated : IssuerDN=%s",
trustedCerts[i].getSubjectDN()));
}
found = validateKeyChain(trustedCerts[i], trustedCerts);
}
} catch (CertPathValidatorException e) {
// validation fail, check next certifiacet in the
// trustedCerts array
}
}
}
return found;
}
/**
*
* @param cert
* is X509Certificate that will be tested
* @return true if cert is self signed, false otherwise
* @throws CertificateException
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
*/
private static boolean isSelfSigned(final X509Certificate cert)
throws CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
try {
PublicKey key = cert.getPublicKey();
cert.verify(key);
return true;
} catch (SignatureException sigEx) {
return false;
} catch (InvalidKeyException keyEx) {
return false;
}
}
/**
* donne le trusted keystore (le charge une seule fois).
*
* @return
* @throws CertificateIsabelSignatureVerificationException
*/
private synchronized static KeyStore getTrustedKeystore() throws CertificateIsabelSignatureVerificationException {
if (trustedKeystore == null) {
trustedKeystore = loadKeystore(TRUSTED_KEYSTORE, TRUSTED_KEYSTORE_PWD);
}
return trustedKeystore;
}
/**
* chargement du keystore spécifié.
*
* @param path
* chemin du keystore, dans le classpath.
* @return le keystore chargé.
* @throws CertificateIsabelSignatureVerificationException
*/
private static KeyStore loadKeystore(final String path, final char[] passsword)
throws CertificateIsabelSignatureVerificationException {
try {
if (logger.isDebugEnabled()) {
logger.debug(String.format("loading keystore %s ...", path));
}
KeyStore ks = KeyStore.getInstance("JKS");
ks.load( //
CertificateChainValidator.class.getResourceAsStream(path) //
, passsword //
);
return ks;
} catch (Exception ex) {
throw new CertificateIsabelSignatureVerificationException(
String.format("Echec lecture du keystore %s", path), ex);
}
}
/**
* Valide le certificat client : s'assure qu'il est valide et vérifie qu'il
* est bien associé à un certifact root connu (dans trusted.jks).
*
* @param certEncoded
* @throws CertificateIsabelSignatureVerificationException
*/
public static void validateClientCertificate(final byte[] certEncoded)
throws CertificateIsabelSignatureVerificationException {
try {
// le keystore contenant tous les certificats reconnus
KeyStore ks = getTrustedKeystore();
// récupère le certificat client
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
X509Certificate certificat = (X509Certificate) certFactory
.generateCertificate(new ByteArrayInputStream(certEncoded));
if (logger.isDebugEnabled()) {
logger.debug(String.format("certificate validation : IssuerDN=%s", certificat.getSubjectDN()));
}
boolean valid = validateKeyChain(certificat, ks);
if (logger.isDebugEnabled()) {
logger.debug(String.format("certificate valid : %s", valid));
}
if (!valid) {
throw new CertificateIsabelSignatureVerificationException("The certificate is not valid");
}
} catch (CertificateIsabelSignatureVerificationException ex) {
throw ex;
} catch (Exception ex) {
throw new CertificateIsabelSignatureVerificationException("Error during validation", ex);
}
}
}
Upvotes: 0
Views: 3359
Reputation: 39271
You can not compare the issuerDN because anyone could create a certificate with that string.
Every certificate has been digitally signed with the private key of the issuer, so you need to verify the signature of the client certificate with the public key of the existing certificates in your truststore. if there is a match, then your certificate is "trusted", but continues with the next one in the certification chain.
Note: i did not check your code. You may want to take a look to the proposed links
Upvotes: 2