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 :
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):
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
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:
EC), not hardware-backed (EC-HSM), it is expected to see an error message from the server telling you the JsonWebKey is badly formed.JsonWebKeyType.EC instead of JsonWebKeyType.EC_HSM for software-backed keys.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.