18

While writing a crypto utility class I ran into a problem with the following method:

public static void destroy(Key key) throws DestroyFailedException {
    if(Destroyable.class.isInstance(key)) {
        ((Destroyable)key).destroy();
    }
}

@Test
public void destroySecretKeySpec() {
    byte[] rawKey = new byte[32];
    new SecureRandom().nextBytes(rawKey);
    try {
        destroy(new SecretKeySpec(rawKey , "AES"));
    } catch(DestroyFailedException e) {
        Assert.fail();
    }
}

In the particular case of javax.crypto.spec.SecretKeySpec the above method would work just fine in java7 because SecretKeySpec (javadocs 7) does not implement Destroyable (javadocs 7)

Now with java8 the class SecretKeySpec (javadocs 8) has been made Destroyable (javadocs 8) and the method Destroyable#destroy is now default which is fine according this statement

Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces.

then the code compiles without any problems despite the fact that the class ScretKeySpec itself has not been changed, alone the interface SecretKey has been.

The problem is that in oracle's jdk8 the destroy method has the following implementation:

public default void destroy() throws DestroyFailedException {
    throw new DestroyFailedException();
}

which leads to an exception at run time.

So the binary compatibility might not have been broken, but existing code has been. The test above passes with java7 but does not with java8

So my questions are:

  • How to deal in general with default methods which might lead to exceptions - because not implemented or not supported - or unexpected behavior at run time? aside from doing

    Method method = key.getClass().getMethod("destroy");
    if(! method.isDefault()) {
        ((Destroyable)key).destroy();
    }
    

    which is only valid for java8 and which might not be correct in future releases, since the default method might get a meaningful implementation.

  • Would it not be better to leave this default method empty instead of throwing an exception (which IMO is misleading since aside from the legit call to destroy nothing has been attempted to effectively destroy the key, an UnsupportedOperationException would have been a better fit and you would know instantly what is going on)

  • Is my approach with (type check/cast/call)

    if(Destroyable.class.isInstance(key))
        ((Destroyable)key).destroy();
    

    for determining whether to destroy or not wrong? What would be an alternative?

  • Is this a misconception or did they just forget to add meaningful implementation in ScretKeySpec?

A4L
  • 17,353
  • 6
  • 49
  • 70
  • 3
    Is there any reason you're not using `instanceof`? – Jeffrey Jul 20 '14 at 14:32
  • @Jeffrey not really, isn't `isInstance` the dynamic equivalent of `instance of`? But according this [answer](http://stackoverflow.com/a/4140165/1113392) I think `instance of` should be fine for my case. Thanks for pointing that. – A4L Jul 20 '14 at 14:39
  • Yup. `isInstance` is the dynamic equivalent of `instanceof`. – Jeffrey Jul 20 '14 at 15:27

2 Answers2

7

Is this a misconception or did they just forget to add meaningful implementation in SecretKeySpec?

Well they didn't forget. SecretKeySpec does need an implementation, but it simply hasn't been done yet. See bug JDK-8008795. Sorry, no ETA on when this will be fixed.

Ideally, valid implementations of destroy would have been added at the time the default method was added and the interface was retrofitted onto existing classes, but it didn't happen, probably because of scheduling.

The notion of "binary compatibility" in the tutorial you cited is a rather strict definition, which is the one used by the Java Language Specification, chapter 13. Basically it's about valid transformations to library classes that do not cause class loading or linking errors at runtime, when combined with classes compiled against older versions of those library classes. This is in contrast to source incompatibility, which causes compile-time errors, and behavioral incompatibility, which causes usually unwanted changes in the runtime behavior of the system. Such as throwing exceptions that weren't thrown before.

This is not to minimize the fact that your code got broken. It's still an incompatibility. (Sorry.)

