mirror of
https://github.com/corda/corda.git
synced 2025-01-20 11:39:09 +00:00
Enforce JSON serialized key format (#1606)
Move Jackson public key encode/decode support away from Kryo serialization format for compactness and to DER format encoded as base64 for compatibility with other systems.
This commit is contained in:
parent
24ff7efd5f
commit
aff4d35ccb
@ -28,8 +28,9 @@ import net.corda.core.transactions.NotaryChangeWireTransaction
|
|||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.utilities.parsePublicKeyBase58
|
import net.corda.core.utilities.base58ToByteArray
|
||||||
import net.corda.core.utilities.toBase58String
|
import net.corda.core.utilities.base64ToByteArray
|
||||||
|
import net.corda.core.utilities.toBase64
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -83,13 +84,9 @@ object JacksonSupport {
|
|||||||
addDeserializer(SecureHash::class.java, SecureHashDeserializer())
|
addDeserializer(SecureHash::class.java, SecureHashDeserializer())
|
||||||
addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
|
addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
|
||||||
|
|
||||||
// For ed25519 pubkeys
|
// Public key types
|
||||||
addSerializer(EdDSAPublicKey::class.java, PublicKeySerializer)
|
addSerializer(PublicKey::class.java, PublicKeySerializer)
|
||||||
addDeserializer(EdDSAPublicKey::class.java, PublicKeyDeserializer)
|
addDeserializer(PublicKey::class.java, PublicKeyDeserializer)
|
||||||
|
|
||||||
// For composite keys
|
|
||||||
addSerializer(CompositeKey::class.java, CompositeKeySerializer)
|
|
||||||
addDeserializer(CompositeKey::class.java, CompositeKeyDeserializer)
|
|
||||||
|
|
||||||
// For NodeInfo
|
// For NodeInfo
|
||||||
// TODO this tunnels the Kryo representation as a Base58 encoded string. Replace when RPC supports this.
|
// TODO this tunnels the Kryo representation as a Base58 encoded string. Replace when RPC supports this.
|
||||||
@ -158,7 +155,7 @@ object JacksonSupport {
|
|||||||
|
|
||||||
object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() {
|
object AnonymousPartySerializer : JsonSerializer<AnonymousParty>() {
|
||||||
override fun serialize(obj: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) {
|
override fun serialize(obj: AnonymousParty, generator: JsonGenerator, provider: SerializerProvider) {
|
||||||
generator.writeString(obj.owningKey.toBase58String())
|
PublicKeySerializer.serialize(obj.owningKey, generator, provider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,8 +165,7 @@ object JacksonSupport {
|
|||||||
parser.nextToken()
|
parser.nextToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this needs to use some industry identifier(s) instead of these keys
|
val key = PublicKeyDeserializer.deserialize(parser, context)
|
||||||
val key = parsePublicKeyBase58(parser.text)
|
|
||||||
return AnonymousParty(key)
|
return AnonymousParty(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,19 +183,24 @@ object JacksonSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val mapper = parser.codec as PartyObjectMapper
|
val mapper = parser.codec as PartyObjectMapper
|
||||||
// TODO: We should probably have a better specified way of identifying X.500 names vs keys
|
// The comma character is invalid in base64, and required as a separator for X.500 names. As Corda
|
||||||
// Base58 keys never include an equals character, while X.500 names always will, so we use that to determine
|
// X.500 names all involve at least three attributes (organisation, locality, country), they must
|
||||||
// how to parse the content
|
// include a comma. As such we can use it as a distinguisher between the two types.
|
||||||
return if (parser.text.contains("=")) {
|
return if (parser.text.contains(",")) {
|
||||||
val principal = CordaX500Name.parse(parser.text)
|
val principal = CordaX500Name.parse(parser.text)
|
||||||
mapper.wellKnownPartyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name $principal")
|
mapper.wellKnownPartyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name $principal")
|
||||||
} else {
|
} else {
|
||||||
val nameMatches = mapper.partiesFromName(parser.text)
|
val nameMatches = mapper.partiesFromName(parser.text)
|
||||||
if (nameMatches.isEmpty()) {
|
if (nameMatches.isEmpty()) {
|
||||||
|
val derBytes = try {
|
||||||
|
parser.text.base64ToByteArray()
|
||||||
|
} catch (e: AddressFormatException) {
|
||||||
|
throw JsonParseException(parser, "Could not find a matching party for '${parser.text}' and is not a base64 encoded public key: " + e.message)
|
||||||
|
}
|
||||||
val key = try {
|
val key = try {
|
||||||
parsePublicKeyBase58(parser.text)
|
Crypto.decodePublicKey(derBytes)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw JsonParseException(parser, "Could not find a matching party for '${parser.text}' and is not a base58 encoded public key")
|
throw JsonParseException(parser, "Could not find a matching party for '${parser.text}' and is not a valid public key: " + e.message)
|
||||||
}
|
}
|
||||||
mapper.partyFromKey(key) ?: throw JsonParseException(parser, "Could not find a Party with key ${key.toStringShort()}")
|
mapper.partyFromKey(key) ?: throw JsonParseException(parser, "Could not find a Party with key ${key.toStringShort()}")
|
||||||
} else if (nameMatches.size == 1) {
|
} else if (nameMatches.size == 1) {
|
||||||
@ -273,39 +274,23 @@ object JacksonSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object PublicKeySerializer : JsonSerializer<EdDSAPublicKey>() {
|
object PublicKeySerializer : JsonSerializer<PublicKey>() {
|
||||||
override fun serialize(obj: EdDSAPublicKey, generator: JsonGenerator, provider: SerializerProvider) {
|
override fun serialize(obj: PublicKey, generator: JsonGenerator, provider: SerializerProvider) {
|
||||||
check(obj.params == Crypto.EDDSA_ED25519_SHA512.algSpec)
|
generator.writeString(obj.encoded.toBase64())
|
||||||
generator.writeString(obj.toBase58String())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object PublicKeyDeserializer : JsonDeserializer<EdDSAPublicKey>() {
|
object PublicKeyDeserializer : JsonDeserializer<PublicKey>() {
|
||||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): EdDSAPublicKey {
|
override fun deserialize(parser: JsonParser, context: DeserializationContext): PublicKey {
|
||||||
return try {
|
return try {
|
||||||
parsePublicKeyBase58(parser.text) as EdDSAPublicKey
|
val derBytes = parser.text.base64ToByteArray()
|
||||||
|
Crypto.decodePublicKey(derBytes)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw JsonParseException(parser, "Invalid public key ${parser.text}: ${e.message}")
|
throw JsonParseException(parser, "Invalid public key ${parser.text}: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object CompositeKeySerializer : JsonSerializer<CompositeKey>() {
|
|
||||||
override fun serialize(obj: CompositeKey, generator: JsonGenerator, provider: SerializerProvider) {
|
|
||||||
generator.writeString(obj.toBase58String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object CompositeKeyDeserializer : JsonDeserializer<CompositeKey>() {
|
|
||||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): CompositeKey {
|
|
||||||
return try {
|
|
||||||
parsePublicKeyBase58(parser.text) as CompositeKey
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw JsonParseException(parser, "Invalid composite key ${parser.text}: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object AmountSerializer : JsonSerializer<Amount<*>>() {
|
object AmountSerializer : JsonSerializer<Amount<*>>() {
|
||||||
override fun serialize(value: Amount<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
override fun serialize(value: Amount<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||||
gen.writeString(value.toString())
|
gen.writeString(value.toString())
|
||||||
|
@ -14,15 +14,16 @@ import net.corda.testing.DUMMY_NOTARY
|
|||||||
import net.corda.testing.MINI_CORP
|
import net.corda.testing.MINI_CORP
|
||||||
import net.corda.testing.TestDependencyInjectionBase
|
import net.corda.testing.TestDependencyInjectionBase
|
||||||
import net.corda.testing.contracts.DummyContract
|
import net.corda.testing.contracts.DummyContract
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.reflect.jvm.jvmName
|
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class JacksonSupportTest : TestDependencyInjectionBase() {
|
class JacksonSupportTest : TestDependencyInjectionBase() {
|
||||||
companion object {
|
companion object {
|
||||||
|
private val SEED = BigInteger.valueOf(20170922L)
|
||||||
val mapper = JacksonSupport.createNonRpcMapper()
|
val mapper = JacksonSupport.createNonRpcMapper()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,15 +38,34 @@ class JacksonSupportTest : TestDependencyInjectionBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun publicKeySerializingWorks() {
|
fun `should serialize Composite keys`() {
|
||||||
val publicKey = generateKeyPair().public
|
val expected = "\"MIHAMBUGE2mtoq+J1bjir/ONk6yd5pab0FoDgaYAMIGiAgECMIGcMDIDLQAwKjAFBgMrZXADIQAgIX1QlJRgaLlD0ttLlJF5kNqT/7P7QwCvrWc9+/248gIBATAyAy0AMCowBQYDK2VwAyEAqS0JPGlzdviBZjB9FaNY+w6cVs3/CQ2A5EimE9Lyng4CAQEwMgMtADAqMAUGAytlcAMhALq4GG0gBQZIlaKE6ucooZsuoKUbH4MtGSmA6cwj136+AgEB\""
|
||||||
|
val innerKeys = (1..3).map { i ->
|
||||||
|
Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, SEED.plus(BigInteger.valueOf(i.toLong()))).public
|
||||||
|
}
|
||||||
|
// Build a 2 of 3 composite key
|
||||||
|
val publicKey = CompositeKey.Builder().let {
|
||||||
|
innerKeys.forEach { key -> it.addKey(key, 1) }
|
||||||
|
it.build(2)
|
||||||
|
}
|
||||||
val serialized = mapper.writeValueAsString(publicKey)
|
val serialized = mapper.writeValueAsString(publicKey)
|
||||||
val parsedKey = mapper.readValue(serialized, EdDSAPublicKey::class.java)
|
assertEquals(expected, serialized)
|
||||||
|
val parsedKey = mapper.readValue(serialized, PublicKey::class.java)
|
||||||
assertEquals(publicKey, parsedKey)
|
assertEquals(publicKey, parsedKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Dummy(val notional: Amount<Currency>)
|
private class Dummy(val notional: Amount<Currency>)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should serialize EdDSA keys`() {
|
||||||
|
val expected = "\"MCowBQYDK2VwAyEACFTgLk1NOqYXAfxLoR7ctSbZcl9KMXu58Mq31Kv1Dwk=\""
|
||||||
|
val publicKey = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, SEED).public
|
||||||
|
val serialized = mapper.writeValueAsString(publicKey)
|
||||||
|
assertEquals(expected, serialized)
|
||||||
|
val parsedKey = mapper.readValue(serialized, PublicKey::class.java)
|
||||||
|
assertEquals(publicKey, parsedKey)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun readAmount() {
|
fun readAmount() {
|
||||||
val oldJson = """
|
val oldJson = """
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"fixedLeg": {
|
"fixedLeg": {
|
||||||
"fixedRatePayer": "8Kqd4oWdx4KQGHGR7xcgpFf9JmP6HiXqTf85NpSgdSu431EGEhujA6ePaFD",
|
"fixedRatePayer": "MCowBQYDK2VwAyEAzswVB9wd3XKVlRwpCIjwla25BE0bc9aW5t8GXWg71Pw=",
|
||||||
"notional": "$25000000",
|
"notional": "$25000000",
|
||||||
"paymentFrequency": "SemiAnnual",
|
"paymentFrequency": "SemiAnnual",
|
||||||
"effectiveDate": "2016-03-11",
|
"effectiveDate": "2016-03-11",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"interestPeriodAdjustment": "Adjusted"
|
"interestPeriodAdjustment": "Adjusted"
|
||||||
},
|
},
|
||||||
"floatingLeg": {
|
"floatingLeg": {
|
||||||
"floatingRatePayer": "8Kqd4oWdx4KQGHGJSFTX4kdZukmHohBRN3gvPekticL4eHTdmbJTVZNZJUj",
|
"floatingRatePayer": "MCowBQYDK2VwAyEAa3nFfmoJUjkoLASBjpYRLz8DpAAbqXpWTCOFKj8epfw=",
|
||||||
"notional": {
|
"notional": {
|
||||||
"quantity": 2500000000,
|
"quantity": 2500000000,
|
||||||
"token": "USD"
|
"token": "USD"
|
||||||
@ -71,7 +71,7 @@
|
|||||||
"notificationTime": "2:00pm London",
|
"notificationTime": "2:00pm London",
|
||||||
"resolutionTime": "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
|
"resolutionTime": "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
|
||||||
"interestRate": {
|
"interestRate": {
|
||||||
"oracle": "Rates Service Provider",
|
"oracle": "C=ES,L=Madrid,O=Rates Service Provider",
|
||||||
"tenor": {
|
"tenor": {
|
||||||
"name": "6M"
|
"name": "6M"
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user