diff --git a/network-management/hsm-for-doorman.conf b/network-management/hsm-for-doorman.conf index 12b3ebefdd..e6573a00f5 100644 --- a/network-management/hsm-for-doorman.conf +++ b/network-management/hsm-for-doorman.conf @@ -4,6 +4,8 @@ keySpecifier = -1 doorman { crlDistributionPoint = "http://test.com/revoked.crl" + compatibilityZoneURL = "http://test.com/api" + crlUpdatePeriod = 200000 validDays = 3650 rootKeyStoreFile = "dummyfile.jks" rootKeyStorePassword = "trustpass" diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/common/HsmBaseTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/common/HsmBaseTest.kt index 8cf4695ac3..81acdade75 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/common/HsmBaseTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/common/HsmBaseTest.kt @@ -28,6 +28,7 @@ import net.corda.nodeapi.internal.crypto.CertificateType import org.junit.Before import org.junit.Rule import org.junit.rules.TemporaryFolder +import java.net.URL import java.nio.file.Path import java.util.* import com.r3.corda.networkmanage.hsm.authentication.AuthMode as SigningServiceAuthMode @@ -127,35 +128,46 @@ abstract class HsmBaseTest { ), hsmUserConfigs) } - protected fun createHsmSigningServiceConfig(): SigningServiceConfig { + protected fun createHsmSigningServiceConfig(doormanCertConfig: DoormanCertificateConfig?, + networkMapCertificateConfig: NetworkMapCertificateConfig?): SigningServiceConfig { return SigningServiceConfig( dataSourceProperties = mock(), device = "${hsmSimulator.port}@${hsmSimulator.host}", keySpecifier = 1, - doorman = DoormanCertificateConfig( - rootKeyStoreFile = rootKeyStoreFile, - keyGroup = DOORMAN_CERT_KEY_GROUP, - validDays = 3650, - rootKeyStorePassword = TRUSTSTORE_PASSWORD, - crlDistributionPoint = "http://test.com/revoked.crl", - authParameters = AuthParametersConfig( - mode = SigningServiceAuthMode.PASSWORD, - threshold = 2 - ) - ), - networkMap = NetworkMapCertificateConfig( - username = "INTEGRATION_TEST", - keyGroup = NETWORK_MAP_CERT_KEY_GROUP, - authParameters = AuthParametersConfig( - mode = SigningServiceAuthMode.PASSWORD, - password = "INTEGRATION_TEST", - threshold = 2 - ) + doorman = doormanCertConfig, + networkMap = networkMapCertificateConfig + ) + } + protected fun createDoormanCertificateConfig(): DoormanCertificateConfig { + return DoormanCertificateConfig( + rootKeyStoreFile = rootKeyStoreFile, + keyGroup = DOORMAN_CERT_KEY_GROUP, + validDays = 3650, + rootKeyStorePassword = TRUSTSTORE_PASSWORD, + crlDistributionPoint = URL("http://test.com/revoked.crl"), + compatibilityZoneURL = URL("http://test.com/api"), + crlUpdatePeriod = 1000, + authParameters = AuthParametersConfig( + mode = SigningServiceAuthMode.PASSWORD, + threshold = 2 ) ) } + protected fun createNetworkMapCertificateConfig(): NetworkMapCertificateConfig { + return NetworkMapCertificateConfig( + username = "INTEGRATION_TEST", + keyGroup = NETWORK_MAP_CERT_KEY_GROUP, + authParameters = AuthParametersConfig( + mode = SigningServiceAuthMode.PASSWORD, + password = "INTEGRATION_TEST", + threshold = 2 + ) + + ) + } + protected fun givenHsmUserAuthenticationInput(username: String = HSM_USERNAME, password: String = HSM_PASSWORD): InputReader { val inputReader = mock() diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmAuthenticatorTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmAuthenticatorTest.kt index 3302813fe2..fc25d27ac5 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmAuthenticatorTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmAuthenticatorTest.kt @@ -23,7 +23,7 @@ class HsmAuthenticatorTest : HsmBaseTest() { fun `Authenticator executes the block once user is successfully authenticated`() { // given val userInput = givenHsmUserAuthenticationInput() - val hsmSigningServiceConfig = createHsmSigningServiceConfig() + val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null) val doormanCertificateConfig = hsmSigningServiceConfig.doorman!! val authenticator = Authenticator(provider = createProvider( doormanCertificateConfig.keyGroup, diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmPermissionTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmPermissionTest.kt index 863e1463a2..177650369d 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmPermissionTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmPermissionTest.kt @@ -56,7 +56,7 @@ class HsmPermissionTest : HsmBaseTest() { val userInput = givenHsmUserAuthenticationInput(HSM_USERNAME_OPS_CERT_) // given HSM CSR signer - val hsmSigningServiceConfig = createHsmSigningServiceConfig() + val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null) val signer = HsmCsrSigner( mock(), hsmSigningServiceConfig.doorman!!.loadRootKeyStore(), @@ -112,7 +112,7 @@ class HsmPermissionTest : HsmBaseTest() { val userInput = givenHsmUserAuthenticationInput(HSM_USERNAME_OPS_CERT) // given HSM CSR signer - val hsmSigningServiceConfig = createHsmSigningServiceConfig() + val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null) val signer = HsmCsrSigner( mock(), hsmSigningServiceConfig.doorman!!.loadRootKeyStore(), diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt index 6b242011f8..a36eb089d3 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmSigningServiceTest.kt @@ -15,6 +15,7 @@ import com.r3.corda.networkmanage.common.HsmBaseTest import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage import com.r3.corda.networkmanage.common.persistence.configureDatabase import com.r3.corda.networkmanage.common.signer.NetworkMapSigner +import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP import com.r3.corda.networkmanage.common.utils.initialiseSerialization import com.r3.corda.networkmanage.hsm.authentication.Authenticator import com.r3.corda.networkmanage.hsm.authentication.createProvider @@ -54,7 +55,7 @@ class HsmSigningServiceTest : HsmBaseTest() { val userInput = givenHsmUserAuthenticationInput() // given HSM CSR signer - val hsmSigningServiceConfig = createHsmSigningServiceConfig() + val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null) val doormanCertificateConfig = hsmSigningServiceConfig.doorman!! val signer = HsmCsrSigner( mock(), @@ -98,7 +99,7 @@ class HsmSigningServiceTest : HsmBaseTest() { val userInput = givenHsmUserAuthenticationInput() // given HSM CSR signer - val hsmSigningServiceConfig = createHsmSigningServiceConfig() + val hsmSigningServiceConfig = createHsmSigningServiceConfig(createDoormanCertificateConfig(), null) val doormanCertificateConfig = hsmSigningServiceConfig.doorman!! val signer = HsmCsrSigner( mock(), @@ -143,14 +144,15 @@ class HsmSigningServiceTest : HsmBaseTest() { val userInput = givenHsmUserAuthenticationInput() // given HSM network map signer - val hsmSigningServiceConfig = createHsmSigningServiceConfig() + val hsmSigningServiceConfig = createHsmSigningServiceConfig(null, createNetworkMapCertificateConfig()) val networkMapCertificateConfig = hsmSigningServiceConfig.networkMap!! val hsmDataSigner = HsmSigner(Authenticator( provider = createProvider( networkMapCertificateConfig.keyGroup, hsmSigningServiceConfig.keySpecifier, hsmSigningServiceConfig.device), - inputReader = userInput)) + inputReader = userInput), + keyName = CORDA_NETWORK_MAP) val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true)) val networkMapStorage = PersistentNetworkMapStorage(database) @@ -170,7 +172,7 @@ class HsmSigningServiceTest : HsmBaseTest() { assertThat(persistedNetworkMap.nodeInfoHashes).isEmpty() } - private fun setupCertificates(){ + private fun setupCertificates() { // when root cert is created run(createGeneratorParameters( keyGroup = ROOT_CERT_KEY_GROUP, diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt index 397a691e5b..794b61ad97 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/SigningServiceIntegrationTest.kt @@ -18,7 +18,7 @@ import com.r3.corda.networkmanage.doorman.DoormanConfig import com.r3.corda.networkmanage.doorman.NetworkManagementServer import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage -import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorage +import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateSigningRequestStorage import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner import net.corda.core.crypto.random63BitValue import net.corda.core.identity.CordaX500Name @@ -77,7 +77,7 @@ class SigningServiceIntegrationTest : HsmBaseTest() { timer.cancel() } - private fun givenSignerSigningAllRequests(storage: SignedCertificateRequestStorage): HsmCsrSigner { + private fun givenSignerSigningAllRequests(storage: SignedCertificateSigningRequestStorage): HsmCsrSigner { // Mock signing logic but keep certificate persistence return mock { on { sign(any()) }.then { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationListStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationListStorage.kt index c2cf2b8cf5..f59634b67d 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationListStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationListStorage.kt @@ -13,7 +13,7 @@ interface CertificateRevocationListStorage { * @param crlIssuer CRL issuer CA type. * @return latest revocation list. */ - fun getCertificateRevocationList(crlIssuer: CrlIssuer): X509CRL + fun getCertificateRevocationList(crlIssuer: CrlIssuer): X509CRL? /** * Persists a new revocation list. Upon saving, statuses diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationRequestStorage.kt index fc87be0ebe..40ffb3ec26 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationRequestStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateRevocationRequestStorage.kt @@ -1,13 +1,20 @@ package com.r3.corda.networkmanage.common.persistence +import net.corda.core.identity.CordaX500Name +import net.corda.core.serialization.CordaSerializable +import net.corda.nodeapi.internal.network.CertificateRevocationRequest import java.math.BigInteger import java.security.cert.CRLReason import java.time.Instant +/** + * This data class is intended to be used internally certificate revocation request service. + */ +@CordaSerializable data class CertificateRevocationRequestData(val requestId: String, // This is a uniquely generated string val certificateSerialNumber: BigInteger, - val revocationTime: Instant?, - val legalName: String, + val modifiedAt: Instant, + val legalName: CordaX500Name, val status: RequestStatus, val reason: CRLReason, val reporter: String) // Username of the reporter @@ -24,13 +31,11 @@ interface CertificateRevocationRequestStorage { * [RequestStatus.NEW], [RequestStatus.APPROVED] or [RequestStatus.REVOKED] * then nothing is persisted and the existing revocation request identifier is returned. * - * @param certificateSerialNumber serial number of the certificate to be revoked. - * @param reason reason for revocation. See [java.security.cert.CRLReason] - * @param reporter who is requesting this revocation + * @param request certificate revocation request to be stored. * * @return identifier of the newly created (or existing) revocation request. */ - fun saveRevocationRequest(certificateSerialNumber: BigInteger, reason: CRLReason, reporter: String): String + fun saveRevocationRequest(request: CertificateRevocationRequest): String /** * Retrieves the revocation request with the given [requestId] diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateSigningRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateSigningRequestStorage.kt index 78c68da89c..fb830b0d7f 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateSigningRequestStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificateSigningRequestStorage.kt @@ -11,6 +11,7 @@ package com.r3.corda.networkmanage.common.persistence import net.corda.core.crypto.SecureHash +import net.corda.core.serialization.CordaSerializable import org.bouncycastle.pkcs.PKCS10CertificationRequest import java.security.cert.CertPath @@ -89,6 +90,7 @@ sealed class CertificateResponse { data class Unauthorised(val message: String) : CertificateResponse() } +@CordaSerializable enum class RequestStatus { /** * The request has been received, this is the initial state in which a request has been created. diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationListStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationListStorage.kt index 1fb909dbff..db5f35bba0 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationListStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationListStorage.kt @@ -11,7 +11,7 @@ import java.security.cert.X509CRL import java.time.Instant class PersistentCertificateRevocationListStorage(private val database: CordaPersistence) : CertificateRevocationListStorage { - override fun getCertificateRevocationList(crlIssuer: CrlIssuer): X509CRL { + override fun getCertificateRevocationList(crlIssuer: CrlIssuer): X509CRL? { return database.transaction { val builder = session.criteriaBuilder val query = builder.createQuery(CertificateRevocationListEntity::class.java).run { @@ -21,7 +21,10 @@ class PersistentCertificateRevocationListStorage(private val database: CordaPers } } // We just want the last signed entry - X509CertificateFactory().delegate.generateCRL(session.createQuery(query).setMaxResults(1).singleResult.crlBytes.inputStream()) as X509CRL + val crlEntity = session.createQuery(query).setMaxResults(1).uniqueResult() + crlEntity?.let { + X509CertificateFactory().delegate.generateCRL(crlEntity.crlBytes.inputStream()) as X509CRL + } } } @@ -41,7 +44,10 @@ class PersistentCertificateRevocationListStorage(private val database: CordaPers private fun revokeCertificate(certificateSerialNumber: BigInteger, time: Instant, transaction: DatabaseTransaction) { val revocation = transaction.uniqueEntityWhere { builder, path -> - builder.equal(path.get(CertificateRevocationRequestEntity::certificateSerialNumber.name), certificateSerialNumber) + builder.and( + builder.equal(path.get(CertificateRevocationRequestEntity::certificateSerialNumber.name), certificateSerialNumber), + builder.notEqual(path.get(CertificateRevocationRequestEntity::status.name), RequestStatus.REJECTED) + ) } revocation ?: throw IllegalStateException("The certificate revocation request for $certificateSerialNumber does not exist") check(revocation.status in arrayOf(RequestStatus.APPROVED, RequestStatus.DONE)) { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorage.kt index 2acc3ff272..808d8f2858 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorage.kt @@ -2,39 +2,41 @@ package com.r3.corda.networkmanage.common.persistence import com.r3.corda.networkmanage.common.persistence.entity.CertificateDataEntity import com.r3.corda.networkmanage.common.persistence.entity.CertificateRevocationRequestEntity +import com.r3.corda.networkmanage.common.persistence.entity.CertificateSigningRequestEntity import net.corda.core.crypto.SecureHash +import net.corda.core.identity.CordaX500Name +import net.corda.nodeapi.internal.network.CertificateRevocationRequest import net.corda.nodeapi.internal.persistence.CordaPersistence +import net.corda.nodeapi.internal.persistence.DatabaseTransaction import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel import java.math.BigInteger -import java.security.cert.CRLReason import java.time.Instant class PersistentCertificateRevocationRequestStorage(private val database: CordaPersistence) : CertificateRevocationRequestStorage { - override fun saveRevocationRequest(certificateSerialNumber: BigInteger, reason: CRLReason, reporter: String): String { + override fun saveRevocationRequest(request: CertificateRevocationRequest): String { return database.transaction(TransactionIsolationLevel.SERIALIZABLE) { + // Get matching CSR + val csr = retrieveCsr(request.certificateSerialNumber, request.csrRequestId, request.legalName) + csr ?: throw IllegalArgumentException("No CSR matching the given criteria was found") // Check if there is an entry for the given certificate serial number val revocation = uniqueEntityWhere { builder, path -> - val serialNumberEqual = builder.equal(path.get(CertificateRevocationRequestEntity::certificateSerialNumber.name), certificateSerialNumber) + val serialNumberEqual = builder.equal(path.get(CertificateRevocationRequestEntity::certificateSerialNumber.name), request.certificateSerialNumber) val statusNotEqualRejected = builder.notEqual(path.get(CertificateRevocationRequestEntity::status.name), RequestStatus.REJECTED) builder.and(serialNumberEqual, statusNotEqualRejected) } if (revocation != null) { revocation.requestId } else { - val certificateData = uniqueEntityWhere { builder, path -> - val serialNumberEqual = builder.equal(path.get(CertificateDataEntity::certificateSerialNumber.name), certificateSerialNumber) - val statusEqualValid = builder.equal(path.get(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID) - builder.and(serialNumberEqual, statusEqualValid) - } + val certificateData = csr.certificateData!! requireNotNull(certificateData) { "The certificate with the given serial number cannot be found." } val requestId = SecureHash.randomSHA256().toString() session.save(CertificateRevocationRequestEntity( - certificateSerialNumber = certificateSerialNumber, - revocationReason = reason, + certificateSerialNumber = certificateData.certificateSerialNumber, + revocationReason = request.reason, requestId = requestId, - modifiedBy = reporter, - certificateData = certificateData!!, - reporter = reporter, + modifiedBy = request.reporter, + certificateData = certificateData, + reporter = request.reporter, legalName = certificateData.legalName() )) requestId @@ -42,8 +44,62 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP } } - override fun getRevocationRequest(requestId: String): CertificateRevocationRequestData? = database.transaction { - getRevocationRequestEntity(requestId)?.toCertificateRevocationRequest() + private fun DatabaseTransaction.retrieveCsr(certificateSerialNumber: BigInteger?, csrRequestId: String?, legalName: CordaX500Name?): CertificateSigningRequestEntity? { + val csr = if (csrRequestId != null) { + uniqueEntityWhere { builder, path -> + builder.and( + builder.equal(path.get(CertificateSigningRequestEntity::requestId.name), csrRequestId), + builder.notEqual(path.get(CertificateSigningRequestEntity::status.name), RequestStatus.REJECTED), + builder.equal(path + .get(CertificateSigningRequestEntity::certificateData.name) + .get(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID) + ) + } + } else if (legalName != null) { + uniqueEntityWhere { builder, path -> + builder.and( + builder.equal(path.get(CertificateSigningRequestEntity::legalName.name), legalName.toString()), + builder.notEqual(path.get(CertificateSigningRequestEntity::status.name), RequestStatus.REJECTED), + builder.equal(path + .get(CertificateSigningRequestEntity::certificateData.name) + .get(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID) + ) + } + } else { + val certificateData = uniqueEntityWhere { builder, path -> + builder.and( + builder.equal(path.get(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID), + builder.equal(path.get(CertificateDataEntity::certificateSerialNumber.name), certificateSerialNumber) + ) + } + certificateData?.certificateSigningRequest + } + return csr?.let { + validateCsrConsistency(certificateSerialNumber, csrRequestId, legalName, it) + } + } + + private fun validateCsrConsistency(certificateSerialNumber: BigInteger?, + csrRequestId: String?, + legalName: CordaX500Name?, + result: CertificateSigningRequestEntity): CertificateSigningRequestEntity { + val certData = result.certificateData!! + require(legalName == null || result.legalName == legalName.toString()) { + "The legal name does not match." + } + require(csrRequestId == null || result.requestId == csrRequestId) { + "The CSR request ID does not match." + } + require(certificateSerialNumber == null || certData.certificateSerialNumber == certificateSerialNumber) { + "The certificate serial number does not match." + } + return result + } + + override fun getRevocationRequest(requestId: String): CertificateRevocationRequestData? { + return database.transaction { + getRevocationRequestEntity(requestId)?.toCertificateRevocationRequest() + } } override fun getRevocationRequests(revocationStatus: RequestStatus): List { @@ -89,9 +145,11 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP } } - private fun getRevocationRequestEntity(requestId: String): CertificateRevocationRequestEntity? = database.transaction { - uniqueEntityWhere { builder, path -> - builder.equal(path.get(CertificateRevocationRequestEntity::requestId.name), requestId) + private fun getRevocationRequestEntity(requestId: String): CertificateRevocationRequestEntity? { + return database.transaction { + uniqueEntityWhere { builder, path -> + builder.equal(path.get(CertificateRevocationRequestEntity::requestId.name), requestId) + } } } @@ -99,8 +157,8 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP return CertificateRevocationRequestData( requestId, certificateSerialNumber, - if (status == RequestStatus.DONE) modifiedAt else null, - legalName, + modifiedAt, + CordaX500Name.parse(legalName), status, revocationReason, reporter) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateRevocationListEntity.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateRevocationListEntity.kt index 32993d0ac6..b541e05179 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateRevocationListEntity.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/entity/CertificateRevocationListEntity.kt @@ -1,7 +1,6 @@ package com.r3.corda.networkmanage.common.persistence.entity import com.r3.corda.networkmanage.common.persistence.CrlIssuer -import org.hibernate.envers.Audited import java.time.Instant import javax.persistence.* @@ -19,11 +18,9 @@ class CertificateRevocationListEntity( @Column(name = "crl_bytes", nullable = false) val crlBytes: ByteArray, - @Audited @Column(name = "signed_by", length = 512) val signedBy: String, - @Audited @Column(name = "modified_at", nullable = false) val modifiedAt: Instant = Instant.now() ) \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/CertificateRevocationListSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/CertificateRevocationListSigner.kt new file mode 100644 index 0000000000..38f6754fd1 --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/signer/CertificateRevocationListSigner.kt @@ -0,0 +1,57 @@ +package com.r3.corda.networkmanage.common.signer + +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData +import com.r3.corda.networkmanage.common.persistence.CrlIssuer +import com.r3.corda.networkmanage.common.persistence.RequestStatus +import com.r3.corda.networkmanage.common.utils.createSignedCrl +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.debug +import net.corda.core.utilities.trace +import java.net.URL +import java.security.cert.X509CRL +import java.security.cert.X509Certificate +import java.time.Duration +import java.time.Instant + +class CertificateRevocationListSigner( + private val revocationListStorage: CertificateRevocationListStorage, + private val issuerCertificate: X509Certificate, + private val updateInterval: Duration, + private val endpoint: URL, + private val signer: Signer) { + private companion object { + private val logger = contextLogger() + } + + /** + * Builds, signs and persists a new certificate revocation list. + * It happens only if there are new entries to be added to the current list. + * + * @param newCRRs list of approved certificate revocation requests. An approved certificate revocation request + * is a request that has status [RequestStatus.APPROVED] - i.e. it has not yet been included in any CRL. + * @param existingCRRs list of revoked certificate revocation requests. A revoked certificate revocation request + * is a request that has status [RequestStatus.DONE] - i.e. it has already been included in another CRL. + * @param signedBy who signs this CRL. + * + * @return A new signed CRL containing both revoked and approved certificate revocation requests. + */ + fun createSignedCRL(newCRRs: List, + existingCRRs: List, + signedBy: String): X509CRL { + require(existingCRRs.all { it.status == RequestStatus.DONE }) { "All existing CRRs need to be in the ${RequestStatus.DONE} state" } + require(newCRRs.all { it.status == RequestStatus.APPROVED }) { "All newly included CRRs need to be in the ${RequestStatus.APPROVED} state" } + logger.info("Signing a new Certificate Revocation List...") + logger.debug("Retrieving approved Certificate Revocation Requests...") + val revocationTime = Instant.now() + val approvedWithTimestamp = newCRRs.map { it.copy(modifiedAt = revocationTime) } + logger.trace { "Approved Certificate Revocation Requests to be included in the new Certificate Revocation List: $approvedWithTimestamp" } + logger.debug("Retrieving revoked Certificate Revocation Requests...") + logger.trace { "Revoked Certificate Revocation Requests to be included in the new Certificate Revocation List: $existingCRRs" } + val crl = createSignedCrl(issuerCertificate, endpoint, updateInterval, signer, existingCRRs + approvedWithTimestamp) + logger.debug { "Created a new Certificate Revocation List $crl" } + revocationListStorage.saveCertificateRevocationList(crl, CrlIssuer.DOORMAN, signedBy, revocationTime) + logger.info("A new Certificate Revocation List has been persisted.") + return crl + } +} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/CrlUtils.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/CrlUtils.kt new file mode 100644 index 0000000000..1691a52e1b --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/utils/CrlUtils.kt @@ -0,0 +1,48 @@ +package com.r3.corda.networkmanage.common.utils + +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData +import com.r3.corda.networkmanage.common.signer.Signer +import net.corda.nodeapi.internal.crypto.X509Utilities +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x509.* +import org.bouncycastle.cert.X509v2CRLBuilder +import org.bouncycastle.cert.jcajce.JcaX509CRLConverter +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.operator.ContentSigner +import java.io.ByteArrayOutputStream +import java.io.OutputStream +import java.net.URL +import java.security.cert.X509CRL +import java.security.cert.X509Certificate +import java.time.Duration +import java.time.Instant +import java.util.* + +fun createSignedCrl(issuerCertificate: X509Certificate, + endpointUrl: URL, + nextUpdateInterval: Duration, + signer: Signer, + includeInCrl: List): X509CRL { + val extensionUtils = JcaX509ExtensionUtils() + val builder = X509v2CRLBuilder(X500Name.getInstance(issuerCertificate.issuerX500Principal.encoded), Date()) + builder.addExtension(Extension.authorityKeyIdentifier, false, extensionUtils.createAuthorityKeyIdentifier(issuerCertificate)) + val issuingDistributionPointName = GeneralName(GeneralName.uniformResourceIdentifier, endpointUrl.toString()) + val issuingDistributionPoint = IssuingDistributionPoint(DistributionPointName(GeneralNames(issuingDistributionPointName)), false, false) + builder.addExtension(Extension.issuingDistributionPoint, true, issuingDistributionPoint) + builder.setNextUpdate(Date((Instant.now() + nextUpdateInterval).toEpochMilli())) + includeInCrl.forEach { + builder.addCRLEntry(it.certificateSerialNumber, Date(it.modifiedAt.toEpochMilli()), it.reason.ordinal) + } + val crlHolder = builder.build(CrlContentSigner(signer)) + return JcaX509CRLConverter().setProvider(BouncyCastleProvider()).getCRL(crlHolder) +} + +private class CrlContentSigner(private val signer: Signer) : ContentSigner { + + private val outputStream = ByteArrayOutputStream() + + override fun getAlgorithmIdentifier(): AlgorithmIdentifier = X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME.signatureOID + override fun getOutputStream(): OutputStream = outputStream + override fun getSignature(): ByteArray = signer.signBytes(outputStream.toByteArray()).bytes +} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationListWebService.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationListWebService.kt new file mode 100644 index 0000000000..e7fdea914d --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationListWebService.kt @@ -0,0 +1,62 @@ +package com.r3.corda.networkmanage.doorman.webservice + +import com.github.benmanes.caffeine.cache.Caffeine +import com.github.benmanes.caffeine.cache.LoadingCache +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage +import com.r3.corda.networkmanage.common.persistence.CrlIssuer +import com.r3.corda.networkmanage.doorman.webservice.CertificateRevocationListWebService.Companion.CRL_PATH +import net.corda.core.utilities.contextLogger +import java.util.concurrent.ExecutionException +import java.util.concurrent.TimeUnit +import javax.ws.rs.GET +import javax.ws.rs.Path +import javax.ws.rs.Produces +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.ok +import javax.ws.rs.core.Response.status + +@Path(CRL_PATH) +class CertificateRevocationListWebService(private val revocationListStorage: CertificateRevocationListStorage, + cacheTimeout: Long) { + companion object { + private val logger = contextLogger() + const val CRL_PATH = "certificate-revocation-list" + const val CRL_DATA_TYPE = "application/pkcs7-crl" + const val DOORMAN = "doorman" + const val ROOT = "root" + } + + private val crlCache: LoadingCache = Caffeine.newBuilder() + .expireAfterWrite(cacheTimeout, TimeUnit.MILLISECONDS) + .build({ key -> + revocationListStorage.getCertificateRevocationList(key)?.encoded + }) + + @GET + @Path(DOORMAN) + @Produces(CRL_DATA_TYPE) + fun getDoormanRevocationList(): Response { + return getCrlResponse(CrlIssuer.DOORMAN) + } + + @GET + @Path(ROOT) + @Produces(CRL_DATA_TYPE) + fun getRootRevocationList(): Response { + return getCrlResponse(CrlIssuer.ROOT) + } + + private fun getCrlResponse(issuer: CrlIssuer): Response { + return try { + val crlBytes = crlCache.get(issuer) + if (crlBytes != null) { + ok(crlBytes).build() + } else { + status(Response.Status.NOT_FOUND).build() + } + } catch (e: Exception) { + logger.error("Error when retrieving the ${issuer.name} crl.", e) + status(Response.Status.INTERNAL_SERVER_ERROR).build() + } + } +} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationRequestWebService.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationRequestWebService.kt new file mode 100644 index 0000000000..ba23b2632f --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/CertificateRevocationRequestWebService.kt @@ -0,0 +1,31 @@ +package com.r3.corda.networkmanage.doorman.webservice + +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestStorage +import com.r3.corda.networkmanage.doorman.webservice.CertificateRevocationRequestWebService.Companion.CRR_PATH +import net.corda.core.serialization.deserialize +import net.corda.nodeapi.internal.network.CertificateRevocationRequest +import java.io.InputStream +import javax.ws.rs.Consumes +import javax.ws.rs.POST +import javax.ws.rs.Path +import javax.ws.rs.Produces +import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.ok + +@Path(CRR_PATH) +class CertificateRevocationRequestWebService(private val revocationRequestStorage: CertificateRevocationRequestStorage) { + + companion object { + const val CRR_PATH = "certificate-revocation-request" + } + + @POST + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + @Produces(MediaType.TEXT_PLAIN) + fun submitRequest(input: InputStream): Response { + val request = input.readBytes().deserialize() + val requestId = revocationRequestStorage.saveRevocationRequest(request) + return ok(requestId).build() + } +} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt index 05377585d0..a7032ed9ef 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/Main.kt @@ -17,6 +17,7 @@ import com.r3.corda.networkmanage.common.utils.initialiseSerialization import com.r3.corda.networkmanage.common.utils.parseConfig import com.r3.corda.networkmanage.hsm.configuration.SigningServiceArgsParser import com.r3.corda.networkmanage.hsm.configuration.SigningServiceConfig +import com.r3.corda.networkmanage.hsm.processor.CrrProcessor import com.r3.corda.networkmanage.hsm.processor.CsrProcessor import com.r3.corda.networkmanage.hsm.processor.NetworkMapProcessor import org.apache.logging.log4j.LogManager @@ -54,12 +55,13 @@ fun main(args: Array) { } initialiseSerialization() - // Create DB connection. val persistence = configureDatabase(config.dataSourceProperties, config.database) if (config.networkMap != null) { NetworkMapProcessor(config.networkMap, config.device, config.keySpecifier, persistence).run() - } else { - CsrProcessor(config.doorman!!, config.device, config.keySpecifier, persistence).showMenu() + } else if (config.doorman != null) { + CsrProcessor(config.doorman, config.device, config.keySpecifier, persistence).showMenu() + } else if (config.doorman != null) { + CrrProcessor(config.doorman, config.device, config.keySpecifier, persistence).showMenu() } } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/SigningServiceConfig.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/SigningServiceConfig.kt index 0f087b889b..2ce0db043a 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/SigningServiceConfig.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/configuration/SigningServiceConfig.kt @@ -23,6 +23,7 @@ import net.corda.core.internal.div import net.corda.nodeapi.internal.config.parseAs import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.persistence.DatabaseConfig +import java.net.URL import java.nio.file.Path import java.nio.file.Paths import java.util.* @@ -54,7 +55,9 @@ data class NetworkMapCertificateConfig(val username: String, /** * Certificate signing requests process specific parameters. */ -data class DoormanCertificateConfig(val crlDistributionPoint: String, +data class DoormanCertificateConfig(val crlDistributionPoint: URL, + val compatibilityZoneURL: URL, + val crlUpdatePeriod: Long, val keyGroup:String, val validDays: Int, val rootKeyStoreFile: Path, diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/persistence/DBSignedCertificateRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/persistence/DBSignedCertificateRequestStorage.kt index a81310fb31..dab1bf6743 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/persistence/DBSignedCertificateRequestStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/persistence/DBSignedCertificateRequestStorage.kt @@ -19,7 +19,7 @@ import java.security.cert.CertPath data class ApprovedCertificateRequestData(val requestId: String, val request: PKCS10CertificationRequest, var certPath: CertPath? = null) -class DBSignedCertificateRequestStorage(database: CordaPersistence) : SignedCertificateRequestStorage { +class DBSignedCertificateRequestStorage(database: CordaPersistence) : SignedCertificateSigningRequestStorage { private val storage = PersistentCertificateSigningRequestStorage(database) diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/persistence/SignedCertificateRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/persistence/SignedCertificateSigningRequestStorage.kt similarity index 95% rename from network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/persistence/SignedCertificateRequestStorage.kt rename to network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/persistence/SignedCertificateSigningRequestStorage.kt index 6c52f41ae8..7520ee408f 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/persistence/SignedCertificateRequestStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/persistence/SignedCertificateSigningRequestStorage.kt @@ -13,7 +13,7 @@ package com.r3.corda.networkmanage.hsm.persistence /** * Provides an API for storing signed CSRs (Certificate Signing Requests). */ -interface SignedCertificateRequestStorage { +interface SignedCertificateSigningRequestStorage { /** * Returns all certificate signing requests that have been approved for signing. diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CrrProcessor.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CrrProcessor.kt new file mode 100644 index 0000000000..355ca12aee --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CrrProcessor.kt @@ -0,0 +1,74 @@ +package com.r3.corda.networkmanage.hsm.processor + +import com.google.common.net.HostAndPort +import com.r3.corda.networkmanage.common.persistence.CrlIssuer +import com.r3.corda.networkmanage.common.persistence.PersistentCertificateRevocationListStorage +import com.r3.corda.networkmanage.common.persistence.PersistentCertificateRevocationRequestStorage +import com.r3.corda.networkmanage.common.persistence.RequestStatus +import com.r3.corda.networkmanage.common.signer.CertificateRevocationListSigner +import com.r3.corda.networkmanage.hsm.authentication.Authenticator +import com.r3.corda.networkmanage.hsm.authentication.createProvider +import com.r3.corda.networkmanage.hsm.configuration.DoormanCertificateConfig +import com.r3.corda.networkmanage.hsm.menu.Menu +import com.r3.corda.networkmanage.hsm.signer.HsmSigner +import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore +import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA +import net.corda.nodeapi.internal.crypto.getX509Certificate +import net.corda.nodeapi.internal.persistence.CordaPersistence +import java.time.Duration + +class CrrProcessor(private val config: DoormanCertificateConfig, + private val device: String, + private val keySpecifier: Int, + private val persistance: CordaPersistence) : Processor() { + private companion object { + private const val RESET = "\u001B[0m" + private const val BLACK = "\u001B[30m" + private const val RED = "\u001B[31m" + private const val GREEN = "\u001B[32m" + private const val YELLOW = "\u001B[33m" + private const val BLUE = "\u001B[34m" + private const val CYAN = "\u001B[36m" + private const val WHITE = "\u001B[37m" + } + + private val auth = config.authParameters + + fun showMenu() { + val authenticator = Authenticator( + provider = createProvider(config.keyGroup, keySpecifier, device), + mode = auth.mode, + authStrengthThreshold = auth.threshold) + Menu().withExceptionHandler(this::processError).setExitOption("2", "Quit").addItem("1", "View current and sign a new certificate revocation list", { + val crlStorage = PersistentCertificateRevocationListStorage(persistance) + val currentCrl = crlStorage.getCertificateRevocationList(CrlIssuer.DOORMAN) + printlnColor("Current CRL:") + printlnColor(currentCrl.toString(), YELLOW) + val crrStorage = PersistentCertificateRevocationRequestStorage(persistance) + val approvedRequests = crrStorage.getRevocationRequests(RequestStatus.APPROVED) + if (approvedRequests.isEmpty()) { + printlnColor("There are no approved Certificate Revocation Requests.", GREEN) + } else { + printlnColor("Following are the approved Certificate Revocation Requests which will be added to the CRL:") + approvedRequests.forEach { + printlnColor("Certificate DN: ${it.legalName}, Certificate serial number: ${it.certificateSerialNumber}", GREEN) + } + } + Menu().withExceptionHandler(this::processError).setExitOption("2", "Go back"). + addItem("1", "Create and sign a new Certificate Revocation List", { + authenticator.connectAndAuthenticate { provider, signers -> + val keyStore = getAndInitializeKeyStore(provider) + val issuerCertificate = keyStore.getX509Certificate(CORDA_INTERMEDIATE_CA) + val crlSigner = CertificateRevocationListSigner( + revocationListStorage = crlStorage, + issuerCertificate = issuerCertificate, + updateInterval = Duration.ofMillis(config.crlUpdatePeriod), + endpoint = config.crlDistributionPoint, + signer = HsmSigner(provider = provider, keyName = CORDA_INTERMEDIATE_CA)) + val currentRequests = crrStorage.getRevocationRequests(RequestStatus.DONE) + crlSigner.createSignedCRL(approvedRequests, currentRequests, signers.toString()) + } + }) + }).showMenu() + } +} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CsrProcessor.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CsrProcessor.kt index 6b8b91fdf6..42efac8ab5 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CsrProcessor.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/CsrProcessor.kt @@ -16,81 +16,84 @@ import com.r3.corda.networkmanage.hsm.configuration.DoormanCertificateConfig import com.r3.corda.networkmanage.hsm.menu.Menu import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData import com.r3.corda.networkmanage.hsm.persistence.DBSignedCertificateRequestStorage +import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateSigningRequestStorage import com.r3.corda.networkmanage.hsm.signer.HsmCsrSigner -import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.persistence.CordaPersistence class CsrProcessor(private val config: DoormanCertificateConfig, private val device: String, private val keySpecifier: Int, - private val database: CordaPersistence) { - companion object { - val logger = contextLogger() + private val database: CordaPersistence) : Processor() { + private companion object { + private val logger = contextLogger() } private val auth = config.authParameters fun showMenu() { val csrStorage = DBSignedCertificateRequestStorage(database) - val sign: (List) -> Unit = { - val signer = config.run { + val authenticator = Authenticator( + provider = createProvider(config.keyGroup, keySpecifier, device), + mode = auth.mode, + authStrengthThreshold = auth.threshold) + val signCsr: (List) -> Unit = { + val csrSigner = config.run { HsmCsrSigner( csrStorage, loadRootKeyStore(), - crlDistributionPoint, + crlDistributionPoint.toString(), null, validDays, - Authenticator( - provider = createProvider(config.keyGroup, keySpecifier, device), - mode = auth.mode, - authStrengthThreshold = auth.threshold)) + authenticator) } logger.debug("Signing requests: $it") - signer.sign(it) + csrSigner.sign(it) } - Menu().withExceptionHandler(this::processError).setExitOption("3", "Quit").addItem("1", "Sign all approved and unsigned CSRs", - { - logger.debug("Fetching approved requests...") - val approved = csrStorage.getApprovedRequests() - logger.debug("Approved requests fetched: $approved") - if (approved.isNotEmpty()) { - if (confirmedSign(approved)) { - sign(approved) - } - } else { - println("There is no approved CSR") - } - }).addItem("2", "List all approved and unsigned CSRs", - { - logger.debug("Fetching approved requests...") - val approved = csrStorage.getApprovedRequests() - logger.debug("Approved requests fetched: $approved") - if (approved.isNotEmpty()) { - println("Approved CSRs:") - approved.forEachIndexed { index, item -> println("${index + 1}. ${item.request.subject}") } - Menu().withExceptionHandler(this::processError).setExitOption("3", "Go back"). - addItem("1", "Sign all listed CSRs", { - if (confirmedSign(approved)) { - sign(approved) - } - }, isTerminating = true). - addItem("2", "Select and sign CSRs", { - val selectedItems = getSelection(approved) - if (confirmedSign(selectedItems)) { - sign(selectedItems) - } - }, isTerminating = true).showMenu() - } else { - println("There is no approved and unsigned CSR") - } - }).showMenu() + showMenu(csrStorage, signCsr) + } + + private fun showMenu(csrStorage: SignedCertificateSigningRequestStorage, signCsr: (List) -> Unit) { + Menu().withExceptionHandler(this::processError).setExitOption("3", "Quit").addItem("1", "Sign all approved and unsigned CSRs", { + logger.debug("Fetching approved requests...") + val approved = csrStorage.getApprovedRequests() + logger.debug("Approved requests fetched: $approved") + if (approved.isNotEmpty()) { + if (confirmedSign(approved)) { + signCsr(approved) + } + } else { + printlnColor("There are no approved CSRs") + } + }).addItem("2", "List all approved and unsigned CSRs", { + logger.debug("Fetching approved requests...") + val approved = csrStorage.getApprovedRequests() + logger.debug("Approved requests fetched: $approved") + if (approved.isNotEmpty()) { + printlnColor("Approved CSRs:") + approved.forEachIndexed { index, item -> printlnColor("${index + 1}. ${item.request.subject}") } + Menu().withExceptionHandler(this::processError).setExitOption("3", "Go back"). + addItem("1", "Sign all listed CSRs", { + if (confirmedSign(approved)) { + signCsr(approved) + } + }, isTerminating = true). + addItem("2", "Select and sign CSRs", { + val selectedItems = getSelection(approved) + if (confirmedSign(selectedItems)) { + signCsr(selectedItems) + } + }, isTerminating = true).showMenu() + } else { + printlnColor("There is no approved and unsigned CSR") + } + }).showMenu() } private fun confirmedSign(selectedItems: List): Boolean { - println("Are you sure you want to sign the following requests:") + printlnColor("Are you sure you want to sign the following requests:") selectedItems.forEachIndexed { index, data -> - println("${index + 1} ${data.request.subject}") + printlnColor("${index + 1} ${data.request.subject}") } var result = false Menu().addItem("Y", "Yes", { result = true }, true).setExitOption("N", "No").showMenu() @@ -101,7 +104,7 @@ class CsrProcessor(private val config: DoormanCertificateConfig, print("CSRs to be signed (comma separated list): ") val line = readLine() if (line == null) { - println("EOF reached") + printlnColor("EOF reached") return emptyList() } return try { @@ -114,14 +117,8 @@ class CsrProcessor(private val config: DoormanCertificateConfig, } } } catch (exception: Exception) { - println(exception.message) + printlnColor(exception.message) emptyList() } } - - fun processError(exception: Exception) { - val processed = mapCryptoServerException(exception) - System.err.println("An error occurred:") - processed.printStackTrace() - } } \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt index 8f453dbb03..dc65244d59 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/NetworkMapProcessor.kt @@ -12,6 +12,7 @@ package com.r3.corda.networkmanage.hsm.processor import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage import com.r3.corda.networkmanage.common.signer.NetworkMapSigner +import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP import com.r3.corda.networkmanage.hsm.authentication.AuthMode import com.r3.corda.networkmanage.hsm.authentication.Authenticator import com.r3.corda.networkmanage.hsm.authentication.createProvider @@ -49,7 +50,8 @@ class NetworkMapProcessor(private val config: NetworkMapCertificateConfig, authParameters.keyFilePath, authParameters.password, authParameters.threshold, - provider = createProvider(keyGroup, keySpecifier, device))) + provider = createProvider(keyGroup, keySpecifier, device)), + keyName = CORDA_NETWORK_MAP) val networkMapSigner = NetworkMapSigner(networkMapStorage, signer) try { logger.info("Executing network map signing...") diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/Processor.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/Processor.kt new file mode 100644 index 0000000000..7cf9d90c31 --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/processor/Processor.kt @@ -0,0 +1,16 @@ +package com.r3.corda.networkmanage.hsm.processor + +import com.r3.corda.networkmanage.hsm.utils.mapCryptoServerException + +abstract class Processor { + + protected fun processError(exception: Exception) { + val processed = mapCryptoServerException(exception) + System.err.println("An error occurred:") + processed.printStackTrace() + } + + protected fun printlnColor(line: String?, color: String = "") { + println(color + line) + } +} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmCsrSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmCsrSigner.kt index 890c1b40bc..dcdebe5349 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmCsrSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmCsrSigner.kt @@ -13,13 +13,11 @@ package com.r3.corda.networkmanage.hsm.signer import com.r3.corda.networkmanage.common.utils.getCertRole import com.r3.corda.networkmanage.hsm.authentication.Authenticator import com.r3.corda.networkmanage.hsm.persistence.ApprovedCertificateRequestData -import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateRequestStorage +import com.r3.corda.networkmanage.hsm.persistence.SignedCertificateSigningRequestStorage import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.createClientCertificate import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.retrieveCertAndKeyPair -import net.corda.core.internal.CertRole import net.corda.core.utilities.contextLogger -import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA @@ -31,7 +29,7 @@ import java.io.PrintStream /** * Encapsulates certificate signing logic */ -class HsmCsrSigner(private val storage: SignedCertificateRequestStorage, +class HsmCsrSigner(private val storage: SignedCertificateSigningRequestStorage, private val rootKeyStore: X509KeyStore, private val csrCertCrlDistPoint: String, private val csrCertCrlIssuer: String?, diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmSigner.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmSigner.kt index 01f5fd49b4..78b0d3ee0f 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmSigner.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/hsm/signer/HsmSigner.kt @@ -10,8 +10,9 @@ package com.r3.corda.networkmanage.hsm.signer +import CryptoServerJCE.CryptoServerProvider +import com.google.common.primitives.Booleans import com.r3.corda.networkmanage.common.signer.Signer -import com.r3.corda.networkmanage.common.utils.CORDA_NETWORK_MAP import com.r3.corda.networkmanage.hsm.authentication.Authenticator import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities import com.r3.corda.networkmanage.hsm.utils.HsmX509Utilities.getAndInitializeKeyStore @@ -22,26 +23,42 @@ import java.security.PrivateKey import java.security.Signature /** - * Signer which connects to a HSM using the given [authenticator] to sign bytes. + * Signer which connects to a HSM using the given [authenticator] and provider to sign bytes. */ -class HsmSigner(private val authenticator: Authenticator) : Signer { +class HsmSigner(private val authenticator: Authenticator? = null, + private val provider: CryptoServerProvider? = null, + private val keyName: String) : Signer { /** * Signs given data using [CryptoServerJCE.CryptoServerProvider], which connects to the underlying HSM. */ - override fun signBytes(data: ByteArray): DigitalSignatureWithCert { - return authenticator.connectAndAuthenticate { provider, _ -> - val keyStore = getAndInitializeKeyStore(provider) - val certificate = keyStore.getX509Certificate(CORDA_NETWORK_MAP) - // Don't worry this is not a real private key but a pointer to one that resides in the HSM. It only works - // when used with the given provider. - val key = keyStore.getKey(CORDA_NETWORK_MAP, null) as PrivateKey - val signature = Signature.getInstance(HsmX509Utilities.SIGNATURE_ALGORITHM, provider).run { - initSign(key) - update(data) - sign() - } - verify(data, signature, certificate.publicKey) - DigitalSignatureWithCert(certificate, signature) + init { + require(Booleans.countTrue(authenticator != null, provider != null) == 1) { + "Either authenticator or provider needs to be non-null." } } -} + + override fun signBytes(data: ByteArray): DigitalSignatureWithCert { + if (provider == null) { + return authenticator!!.connectAndAuthenticate { provider, _ -> + signBytes(data, provider) + } + } else { + return signBytes(data, provider) + } + } + + private fun signBytes(data: ByteArray, provider: CryptoServerProvider): DigitalSignatureWithCert { + val keyStore = getAndInitializeKeyStore(provider) + val certificate = keyStore.getX509Certificate(keyName) + // Don't worry this is not a real private key but a pointer to one that resides in the HSM. It only works + // when used with the given provider. + val key = keyStore.getKey(keyName, null) as PrivateKey + val signature = Signature.getInstance(HsmX509Utilities.SIGNATURE_ALGORITHM, provider).run { + initSign(key) + update(data) + sign() + } + verify(data, signature, certificate.publicKey) + return DigitalSignatureWithCert(certificate, signature) + } +} \ No newline at end of file diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationListStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationListStorageTest.kt index 99e3becbff..f0200491c9 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationListStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationListStorageTest.kt @@ -2,6 +2,7 @@ package com.r3.corda.networkmanage.common.persistence import com.r3.corda.networkmanage.TestBase import net.corda.core.utilities.minutes +import net.corda.nodeapi.internal.network.CertificateRevocationRequest import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.internal.DEV_INTERMEDIATE_CA @@ -52,7 +53,10 @@ class PersistentCertificateRevocationListStorageTest : TestBase() { fun `Saving CRL persists it in the DB and changes the status of the certificate revocation requests to DONE`() { // given val certificate = createNodeCertificate(csrStorage) - val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER) + val requestId = crrStorage.saveRevocationRequest(CertificateRevocationRequest( + certificateSerialNumber = certificate.serialNumber, + reason = REVOCATION_REASON, + reporter = REPORTER)) crrStorage.approveRevocationRequest(requestId, "Approver") val revocationRequest = crrStorage.getRevocationRequest(requestId)!! val crl = createDummyCertificateRevocationList(listOf(revocationRequest.certificateSerialNumber)) @@ -72,16 +76,25 @@ class PersistentCertificateRevocationListStorageTest : TestBase() { @Test fun `Saving CRL does not change the status of other requests`() { // given - val done = crrStorage.saveRevocationRequest(createNodeCertificate(csrStorage, legalName = "Bank A").serialNumber, REVOCATION_REASON, REPORTER) + val done = crrStorage.saveRevocationRequest(CertificateRevocationRequest( + certificateSerialNumber = createNodeCertificate(csrStorage, legalName = "Bank A").serialNumber, + reason = REVOCATION_REASON, + reporter = REPORTER)) crrStorage.approveRevocationRequest(done, "Approver") val doneRevocationRequest = crrStorage.getRevocationRequest(done)!! - val new = crrStorage.saveRevocationRequest(createNodeCertificate(csrStorage, legalName = "Bank B").serialNumber, REVOCATION_REASON, REPORTER) + val new = crrStorage.saveRevocationRequest(CertificateRevocationRequest( + certificateSerialNumber = createNodeCertificate(csrStorage, legalName = "Bank B").serialNumber, + reason = REVOCATION_REASON, + reporter = REPORTER)) val crl = createDummyCertificateRevocationList(listOf(doneRevocationRequest.certificateSerialNumber)) crlStorage.saveCertificateRevocationList(crl, CrlIssuer.DOORMAN, "TestSigner", Instant.now()) - val approved = crrStorage.saveRevocationRequest(createNodeCertificate(csrStorage, legalName = "Bank C").serialNumber, REVOCATION_REASON, REPORTER) + val approved = crrStorage.saveRevocationRequest(CertificateRevocationRequest( + certificateSerialNumber = createNodeCertificate(csrStorage, legalName = "Bank C").serialNumber, + reason = REVOCATION_REASON, + reporter = REPORTER)) crrStorage.approveRevocationRequest(approved, "Approver") val approvedRevocationRequest = crrStorage.getRevocationRequest(approved)!! diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorageTest.kt index 141602b441..ca4328dc56 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRevocationRequestStorageTest.kt @@ -1,6 +1,7 @@ package com.r3.corda.networkmanage.common.persistence import com.r3.corda.networkmanage.TestBase +import net.corda.nodeapi.internal.network.CertificateRevocationRequest import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.node.MockServices @@ -41,7 +42,10 @@ class PersistentCertificateRevocationRequestStorageTest : TestBase() { val certificate = createNodeCertificate(csrStorage) // when - val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER) + val requestId = crrStorage.saveRevocationRequest(CertificateRevocationRequest( + certificateSerialNumber = certificate.serialNumber, + reason = REVOCATION_REASON, + reporter = REPORTER)) // then assertNotNull(crrStorage.getRevocationRequest(requestId)).apply { @@ -55,7 +59,10 @@ class PersistentCertificateRevocationRequestStorageTest : TestBase() { fun `Retrieving a certificate revocation request succeeds`() { // given val certificate = createNodeCertificate(csrStorage) - val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER) + val requestId = crrStorage.saveRevocationRequest(CertificateRevocationRequest( + certificateSerialNumber = certificate.serialNumber, + reason = REVOCATION_REASON, + reporter = REPORTER)) // when val request = crrStorage.getRevocationRequest(requestId) @@ -68,10 +75,16 @@ class PersistentCertificateRevocationRequestStorageTest : TestBase() { fun `Retrieving a certificate revocation requests by status returns correct data`() { // given (1..10).forEach { - crrStorage.saveRevocationRequest(createNodeCertificate(csrStorage, "LegalName" + it.toString()).serialNumber, REVOCATION_REASON, REPORTER) + crrStorage.saveRevocationRequest(CertificateRevocationRequest( + certificateSerialNumber = createNodeCertificate(csrStorage, "LegalName" + it.toString()).serialNumber, + reason = REVOCATION_REASON, + reporter = REPORTER)) } (11..15).forEach { - val requestId = crrStorage.saveRevocationRequest(createNodeCertificate(csrStorage, "LegalName" + it.toString()).serialNumber, REVOCATION_REASON, REPORTER) + val requestId = crrStorage.saveRevocationRequest(CertificateRevocationRequest( + certificateSerialNumber = createNodeCertificate(csrStorage, "LegalName" + it.toString()).serialNumber, + reason = REVOCATION_REASON, + reporter = REPORTER)) crrStorage.approveRevocationRequest(requestId, "Approver") } @@ -89,7 +102,10 @@ class PersistentCertificateRevocationRequestStorageTest : TestBase() { // then assertFailsWith(IllegalArgumentException::class) { // when - crrStorage.saveRevocationRequest(BigInteger.TEN, REVOCATION_REASON, REPORTER) + crrStorage.saveRevocationRequest(CertificateRevocationRequest( + certificateSerialNumber = BigInteger.TEN, + reason = REVOCATION_REASON, + reporter = REPORTER)) } } @@ -97,7 +113,10 @@ class PersistentCertificateRevocationRequestStorageTest : TestBase() { fun `Approving a certificate revocation request changes its status`() { // given val certificate = createNodeCertificate(csrStorage) - val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER) + val requestId = crrStorage.saveRevocationRequest(CertificateRevocationRequest( + certificateSerialNumber = certificate.serialNumber, + reason = REVOCATION_REASON, + reporter = REPORTER)) // when crrStorage.approveRevocationRequest(requestId, "Approver") @@ -112,7 +131,10 @@ class PersistentCertificateRevocationRequestStorageTest : TestBase() { fun `Rejecting a certificate revocation request changes its status`() { // given val certificate = createNodeCertificate(csrStorage) - val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER) + val requestId = crrStorage.saveRevocationRequest(CertificateRevocationRequest( + certificateSerialNumber = certificate.serialNumber, + reason = REVOCATION_REASON, + reporter = REPORTER)) // when crrStorage.rejectRevocationRequest(requestId, "Rejector", "No reason") diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/CertificateRevocationListSignerTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/CertificateRevocationListSignerTest.kt new file mode 100644 index 0000000000..e180c3e429 --- /dev/null +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/signer/CertificateRevocationListSignerTest.kt @@ -0,0 +1,73 @@ +package com.r3.corda.networkmanage.common.signer + +import com.nhaarman.mockito_kotlin.* +import com.r3.corda.networkmanage.TestBase +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationListStorage +import com.r3.corda.networkmanage.common.persistence.CertificateRevocationRequestData +import com.r3.corda.networkmanage.common.persistence.CrlIssuer +import com.r3.corda.networkmanage.common.persistence.RequestStatus +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.random63BitValue +import net.corda.core.crypto.sign +import net.corda.core.identity.CordaX500Name +import net.corda.core.internal.DigitalSignatureWithCert +import net.corda.core.utilities.millis +import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA +import org.junit.Before +import org.junit.Test +import java.math.BigInteger +import java.net.URL +import java.security.cert.CRLReason +import java.security.cert.X509CRL +import java.time.Instant +import kotlin.test.assertNotNull + +class CertificateRevocationListSignerTest : TestBase() { + private val signer = TestSigner() + private lateinit var crlStorage: CertificateRevocationListStorage + private lateinit var crlSigner: CertificateRevocationListSigner + + @Before + fun setUp() { + crlStorage = mock() + crlSigner = CertificateRevocationListSigner(crlStorage, DEV_INTERMEDIATE_CA.certificate, 600.millis, URL("http://dummy.com"), signer) + } + + @Test + fun `signCertificateRevocationList creates correct CRL and saves c`() { + // given + val approvedReq = givenCertificateRevocationRequest(RequestStatus.APPROVED) + val revokedReq = givenCertificateRevocationRequest(RequestStatus.DONE) + val signedBy = "Signer" + + // when + crlSigner.createSignedCRL(listOf(approvedReq), listOf(revokedReq), signedBy) + + // then + argumentCaptor().apply { + verify(crlStorage).saveCertificateRevocationList(capture(), eq(CrlIssuer.DOORMAN), eq(signedBy), any()) + val crl = firstValue + crl.verify(DEV_INTERMEDIATE_CA.keyPair.public) + assertNotNull(crl.getRevokedCertificate(approvedReq.certificateSerialNumber)) + assertNotNull(crl.getRevokedCertificate(revokedReq.certificateSerialNumber)) + } + } + + private class TestSigner : Signer { + override fun signBytes(data: ByteArray): DigitalSignatureWithCert { + return DigitalSignatureWithCert(DEV_INTERMEDIATE_CA.certificate, DEV_INTERMEDIATE_CA.keyPair.private.sign(data).bytes) + } + } + + private fun givenCertificateRevocationRequest(status: RequestStatus): CertificateRevocationRequestData { + return CertificateRevocationRequestData( + SecureHash.randomSHA256().toString(), + BigInteger.valueOf(random63BitValue()), + Instant.now(), + CordaX500Name.parse("CN=Bank A, O=$status, L=London, C=GB"), + status, + CRLReason.KEY_COMPROMISE, + "Reporter") + } + +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/CertificateRevocationRequest.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/CertificateRevocationRequest.kt new file mode 100644 index 0000000000..2001bd6a62 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/network/CertificateRevocationRequest.kt @@ -0,0 +1,23 @@ +package net.corda.nodeapi.internal.network + +import net.corda.core.identity.CordaX500Name +import net.corda.core.serialization.CordaSerializable +import java.math.BigInteger +import java.security.cert.CRLReason + +/** + * This data class is intended to be used by the certificate revocation request (CRR) service client to create a new + * CRR submission. + */ +@CordaSerializable +data class CertificateRevocationRequest(val certificateSerialNumber: BigInteger? = null, + val csrRequestId: String? = null, + val legalName: CordaX500Name? = null, + val reason: CRLReason, + val reporter: String) { + init { + require(certificateSerialNumber != null || csrRequestId != null || legalName != null) { + "At least one of the following needs to be specified: certificateSerialNumber, csrRequestId, legalName." + } + } +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/BigIntegerSerializer.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/BigIntegerSerializer.kt index 268676c312..ade6151fb7 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/BigIntegerSerializer.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/serialization/amqp/custom/BigIntegerSerializer.kt @@ -1,3 +1,13 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + package net.corda.nodeapi.internal.serialization.amqp.custom import net.corda.nodeapi.internal.serialization.amqp.CustomSerializer