mirror of
https://github.com/corda/corda.git
synced 2025-01-20 03:36:29 +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.WireTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.parsePublicKeyBase58
|
||||
import net.corda.core.utilities.toBase58String
|
||||
import net.corda.core.utilities.base58ToByteArray
|
||||
import net.corda.core.utilities.base64ToByteArray
|
||||
import net.corda.core.utilities.toBase64
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
import java.math.BigDecimal
|
||||
import java.security.PublicKey
|
||||
@ -83,13 +84,9 @@ object JacksonSupport {
|
||||
addDeserializer(SecureHash::class.java, SecureHashDeserializer())
|
||||
addDeserializer(SecureHash.SHA256::class.java, SecureHashDeserializer())
|
||||
|
||||
// For ed25519 pubkeys
|
||||
addSerializer(EdDSAPublicKey::class.java, PublicKeySerializer)
|
||||
addDeserializer(EdDSAPublicKey::class.java, PublicKeyDeserializer)
|
||||
|
||||
// For composite keys
|
||||
addSerializer(CompositeKey::class.java, CompositeKeySerializer)
|
||||
addDeserializer(CompositeKey::class.java, CompositeKeyDeserializer)
|
||||
// Public key types
|
||||
addSerializer(PublicKey::class.java, PublicKeySerializer)
|
||||
addDeserializer(PublicKey::class.java, PublicKeyDeserializer)
|
||||
|
||||
// For NodeInfo
|
||||
// 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>() {
|
||||
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()
|
||||
}
|
||||
|
||||
// TODO this needs to use some industry identifier(s) instead of these keys
|
||||
val key = parsePublicKeyBase58(parser.text)
|
||||
val key = PublicKeyDeserializer.deserialize(parser, context)
|
||||
return AnonymousParty(key)
|
||||
}
|
||||
}
|
||||
@ -187,19 +183,24 @@ object JacksonSupport {
|
||||
}
|
||||
|
||||
val mapper = parser.codec as PartyObjectMapper
|
||||
// TODO: We should probably have a better specified way of identifying X.500 names vs keys
|
||||
// Base58 keys never include an equals character, while X.500 names always will, so we use that to determine
|
||||
// how to parse the content
|
||||
return if (parser.text.contains("=")) {
|
||||
// The comma character is invalid in base64, and required as a separator for X.500 names. As Corda
|
||||
// X.500 names all involve at least three attributes (organisation, locality, country), they must
|
||||
// include a comma. As such we can use it as a distinguisher between the two types.
|
||||
return if (parser.text.contains(",")) {
|
||||
val principal = CordaX500Name.parse(parser.text)
|
||||
mapper.wellKnownPartyFromX500Name(principal) ?: throw JsonParseException(parser, "Could not find a Party with name $principal")
|
||||
} else {
|
||||
val nameMatches = mapper.partiesFromName(parser.text)
|
||||
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 {
|
||||
parsePublicKeyBase58(parser.text)
|
||||
Crypto.decodePublicKey(derBytes)
|
||||
} 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()}")
|
||||
} else if (nameMatches.size == 1) {
|
||||
@ -273,39 +274,23 @@ object JacksonSupport {
|
||||
}
|
||||
}
|
||||
|
||||
object PublicKeySerializer : JsonSerializer<EdDSAPublicKey>() {
|
||||
override fun serialize(obj: EdDSAPublicKey, generator: JsonGenerator, provider: SerializerProvider) {
|
||||
check(obj.params == Crypto.EDDSA_ED25519_SHA512.algSpec)
|
||||
generator.writeString(obj.toBase58String())
|
||||
object PublicKeySerializer : JsonSerializer<PublicKey>() {
|
||||
override fun serialize(obj: PublicKey, generator: JsonGenerator, provider: SerializerProvider) {
|
||||
generator.writeString(obj.encoded.toBase64())
|
||||
}
|
||||
}
|
||||
|
||||
object PublicKeyDeserializer : JsonDeserializer<EdDSAPublicKey>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): EdDSAPublicKey {
|
||||
object PublicKeyDeserializer : JsonDeserializer<PublicKey>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext): PublicKey {
|
||||
return try {
|
||||
parsePublicKeyBase58(parser.text) as EdDSAPublicKey
|
||||
val derBytes = parser.text.base64ToByteArray()
|
||||
Crypto.decodePublicKey(derBytes)
|
||||
} catch (e: Exception) {
|
||||
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<*>>() {
|
||||
override fun serialize(value: Amount<*>, gen: JsonGenerator, serializers: SerializerProvider) {
|
||||
gen.writeString(value.toString())
|
||||
|
@ -14,15 +14,16 @@ import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.MINI_CORP
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.math.BigInteger
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class JacksonSupportTest : TestDependencyInjectionBase() {
|
||||
companion object {
|
||||
private val SEED = BigInteger.valueOf(20170922L)
|
||||
val mapper = JacksonSupport.createNonRpcMapper()
|
||||
}
|
||||
|
||||
@ -37,15 +38,34 @@ class JacksonSupportTest : TestDependencyInjectionBase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun publicKeySerializingWorks() {
|
||||
val publicKey = generateKeyPair().public
|
||||
fun `should serialize Composite keys`() {
|
||||
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 parsedKey = mapper.readValue(serialized, EdDSAPublicKey::class.java)
|
||||
assertEquals(expected, serialized)
|
||||
val parsedKey = mapper.readValue(serialized, PublicKey::class.java)
|
||||
assertEquals(publicKey, parsedKey)
|
||||
}
|
||||
|
||||
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
|
||||
fun readAmount() {
|
||||
val oldJson = """
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"fixedLeg": {
|
||||
"fixedRatePayer": "8Kqd4oWdx4KQGHGR7xcgpFf9JmP6HiXqTf85NpSgdSu431EGEhujA6ePaFD",
|
||||
"fixedRatePayer": "MCowBQYDK2VwAyEAzswVB9wd3XKVlRwpCIjwla25BE0bc9aW5t8GXWg71Pw=",
|
||||
"notional": "$25000000",
|
||||
"paymentFrequency": "SemiAnnual",
|
||||
"effectiveDate": "2016-03-11",
|
||||
@ -22,7 +22,7 @@
|
||||
"interestPeriodAdjustment": "Adjusted"
|
||||
},
|
||||
"floatingLeg": {
|
||||
"floatingRatePayer": "8Kqd4oWdx4KQGHGJSFTX4kdZukmHohBRN3gvPekticL4eHTdmbJTVZNZJUj",
|
||||
"floatingRatePayer": "MCowBQYDK2VwAyEAa3nFfmoJUjkoLASBjpYRLz8DpAAbqXpWTCOFKj8epfw=",
|
||||
"notional": {
|
||||
"quantity": 2500000000,
|
||||
"token": "USD"
|
||||
@ -71,7 +71,7 @@
|
||||
"notificationTime": "2:00pm London",
|
||||
"resolutionTime": "2:00pm London time on the first LocalBusiness Day following the date on which the notice is given ",
|
||||
"interestRate": {
|
||||
"oracle": "Rates Service Provider",
|
||||
"oracle": "C=ES,L=Madrid,O=Rates Service Provider",
|
||||
"tenor": {
|
||||
"name": "6M"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user