Class ExternalKeyStoreTest


  • public class ExternalKeyStoreTest
    extends java.lang.Object
    This test class validates a LLSEC-External-KeyStore Abstraction Layer implementation.

    It requires the external key store to be provisioned with at least a private key, a certificate chain, a secret key and a trusted certificate (not attached to a key, i.e. with the same alias). Such a PKCS12 keystore is provided in resources, it must be configured with the Application option: -Dej.crypto.mock.externalks.path=${project_loc:/security-testsuite}/src/test/resources/test-keystore.p12

    To configure the tests, set the following properties:

    • com.microej.crypto.test.ExternalKeyStoreTest.keystore.type: the type of the KeyStore to test (see KeyStore.getInstance(String)) (defaults to "EXTERNALKS"),
    • com.microej.crypto.test.ExternalKeyStoreTest.keystore.password: the password of the KeyStore to test (see KeyStore.load(java.io.InputStream, char[])) (defaults to "changeit"),
    • com.microej.crypto.test.ExternalKeyStoreTest.keystore.size: the expected number of entries in the provisioned KeyStore, or a negative value when the number of entries is not known at configuration time (defaults to 4),
    • com.microej.crypto.test.ExternalKeyStoreTest.privatekey.extractable: whether KeyStore.getKey().getEncoded() returns the raw key bytes for a private key imported via setKeyEntry. Set to false for HSM-backed keystores (e.g. PKCS#11) where the key never leaves the token, which makes the test require the null that java.security.Key.getEncoded() specifies for a key with no encoded form; set to true for file-backed keystores like PKCS#12 (defaults to true),
    • com.microej.crypto.test.ExternalKeyStoreTest.privatekey.import.algorithms: the algorithms of the private keys the import tests set through setKeyEntry, a comma separated list of RSA and EC. For each one the tests set the committed key of the first configured RSA key size or EC curve, its certificate, and the same key encrypted with the key store password (defaults to "RSA,EC", also when the property is empty),
    • com.microej.crypto.test.ExternalKeyStoreTest.privatekey.alias: the alias of a valid private key (defaults to "test-entity"),
    • com.microej.crypto.test.ExternalKeyStoreTest.privatekey.tmp.alias: the alias used by tests that mutate a private-key entry (set/delete) so they don't pollute the production private-key alias (defaults to "test-tmp-private"),
    • com.microej.crypto.test.ExternalKeyStoreTest.privatekey.password: the password for this private key (defaults to "changeit"),
    • com.microej.crypto.test.ExternalKeyStoreTest.privatekey.signature.algorithm: the signature algorithm to use for testing the use of the private key from the KeyStore (defaults to "SHA256withECDSA"),
    • com.microej.crypto.test.ExternalKeyStoreTest.privatekey.signature.data: the payload to sign when testing signature with the private key from the KeyStore; the produced signature is verified against the trusted certificate rather than a fixed expected output (defaults to "Hello World!"),
    • com.microej.crypto.test.ExternalKeyStoreTest.privatekey.keyagreement.algorithm: the key agreement algorithm to use for testing key agreement with the private key from the KeyStore (defaults to "ECDH"),
    • com.microej.crypto.test.ExternalKeyStoreTest.privatekey.keyagreement.secret: the expected shared secret (base64-encoded) computed by key agreement between the private key from the KeyStore and the public key from the trusted certificate (defaults to "Lg7mq+j/QKp8sk+zMK0tPmnKvpQhS7yZEAr4Y/HN7JOMp/ePumCoWCY/hA0llZYO"),
    • com.microej.crypto.test.ExternalKeyStoreTest.secretkey.extractable: whether KeyStore.getKey().getEncoded() returns the raw key bytes for a secret key imported via setKeyEntry. Set to false for HSM-backed keystores (e.g. PKCS#11), which makes the test require the null that java.security.Key.getEncoded() specifies for a key with no encoded form; set to true for file-backed keystores like PKCS#12 (defaults to true),
    • com.microej.crypto.test.ExternalKeyStoreTest.secretkey.data: the encoded secret key data in base64 (defaults to "L83/flz9vi46b8aX57Trqg=="),
    • com.microej.crypto.test.ExternalKeyStoreTest.secretkey.algorithm: the algorithm of the secret key (defaults to "AES"),
    • com.microej.crypto.test.ExternalKeyStoreTest.secretkey.alias: the alias of a valid secret key (defaults to "test-secret"),
    • com.microej.crypto.test.ExternalKeyStoreTest.secretkey.tmp.alias: the alias used by tests that mutate a secret-key entry (set/delete) so they don't pollute the production secret-key alias (defaults to "test-tmp-secret"),
    • com.microej.crypto.test.ExternalKeyStoreTest.secretkey.import.loops: the number of set, get and delete cycles of the repeated secret-key import test (defaults to 10),
    • com.microej.crypto.test.ExternalKeyStoreTest.getcertificate.repeat: the number of gets of the repeated certificate get test (defaults to 200),
    • com.microej.crypto.test.ExternalKeyStoreTest.secretkey.password: the password for this secret key (defaults to "changeit"),
    • com.microej.crypto.test.ExternalKeyStoreTest.secretkey.cipher.transformation: the cipher transformation to use for testing the use of the secret key from the KeyStore (defaults to "AES/CBC/NoPadding"),
    • com.microej.crypto.test.ExternalKeyStoreTest.secretkey.cipher.payload: the plaintext payload to encrypt when testing the cipher, as a hex string (defaults to "00112233445566778899AABBCCDDEEFF"),
    • com.microej.crypto.test.ExternalKeyStoreTest.secretkey.cipher.iv: the IV used by the cipher, as a hex string (defaults to "00112233445566778899AABBCCDDEEFF"),
    • com.microej.crypto.test.ExternalKeyStoreTest.secretkey.cipher.encrypted: the expected ciphertext (base64-encoded) produced by the cipher on the configured payload/IV (defaults to "vF23ACh1j3eRnVIkfKq9rb+8FpSYLFE3TcthFDnmOcg="),
    • com.microej.crypto.test.ExternalKeyStoreTest.certificate.data: an X.509 certificate (base64-encoded DER) used by mutation tests when importing a trusted-certificate or certificate-chain entry (defaults to a built-in test certificate),
    • com.microej.crypto.test.ExternalKeyStoreTest.certificate.alias: the alias of a valid trusted certificate, not attached to a key (defaults to "test-trusted-cert"),
    • com.microej.crypto.test.ExternalKeyStoreTest.certificate.tmp.alias: the alias used by tests that mutate a trusted-certificate entry (set/delete) so they don't pollute the production certificate alias (defaults to "test-tmp-cert"),
    • com.microej.crypto.test.ExternalKeyStoreTest.certificatechain.alias: the alias of a valid certificate chain (defaults to "test-entity"),
    • com.microej.crypto.test.ExternalKeyStoreTest.certificatechain.length: the expected length of the certificate chain at the certificatechain alias (defaults to 2),

    The tests that set an entry use the tmp aliases only, and never delete an entry they did not set: a test aborts with the name of the alias when its temporary alias already holds an entry. This matters on a device, where the key store outlives the test image and a run killed mid-test leaves its temporary alias occupied.

    • Constructor Detail

      • ExternalKeyStoreTest

        public ExternalKeyStoreTest()
    • Method Detail

      • setUpTime

        public static void setUpTime()
      • checkKeyStoreIsProvisioned

        public void checkKeyStoreIsProvisioned()
                                        throws java.lang.Exception
        Checks the entries these tests get are present, and reports which are missing when they are not.

        Without this, a key store that was never provisioned fails every test on an assertion about a null key, which says nothing about the cause. An alias configured to the empty string is not required.

        Throws:
        java.lang.Exception - if the key store cannot be loaded
      • testListAliases

        public void testListAliases()
                             throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testSizeFollowsSetAndDelete

        public void testSizeFollowsSetAndDelete()
                                         throws java.lang.Exception
        Checks that size() follows what is set: one more after a set, back to the initial value after the delete, and always equal to the number of aliases enumerated.
        Throws:
        java.lang.Exception - on any error
      • testIsCertificateEntryFollowsWhatIsSet

        public void testIsCertificateEntryFollowsWhatIsSet()
                                                    throws java.lang.Exception
        Checks that a certificate alias is reported as a certificate entry only while it holds a certificate.
        Throws:
        java.lang.Exception - on any error
      • testUnknownAliasIsNotAnEntry

        public void testUnknownAliasIsNotAnEntry()
                                          throws java.lang.Exception
        Checks that an unknown alias is reported as absent by every call that takes one.
        Throws:
        java.lang.Exception - on any error
      • testSetKeyEntryTwiceOnSameAlias

        public void testSetKeyEntryTwiceOnSameAlias()
                                             throws java.lang.Exception
        Checks that a second set into an alias that already holds an entry leaves the alias in a consistent state.

        The contract allows two outcomes. A key store that accepts the set replaces the entry: the second value is held. A key store that refuses it, because each alias holds one entry until it is deleted, throws a KeyStoreException: the first value is still held. What the contract forbids is a set that neither throws nor takes effect.

        Throws:
        java.lang.Exception - on any error
      • testRepeatedGetCertificate

        public void testRepeatedGetCertificate()
                                        throws java.lang.Exception
        Gets the trusted certificate repeatedly, to catch a leak or a stale handle in the get path.

        Each get allocates a native handle that the library releases once the certificate bytes are copied. A handle left allocated exhausts the native heap after enough gets. Each get must return the same encoded certificate as the first one: a stale buffer that still parses as a certificate is detected by the comparison, not by the null checks.

        Throws:
        java.lang.Exception - on any error
      • testListAliasesDoesNotLeakStaleBytes

        public void testListAliasesDoesNotLeakStaleBytes()
                                                  throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testContainsAlias

        public void testContainsAlias()
                               throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testGetKey

        public void testGetKey()
                        throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testGetKeyForInvalidAliasReturnsNull

        public void testGetKeyForInvalidAliasReturnsNull()
                                                  throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testGetKeyForNonKeyAliasReturnsNull

        public void testGetKeyForNonKeyAliasReturnsNull()
                                                 throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testGetCertificate

        public void testGetCertificate()
                                throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testGetCertificateForInvalidAliasReturnsNull

        public void testGetCertificateForInvalidAliasReturnsNull()
                                                          throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testGetCertificateForNonCertificateAliasReturnsNull

        public void testGetCertificateForNonCertificateAliasReturnsNull()
                                                                 throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testGetCertificateChain

        public void testGetCertificateChain()
                                     throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testGetCertificateChainForInvalidAliasReturnsNull

        public void testGetCertificateChainForInvalidAliasReturnsNull()
                                                               throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testCheckEntryType

        public void testCheckEntryType()
                                throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testSetCertificateAndDeleteEntry

        public void testSetCertificateAndDeleteEntry()
                                              throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testSetPrivateKeyProtectedAndDeleteEntry

        public void testSetPrivateKeyProtectedAndDeleteEntry()
                                                      throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testSetSecretKeyAndDeleteEntry

        public void testSetSecretKeyAndDeleteEntry()
                                            throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testLoadWithWrongPasswordIsRejected

        public void testLoadWithWrongPasswordIsRejected()
                                                 throws java.lang.Exception
        Loading the KeyStore with a wrong password fails with an IOException caused by an UnrecoverableKeyException, as KeyStore.load(InputStream, char[]) specifies, so a caller can tell a wrong password from any other load failure.

        A hardware token counts failed logins and locks after a few in a row. The correct login that follows the wrong one resets that count, so a run of this test uses up no retry of the token.

        Throws:
        java.lang.Exception - if the KeyStore cannot be created or loaded with the correct password
      • testDeleteEntryInvalidAlias

        public void testDeleteEntryInvalidAlias()
                                         throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testRepeatedSetGetDeleteSecretKey

        public void testRepeatedSetGetDeleteSecretKey()
                                               throws java.lang.Exception
        Checks that repeated set, get and delete cycles of a secret key keep returning the value set.

        Each cycle sets a different known value, so a stale native buffer left from a previous cycle cannot pass. The cycles also exercise the release path of the native key entry: a leak shows on a bounded native heap.

        Throws:
        java.lang.Exception - on any error
      • testImportedPrivateKeyFromDataIsUsable

        public void testImportedPrivateKeyFromDataIsUsable()
                                                    throws java.lang.Exception
        Checks that a private key imported through KeyStore.setKeyEntry(String, byte[], Certificate[]) is usable, not merely retrievable.
        Throws:
        java.lang.Exception - on any error
      • testImportedPrivateKeyFromKeyObjectIsUsable

        public void testImportedPrivateKeyFromKeyObjectIsUsable()
                                                         throws java.lang.Exception
        Checks that a private key imported through KeyStore.setKeyEntry(String, Key, char[], Certificate[]) is usable, not merely retrievable.

        The two overloads reach the native layer by different routes, one hands over the encoded key, the other a key object the native side exports itself, so each needs its own proof that the material set is correct.

        Throws:
        java.lang.Exception - on any error
      • testSetCertificateEntry

        public void testSetCertificateEntry()
                                     throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testSetKeyEntryPrivateKeyAndCertificateChain

        public void testSetKeyEntryPrivateKeyAndCertificateChain()
                                                          throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testSetKeyEntryWithUnencodableCertificateLeavesAliasFree

        public void testSetKeyEntryWithUnencodableCertificateLeavesAliasFree()
                                                                      throws java.lang.Exception
        A key entry whose certificate chain cannot be fully handed over is not written: setKeyEntry() fails and the alias stays free.

        The library declares the chain length when it opens the entry, hands the certificates over one by one, and closes the entry from a finally block even when one of them failed. The close is where a backend commits the entry to its store, and the only place that can tell a complete chain from one the library gave up on. A backend that commits what it received leaves an alias holding a key with a truncated chain.

        Throws:
        java.lang.Exception
      • testSetKeyEntryWithUnencodableCertificateKeepsPreviousEntry

        public void testSetKeyEntryWithUnencodableCertificateKeepsPreviousEntry()
                                                                         throws java.lang.Exception
        A failed setKeyEntry() on an alias that holds an entry leaves that entry as it was.

        The other half of testSetKeyEntryWithUnencodableCertificateLeavesAliasFree(): a backend that replaces the entry before the chain is complete, or removes it on the abort, destroys a valid entry on a failed set.

        Throws:
        java.lang.Exception
      • testSetKeyEntryPrivateKeyProtected

        public void testSetKeyEntryPrivateKeyProtected()
                                                throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testSetKeyEntrySecretKey

        public void testSetKeyEntrySecretKey()
                                      throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testGetCertificateChainForNonCertificateAliasReturnsNull

        public void testGetCertificateChainForNonCertificateAliasReturnsNull()
                                                                      throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testGetCertificateForCertificateChainAliasReturnsFirstCertificate

        public void testGetCertificateForCertificateChainAliasReturnsFirstCertificate()
                                                                               throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testCanUsePrivateKeyFromKeyStoreForSignatureAndVerifyItWithCertificateWithPayload

        public void testCanUsePrivateKeyFromKeyStoreForSignatureAndVerifyItWithCertificateWithPayload()
                                                                                               throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testCanUsePrivateKeyFromKeyStoreForSignatureAndVerifyItWithCertificate

        public void testCanUsePrivateKeyFromKeyStoreForSignatureAndVerifyItWithCertificate()
                                                                                    throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testCanUseSecretKeyFromKeyStoreForCipher

        public void testCanUseSecretKeyFromKeyStoreForCipher()
                                                      throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testCanUsePrivateKeyFromKeyStoreForKeyAgreement

        public void testCanUsePrivateKeyFromKeyStoreForKeyAgreement()
                                                             throws java.lang.Exception
        Throws:
        java.lang.Exception
      • testGcConsistency

        public void testGcConsistency()
                               throws java.lang.Exception
        Throws:
        java.lang.Exception