diff --git a/doorman/build.gradle b/doorman/build.gradle index 2ddaa94d26..c117d5d615 100644 --- a/doorman/build.gradle +++ b/doorman/build.gradle @@ -1,7 +1,7 @@ ext { // We use Corda release artifact dependencies instead of project dependencies to make sure each doorman releases are // align with the corresponding Corda release. - corda_dependency_version = '0.16-20170913.101300-6' + corda_dependency_version = '1.0.0' } version "$corda_dependency_version" diff --git a/doorman/src/integration-test/kotlin/com/r3/corda/doorman/DoormanIntegrationTest.kt b/doorman/src/integration-test/kotlin/com/r3/corda/doorman/DoormanIntegrationTest.kt index d0485fa66b..15ab8e6169 100644 --- a/doorman/src/integration-test/kotlin/com/r3/corda/doorman/DoormanIntegrationTest.kt +++ b/doorman/src/integration-test/kotlin/com/r3/corda/doorman/DoormanIntegrationTest.kt @@ -9,8 +9,7 @@ import com.r3.corda.doorman.signer.LocalSigner import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name -import net.corda.core.utilities.cert -import net.corda.core.utilities.subject +import net.corda.core.internal.cert import net.corda.node.utilities.* import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.NetworkRegistrationHelper @@ -31,8 +30,7 @@ class DoormanIntegrationTest { @Test fun `Network Registration With Doorman`() { val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Integration Test Corda Node Root CA", organisation = "R3 Ltd", - locality = "London", country = "GB").x500Name, rootCAKey) + val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Integration Test Corda Node Root CA", organisation = "R3 Ltd", locality = "London", country = "GB"), rootCAKey) val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, CordaX500Name(commonName = "Integration Test Corda Node Intermediate CA", locality = "London", country = "GB", organisation = "R3 Ltd"), intermediateCAKey.public) @@ -62,19 +60,19 @@ class DoormanIntegrationTest { loadKeyStore(config.nodeKeystore, config.keyStorePassword).apply { assert(containsAlias(X509Utilities.CORDA_CLIENT_CA)) - assertEquals(ALICE.name.copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN).x500Name, getX509Certificate(X509Utilities.CORDA_CLIENT_CA).subject) + assertEquals(ALICE.name.copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN).x500Principal, getX509Certificate(X509Utilities.CORDA_CLIENT_CA).subjectX500Principal) assertEquals(listOf(intermediateCACert.cert, rootCACert.cert), getCertificateChain(X509Utilities.CORDA_CLIENT_CA).drop(1).toList()) } loadKeyStore(config.sslKeystore, config.keyStorePassword).apply { assert(containsAlias(X509Utilities.CORDA_CLIENT_TLS)) - assertEquals(ALICE.name.copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN).x500Name, getX509Certificate(X509Utilities.CORDA_CLIENT_TLS).subject) + assertEquals(ALICE.name.x500Principal, getX509Certificate(X509Utilities.CORDA_CLIENT_TLS).subjectX500Principal) assertEquals(listOf(intermediateCACert.cert, rootCACert.cert), getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).drop(2).toList()) } loadKeyStore(config.trustStoreFile, config.trustStorePassword).apply { assert(containsAlias(X509Utilities.CORDA_ROOT_CA)) - assertEquals(rootCACert.cert.subject, getX509Certificate(X509Utilities.CORDA_ROOT_CA).subject) + assertEquals(rootCACert.cert.subjectX500Principal, getX509Certificate(X509Utilities.CORDA_ROOT_CA).subjectX500Principal) } doorman.close() } diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt index 2db1035d8e..c2cb8b7e48 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/Main.kt @@ -9,6 +9,7 @@ import com.r3.corda.doorman.persistence.DBCertificateRequestStorage import com.r3.corda.doorman.persistence.DoormanSchemaService import com.r3.corda.doorman.signer.* import net.corda.core.crypto.Crypto +import net.corda.core.identity.CordaX500Name import net.corda.core.internal.createDirectories import net.corda.core.utilities.loggerFor import net.corda.core.utilities.seconds @@ -16,7 +17,6 @@ import net.corda.node.utilities.* import net.corda.node.utilities.X509Utilities.CORDA_INTERMEDIATE_CA import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA import net.corda.node.utilities.X509Utilities.createCertificate -import org.bouncycastle.asn1.x500.X500Name import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.handler.HandlerCollection @@ -32,7 +32,6 @@ import java.time.Instant import kotlin.concurrent.thread import kotlin.system.exitProcess - /** * DoormanServer runs on Jetty server and provide certificate signing service via http. * The server will require keystorePath, keystore password and key password via command line input. @@ -134,7 +133,7 @@ private fun DoormanParameters.generateRootKeyPair() { } val selfSignKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val selfSignCert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Root CA, O=R3, OU=Corda, L=London, C=GB"), selfSignKey) + val selfSignCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Root CA", organisation = "R3 Ltd", locality = "London", country = "GB", organisationUnit = "Corda", state = null), selfSignKey) rootStore.addOrReplaceKey(CORDA_ROOT_CA, selfSignKey.private, rootPrivateKeyPassword.toCharArray(), arrayOf(selfSignCert)) rootStore.save(rootStorePath, rootKeystorePassword) @@ -172,7 +171,8 @@ private fun DoormanParameters.generateCAKeyPair() { } val intermediateKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val intermediateCert = createCertificate(CertificateType.INTERMEDIATE_CA, rootKeyAndCert.certificate, rootKeyAndCert.keyPair, X500Name("CN=Corda Intermediate CA, O=R3, OU=Corda, L=London, C=GB"), intermediateKey.public) + val intermediateCert = createCertificate(CertificateType.INTERMEDIATE_CA, rootKeyAndCert.certificate, rootKeyAndCert.keyPair, + CordaX500Name(commonName = "Corda Intermediate CA", organisation = "R3 Ltd", organisationUnit = "Corda", locality = "London", country = "GB", state = null), intermediateKey.public) keyStore.addOrReplaceKey(CORDA_INTERMEDIATE_CA, intermediateKey.private, caPrivateKeyPassword.toCharArray(), arrayOf(intermediateCert, rootKeyAndCert.certificate)) keyStore.save(keystorePath, keystorePassword) @@ -207,7 +207,7 @@ private fun DoormanParameters.startDoorman(isLocalSigning: Boolean = false) { } private fun buildLocalSigner(storage: CertificationRequestStorage, parameters: DoormanParameters): Signer { - checkNotNull(parameters.keystorePath) {"The keystorePath parameter must be specified when using local signing!"} + checkNotNull(parameters.keystorePath) { "The keystorePath parameter must be specified when using local signing!" } // Get password from console if not in config. val keystorePassword = parameters.keystorePassword ?: readPassword("Keystore Password: ") val caPrivateKeyPassword = parameters.caPrivateKeyPassword ?: readPassword("CA Private Key Password: ") diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DBCertificateRequestStorage.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DBCertificateRequestStorage.kt index 7ba749d562..9bd476cc9b 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DBCertificateRequestStorage.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DBCertificateRequestStorage.kt @@ -2,6 +2,7 @@ package com.r3.corda.doorman.persistence import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.x500Name import net.corda.node.utilities.CordaPersistence import org.bouncycastle.pkcs.PKCS10CertificationRequest import java.io.ByteArrayInputStream @@ -79,7 +80,7 @@ open class DBCertificateRequestStorage(private val database: CordaPersistence) : database.transaction { val (legalName, rejectReason) = try { // This will fail with IllegalArgumentException if subject name is malformed. - val legalName = CordaX500Name.build(certificationData.request.subject).copy(commonName = null) + val legalName = CordaX500Name.parse(certificationData.request.subject.toString()).copy(commonName = null) // Checks database for duplicate name. val query = session.criteriaBuilder.run { val criteriaQuery = createQuery(CertificateSigningRequest::class.java) diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DoormanSchemaService.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DoormanSchemaService.kt index 4e765bbd8f..2e65d4b708 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DoormanSchemaService.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/DoormanSchemaService.kt @@ -12,10 +12,15 @@ class DoormanSchemaService : SchemaService { object DoormanServicesV1 : MappedSchema(schemaFamily = DoormanServices.javaClass, version = 1, mappedTypes = listOf(DBCertificateRequestStorage.CertificateSigningRequest::class.java)) - override val schemaOptions: Map = mapOf(Pair(DoormanServicesV1, SchemaService.SchemaOptions())) + override var schemaOptions: Map = mapOf(Pair(DoormanServicesV1, SchemaService.SchemaOptions())) override fun selectSchemas(state: ContractState): Iterable = setOf(DoormanServicesV1) override fun generateMappedObject(state: ContractState, schema: MappedSchema): PersistentState = throw UnsupportedOperationException() + override fun registerCustomSchemas(customSchemas: Set) { + schemaOptions = schemaOptions.plus(customSchemas.map { mappedSchema -> + Pair(mappedSchema, SchemaService.SchemaOptions()) + }) + } } \ No newline at end of file diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/JiraCertificateRequestStorage.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/persistence/JiraCertificateRequestStorage.kt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/signer/CsrHandler.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/signer/CsrHandler.kt index d083c3bc5e..2595db1813 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/signer/CsrHandler.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/signer/CsrHandler.kt @@ -8,10 +8,10 @@ import com.atlassian.jira.rest.client.api.domain.input.TransitionInput import com.r3.corda.doorman.persistence.CertificateResponse import com.r3.corda.doorman.persistence.CertificationRequestData import com.r3.corda.doorman.persistence.CertificationRequestStorage -import net.corda.core.utilities.country -import net.corda.core.utilities.locality +import net.corda.core.internal.country +import net.corda.core.internal.locality +import net.corda.core.internal.organisation import net.corda.core.utilities.loggerFor -import net.corda.core.utilities.organisation import net.corda.node.utilities.X509Utilities import org.bouncycastle.asn1.x500.style.BCStyle import org.bouncycastle.openssl.jcajce.JcaPEMWriter diff --git a/doorman/src/main/kotlin/com/r3/corda/doorman/signer/Signer.kt b/doorman/src/main/kotlin/com/r3/corda/doorman/signer/Signer.kt index c303f2f572..07c2b7ba96 100644 --- a/doorman/src/main/kotlin/com/r3/corda/doorman/signer/Signer.kt +++ b/doorman/src/main/kotlin/com/r3/corda/doorman/signer/Signer.kt @@ -4,6 +4,7 @@ import com.r3.corda.doorman.buildCertPath import com.r3.corda.doorman.persistence.CertificationRequestStorage import com.r3.corda.doorman.toX509Certificate import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.x500Name import net.corda.node.utilities.CertificateAndKeyPair import net.corda.node.utilities.CertificateType import net.corda.node.utilities.X509Utilities @@ -28,12 +29,12 @@ class LocalSigner(private val storage: CertificationRequestStorage, // please see [sun.security.x509.X500Name.isWithinSubtree()] for more information. // We assume all attributes in the subject name has been checked prior approval. // TODO: add validation to subject name. - val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, CordaX500Name.build(request.subject).copy(commonName = null).x500Name))), arrayOf()) + val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, CordaX500Name.parse(request.subject.toString()).copy(commonName = null).x500Name))), arrayOf()) val ourCertificate = caCertAndKey.certificate val clientCertificate = X509Utilities.createCertificate(CertificateType.CLIENT_CA, caCertAndKey.certificate, caCertAndKey.keyPair, - CordaX500Name.build(request.subject).copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN), + CordaX500Name.parse(request.subject.toString()).copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN), request.publicKey, nameConstraints = nameConstraints).toX509Certificate() buildCertPath(clientCertificate, ourCertificate.toX509Certificate(), rootCACert) diff --git a/doorman/src/test/kotlin/com/r3/corda/doorman/DoormanServiceTest.kt b/doorman/src/test/kotlin/com/r3/corda/doorman/DoormanServiceTest.kt index 7ce497ceb4..7e6ab16a37 100644 --- a/doorman/src/test/kotlin/com/r3/corda/doorman/DoormanServiceTest.kt +++ b/doorman/src/test/kotlin/com/r3/corda/doorman/DoormanServiceTest.kt @@ -9,6 +9,7 @@ import com.r3.corda.doorman.signer.DefaultCsrHandler import com.r3.corda.doorman.signer.LocalSigner import net.corda.core.crypto.Crypto import net.corda.core.crypto.SecureHash +import net.corda.core.identity.CordaX500Name import net.corda.node.utilities.CertificateAndKeyPair import net.corda.node.utilities.CertificateStream import net.corda.node.utilities.CertificateType @@ -38,7 +39,7 @@ import kotlin.test.assertEquals class DoormanServiceTest { private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - private val rootCACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Node Root CA,L=London"), rootCAKey) + private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", locality = "London", organisation = "R3 Ltd", country = "GB"), rootCAKey) private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public) private lateinit var doormanServer: DoormanServer @@ -66,7 +67,7 @@ class DoormanServiceTest { startSigningServer(storage) val keyPair = Crypto.generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) - val request = X509Utilities.createCertificateSigningRequest(X500Name("CN=LegalName"), "my@mail.com", keyPair) + val request = X509Utilities.createCertificateSigningRequest(CordaX500Name(locality = "London", organisation = "Legal Name", country = "GB"), "my@mail.com", keyPair) // Post request to signing server via http. assertEquals(id, submitRequest(request)) @@ -91,7 +92,7 @@ class DoormanServiceTest { on { signCertificate(eq(id), any(), any()) }.then { @Suppress("UNCHECKED_CAST") val certGen = it.arguments[2] as ((CertificationRequestData) -> CertPath) - val request = CertificationRequestData("", "", X509Utilities.createCertificateSigningRequest(X500Name("CN=LegalName,L=London"), "my@mail.com", keyPair)) + val request = CertificationRequestData("", "", X509Utilities.createCertificateSigningRequest(CordaX500Name(locality = "London", organisation = "LegalName", country = "GB"), "my@mail.com", keyPair)) certificateStore[id] = certGen(request) true } @@ -115,7 +116,7 @@ class DoormanServiceTest { assertEquals(3, certificates.size) certificates.first().run { - assertThat(subjectDN.name).contains("CN=LegalName") + assertThat(subjectDN.name).contains("O=LegalName") assertThat(subjectDN.name).contains("L=London") } @@ -141,7 +142,7 @@ class DoormanServiceTest { on { signCertificate(eq(id), any(), any()) }.then { @Suppress("UNCHECKED_CAST") val certGen = it.arguments[2] as ((CertificationRequestData) -> CertPath) - val request = CertificationRequestData("", "", X509Utilities.createCertificateSigningRequest(X500Name("CN=LegalName,L=London"), "my@mail.com", keyPair)) + val request = CertificationRequestData("", "", X509Utilities.createCertificateSigningRequest(CordaX500Name(locality = "London", organisation = "Legal Name", country = "GB"), "my@mail.com", keyPair)) certificateStore[id] = certGen(request) true } @@ -169,7 +170,7 @@ class DoormanServiceTest { val sslCert = X509Utilities.createCertificate(CertificateType.TLS, X509CertificateHolder(certificates.first().encoded), keyPair, X500Name("CN=LegalName,L=London"), sslKey.public).toX509Certificate() // TODO: This is temporary solution, remove all certificate re-shaping after identity refactoring is done. - X509Utilities.validateCertificateChain(X509CertificateHolder(certificates.last().encoded), sslCert, *certificates.toTypedArray()) + X509Utilities.validateCertificateChain(certificates.last(), sslCert, *certificates.toTypedArray()) } @Test diff --git a/doorman/src/test/kotlin/com/r3/corda/doorman/internal/persistence/DBCertificateRequestStorageTest.kt b/doorman/src/test/kotlin/com/r3/corda/doorman/internal/persistence/DBCertificateRequestStorageTest.kt index beb7944ef2..1665f60c51 100644 --- a/doorman/src/test/kotlin/com/r3/corda/doorman/internal/persistence/DBCertificateRequestStorageTest.kt +++ b/doorman/src/test/kotlin/com/r3/corda/doorman/internal/persistence/DBCertificateRequestStorageTest.kt @@ -106,7 +106,7 @@ class DBCertificateRequestStorageTest { storage.signCertificate(requestId) { JcaPKCS10CertificationRequest(csr.request).run { val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Node Root CA,L=London"), rootCAKey) + val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", locality = "London", organisation = "R3 LTD", country = "GB"), rootCAKey) val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public) val ourCertificate = X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey).toX509Certificate() @@ -127,7 +127,7 @@ class DBCertificateRequestStorageTest { val generateCert: CertificationRequestData.() -> CertPath = { JcaPKCS10CertificationRequest(csr.request).run { val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val rootCACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Corda Node Root CA,L=London"), rootCAKey) + val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", locality = "London", organisation = "R3 LTD", country = "GB"), rootCAKey) val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public) val ourCertificate = X509Utilities.createCertificate(CertificateType.TLS, intermediateCACert, intermediateCAKey, subject, publicKey).toX509Certificate() @@ -188,7 +188,7 @@ class DBCertificateRequestStorageTest { val request = CertificationRequestData( "hostname", "0.0.0.0", - X509Utilities.createCertificateSigningRequest(CordaX500Name(organisation = legalName, locality = "London", country = "GB").x500Name, "my@mail.com", keyPair)) + X509Utilities.createCertificateSigningRequest(CordaX500Name(organisation = legalName, locality = "London", country = "GB"), "my@mail.com", keyPair)) return Pair(request, keyPair) } diff --git a/signing-server/build.gradle b/signing-server/build.gradle index 01fcc51b8f..4a9a76acd8 100644 --- a/signing-server/build.gradle +++ b/signing-server/build.gradle @@ -84,5 +84,5 @@ dependencies { // Unit testing helpers. testCompile 'junit:junit:4.12' testCompile "org.assertj:assertj-core:${assertj_version}" - testCompile project(':doorman') + integrationTestCompile project(':doorman') } \ No newline at end of file diff --git a/signing-server/src/integration-test/kotlin/com/r3/corda/signing/SigningServiceIntegrationTest.kt b/signing-server/src/integration-test/kotlin/com/r3/corda/signing/SigningServiceIntegrationTest.kt index ff63522880..47d7398c98 100644 --- a/signing-server/src/integration-test/kotlin/com/r3/corda/signing/SigningServiceIntegrationTest.kt +++ b/signing-server/src/integration-test/kotlin/com/r3/corda/signing/SigningServiceIntegrationTest.kt @@ -67,7 +67,7 @@ class SigningServiceIntegrationTest { // Create all certificates val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Integration Test Corda Node Root CA", - organisation = "R3 Ltd", locality = "London", country = "GB").x500Name, rootCAKey) + organisation = "R3 Ltd", locality = "London", country = "GB"), rootCAKey) val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, CordaX500Name(commonName = "Integration Test Corda Node Intermediate CA", locality = "London", country = "GB",