Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extension Signing request endpoint #982

Merged
merged 34 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a9e8973
feat!: Generic signing extension handler
usmansaleem Mar 20, 2024
e4bf550
cq: Remove unnecessary LOG variable
usmansaleem Mar 20, 2024
91cfe43
feat: Use base64 encoding of payload in response
usmansaleem Mar 20, 2024
03ced53
tests - Add acceptance tests
usmansaleem Mar 21, 2024
2e99ece
changelog
usmansaleem Mar 21, 2024
b3d5707
fix: Use specific body for extension signing
usmansaleem Mar 25, 2024
c12c749
test: Update Acceptance tests
usmansaleem Mar 25, 2024
12e5b40
Update cli option
usmansaleem Mar 26, 2024
423e042
Deleting Eth1 AT
usmansaleem Mar 26, 2024
ee024bc
Refactor signing data to use header.payload.sig format
usmansaleem Mar 26, 2024
8d08221
Refactor signing extension to eth2 mode. Fix acceptance test
usmansaleem Mar 27, 2024
87ea174
spotless fix
usmansaleem Mar 27, 2024
d413431
Merge remote-tracking branch 'upstream/master' into sign_extension
usmansaleem Apr 2, 2024
4cd29c6
changelog
usmansaleem Apr 2, 2024
9422720
changing json response format
usmansaleem Apr 2, 2024
bf3dada
Merge remote-tracking branch 'upstream/master' into sign_extension
usmansaleem Apr 5, 2024
a1fea94
Merge remote-tracking branch 'upstream/master' into sign_extension
usmansaleem Apr 8, 2024
5a2903f
fix: Update bouncycastle libraries
usmansaleem Apr 15, 2024
f7cb63e
fix: Update transitive dependency threetenbp and google cloud secretm…
usmansaleem Apr 15, 2024
0c11f44
build: assign dependency scan nvd api key from env variable
usmansaleem Apr 15, 2024
9aa7ecc
changelog
usmansaleem Apr 15, 2024
11c1dd6
fix: Update guava and commons-logging libraries
usmansaleem Apr 15, 2024
458975c
Merge branch 'vuln_scan_fix' into sign_extension
usmansaleem Apr 15, 2024
23984b2
fix: Update response to return hex encoded signature
usmansaleem Apr 15, 2024
7f06899
Merge remote-tracking branch 'upstream/master' into sign_extension
usmansaleem Apr 15, 2024
17692cf
Enforce strict fields parsing for SigningExtensionHandler
usmansaleem May 1, 2024
3c60ee6
Send Base64 encoded payload in the response
usmansaleem May 1, 2024
e58d150
Use appropriate type for tinestamp
usmansaleem May 1, 2024
c4fe46f
Merge remote-tracking branch 'upstream/master' into sign_extension
usmansaleem May 1, 2024
6cbae83
Merge remote-tracking branch 'upstream/master' into sign_extension
usmansaleem May 2, 2024
38a6db7
fix: Always use application/json as Content-Type header
usmansaleem May 8, 2024
a6eb923
build - debug information in docker test.sh
usmansaleem May 8, 2024
acf0714
build - fix docker goss test
usmansaleem May 8, 2024
e11ae2b
build - store docker dgoss test results
usmansaleem May 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

## Next Version

