-2

What's the correct way to extends Java's X509Certificate in order to add custom functions to it? Let's take this:

public abstract class MyX509Certificate extends java.security.cert.X509Certificate {
    protected X509Certificate() {
        super();
    }
}

So normally you just get the certificate from the keystore and cast it like this:

X509Certificate cert = (X509Certificate) keystore.getCertificate(alias);

And I'm trying to cast mine like this:

MyX509Certificate my = (MyX509Certificate) keystore.getCertificate(alias);

But I get:

Exception in thread "main" java.lang.ClassCastException: sun.security.x509.X509CertImpl cannot be cast to ca.carillon.crypto.cert.MyX509Certificate

Why?

codenamezero
  • 2,724
  • 29
  • 64
  • https://stackoverflow.com/questions/4862960/explicit-casting-from-super-class-to-subclass –  Oct 26 '17 at 23:09
  • LOL if only it was that easy I wouldn't have asked. – codenamezero Oct 27 '17 at 13:31
  • You asked why you can't cast a superclass to a subclass. If the linked question doesn't answer that, please [edit] the question and be more specific. –  Oct 27 '17 at 13:32
  • The thing is, I'm not even sure how to ask this question. Because the `getCertificate()` function returns a `Certificate` and the abstract class defined by `java.security.cert.X509Certificate` extends `Certificate`, therefore, if my own class extends `X509Certificate` I thought I should be able to cast it the same... but no. And the weirdest thing is that, the error complains about `X509CertImpl` which I don't see it anywhere on how that is even being implemented or linked to `Certificate` or `X509Certificate` – codenamezero Oct 27 '17 at 13:38

2 Answers2

3

The error message says:

java.lang.ClassCastException: sun.security.x509.X509CertImpl cannot be cast to ca.carillon.crypto.cert.MyX509Certificate

It's basically saying that one type (sun.security.x509.X509CertImpl) can't be cast to another type (your MyX509Certificate class).

The sun.security.x509.X509CertImpl is a subclass of java.security.cert.X509Certificate and it's being returned by keystore.getCertificate method.

What happens is that X509Certificate is an abstract class, and keystore.getCertificate will return an internal implementation (a subclass) of it. And this subclass can be different, depending on your environment (JVM, provider, etc). In this case, it's returning a sun.security.x509.X509CertImpl - you don't see this class anywhere because it's internal to the JVM, and in most cases you actually don't need to know it. All you need to know is that it's a X509Certificate.


When you create your own subclass of X509Certificate, the hierarchy will be like this:

                   X509Certificate <- super class
                           |
         ------------------------------------- 
         |                                   |
sun.security.x509.X509CertImpl        MyX509Certificate  <- subclasses of X509Certificate

Both the class returned by the kesytore (X509CertImpl) and your custom class (MyX509Certificate) are subclasses of X509Certificate.

When the keystore returns a X509CertImpl, it's ok to cast it to a X509Certificate (a subclass instance can be assigned to a variable with the superclass's type).

But if you try to do:

MyX509Certificate my = (MyX509Certificate) keystore.getCertificate(alias);

The keystore is returning a X509CertImpl, which is a X509Certificate, but it's not a MyX509Certificate - you can't cast a "sibling" class to another. The X509CertImpl only knows about its superclass (X509Certificate), but it doesn't know about MyX509Certificate.


If you want to add functionality to a X509Certificate, perhaps extending it is not the best solution and you should go for the "Composition over Inheritance" approach.

You can create a class that has a X509Certificate (AKA "a wrapper") and adds functionality by using it:

public class MyCustomCertificate {

    // wraps a X509Certificate
    private X509Certificate cert;

    public MyCustomCertificate(X509Certificate cert) {
        this.cert = cert;
    }

    public void myCustomFunctionality1() {
        // do something with this.cert
    }

    public void myCustomFunctionality2() {
        // do something with this.cert
    }

    // and so on...
}

Then you create this class using the certificate returned by the keystore:

X509Certificate x509Cert = (X509Certificate) keystore.getCertificate(alias);
MyCustomCertificate myCert = new MyCustomCertificate(x509Cert);

X509Certificate has lots of unimplemented methods, and extending it will require you to implement them (which is not worth doing, IMO). Instead, you can use the certificate returned by the keystore (that already implements all those methods) and add the functionalities you want.


If you really want to extend X509Certificate, though, you could delegate the methods to the wrapped certificate:

public class MyCustomCertificate extends X509Certificate {

    // wraps a X509Certificate
    private X509Certificate cert;

    public MyCustomCertificate(X509Certificate cert) {
        this.cert = cert;
    }

    public void myCustomFunctionality1() {
        // do something with this.cert
    }

    public void myCustomFunctionality2() {
        // do something with this.cert
    }

    // delegate all X509Certificate methods to the wrapped certificate
    public PublicKey getPublicKey() {
        return this.cert.getPublicKey();
    }

    public Principal getSubjectDN() {
        return this.cert.getSubjectDN();
    }

    // and so on...
}
1

As mentioned in the other answer, you should not be attempting to create your own class. You should use a another security provider.


To answer your actual question as to why you cannot cast to your class there a reason. The instantiated object is not a MyX509Certificate and thus cannot be cast. A class can only be cast to a type if it is that type.

For example:

public abstract class AbstractParentClass {
}

public class ChildClassA extends AbstractParentClass {
}

public class ChildClassB extends AbstractParentClass {
}

You can now instantiate ChildClassA and assign it to a variable of type AbstractParentClass and then cast it to a ChildClassA variable

public static void main(String[] args){
    AbstractParentClass instance = new ChildClassA();
    ChildClassA sameInstance = (ChildClassA) instance;
}

What you cannot do is cast it to a class that is not part of its class taxonomy.

AbstractParentClass instance = new ChildClassA();
// This is runtime error because ChildClassB is not a ChildClassA
ChildClassB sameInstance = (ChildClassB) instance;

So unless you're instantiating the class yourself, which you aren't because you're retrieving it from the keystore, that class isn't of type MyX509Certificate.

Niko
  • 4,158
  • 9
  • 46
  • 85