problem: cannot verify signature in browser
solution: support NIST P-256 curve which is supported by browser's webcrypto
This commit is contained in:
committed by
GitHub
parent
aeea27bb70
commit
de5720897a
@@ -578,7 +578,7 @@ signed-response:
|
|||||||
|
|
||||||
| `algorithm`
|
| `algorithm`
|
||||||
| `SECP256K1`
|
| `SECP256K1`
|
||||||
| SECP256K1 only possible at this moment
|
| `SECP256K1` or `NIST-P256`
|
||||||
|
|
||||||
| `private-key`
|
| `private-key`
|
||||||
|
|
|
|
||||||
|
|||||||
@@ -5,13 +5,25 @@ import java.util.Locale
|
|||||||
class SignatureConfig {
|
class SignatureConfig {
|
||||||
|
|
||||||
enum class Algorithm {
|
enum class Algorithm {
|
||||||
SECP256K1
|
SECP256K1,
|
||||||
|
NIST_P256;
|
||||||
|
|
||||||
|
fun getCurveName(): String {
|
||||||
|
return if (this == SECP256K1) {
|
||||||
|
"secp256k1"
|
||||||
|
} else if (this == NIST_P256) {
|
||||||
|
"secp256r1"
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun algorithmOfString(algo: String): Algorithm {
|
fun algorithmOfString(algo: String): Algorithm {
|
||||||
val algorithm = when (algo.uppercase(Locale.getDefault())) {
|
val algorithm = when (algo.uppercase(Locale.getDefault())) {
|
||||||
"SECP256K1" -> Algorithm.SECP256K1
|
"SECP256K1" -> Algorithm.SECP256K1
|
||||||
|
"NIST_P256", "NIST-P256", "NISTP256", "SECP256R1" -> Algorithm.NIST_P256
|
||||||
else -> throw IllegalArgumentException("Unknown algorithm or not allowed")
|
else -> throw IllegalArgumentException("Unknown algorithm or not allowed")
|
||||||
}
|
}
|
||||||
return algorithm
|
return algorithm
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import java.security.MessageDigest
|
|||||||
import java.security.Signature
|
import java.security.Signature
|
||||||
import java.security.interfaces.ECPrivateKey
|
import java.security.interfaces.ECPrivateKey
|
||||||
|
|
||||||
class Secp256KSigner(
|
class EcdsaSigner(
|
||||||
private val privateKey: ECPrivateKey,
|
private val privateKey: ECPrivateKey,
|
||||||
val keyId: Long,
|
val keyId: Long,
|
||||||
) : ResponseSigner {
|
) : ResponseSigner {
|
||||||
@@ -37,28 +37,32 @@ open class ResponseSignerFactory(
|
|||||||
private fun readKey(algorithm: SignatureConfig.Algorithm, pem: PemObject): Pair<ECPrivateKey, Long> {
|
private fun readKey(algorithm: SignatureConfig.Algorithm, pem: PemObject): Pair<ECPrivateKey, Long> {
|
||||||
val keyFactory = KeyFactory.getInstance("EC")
|
val keyFactory = KeyFactory.getInstance("EC")
|
||||||
val key = when (algorithm) {
|
val key = when (algorithm) {
|
||||||
SignatureConfig.Algorithm.SECP256K1 -> {
|
SignatureConfig.Algorithm.SECP256K1, SignatureConfig.Algorithm.NIST_P256 -> {
|
||||||
val keySpec = PKCS8EncodedKeySpec(pem.content)
|
val keySpec = PKCS8EncodedKeySpec(pem.content)
|
||||||
keyFactory.generatePrivate(keySpec)
|
keyFactory.generatePrivate(keySpec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key !is ECPrivateKey) {
|
if (key !is ECPrivateKey) {
|
||||||
throw IllegalStateException("Only ECDSA SECP256K1 keys are allowed")
|
throw IllegalStateException("Only EC keys are allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.params.toString() != "secp256k1 (1.3.132.0.10)") {
|
if (algorithm == SignatureConfig.Algorithm.SECP256K1 && key.params.toString().indexOf(SignatureConfig.Algorithm.SECP256K1.getCurveName()) < 0) {
|
||||||
throw IllegalStateException("Only SECP256K1 are allowed for signing a response")
|
throw IllegalStateException("Key is not SECP256K1, generate SECP256K1 or use another algorithm")
|
||||||
}
|
}
|
||||||
|
|
||||||
val publicKey = extractPublicKey(keyFactory, key)
|
if (algorithm == SignatureConfig.Algorithm.NIST_P256 && key.params.toString().indexOf(SignatureConfig.Algorithm.NIST_P256.getCurveName()) < 0) {
|
||||||
|
throw IllegalStateException("Key is not NIST P256, generate NIST P256 or use another algorithm")
|
||||||
|
}
|
||||||
|
|
||||||
|
val publicKey = extractPublicKey(keyFactory, key, algorithm)
|
||||||
val id = getPublicKeyId(publicKey)
|
val id = getPublicKeyId(publicKey)
|
||||||
|
|
||||||
return Pair(key, id)
|
return Pair(key, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun extractPublicKey(keyFactory: KeyFactory, privateKey: ECPrivateKey): PublicKey {
|
fun extractPublicKey(keyFactory: KeyFactory, privateKey: ECPrivateKey, algorithm: SignatureConfig.Algorithm): PublicKey {
|
||||||
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
val ecSpec = ECNamedCurveTable.getParameterSpec(algorithm.getCurveName())
|
||||||
val q: ECPoint = ecSpec.g.multiply(privateKey.s)
|
val q: ECPoint = ecSpec.g.multiply(privateKey.s)
|
||||||
return keyFactory.generatePublic(ECPublicKeySpec(q, ecSpec))
|
return keyFactory.generatePublic(ECPublicKeySpec(q, ecSpec))
|
||||||
}
|
}
|
||||||
@@ -79,7 +83,7 @@ open class ResponseSignerFactory(
|
|||||||
return NoSigner()
|
return NoSigner()
|
||||||
}
|
}
|
||||||
val key = readKey(config.algorithm, config.privateKey!!)
|
val key = readKey(config.algorithm, config.privateKey!!)
|
||||||
return Secp256KSigner(key.first, key.second)
|
return EcdsaSigner(key.first, key.second)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getObjectType(): Class<*>? {
|
override fun getObjectType(): Class<*>? {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package io.emeraldpay.dshackle.upstream.signature
|
package io.emeraldpay.dshackle.upstream.signature
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.config.SignatureConfig
|
import io.emeraldpay.dshackle.config.SignatureConfig
|
||||||
import io.emeraldpay.dshackle.config.SignatureConfigReader
|
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
import org.apache.commons.codec.binary.Hex
|
import org.apache.commons.codec.binary.Hex
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
@@ -13,14 +11,13 @@ import spock.lang.Specification
|
|||||||
import java.security.KeyFactory
|
import java.security.KeyFactory
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.security.Security
|
import java.security.Security
|
||||||
import java.security.Signature
|
import java.security.Signature
|
||||||
import java.security.interfaces.ECPrivateKey
|
import java.security.interfaces.ECPrivateKey
|
||||||
import java.security.spec.ECGenParameterSpec
|
import java.security.spec.ECGenParameterSpec
|
||||||
import java.security.spec.PKCS8EncodedKeySpec
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
|
|
||||||
class Secp256KSignerSpec extends Specification {
|
class EcdsaSignerSpec extends Specification {
|
||||||
|
|
||||||
def setupSpec() {
|
def setupSpec() {
|
||||||
Security.addProvider(new BouncyCastleProvider())
|
Security.addProvider(new BouncyCastleProvider())
|
||||||
@@ -48,12 +45,34 @@ class Secp256KSignerSpec extends Specification {
|
|||||||
file.delete()
|
file.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def "Reads private key NIST P256"() {
|
||||||
|
setup:
|
||||||
|
def file = File.createTempFile("test", ".pem")
|
||||||
|
def keygen = KeyPairGenerator.getInstance("EC")
|
||||||
|
keygen.initialize(new ECGenParameterSpec("secp256r1"))
|
||||||
|
def key = keygen.generateKeyPair()
|
||||||
|
def keyBuilder = new PKCS8EncodedKeySpec(key.getPrivate().getEncoded())
|
||||||
|
def writer = new PemWriter(new FileWriter(file.path))
|
||||||
|
writer.writeObject(new PemObject("PRIVATE KEY", keyBuilder.getEncoded()))
|
||||||
|
writer.close()
|
||||||
|
|
||||||
|
when:
|
||||||
|
def signer = new ResponseSignerFactory(new SignatureConfig())
|
||||||
|
def act = signer.readKey(SignatureConfig.Algorithm.NIST_P256, file.absolutePath).first
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == key.getPrivate()
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
|
||||||
def "Id is a hash of x509 public key"() {
|
def "Id is a hash of x509 public key"() {
|
||||||
setup:
|
setup:
|
||||||
def conf = new SignatureConfig()
|
def conf = new SignatureConfig()
|
||||||
conf.enabled = true
|
conf.enabled = true
|
||||||
conf.privateKey = "testing/dshackle/test_key"
|
conf.privateKey = "testing/dshackle/test_key"
|
||||||
def signer = new ResponseSignerFactory(conf).getObject() as Secp256KSigner
|
def signer = new ResponseSignerFactory(conf).getObject() as EcdsaSigner
|
||||||
|
|
||||||
// To verify the test, check the hash of test key above:
|
// To verify the test, check the hash of test key above:
|
||||||
//
|
//
|
||||||
@@ -73,7 +92,7 @@ class Secp256KSignerSpec extends Specification {
|
|||||||
def up = Mock(Upstream) {
|
def up = Mock(Upstream) {
|
||||||
_ * getId() >> "infura"
|
_ * getId() >> "infura"
|
||||||
}
|
}
|
||||||
def signer = new Secp256KSigner(Stub(ECPrivateKey), 100L)
|
def signer = new EcdsaSigner(Stub(ECPrivateKey), 100L)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = signer.wrapMessage(10, "test".bytes, up)
|
def act = signer.wrapMessage(10, "test".bytes, up)
|
||||||
@@ -96,7 +115,7 @@ class Secp256KSignerSpec extends Specification {
|
|||||||
verifier.initVerify(pair.getPublic())
|
verifier.initVerify(pair.getPublic())
|
||||||
verifier.update("DSHACKLESIG/10/infura/9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08".getBytes())
|
verifier.update("DSHACKLESIG/10/infura/9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08".getBytes())
|
||||||
|
|
||||||
def signer = new Secp256KSigner((pair.getPrivate() as ECPrivateKey), 100L)
|
def signer = new EcdsaSigner((pair.getPrivate() as ECPrivateKey), 100L)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def sig = signer.sign(10, result, up)
|
def sig = signer.sign(10, result, up)
|
||||||
@@ -121,12 +140,12 @@ class Secp256KSignerSpec extends Specification {
|
|||||||
def factory = new ResponseSignerFactory(conf)
|
def factory = new ResponseSignerFactory(conf)
|
||||||
|
|
||||||
def sk = factory.readKey(conf.algorithm, conf.privateKey).first
|
def sk = factory.readKey(conf.algorithm, conf.privateKey).first
|
||||||
def pk = factory.extractPublicKey(KeyFactory.getInstance("EC"), sk)
|
def pk = factory.extractPublicKey(KeyFactory.getInstance("EC"), sk, SignatureConfig.Algorithm.SECP256K1)
|
||||||
def verifier = Signature.getInstance("SHA256withECDSA")
|
def verifier = Signature.getInstance("SHA256withECDSA")
|
||||||
verifier.initVerify(pk)
|
verifier.initVerify(pk)
|
||||||
verifier.update("DSHACKLESIG/10/infura/${Hex.encodeHexString(sha256.digest(result))}".getBytes())
|
verifier.update("DSHACKLESIG/10/infura/${Hex.encodeHexString(sha256.digest(result))}".getBytes())
|
||||||
|
|
||||||
def signer = factory.getObject() as Secp256KSigner
|
def signer = factory.getObject() as EcdsaSigner
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def sig = signer.sign(10, result, up)
|
def sig = signer.sign(10, result, up)
|
||||||
@@ -1,13 +1,25 @@
|
|||||||
package testing
|
package testing
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
|
||||||
class CommonHandlers implements CallHandler {
|
class CommonHandlers implements CallHandler {
|
||||||
|
|
||||||
|
ObjectMapper objectMapper
|
||||||
|
ResourceResponse resourceResponse
|
||||||
|
|
||||||
|
CommonHandlers(ObjectMapper objectMapper) {
|
||||||
|
this.objectMapper = objectMapper
|
||||||
|
this.resourceResponse = new ResourceResponse(objectMapper)
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Result handle(String method, List<Object> params) {
|
Result handle(String method, List<Object> params) {
|
||||||
if (method == "eth_syncing") {
|
if (method == "eth_syncing") {
|
||||||
return Result.ok(false)
|
return Result.ok(false)
|
||||||
}
|
}
|
||||||
|
if (method == "eth_chainId") {
|
||||||
|
return resourceResponse.respondWith("chain-id.json")
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class SimpleUpstream {
|
|||||||
internalHandler = new InternalHandler()
|
internalHandler = new InternalHandler()
|
||||||
|
|
||||||
handlers << new TestcaseHandler(objectMapper)
|
handlers << new TestcaseHandler(objectMapper)
|
||||||
handlers << new CommonHandlers()
|
handlers << new CommonHandlers(objectMapper)
|
||||||
handlers << new BlocksHandler(objectMapper)
|
handlers << new BlocksHandler(objectMapper)
|
||||||
handlers << new PingPongHandler()
|
handlers << new PingPongHandler()
|
||||||
handlers << internalHandler
|
handlers << internalHandler
|
||||||
|
|||||||
5
testing/simple-upstream/src/main/resources/chain-id.json
Normal file
5
testing/simple-upstream/src/main/resources/chain-id.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"id": 83,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": "0x3d"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user