problem: cannot verify signature in browser

solution: support NIST P-256 curve which is supported by browser's webcrypto
This commit is contained in:
Vyacheslav Shebanov
2022-06-17 05:08:18 +03:00
committed by GitHub
parent aeea27bb70
commit de5720897a
8 changed files with 73 additions and 21 deletions

View File

@@ -578,7 +578,7 @@ signed-response:
| `algorithm` | `algorithm`
| `SECP256K1` | `SECP256K1`
| SECP256K1 only possible at this moment | `SECP256K1` or `NIST-P256`
| `private-key` | `private-key`
| |

View File

@@ -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

View File

@@ -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 {

View File

@@ -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<*>? {

View File

@@ -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)

View File

@@ -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
} }
} }

View File

@@ -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

View File

@@ -0,0 +1,5 @@
{
"id": 83,
"jsonrpc": "2.0",
"result": "0x3d"
}