### Features Added
- Added endpoint `/eth/v1/ext/sign/:identifier` which is enabled using cli option `--Xgeneric-signing-ext-enabled=true`. This endpoint allows signing arbitrary data. [#982](https://github.com/Consensys/web3signer/pull/982)

### Bugs fixed
- Update postgresql to fix CVE-2024-1597
- Fix cached gvr to be thread-safe during first boot. [#978](https://github.com/Consensys/web3signer/issues/978)

---
## 24.2.0

This is a required update for Mainnet users containing the configuration for the Deneb upgrade on March 13th. This update is required for Gnosis Deneb network upgrade on March 11th. For all other networks, this update is optional.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public class Signer extends FilecoinJsonRpcEndpoint {
public static final String ETH2_PUBLIC_KEYS = "/api/v1/eth2/publicKeys"; // bls keys
public static final String RELOAD_ENDPOINT = "/reload";

public static final String GENERIC_SIGN_EXT_ENDPOINT = "/eth/v1/ext/sign/{identifier}";
usmansaleem marked this conversation as resolved.
Show resolved Hide resolved

public static final ObjectMapper ETH_2_INTERFACE_OBJECT_MAPPER =
SigningObjectMapperFactory.createObjectMapper().setSerializationInclusion(Include.NON_NULL);
private static final String METRICS_ENDPOINT = "/metrics";
Expand Down Expand Up @@ -175,6 +177,17 @@ public Response eth2Sign(
.post(signPath(BLS));
}

public Response signGenericData(
final String publicKey, final String data, final ContentType acceptMediaType) {
return given()
.baseUri(getUrl())
.contentType(ContentType.JSON)
.accept(acceptMediaType)
.pathParam("identifier", publicKey)
.body(data)
.post(GENERIC_SIGN_EXT_ENDPOINT);
}

public Response callApiPublicKeys(final KeyType keyType) {
return given().baseUri(getUrl()).get(publicKeysPath(keyType));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public class SignerConfiguration {
private final ChainIdProvider chainIdProvider;
private final Optional<KeystoresParameters> v3KeystoresBulkloadParameters;

private final boolean genericSigningExtEnabled;

public SignerConfiguration(
final String hostname,
final int httpRpcPort,
Expand Down Expand Up @@ -123,7 +125,8 @@ public SignerConfiguration(
final int downstreamHttpPort,
final Optional<ClientTlsOptions> downstreamTlsOptions,
final ChainIdProvider chainIdProvider,
final Optional<KeystoresParameters> v3KeystoresBulkloadParameters) {
final Optional<KeystoresParameters> v3KeystoresBulkloadParameters,
final boolean genericSigningExtEnabled) {
this.hostname = hostname;
this.logLevel = logLevel;
this.httpRpcPort = httpRpcPort;
Expand Down Expand Up @@ -168,6 +171,7 @@ public SignerConfiguration(
this.downstreamTlsOptions = downstreamTlsOptions;
this.chainIdProvider = chainIdProvider;
this.v3KeystoresBulkloadParameters = v3KeystoresBulkloadParameters;
this.genericSigningExtEnabled = genericSigningExtEnabled;
}

public String hostname() {
Expand Down Expand Up @@ -353,4 +357,8 @@ public ChainIdProvider getChainIdProvider() {
public Optional<KeystoresParameters> getV3KeystoresBulkloadParameters() {
return v3KeystoresBulkloadParameters;
}

public boolean isGenericSigningExtEnabled() {
return genericSigningExtEnabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public class SignerConfigurationBuilder {

private KeystoresParameters v3KeystoresBulkloadParameters;

private boolean genericSigningExtEnabled;

public SignerConfigurationBuilder withLogLevel(final Level logLevel) {
this.logLevel = logLevel;
return this;
Expand Down Expand Up @@ -318,6 +320,12 @@ public SignerConfigurationBuilder withV3KeystoresBulkloadParameters(
return this;
}

public SignerConfigurationBuilder withGenericSigningExtEnabled(
final boolean genericSigningExtEnabled) {
this.genericSigningExtEnabled = genericSigningExtEnabled;
return this;
}

public SignerConfiguration build() {
if (mode == null) {
throw new IllegalArgumentException("Mode cannot be null");
Expand Down Expand Up @@ -366,6 +374,7 @@ public SignerConfiguration build() {
downstreamHttpPort,
Optional.ofNullable(downstreamTlsOptions),
chainIdProvider,
Optional.ofNullable(v3KeystoresBulkloadParameters));
Optional.ofNullable(v3KeystoresBulkloadParameters),
genericSigningExtEnabled);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ public List<String> createCmdLineParams() {

yamlConfig.append(createServerTlsArgs());

if (signerConfig.isGenericSigningExtEnabled()) {
yamlConfig.append(
String.format(YAML_BOOLEAN_FMT, "Xgeneric-signing-ext-enabled", Boolean.TRUE));
}

params.add(signerConfig.getMode()); // sub-command .. it can't go to config file

if (signerConfig.getMode().equals("eth2")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ public List<String> createCmdLineParams() {

params.addAll(createServerTlsArgs());

if (signerConfig.isGenericSigningExtEnabled()) {
params.add("--Xgeneric-signing-ext-enabled=true");
}

params.add(signerConfig.getMode());

if (signerConfig.getMode().equals("eth2")) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright 2024 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.web3signer.tests.signing;

import static io.restassured.http.ContentType.JSON;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.web3j.crypto.Sign.signedMessageToKey;
import static tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils.createPublicKey;

import tech.pegasys.web3signer.core.service.jsonrpc.handlers.signing.ConfigurationChainId;
import tech.pegasys.web3signer.dsl.signer.SignerConfigurationBuilder;
import tech.pegasys.web3signer.dsl.utils.MetadataFileHelpers;
import tech.pegasys.web3signer.signing.KeyType;

import java.io.File;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.security.SignatureException;
import java.security.interfaces.ECPublicKey;

import com.google.common.io.Resources;
import io.restassured.response.Response;
import io.vertx.core.json.JsonObject;
import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.web3j.crypto.Sign;

public class Eth1GenericSigningAcceptanceTest extends SigningAcceptanceTestBase {
private static final String DATA =
"""
{
"message": {
"platform": "AT",
"timestamp": "185921877"
}
}
""";

public static final String PUBLIC_KEY_HEX_STRING =
"0x09b02f8a5fddd222ade4ea4528faefc399623af3f736be3c44f03e2df22fb792f3931a4d9573d333ca74343305762a753388c3422a86d98b713fc91c1ea04842";

private static final MetadataFileHelpers METADATA_FILE_HELPERS = new MetadataFileHelpers();

@BeforeEach
void setup() throws URISyntaxException {
final String keyPath =
new File(Resources.getResource("secp256k1/wallet.json").toURI()).getAbsolutePath();

METADATA_FILE_HELPERS.createKeyStoreYamlFileAt(
testDirectory.resolve(PUBLIC_KEY_HEX_STRING + ".yaml"),
Path.of(keyPath),
"pass",
KeyType.SECP256K1);

final SignerConfigurationBuilder builder = new SignerConfigurationBuilder();
builder
.withKeyStoreDirectory(testDirectory)
.withMode("eth1")
.withChainIdProvider(new ConfigurationChainId(DEFAULT_CHAIN_ID))
.withGenericSigningExtEnabled(true);

startSigner(builder.build());
}

@Test
void signGenericData() {
final Response response = signer.signGenericData(PUBLIC_KEY_HEX_STRING, DATA, JSON);

final JsonObject jsonBody = verifyStatusAndGetBody(response);

final Bytes signature = Bytes.fromHexString(jsonBody.getString("signature"));
assertThat(
verifySECP256K1Signature(
createPublicKey(Bytes.fromHexString(PUBLIC_KEY_HEX_STRING)),
DATA.getBytes(UTF_8),
signature))
.isTrue();

final String payload = jsonBody.getString("payload");
assertThat(Bytes.of(DATA.getBytes(UTF_8))).isEqualTo(Bytes.fromBase64String(payload));
}

@Test
void invalidIdentifierCausesNotFound() {
final Response response = signer.signGenericData("0x1234", DATA, JSON);
response.then().statusCode(404);
}

private JsonObject verifyStatusAndGetBody(final Response response) {
response.then().statusCode(200).contentType(JSON);
return new JsonObject(response.body().print());
}

private boolean verifySECP256K1Signature(
final ECPublicKey publicKey, final byte[] data, final Bytes signature) {

final byte[] r = signature.slice(0, 32).toArray();
final byte[] s = signature.slice(32, 32).toArray();
final byte[] v = signature.slice(64).toArray();
final BigInteger messagePublicKey = recoverPublicKey(data, new Sign.SignatureData(v, r, s));
return createPublicKey(messagePublicKey).equals(publicKey);
}

private BigInteger recoverPublicKey(final byte[] data, final Sign.SignatureData signature) {
try {
return signedMessageToKey(data, signature);
} catch (final SignatureException e) {
throw new IllegalStateException("signature cannot be recovered", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright 2024 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.web3signer.tests.signing;

import static io.restassured.http.ContentType.JSON;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static tech.pegasys.teku.spec.SpecMilestone.DENEB;

import tech.pegasys.teku.bls.BLS;
import tech.pegasys.teku.bls.BLSKeyPair;
import tech.pegasys.teku.bls.BLSPublicKey;
import tech.pegasys.teku.bls.BLSSecretKey;
import tech.pegasys.teku.bls.BLSSignature;
import tech.pegasys.teku.spec.networks.Eth2Network;
import tech.pegasys.web3signer.dsl.signer.SignerConfigurationBuilder;
import tech.pegasys.web3signer.dsl.utils.MetadataFileHelpers;
import tech.pegasys.web3signer.signing.KeyType;

import java.nio.file.Path;

import io.restassured.response.Response;
import io.vertx.core.json.JsonObject;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class Eth2GenericSigningAcceptanceTest extends SigningAcceptanceTestBase {
private static final String DATA =
"""
{
"message": {
"platform": "AT",
"timestamp": "185921877"
}
}
""";

private static final String PRIVATE_KEY =
"3ee2224386c82ffea477e2adf28a2929f5c349165a4196158c7f3a2ecca40f35";
private static final MetadataFileHelpers METADATA_FILE_HELPERS = new MetadataFileHelpers();
private static final BLSSecretKey KEY =
BLSSecretKey.fromBytes(Bytes32.fromHexString(PRIVATE_KEY));
private static final BLSKeyPair KEY_PAIR = new BLSKeyPair(KEY);
private static final BLSPublicKey PUBLIC_KEY = KEY_PAIR.getPublicKey();

@BeforeEach
void setup() {
final String configFilename = PUBLIC_KEY.toString().substring(2);
final Path keyConfigFile = testDirectory.resolve(configFilename + ".yaml");
METADATA_FILE_HELPERS.createUnencryptedYamlFileAt(keyConfigFile, PRIVATE_KEY, KeyType.BLS);

setForkEpochsAndStartSigner(
new SignerConfigurationBuilder()
.withKeyStoreDirectory(testDirectory)
.withMode("eth2")
.withNetwork(Eth2Network.MINIMAL.configName())
.withGenericSigningExtEnabled(true),
DENEB);
}

@Test
void signGenericData() {
final Response response = signer.signGenericData(PUBLIC_KEY.toString(), DATA, JSON);

final JsonObject jsonBody = verifyStatusAndGetBody(response);

final Bytes signature = Bytes.fromHexString(jsonBody.getString("signature"));
assertThat(
BLS.verify(
PUBLIC_KEY,
Bytes.of(DATA.getBytes(UTF_8)),
BLSSignature.fromBytesCompressed(signature)))
.isTrue();

final String payload = jsonBody.getString("payload");
assertThat(Bytes.of(DATA.getBytes(UTF_8))).isEqualTo(Bytes.fromBase64String(payload));
}

@Test
void invalidIdentifierCausesNotFound() {
final Response response = signer.signGenericData("0x1234", DATA, JSON);
response.then().statusCode(404);
}

private JsonObject verifyStatusAndGetBody(final Response response) {
response.then().statusCode(200).contentType(JSON);
return new JsonObject(response.body().print());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,13 @@ protected void setupEth2Signer(final Eth2Network eth2Network, final SpecMileston
.withKeyStoreDirectory(testDirectory)
.withMode("eth2")
.withNetwork(eth2Network.configName());

setForkEpochs(specMilestone, builder);

startSigner(builder.build());
setForkEpochsAndStartSigner(builder, specMilestone);
}

protected void setupEth2Signer(final Path networkConfigFile, final SpecMilestone specMilestone) {
final SignerConfigurationBuilder builder = new SignerConfigurationBuilder();
builder.withKeyStoreDirectory(testDirectory).withMode("eth2").withNetwork(networkConfigFile);

setForkEpochs(specMilestone, builder);

startSigner(builder.build());
setForkEpochsAndStartSigner(builder, specMilestone);
}

protected void setupEth2SignerWithCustomNetworkConfig(final Path networkConfigFile) {
Expand All @@ -73,6 +67,12 @@ protected void setupEth2SignerWithCustomNetworkConfig(final Path networkConfigFi
startSigner(builder.build());
}

protected void setForkEpochsAndStartSigner(
final SignerConfigurationBuilder builder, final SpecMilestone specMilestone) {
setForkEpochs(specMilestone, builder);
startSigner(builder.build());
}

private void setForkEpochs(
final SpecMilestone specMilestone, final SignerConfigurationBuilder builder) {
switch (specMilestone) {
Expand Down
Loading
Loading