Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Security - X509 Certificate Verification with Public Key

I am working on a project has so much security operations. I have never struggled security issues before. Therefore, my question can be beginner level.

In my problem, I am getting a byte array data has a certificate and some other parameters. I need to verify this certificate and its signature. But I couldn't handle signature verification. In fact, I don't know which public key should I use to verify.

The code is below. Thanks for help..!

public boolean startValidation(PublicKey publicKey) {
    CertificateFactory cf;
    try {
         cf = CertificateFactory.getInstance("X.509");
    } catch (CertificateException e) {
        e.printStackTrace();
        return false;
    }
    try {
        certificate = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certBytes));
    } catch (CertificateException e) {
        e.printStackTrace();
        setCertError(0);
        return false;
    }
    if (!checkProvider()){
        setCertError(1);
        return false;
    }

    boolean[] usages = certificate.getKeyUsage();
    boolean usage = usages[0] && usages[2];
    if (!usage){
        setCertError(2);
    }

    try {
        certificate.checkValidity();
    } catch (CertificateNotYetValidException e) {
        e.printStackTrace();
        setCertError(3);
        return false;
    } catch (CertificateExpiredException e) {
        e.printStackTrace();
        setCertError(4);
        return false;
    }

    System.out.println("Sign Algorithm Name " + certificate.getSigAlgName());
    //Output is SHA256WithRSAEncryption

   // Problem area
    try {
        Signature signature = Signature.getInstance("SHA256WithRSA");
        signature.initVerify(publicKey); //Should I use this certificate.getPublicKey() or what ??
        if(signature.verify(certificate.getSignature())){
            System.out.println("Accepted");
        }else System.out.println("Failure");
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (SignatureException e) {
        e.printStackTrace();
    }

    System.out.println("Public Key Format " + publicKey.getFormat() + "\nPublic Key Algorithm " + publicKey.getAlgorithm() );
    System.out.println("Subject DN -> " + certificate.getSubjectDN().getName());


    return true;
}
like image 212
misman Avatar asked Dec 18 '22 22:12

misman


1 Answers

Typical PKI systems use Certificate Authorities to issue certificates to subjects (by signing them). By signing Certificate Authority forms a chain from the CA to the subject's certificate, this chain can contain multiple CA's if CA1 (root CA) sings CA2's (intermediate CA) certificate which in turn sings the subject's certificate. This is very common on the Internet (for SSL/TLS) and in digital signature scenarios.

So you most likely need at least one CA's certificate and it's public key to verify the subject's certificate. Your program can support multiple independent CA's as well. The CA's your program accepts are usually called Trust Anchors. Keeping the Trust Anchors in a KeyStore is also very convenient.

So, you'll probably end up with a KeyStore containing the Trust Anchors and intermediate CA's. From there on you'll need to form the chain and validate the signatures of all certificates in the chin from the root CA (Trust Anchor) to the subject's certificate that you're trying to validate.

For a proper certificate validation you'll need to check other things besides the chain's signature such as key usage, basic constraints, revocation information, etc. All of this is specified in the RFC5280 in Certificate Path Validation.

Luckily, there is already Java API in the form of CertPath API that can help you with that. A very rudimentary example that could fit your use case would be:

final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

final X509Certificate certificateToCheck = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certBytes));

final KeyStore trustStore = KeyStore.getInstance("JKS");
InputStream keyStoreStream = ...
trustStore.load(keyStoreStrem, "your password".toCharArray());

final CertPathBuilder certPathBuilder = CertPathBuilder.getInstance("PKIX");
final X509CertSelector certSelector = new X509CertSelector();
certSelector.setCertificate(certificateToCheck);

final CertPathParameters certPathParameters = new PKIXBuilderParameters(trustStore, certSelector);
final CertPathBuilderResult certPathBuilderResult = certPathBuilder.build(certPathParameters);
final CertPath certPath = certPathBuilderResult.getCertPath();

final CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX");
final PKIXParameters validationParameters = new PKIXParameters(trustStore);
validationParameters.setRevocationEnabled(true); // if you want to check CRL
final X509CertSelector keyUsageSelector = new X509CertSelector();
keyUsageSelector.setKeyUsage(new boolean[] { true, false, true }); // to check digitalSignature and keyEncipherment bits
validationParameters.setTargetCertConstraints(keyUsageSelector);
final PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) certPathValidator.validate(certPath, validationParameters);

System.out.println(result);

But be advised that there are many more nuances and details that must be properly addressed in order to make the implementation truly secure. Bad certificate path checking is common reason of security problems.

like image 130
Zoran Regvart Avatar answered Dec 21 '22 11:12

Zoran Regvart