ECDHDecrypter with unextractable private key from HSM

Issue #291 resolved
Fernando González Callejas created an issue

Hello all,

I'm using Java 1.8, Nimbus 6.0.1, an Utimaco HSM device and PKSC11 to generate/store keys to encrypt, decrypt, sign data, I already implemented encryption and signing using ECDHEncrypter and ECDSASigner.

I have a problem when I want to decryp data, the ECDHDecrypter requires a ECKey or a ECPrivateKey, but I can only get a reference to a key/PrivateKey from HSM which is unexctractable.

Could you please share an example of ECHDecrytper using an unextractable private key coming from an HSM device.

Thank you.

Comments (11)

  1. Fernando González Callejas reporter

    I've already checked that, your example only shows how to sign with a RSASSASigner to which you can just pass a PrivateKey (RSA key)

    In my case, to create the ECDHDecrypter I need an ECKey or an ECPrivateKey first, but I can only get a reference to a Key/PrivateKey from HSM which is unexctractable.

    Can you please share a tip/example on how to create a Nimbus ECHDecrytper using an unextractable private key coming from an HSM device.

    I already configured the HSM and I'm getting the provider and the keys from it, here is what I'm doing:

    PKCS11 Config:

    name=CryptoServer
    library=PATH_TO_THE_PKCS11_LIBRARY
    
    attributes(*,CKO_PRIVATE_KEY,*) = {
        CKA_DERIVE = true
        CKA_SIGN = true
        CKA_DECRYPT = true
    }
    

    This is my test class:

    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.math.BigInteger;
    import java.security.InvalidAlgorithmParameterException;
    import java.security.Key;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.PrivateKey;
    import java.security.Provider;
    import java.security.PublicKey;
    import java.security.Security;
    import java.security.UnrecoverableKeyException;
    import java.security.cert.CertificateException;
    import java.security.cert.CertificateFactory;
    import java.security.cert.X509Certificate;
    import java.security.interfaces.ECPrivateKey;
    import java.security.interfaces.ECPublicKey;
    import java.security.spec.ECGenParameterSpec;
    import java.text.ParseException;
    import java.time.LocalDateTime;
    import java.time.ZoneOffset;
    import java.util.Collections;
    import java.util.Date;
    
    import org.bouncycastle.asn1.ASN1Sequence;
    import org.bouncycastle.asn1.x500.X500Name;
    import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
    import org.bouncycastle.cert.X509CertificateHolder;
    import org.bouncycastle.cert.X509v3CertificateBuilder;
    import org.bouncycastle.operator.ContentSigner;
    import org.bouncycastle.operator.OperatorCreationException;
    import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
    
    import com.nimbusds.jose.EncryptionMethod;
    import com.nimbusds.jose.JOSEException;
    import com.nimbusds.jose.JWEAlgorithm;
    import com.nimbusds.jose.JWEDecrypter;
    import com.nimbusds.jose.JWEEncrypter;
    import com.nimbusds.jose.JWEHeader;
    import com.nimbusds.jose.JWEObject;
    import com.nimbusds.jose.Payload;
    import com.nimbusds.jose.crypto.ECDHDecrypter;
    import com.nimbusds.jose.crypto.ECDHEncrypter;
    
    import sun.security.pkcs11.SunPKCS11;
    
    public class JWEDecryptHSM {
    
        static String configName = "PATH_TO_THE_PKCS11_CONFIG";
        static String subject = "CN=Company-123456";
        static String signatureAlgorithm = "SHA256WithECDSA";
        static char[] keyPassword = "1234".toCharArray();
    
        public static void main(String[] args) {
            try {
                Provider provider = new SunPKCS11(configName);
                Security.addProvider(provider);
                System.out.println("Provider Name: " + provider.getName());
                char[] pin = "1234".toCharArray();
                KeyStore ks = KeyStore.getInstance("PKCS11", provider);
                ks.load(null, pin);
    
    //          for (String alias : Collections.list(ks.aliases())) {
    //              System.out.println(alias);
    //          }
    
                String keyName = "PrivateTest";
                // Generate the Keypair to encrypt the contract data
                KeyPair keyPair = generateKeyPair(provider);
                PrivateKey privateKey = keyPair.getPrivate();
                PublicKey publicKey = keyPair.getPublic();
                System.out.println("PUB. KEY: " + publicKey);
                System.out.println("PRIV. KEY: " + privateKey);
                storeKey(ks, keyPair, keyName, provider);
                Key hsmPrivateKey = ks.getKey(keyName, keyPassword);
                Key hsmPublicKey = ks.getCertificate(keyName).getPublicKey();
                System.out.println("HSM PUB. KEY: " + hsmPublicKey);
                System.out.println("HSM PRIV. KEY: " + hsmPrivateKey);
    //          generateKeyAgreement(hsmPublicKey);
    
                // NIMBUS 
                try {
                    // Encrypt
                    ECPublicKey ecPublicKey = (ECPublicKey) hsmPublicKey;
                    JWEHeader.Builder builder = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES_A256KW, EncryptionMethod.A256CBC_HS512);
                    builder.keyID("XXX");
                    JWEHeader jweHeader = builder.build();
                    Payload payload = new Payload("Hello nimbus...".getBytes());
                    JWEObject encryptedContainer = new JWEObject(jweHeader, payload);
                    JWEEncrypter encrypter = new ECDHEncrypter(ecPublicKey);
                    encryptedContainer.encrypt(encrypter);
                    String encryptedData = encryptedContainer.serialize();
                    System.out.println("ENCRYPTED: " + encryptedData);
    
                    // Decrypt
                    JWEObject jwe = JWEObject.parse(encryptedData);
                    JWEDecrypter decrypter = new ECDHDecrypter((ECPrivateKey) hsmPrivateKey);  // <- Here is failing
                    decrypter.getJCAContext().setProvider(provider);
                    jwe.decrypt(decrypter);
                    String decrypted = jwe.getPayload().toString();
                    System.out.println("DECRYPTED: " + decrypted);
    
                } catch (JOSEException | ParseException e) {
                    e.printStackTrace();
                }
    
            } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | UnrecoverableKeyException e) {
                e.printStackTrace();
            }
        }
    
        public static void storeKey(KeyStore ks, KeyPair keyPair, String keyName, Provider provider) {
            try {
                X509Certificate[] chain = generateV3Certificate(keyPair, provider);
                ks.setKeyEntry(keyName, keyPair.getPrivate(), keyPassword, chain);
                ks.store(null);
                System.out.println("============================= Stored Keys =============================");
                for (String alias : Collections.list(ks.aliases())) {
                    System.out.println(alias);
                }
            } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
                e.printStackTrace();
            }
        }
    
        public static X509Certificate[] generateV3Certificate(KeyPair pair, Provider provider) {
            X509Certificate[] chain = null;
            try {
                X509Certificate certificate = null;
                X509v3CertificateBuilder certificateGenerator = new X509v3CertificateBuilder(
                        new X500Name("C=DE, L=Wolfsburg, O=My CA Inc"), 
                        BigInteger.valueOf(System.currentTimeMillis()), 
                        Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)), 
                        Date.from(LocalDateTime.now().plusDays(365).toInstant(ZoneOffset.UTC)), 
                        new X500Name(subject), 
                        SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(pair.getPublic().getEncoded())) 
                    );
                ContentSigner sigGen = new JcaContentSignerBuilder(signatureAlgorithm).build(pair.getPrivate());
                X509CertificateHolder holder = certificateGenerator.build(sigGen);
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                certificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(holder.toASN1Structure().getEncoded()));
                chain = new X509Certificate[] { certificate };
            } catch (OperatorCreationException | CertificateException | IOException e) {
                e.printStackTrace();
            }
            return chain;
        }
    
        public static KeyPair generateKeyPair(Provider provider) {
            KeyPair keyPair = null;
            try {
                KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", provider); //"EC"
                ECGenParameterSpec ecParameterSpec = new ECGenParameterSpec("secp256r1"); //secp256r1
                keyPairGenerator.initialize(ecParameterSpec);
                keyPair = keyPairGenerator.generateKeyPair();
            } catch (NoSuchAlgorithmException exception) {
                exception.printStackTrace();
            } catch (InvalidAlgorithmParameterException e) {
                e.printStackTrace();
            }
            return keyPair;
        }
    
    }
    

    Thank you.

  2. Fernando González Callejas reporter

    I already tried to create an ECKey like this:

    ECKey ecKey = new ECKey.Builder(Curve.P_256, ecPublicKey).privateKey((PrivateKey) hsmPrivateKey).build();
    

    But I'm getting this exception:

    com.nimbusds.jose.JOSEException
        at com.nimbusds.jose.JWEObject.decrypt(JWEObject.java:429)
        at JWEDecryptHSM.main(JWEDecryptHSM.java:121)
    Caused by: java.lang.NullPointerException
        at com.nimbusds.jose.crypto.utils.ECChecks.isPointOnCurve(ECChecks.java:55)
        at com.nimbusds.jose.crypto.ECDHDecrypter.decrypt(ECDHDecrypter.java:219)
        at com.nimbusds.jose.JWEObject.decrypt(JWEObject.java:415)
        ... 1 more
    

    Thank you.

  3. Connect2id OSS
    • changed status to open

    Hi,

    The HSM based KeyStore can only return PrivateKey instances, not ECPrivateKey instances.

    We'll update the API accordingly. Unfortunately we don't have an HSM to test that, so we'll rely on your input. We have an HSM for testing that does only RSA.

  4. Fernando González Callejas reporter

    Hello,

    Exactly, that's the problem, and the point is Nimbus does not support this (yet).

    If you need some support on this I'm glad to help, even if it's just for testing :)

    You can find an Utimaco HSM simulator here.

    I'll keep one eye on this, is a crucial part of our project.

    Thank you,

  5. Fernando González Callejas reporter

    Hi, I'm testing, now I'm able to create the ECDHDecrypter but is giving me an error while trying to do the key agreement, I think is a problem related to the HSM, still working on it.

    Thanks!

  6. Log in to comment