Skip to content

Commit 86df48c

Browse files
authored
Merge pull request #2105 from hyperledger/feature/add_kms_support
Add HSM kms implementation
2 parents 8fa31dd + cca45d1 commit 86df48c

File tree

4 files changed

+107
-0
lines changed

4 files changed

+107
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
1212
### Features
1313

1414
* bump snapshot version to 4.12.3 [#2101](https://github.com/hyperledger/web3j/pull/2101)
15+
* Add HSM kms implementation [#2105](https://github.com/hyperledger/web3j/pull/2105)
1516

1617
### BREAKING CHANGES
1718

build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ext {
2626
picocliVersion = '4.7.6'
2727
ensAdraffyVersion = '0.2.0'
2828
kzg4844Version = '2.0.0'
29+
awsSdkVersion = '2.27.24'
2930
tuweniVersion = '2.4.2'
3031
// test dependencies
3132
equalsverifierVersion = '3.16.1'

core/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies {
2121
"io.github.adraffy:ens-normalize:$ensAdraffyVersion",
2222
"io.tmio:tuweni-bytes:$tuweniVersion",
2323
"io.tmio:tuweni-units:$tuweniVersion"
24+
implementation "software.amazon.awssdk:kms:$awsSdkVersion"
2425
testImplementation project(path: ':crypto', configuration: 'testArtifacts'),
2526
"nl.jqno.equalsverifier:equalsverifier:$equalsverifierVersion",
2627
"ch.qos.logback:logback-classic:$logbackVersion"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2024 Web3 Labs Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package org.web3j.service;
14+
15+
import java.security.MessageDigest;
16+
import java.security.NoSuchAlgorithmException;
17+
18+
import software.amazon.awssdk.core.SdkBytes;
19+
import software.amazon.awssdk.services.kms.KmsClient;
20+
import software.amazon.awssdk.services.kms.model.MessageType;
21+
import software.amazon.awssdk.services.kms.model.SignRequest;
22+
import software.amazon.awssdk.services.kms.model.SigningAlgorithmSpec;
23+
import software.amazon.awssdk.services.kms.model.VerifyRequest;
24+
25+
import org.web3j.crypto.CryptoUtils;
26+
import org.web3j.crypto.HSMPass;
27+
import org.web3j.crypto.Sign;
28+
29+
/**
30+
* HSM request processor for AWS KMS. Notice the KMS key must be ECC_SECG_P256K1, this key is
31+
* supported in crypto space.
32+
*/
33+
public class HSMAwsKMSRequestProcessor implements HSMRequestProcessor {
34+
35+
private KmsClient kmsClient;
36+
private String keyID;
37+
38+
public HSMAwsKMSRequestProcessor(KmsClient kmsClient, String keyID) {
39+
this.kmsClient = kmsClient;
40+
this.keyID = keyID;
41+
}
42+
43+
/**
44+
* Entry point method which creates the KMS sign request
45+
*
46+
* @param dataToSign - data to be signed
47+
* @param pass - public key of the asymmetric KMS key pair used for signing The @{@link
48+
* org.web3j.crypto.HSMPass} should be instantiated before this method call. Use the
49+
* following code for getting and setting the public key:
50+
* <p>byte[] rawPublicKey = KmsClient.create() .getPublicKey((var builder) -> {
51+
* builder.keyId(kmsKeyId); }) .publicKey() .asByteArray();
52+
* <p>byte[] publicKey = SubjectPublicKeyInfo .getInstance(rawPublicKey) .getPublicKeyData()
53+
* .getBytes();
54+
* <p>BigInteger publicKey = new BigInteger(1, Arrays.copyOfRange(publicKey, 1,
55+
* publicKey.length));
56+
* <p>HSMPass pass = new HSMPass(null, publicKey);
57+
* @return SignatureData v | r | s
58+
*/
59+
@Override
60+
public Sign.SignatureData callHSM(byte[] dataToSign, HSMPass pass) {
61+
byte[] dataHash = new byte[0];
62+
try {
63+
dataHash = MessageDigest.getInstance("SHA-256").digest(dataToSign);
64+
} catch (NoSuchAlgorithmException e) {
65+
throw new IllegalArgumentException(
66+
"Algorithm SHA-256 is not available for the given data!");
67+
}
68+
69+
// Create the SignRequest for AWS KMS
70+
var signRequest =
71+
SignRequest.builder()
72+
.keyId(keyID)
73+
.message(SdkBytes.fromByteArray(dataHash))
74+
.messageType(MessageType.DIGEST)
75+
.signingAlgorithm(SigningAlgorithmSpec.ECDSA_SHA_256)
76+
.build();
77+
78+
// Sign the data using AWS KMS
79+
var signResult = kmsClient.sign(signRequest);
80+
var signatureBuffer = signResult.signature().asByteBuffer();
81+
82+
// Convert the signature to byte array
83+
var signBytes = new byte[signatureBuffer.remaining()];
84+
signatureBuffer.get(signBytes);
85+
86+
// Verify signature on KMS
87+
var verifyRequest =
88+
VerifyRequest.builder()
89+
.keyId(keyID)
90+
.message(SdkBytes.fromByteArray(dataHash))
91+
.messageType(MessageType.DIGEST)
92+
.signingAlgorithm(SigningAlgorithmSpec.ECDSA_SHA_256)
93+
.signature(SdkBytes.fromByteArray(signBytes))
94+
.build();
95+
96+
var verifyRequestResult = kmsClient.verify(verifyRequest);
97+
if (!verifyRequestResult.signatureValid()) {
98+
throw new RuntimeException("KMS signature is not valid!");
99+
}
100+
101+
var signature = CryptoUtils.fromDerFormat(signBytes);
102+
return Sign.createSignatureData(signature, pass.getPublicKey(), dataHash);
103+
}
104+
}

0 commit comments

Comments
 (0)