Azure-sdk-for-java: [BUG] Failure importing EC-HSM Keypair into a HSM Keyvault

Created on 18 Apr 2019  路  8Comments  路  Source: Azure/azure-sdk-for-java

Describe the bug
Some inconsistency in the SDK leads to the impossibility to import an existing SECP256K1 Keypair in a HSM-enabled Azure Keyvault, due to :

  1. an EC-HSM JsonWebKey can not hold a private key (code proving it is next)
  2. inconsistency when checking the validity of a JsonWebKey -- the isValid() method returns true but the call to the HSM results in a 400 error code, key must be valid.

Exception or Stack Trace
Add the exception log and stack trace if available
1

JSON WEB KEY:is valid? false

2

JSON WEB KEY:is valid? true
com.microsoft.azure.keyvault.models.KeyVaultErrorException: Status code 400, {"error":{"code":"BadParameter","message":"The property \"key\" must be a valid JsonWebKey object."}}`

To Reproduce
Use the following :

<dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-keyvault</artifactId>
            <version>1.2.1</version>
        </dependency>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-keyvault-cryptography</artifactId>
            <version>1.2.1</version>
        </dependency>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-keyvault-extensions</artifactId>
            <version>1.2.1</version>
        </dependency>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-keyvault-core</artifactId>
            <version>1.2.1</version>
        </dependency>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-keyvault-webkey</artifactId>
            <version>1.2.1</version>
        </dependency>

Generate an Ethereum Keypair (secp256k1 curve) and try to add it to an HSM-enabled Keyvault as EC-HSM keys using this SDK.

Code Snippet
1.


Security.addProvider(new BouncyCastleProvider());
        ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
        ECDomainParameters domain = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN());
        ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(spec.getCurve().decodePoint(prefixedPubKey),
                domain);
        ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(keypair.getPrivateKey(), domain);

JsonWebKey jwk = new JsonWebKey().withKty(JsonWebKeyType.EC_HSM).withCrv(new JsonWebKeyCurveName("SECP256K1"))
                .withX(publicKeyParams.getQ().getAffineXCoord().getEncoded())
                .withY(publicKeyParams.getQ().getAffineYCoord().getEncoded())
                .withD(privateKeyParams.getD().toByteArray()).withKeyOps(keyOps);

        LOG.debug("JSON WEB KEY:is valid? {}", jwk.isValid());

        if (!jwk.isValid()) {
            LOG.error("!! INVALID KEY !!");
            return null;
        }

I adapted the fromEC() method of the JsonWebKey class because I wanted to use EC-HSM as kty instead of EC.
result shows !! INVALID KEY !! which is strange, how to import a Key that could sign without the private key? (and no way to recover the private key from the public key of course)

2.

        Security.addProvider(new BouncyCastleProvider());
        ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
        ECDomainParameters domain = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN());
        ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(spec.getCurve().decodePoint(prefixedPubKey),
                domain);
        ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(keypair.getPrivateKey(), domain);

        JsonWebKey jwk = new JsonWebKey().withKty(JsonWebKeyType.EC_HSM).withCrv(new JsonWebKeyCurveName("SECP256K1"))
                .withX(publicKeyParams.getQ().getAffineXCoord().getEncoded())
                .withY(publicKeyParams.getQ().getAffineYCoord().getEncoded())
                // .withD(privateKeyParams.getD().toByteArray())
                .withKeyOps(keyOps);

        LOG.debug("JSON WEB KEY:is valid? {}", jwk.isValid());

        if (!jwk.isValid()) {
            LOG.error("!! INVALID KEY !!");
            return null;
        }

        Builder builder = new Builder(URL_BASE, keyName, jwk).withHsm(true)
                .withAttributes(new KeyAttributes().withEnabled(true));

        this.client.importKey(builder.build());

Only the privateKey addition that is commented out. Strange that after that, the validity check passes but not when calling the Keyvault...

JSON WEB KEY:is valid? true
com.microsoft.azure.keyvault.models.KeyVaultErrorException: Status code 400, {"error":{"code":"BadParameter","message":"The property \"key\" must be a valid JsonWebKey object."}}`

Expected behavior
I would have expected to be able to add the keypair (private + public) into the HSM.

Setup (please complete the following information):

  • OS: Ubuntu 16.04 LTS
  • IDE : Visual Studio Code
  • Version : 1.2.1

Additional context
None

