problem: users want to verify that response are coming from their nodes
solution: edge node can sign received valued Co-authored-by: Igor Artamonov <igor@artamonov.ru>
This commit is contained in:
committed by
GitHub
parent
530811d272
commit
d1ad77a345
@@ -31,6 +31,12 @@ cache:
|
||||
redis:
|
||||
enabled: false
|
||||
|
||||
signature:
|
||||
enabled: true
|
||||
algorithm: ECDSA
|
||||
signatureScheme: SHA256withECDSA
|
||||
privateKey: "./test_key"
|
||||
|
||||
proxy:
|
||||
port: 18080
|
||||
tls:
|
||||
|
||||
5
testing/dshackle/test_key
Normal file
5
testing/dshackle/test_key
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQglWZBwGvH/I/TqQb3uPGq
|
||||
d/6MB2tgFXUfQCYj5RmaXV2hRANCAATfN610x3JM7+xMUIt46sjmaiJm3ZGN9RV1
|
||||
q+GiuCp36DECozTVx/lDhBnwg2d71HKRxCxsthdB8NclsYybn6B2
|
||||
-----END PRIVATE KEY-----
|
||||
4
testing/dshackle/test_key.pub
Normal file
4
testing/dshackle/test_key.pub
Normal file
@@ -0,0 +1,4 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE3zetdMdyTO/sTFCLeOrI5moiZt2RjfUV
|
||||
davhorgqd+gxAqM01cf5Q4QZ8INne9RykcQsbLYXQfDXJbGMm5+gdg==
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -7,6 +7,7 @@ plugins {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven { url "https://maven.emrld.io" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -14,12 +15,25 @@ dependencies {
|
||||
implementation "org.codehaus.groovy:groovy:3.0.4"
|
||||
implementation "com.fasterxml.jackson.core:jackson-core:2.9.8"
|
||||
implementation "com.fasterxml.jackson.core:jackson-databind:2.9.8"
|
||||
implementation "io.grpc:grpc-netty:1.46.0"
|
||||
implementation "org.bouncycastle:bcprov-jdk15on:1.61"
|
||||
implementation("io.emeraldpay:emerald-api:0.11.1") {
|
||||
exclude group: 'com.salesforce.servicelibs', module: 'reactor-grpc'
|
||||
}
|
||||
|
||||
testImplementation "org.spockframework:spock-core:2.0-M3-groovy-3.0"
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
substitute module("io.emeraldpay:emerald-api") using project(":api") because "we work with the unreleased development version"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test {
|
||||
systemProperty "trialMode", project.getProperty("dshackleTrialMode")
|
||||
systemProperty "signatureKey", project.getProperty("signatureKey")
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events "PASSED", "FAILED"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.emeraldpay.dshackle.testing.trial
|
||||
|
||||
interface Client {
|
||||
Map<String, Object> execute(String method, List<Object> params)
|
||||
Map<String, Object> execute(Object id, String method, List<Object> params)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.emeraldpay.dshackle.testing.trial
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.google.common.primitives.Bytes
|
||||
import com.google.protobuf.ByteString
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.api.proto.ReactorBlockchainGrpc
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.grpc.ManagedChannel
|
||||
import io.grpc.netty.NettyChannelBuilder
|
||||
|
||||
class ProtoClient implements Client {
|
||||
private int sequence = 0;
|
||||
private ObjectMapper objectMapper;
|
||||
private ReactorBlockchainGrpc.ReactorBlockchainStub stub
|
||||
private Chain chain
|
||||
|
||||
ProtoClient(ManagedChannel channel, Chain chain) {
|
||||
this.stub = ReactorBlockchainGrpc.newReactorStub(channel)
|
||||
this.objectMapper = new ObjectMapper()
|
||||
this.chain= chain
|
||||
}
|
||||
|
||||
static ProtoClient create(String host, int port, Chain chain) {
|
||||
def channel = NettyChannelBuilder.forAddress(host, port)
|
||||
.maxInboundMessageSize(Integer.MAX_VALUE)
|
||||
.usePlaintext()
|
||||
new ProtoClient(channel.build(), chain)
|
||||
}
|
||||
|
||||
static ProtoClient basic() {
|
||||
create("localhost", 12448, Chain.ETHEREUM)
|
||||
}
|
||||
|
||||
BlockchainOuterClass.NativeCallReplyItem executeNative(String method, List<Object> params, Long nonce) {
|
||||
def req = BlockchainOuterClass.NativeCallRequest
|
||||
.newBuilder()
|
||||
.setChain(Common.ChainRef.CHAIN_ETHEREUM)
|
||||
.addItems(BlockchainOuterClass.NativeCallItem
|
||||
.newBuilder()
|
||||
.setId(0)
|
||||
.setNonce(nonce)
|
||||
.setMethod(method)
|
||||
.setPayload(ByteString.copyFrom(objectMapper.writeValueAsBytes(params)))
|
||||
.build()
|
||||
).build()
|
||||
stub.nativeCall(req)
|
||||
.single()
|
||||
.block()
|
||||
}
|
||||
|
||||
Map<String, Object> execute(String method, List<Object> params) {
|
||||
return execute(sequence++, method, params)
|
||||
}
|
||||
Map<String, Object> execute(Object id, String method, List<Object> params) {
|
||||
def result = executeNative(method, params, 0L)
|
||||
if (result.errorMessage != "") {
|
||||
return [error: result.errorMessage]
|
||||
} else {
|
||||
return objectMapper.readerFor(Map).readValue(Bytes.concat("{\"result\": ".bytes, result.payload.toByteArray(), "}".bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import org.apache.http.impl.client.CloseableHttpClient
|
||||
import org.apache.http.impl.client.HttpClientBuilder
|
||||
import org.apache.http.impl.client.HttpClients
|
||||
|
||||
class ProxyClient {
|
||||
class ProxyClient implements Client {
|
||||
|
||||
private int sequence = 0;
|
||||
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
package io.emeraldpay.dshackle.testing.trial.basicproxy
|
||||
|
||||
import com.google.common.primitives.Bytes
|
||||
import com.google.common.primitives.Longs
|
||||
import io.emeraldpay.dshackle.testing.trial.ProtoClient
|
||||
import io.emeraldpay.dshackle.testing.trial.ProxyClient
|
||||
import org.apache.commons.codec.binary.Hex
|
||||
import spock.lang.IgnoreIf
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
import java.security.KeyFactory
|
||||
import org.bouncycastle.util.io.pem.PemReader
|
||||
|
||||
import java.security.MessageDigest
|
||||
import java.security.Signature
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
|
||||
@IgnoreIf({ System.getProperty('trialMode') != 'basic' })
|
||||
class StandardCallsSpec extends Specification {
|
||||
|
||||
def client = ProxyClient.forPrefix("eth")
|
||||
@Shared client_proto = ProtoClient.basic()
|
||||
@Shared client_proxy = ProxyClient.forPrefix("eth")
|
||||
|
||||
@Shared clients = [client_proto, client_proxy]
|
||||
|
||||
def "get height"() {
|
||||
when:
|
||||
@@ -15,6 +30,8 @@ class StandardCallsSpec extends Specification {
|
||||
then:
|
||||
act.result == "0x100001"
|
||||
act.error == null
|
||||
where:
|
||||
client << clients
|
||||
}
|
||||
|
||||
def "get block"() {
|
||||
@@ -31,6 +48,8 @@ class StandardCallsSpec extends Specification {
|
||||
]
|
||||
}
|
||||
act.error == null
|
||||
where:
|
||||
client << clients
|
||||
}
|
||||
|
||||
def "get non-existing block"() {
|
||||
@@ -39,6 +58,8 @@ class StandardCallsSpec extends Specification {
|
||||
then:
|
||||
act.result == null
|
||||
act.error == null
|
||||
where:
|
||||
client << clients
|
||||
}
|
||||
|
||||
def "get tx"() {
|
||||
@@ -50,6 +71,8 @@ class StandardCallsSpec extends Specification {
|
||||
blockHash == "0x9a834c53bbee9c2665a5a84789a1d1ad73750b2d77b50de44f457f411d02e52e"
|
||||
}
|
||||
act.error == null
|
||||
where:
|
||||
client << clients
|
||||
}
|
||||
|
||||
def "get non-existing tx"() {
|
||||
@@ -58,6 +81,8 @@ class StandardCallsSpec extends Specification {
|
||||
then:
|
||||
act.result == null
|
||||
act.error == null
|
||||
where:
|
||||
client << clients
|
||||
}
|
||||
|
||||
def "get block with txes"() {
|
||||
@@ -76,6 +101,8 @@ class StandardCallsSpec extends Specification {
|
||||
}
|
||||
}
|
||||
act.error == null
|
||||
where:
|
||||
client << clients
|
||||
}
|
||||
|
||||
def "returns original block json"() {
|
||||
@@ -87,6 +114,8 @@ class StandardCallsSpec extends Specification {
|
||||
testFoo == "bar"
|
||||
}
|
||||
act.error == null
|
||||
where:
|
||||
client << clients
|
||||
}
|
||||
|
||||
def "returns original block json with tx"() {
|
||||
@@ -98,5 +127,33 @@ class StandardCallsSpec extends Specification {
|
||||
testFoo == "bar"
|
||||
}
|
||||
act.error == null
|
||||
where:
|
||||
client << clients
|
||||
}
|
||||
|
||||
def "check response signature with nonce"() {
|
||||
when:
|
||||
def act = client_proto.executeNative("eth_blockNumber", [], 10)
|
||||
def keyFactory = KeyFactory.getInstance("EC")
|
||||
def key = new File(System.getProperty('signatureKey'))
|
||||
def reader = new PemReader(key.newReader())
|
||||
def keySpec = new X509EncodedKeySpec(reader.readPemObject().getContent())
|
||||
def pubKey = keyFactory.generatePublic(keySpec)
|
||||
def sig = Signature.getInstance("SHA256withECDSA")
|
||||
sig.initVerify(pubKey)
|
||||
def sep = "/".bytes
|
||||
def digest = MessageDigest.getInstance("SHA-256")
|
||||
def messageHash = digest.digest(act.payload.toByteArray())
|
||||
sig.update(Bytes.concat("DSHACKLESIG".bytes, sep, Longs.toByteArray(10), sep, messageHash))
|
||||
then:
|
||||
(new String(act.payload.toByteArray())) == "\"0x100001\""
|
||||
sig.verify(act.signature.sig.toByteArray())
|
||||
}
|
||||
|
||||
def "check response signature without nonce"() {
|
||||
when:
|
||||
def act = client_proto.executeNative("eth_blockNumber", [], 0L)
|
||||
then:
|
||||
act.signature.sig.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user