As a workaround, you might add instanceof PrivateKey || instanceof SecretKey (since these are apparently the classes that lack destroy implementations) and have the test assert that they do throw DestroyFailedException, else if instanceof Destroyable execute the remainder of the logic in your test. The test will fail again when these instances get reasonable destroy implementations in some future version of Java; this will be a signal to change the test back to calling destroy on all Destroyables. (An alternative might be to ignore these classes entirely, but then valid code paths might end up remaining uncovered for quite some time.)

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • 1
    Thank you very much for this answer and sorry if my last question sounded a bit harsh. As for the workaround I have managed to implement a `isDestroyable(Key)` method which returns `key instanceof Destroyable && ! ReflectUtil.isDefault(key, "destroy")`, the latter makes \*cough\* (ab) \*cough\* \*cough\* use of reflection, it basically gets the `java.lang.reflect.Method` object of `isDefault` `m1` from the same class, if such a method is present, then get the `java.lang.reflect.Method` object of `destroy` `m2` from key's class and return the result of `m1.invoke(m2)`. – A4L Jul 21 '14 at 09:08
  • 1
    ... This assumes that the default implementation will always throw a `DestroyFailedException`, which is unlikely to change, since this is a contract: [The default implementation throws DestroyFailedException.](http://docs.oracle.com/javase/8/docs/api/javax/security/auth/Destroyable.html#destroy--). This methods would work for all java versions without any change. If in a later verstion the tests fail, then it is because a destruction has realy been attempet and it failed wich is another story. Thank you again. – A4L Jul 21 '14 at 09:10
5

I'm only speculating, but I think the idea behind throwing an exception in the default implementation of destroy, is to alert you that the sensitive data wasn't destroyed. If the default implementation was empty, and there's no implementation overriding the default one, you might assume by mistake that the sensitive data was destroyed.

I think you should catch the DestroyFailedException exception anyway, regardless of whether it is thrown from the default implementation or from a real implementation, since it warns you that nothing was destroyed, and you should decide how to handle this situation.

The contract of the destroy method, which hasn't changed between Java 7 and Java 8 (aside from the comment about the default implementation) says - Sensitive information associated with this Object is destroyed or cleared. Subsequent calls to certain methods on this Object will result in an IllegalStateException being thrown.

and :

Throws:
DestroyFailedException - if the destroy operation fails.

If destroy fails, subsequent calls to certain methods on this Object will not result in an IllegalStateException being thrown. That's still true if destroyed did nothing, and therefore the default implementation (which does nothing) throws DestroyFailedException.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • Leaving the method empty is not the best choice, that's correct. The exception is checked, so one should catch and handle it, but to do so correctly I think it is necessary to know whether the process of destruction has failed or because there is nothing to destroy at all. That's why I stated that an `UnsupportedOperationException` would have been more helpful. You would make a decision according to the type of the exception. Do you think of any way to handle this correctly aside from checking the type of the concrete implementation, i.e `SecreKeySpec` or it's super type `SecretKey`? – A4L Jul 20 '14 at 14:01
  • 1
    @A4L I don't know how you are planning to handle the `DestroyFailedException`, but I don't see a reason to have a different handling in the case it was thrown from the default implementation. The face that the default implementation of destroy was called doesn't mean there is nothing to destroy at all, it means the instance for which you call `destroy` has no available implementation to perform that destruction. – Eran Jul 20 '14 at 14:20
  • 2
    "the idea behind throwing an exception in the default implementation of destroy, is to alert you that the sensitive data wasn't destroyed"---but the old status, where the class didn't even declare itself destroyable, seems to have signaled that even better, and at development time. So the real question is, why force `Destroyable` on everything, be it truly destroyable or not? – William F. Jameson Jul 20 '14 at 14:54
  • @Eran My phrasing *because there is nothing to destroy at all* was misleading, sorry about that. I meant it exactly as you said *the instance for which you call destroy has no available implementation to perform that destruction*. In fact and unlike public keys I think a secret key should be destroyed since it contains sensitive data. The class `ScretKeySpec` is just an actual example which revealed a general issue which may arise with default methods and which my question is actually addressing. – A4L Jul 20 '14 at 15:14
  • @A4L I'm not sure if there are other default interface implementations which just throw exceptions, and whether what I wrote here would apply to them or not, but in this specific case, you have to handle `DestroyFailedException` anyway (whether it's Java 7 or Java 8), so I don't see the problem. – Eran Jul 20 '14 at 15:19