Information Checklist
Kindly make sure that you have added all the following information above and checkoff the required fields otherwise we will treat the issuer as an incomplete report

  • [x] Bug Description Added
  • [x] Repro Steps Added
  • [x] Setup information Added
Client KeyVault bug customer-reported

All 8 comments

Thank you for opening this issue! We are routing it to the appropriate team for follow up.

Hello, any news guys ?

Hello, do you plan to handle this bug? If so, when?
Thx

Hello.
Any news on this bug ?
Thx

This issue is being investigated this week and next by @vcolin7

/cc @schaabs @AlexGhiondea as fyi

Hi @ArvsIndrarys, the Azure SDK team has released a new set of libraries (called Track 2) for Key Vault containing a number of improvements over the previous version. We recommend you use it for all your Key Vault needs from now on. You can add the new Keys library to your project by adding this excerpt to its POM:

<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-security-keyvault-keys</artifactId>
    <version>4.1.5</version>
</dependency>

Now, speaking of the issue at hand, there are a few things I'd like to clarify:

  1. If the key that is created using the aforementioned code is software-backed (EC), not hardware-backed (EC-HSM), it is expected to see an error message from the server telling you the JsonWebKey is badly formed.
  2. The good news is that you can still import a key with all the same characteristics (including curve type) to your Key Vault. Just use JsonWebKeyType.EC instead of JsonWebKeyType.EC_HSM for software-backed keys.
  3. If you want to import a hardware-backed key without it leaving the HSM you need to follow this process. Unfortunately, this means you cannot just use the Azure SDK to do the this.
  4. Another alternative is creating an EC-HSM key directly on Azure Key Vault and using it for all your desired operations. Here's some sample code using the latest version of the Track 2 library:
KeyClient keyClient = new KeyClientBuilder()
    .vaultUrl("https://vicolina-kv-premium.vault.azure.net/")
    .credential(new DefaultAzureCredentialBuilder().build())
    .buildClient();

Response<KeyVaultKey> createKeyResponse =
    keyClient.createEcKeyWithResponse(
        new CreateEcKeyOptions("MyEcHsmKey")
            .setExpiresOn(OffsetDateTime.now().plusYears(1)) // This is optional
            .setHardwareProtected(true) // This makes the created key EC-HSM instead of just EC
            .setCurveName(KeyCurveName.P_256K) // This is the same as "SECP256K1"
            .setKeyOperations(KeyOperation.SIGN, KeyOperation.VERIFY) // This is optional
            .setEnabled(true), new Context("key1", "value1"));

System.out.printf("Create Key operation succeeded with status code %s \n", createKeyResponse.getStatusCode());

I hope this clarifies most questions you might have. If there is anything that was not clear or if you have more questions feel free to reach out to @ me. I will be closing this issue for the time being :)

As a final note, I'll need to investigate a bit more about to why an HSM JsonWebKey is considered invalid when it contains a private key. If we determine there's a bug in the validity check I will open a new issue to address it.

@ArvsIndrarys If you want to import an EC-HSM key using the Track2 SDK without going through the BYOK process, you can use the importKey() or importKeyWithResponse() operations in KeyClient and KeyAsyncClient. Here's a code sample using Java Security APIs:

// Create a Credential object containing your authentication details.
ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
    .clientId("<client-ID>")
    .clientSecret("<client-secret>")
    .tenantId("<tenant-ID>")
    .build();

// Create a Key client and pass said credentials to it.
KeyClient keyClient = new KeyClientBuilder()
    .vaultUrl("<your-premium-key-vault-uri>")
    .credential(clientSecretCredential)
    .buildClient();

// Create a JsonWebKey object containing your key's contents.
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
KeyPair keyPair = new KeyPair(publicKey, privateKey);
Provider provider = Security.getProvider("SunEC");
JsonWebKey jsonWebKey = JsonWebKey.fromEc(keyPair, provider);

// Import the key using the KeyClient while setting the hardwareProtected property to true.
KeyVaultKey importedKey =
    keyClient.importKey(new ImportKeyOptions("TestKeyECHSMImport2", jsonWebKey).setHardwareProtected(true));

    System.out.printf("Import key operation succeeded with name: %s and key type: %s \n", importedKey.getName(),
        importedKey.getKeyType());

Pinging @VinceBCD & @hhanquez since they were interested in this issue as well.

Was this page helpful?
0 / 5 - 0 ratings