Using CordaX500Name in the doorman database entity objects (ENT-1524) (#573)

certificate_signing_request.legal_name is nullable - invalid names from CSRs are not stored in the db.
This commit is contained in:
Shams Asari 2018-03-16 18:45:28 +00:00 committed by GitHub
parent 908a3badf1
commit 060f7cc3cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 73 additions and 55 deletions

View File

@ -11,6 +11,7 @@
package com.r3.corda.networkmanage.common.persistence package com.r3.corda.networkmanage.common.persistence
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.security.cert.CertPath import java.security.cert.CertPath
@ -18,7 +19,7 @@ import java.security.cert.CertPath
data class CertificateData(val certStatus: CertificateStatus, val certPath: CertPath) data class CertificateData(val certStatus: CertificateStatus, val certPath: CertPath)
data class CertificateSigningRequest(val requestId: String, data class CertificateSigningRequest(val requestId: String,
val legalName: String, val legalName: CordaX500Name?,
val publicKeyHash: SecureHash, val publicKeyHash: SecureHash,
val status: RequestStatus, val status: RequestStatus,
val request: PKCS10CertificationRequest, val request: PKCS10CertificationRequest,

View File

@ -37,7 +37,7 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
modifiedBy = request.reporter, modifiedBy = request.reporter,
certificateData = certificateData, certificateData = certificateData,
reporter = request.reporter, reporter = request.reporter,
legalName = certificateData.legalName() legalName = certificateData.legalName
)) ))
requestId requestId
} }
@ -84,7 +84,7 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
legalName: CordaX500Name?, legalName: CordaX500Name?,
result: CertificateSigningRequestEntity): CertificateSigningRequestEntity { result: CertificateSigningRequestEntity): CertificateSigningRequestEntity {
val certData = result.certificateData!! val certData = result.certificateData!!
require(legalName == null || result.legalName == legalName.toString()) { require(legalName == null || result.legalName == legalName) {
"The legal name does not match." "The legal name does not match."
} }
require(csrRequestId == null || result.requestId == csrRequestId) { require(csrRequestId == null || result.requestId == csrRequestId) {
@ -98,7 +98,7 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
override fun getRevocationRequest(requestId: String): CertificateRevocationRequestData? { override fun getRevocationRequest(requestId: String): CertificateRevocationRequestData? {
return database.transaction { return database.transaction {
getRevocationRequestEntity(requestId)?.toCertificateRevocationRequest() getRevocationRequestEntity(requestId)?.toCertificateRevocationRequestData()
} }
} }
@ -110,7 +110,7 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
where(builder.equal(get<RequestStatus>(CertificateRevocationRequestEntity::status.name), revocationStatus)) where(builder.equal(get<RequestStatus>(CertificateRevocationRequestEntity::status.name), revocationStatus))
} }
} }
session.createQuery(query).resultList.map { it.toCertificateRevocationRequest() } session.createQuery(query).resultList.map { it.toCertificateRevocationRequestData() }
} }
} }
@ -153,14 +153,15 @@ class PersistentCertificateRevocationRequestStorage(private val database: CordaP
} }
} }
private fun CertificateRevocationRequestEntity.toCertificateRevocationRequest(): CertificateRevocationRequestData { private fun CertificateRevocationRequestEntity.toCertificateRevocationRequestData(): CertificateRevocationRequestData {
return CertificateRevocationRequestData( return CertificateRevocationRequestData(
requestId, requestId,
certificateSerialNumber, certificateSerialNumber,
modifiedAt, modifiedAt,
CordaX500Name.parse(legalName), legalName,
status, status,
revocationReason, revocationReason,
reporter) reporter
)
} }
} }

View File

@ -61,27 +61,21 @@ class PersistentCertificateSigningRequestStorage(private val database: CordaPers
override fun saveRequest(request: PKCS10CertificationRequest): String { override fun saveRequest(request: PKCS10CertificationRequest): String {
val requestId = SecureHash.randomSHA256().toString() val requestId = SecureHash.randomSHA256().toString()
database.transaction(TransactionIsolationLevel.SERIALIZABLE) { database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
val requestEntity = try { val legalNameOrRejectMessage = try {
val legalName = validateRequestAndParseLegalName(request) validateRequestAndParseLegalName(request)
CertificateSigningRequestEntity(
requestId = requestId,
legalName = legalName,
publicKeyHash = toSupportedPublicKey(request.subjectPublicKeyInfo).hashString(),
requestBytes = request.encoded,
modifiedBy = CertificateSigningRequestStorage.DOORMAN_SIGNATURE,
status = RequestStatus.NEW
)
} catch (e: RequestValidationException) { } catch (e: RequestValidationException) {
CertificateSigningRequestEntity( e.rejectMessage
}
val requestEntity = CertificateSigningRequestEntity(
requestId = requestId, requestId = requestId,
legalName = e.parsedLegalName, legalName = legalNameOrRejectMessage as? CordaX500Name,
publicKeyHash = toSupportedPublicKey(request.subjectPublicKeyInfo).hashString(), publicKeyHash = toSupportedPublicKey(request.subjectPublicKeyInfo).hashString(),
requestBytes = request.encoded, requestBytes = request.encoded,
remark = e.rejectMessage, remark = legalNameOrRejectMessage as? String,
modifiedBy = CertificateSigningRequestStorage.DOORMAN_SIGNATURE, modifiedBy = CertificateSigningRequestStorage.DOORMAN_SIGNATURE,
status = RequestStatus.REJECTED status = if (legalNameOrRejectMessage is CordaX500Name) RequestStatus.NEW else RequestStatus.REJECTED
) )
}
session.save(requestEntity) session.save(requestEntity)
} }
return requestId return requestId
@ -155,7 +149,7 @@ class PersistentCertificateSigningRequestStorage(private val database: CordaPers
} }
} }
private fun DatabaseTransaction.validateRequestAndParseLegalName(request: PKCS10CertificationRequest): String { private fun DatabaseTransaction.validateRequestAndParseLegalName(request: PKCS10CertificationRequest): CordaX500Name {
// It's important that we always use the toString() output of CordaX500Name as it standardises the string format // It's important that we always use the toString() output of CordaX500Name as it standardises the string format
// to make querying possible. // to make querying possible.
val legalName = try { val legalName = try {
@ -169,21 +163,21 @@ class PersistentCertificateSigningRequestStorage(private val database: CordaPers
// TODO consider scenario: There is a CSR that is signed but the certificate itself has expired or was revoked // TODO consider scenario: There is a CSR that is signed but the certificate itself has expired or was revoked
// Also, at the moment we assume that once the CSR is approved it cannot be rejected. // Also, at the moment we assume that once the CSR is approved it cannot be rejected.
// What if we approved something by mistake. // What if we approved something by mistake.
nonRejectedRequestExists(CertificateSigningRequestEntity::legalName.name, legalName.toString()) -> throw RequestValidationException(legalName.toString(), "Duplicate legal name") nonRejectedRequestExists(CertificateSigningRequestEntity::legalName.name, legalName) -> throw RequestValidationException(legalName.toString(), "Duplicate legal name")
//TODO Consider following scenario: There is a CSR that is signed but the certificate itself has expired or was revoked //TODO Consider following scenario: There is a CSR that is signed but the certificate itself has expired or was revoked
nonRejectedRequestExists(CertificateSigningRequestEntity::publicKeyHash.name, toSupportedPublicKey(request.subjectPublicKeyInfo).hashString()) -> throw RequestValidationException(legalName.toString(), "Duplicate public key") nonRejectedRequestExists(CertificateSigningRequestEntity::publicKeyHash.name, toSupportedPublicKey(request.subjectPublicKeyInfo).hashString()) -> throw RequestValidationException(legalName.toString(), "Duplicate public key")
else -> legalName.toString() else -> legalName
} }
} }
/** /**
* Check if "non-rejected" request exists with provided column and value. * Check if "non-rejected" request exists with provided column and value.
*/ */
private fun DatabaseTransaction.nonRejectedRequestExists(columnName: String, value: String): Boolean { private fun DatabaseTransaction.nonRejectedRequestExists(columnName: String, value: Any): Boolean {
val query = session.criteriaBuilder.run { val query = session.criteriaBuilder.run {
val criteriaQuery = createQuery(CertificateSigningRequestEntity::class.java) val criteriaQuery = createQuery(CertificateSigningRequestEntity::class.java)
criteriaQuery.from(CertificateSigningRequestEntity::class.java).run { criteriaQuery.from(CertificateSigningRequestEntity::class.java).run {
val valueQuery = equal(get<String>(columnName), value) val valueQuery = equal(get<CordaX500Name>(columnName), value)
val statusQuery = notEqual(get<RequestStatus>(CertificateSigningRequestEntity::status.name), RequestStatus.REJECTED) val statusQuery = notEqual(get<RequestStatus>(CertificateSigningRequestEntity::status.name), RequestStatus.REJECTED)
criteriaQuery.where(and(valueQuery, statusQuery)) criteriaQuery.where(and(valueQuery, statusQuery))
} }
@ -191,5 +185,5 @@ class PersistentCertificateSigningRequestStorage(private val database: CordaPers
return session.createQuery(query).setMaxResults(1).resultList.isNotEmpty() return session.createQuery(query).setMaxResults(1).resultList.isNotEmpty()
} }
private class RequestValidationException(val parsedLegalName: String, val rejectMessage: String) : Exception("Validation failed for $parsedLegalName. $rejectMessage.") private class RequestValidationException(subjectName: String, val rejectMessage: String) : Exception("Validation failed for $subjectName. $rejectMessage.")
} }

View File

@ -1,6 +1,7 @@
package com.r3.corda.networkmanage.common.persistence.entity package com.r3.corda.networkmanage.common.persistence.entity
import com.r3.corda.networkmanage.common.persistence.RequestStatus import com.r3.corda.networkmanage.common.persistence.RequestStatus
import net.corda.core.identity.CordaX500Name
import org.hibernate.envers.Audited import org.hibernate.envers.Audited
import java.math.BigInteger import java.math.BigInteger
import java.security.cert.CRLReason import java.security.cert.CRLReason
@ -25,7 +26,8 @@ class CertificateRevocationRequestEntity(
val certificateSerialNumber: BigInteger, val certificateSerialNumber: BigInteger,
@Column(name = "legal_name", length = 256, nullable = false) @Column(name = "legal_name", length = 256, nullable = false)
val legalName: String, @Convert(converter = CordaX500NameAttributeConverter::class)
val legalName: CordaX500Name,
@Audited @Audited
@Column(name = "status", nullable = false) @Column(name = "status", nullable = false)
@ -56,7 +58,7 @@ class CertificateRevocationRequestEntity(
certificateData: CertificateDataEntity = this.certificateData, certificateData: CertificateDataEntity = this.certificateData,
certificateSerialNumber: BigInteger = this.certificateSerialNumber, certificateSerialNumber: BigInteger = this.certificateSerialNumber,
status: RequestStatus = this.status, status: RequestStatus = this.status,
legalName: String = this.legalName, legalName: CordaX500Name = this.legalName,
reporter: String = this.reporter, reporter: String = this.reporter,
modifiedBy: String = this.modifiedBy, modifiedBy: String = this.modifiedBy,
modifiedAt: Instant = this.modifiedAt, modifiedAt: Instant = this.modifiedAt,

View File

@ -16,11 +16,12 @@ import com.r3.corda.networkmanage.common.persistence.CertificateStatus
import com.r3.corda.networkmanage.common.persistence.RequestStatus import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.common.utils.buildCertPath import com.r3.corda.networkmanage.common.utils.buildCertPath
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.nodeapi.internal.crypto.x509Certificates
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.hibernate.envers.Audited import org.hibernate.envers.Audited
import java.math.BigInteger import java.math.BigInteger
import java.security.cert.CertPath import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.time.Instant import java.time.Instant
import javax.persistence.* import javax.persistence.*
@ -32,8 +33,9 @@ data class CertificateSigningRequestEntity(
val requestId: String, val requestId: String,
// TODO: Store X500Name with a proper schema. // TODO: Store X500Name with a proper schema.
@Column(name = "legal_name", length = 256, nullable = false) @Column(name = "legal_name", length = 256)
val legalName: String, @Convert(converter = CordaX500NameAttributeConverter::class)
val legalName: CordaX500Name?,
@Column(name = "public_key_hash", length = 64) @Column(name = "public_key_hash", length = 64)
val publicKeyHash: String, val publicKeyHash: String,
@ -66,16 +68,18 @@ data class CertificateSigningRequestEntity(
@JoinColumn(name = "private_network", foreignKey = ForeignKey(name = "FK_CSR_PN")) @JoinColumn(name = "private_network", foreignKey = ForeignKey(name = "FK_CSR_PN"))
val privateNetwork: PrivateNetworkEntity? = null val privateNetwork: PrivateNetworkEntity? = null
) { ) {
fun toCertificateSigningRequest() = CertificateSigningRequest( fun toCertificateSigningRequest(): CertificateSigningRequest {
requestId = requestId, return CertificateSigningRequest(
legalName = legalName, requestId = requestId,
publicKeyHash = SecureHash.parse(publicKeyHash), legalName = legalName,
status = status, publicKeyHash = SecureHash.parse(publicKeyHash),
request = request(), status = status,
remark = remark, request = request(),
modifiedBy = modifiedBy, remark = remark,
certData = certificateData?.toCertificateData() modifiedBy = modifiedBy,
) certData = certificateData?.toCertificateData()
)
}
private fun request() = PKCS10CertificationRequest(requestBytes) private fun request() = PKCS10CertificationRequest(requestBytes)
} }
@ -108,8 +112,8 @@ data class CertificateDataEntity(
) )
} }
fun legalName(): String { val legalName: CordaX500Name get() {
return (toCertificatePath().certificates.first() as X509Certificate).subjectX500Principal.name return CordaX500Name.build(toCertificatePath().x509Certificates[0].subjectX500Principal)
} }
fun copy(certificateStatus: CertificateStatus = this.certificateStatus, fun copy(certificateStatus: CertificateStatus = this.certificateStatus,

View File

@ -0,0 +1,15 @@
package com.r3.corda.networkmanage.common.persistence.entity
import net.corda.core.identity.CordaX500Name
import javax.persistence.AttributeConverter
class CordaX500NameAttributeConverter : AttributeConverter<CordaX500Name, String> {
override fun convertToDatabaseColumn(attribute: CordaX500Name?): String? = attribute?.toString()
override fun convertToEntityAttribute(dbData: String?): CordaX500Name? = dbData?.let { CordaX500Name.parse(it) }
}
// TODO Use SecureHash in entities
//class SecureHashAttributeConverter : AttributeConverter<SecureHash, String> {
// override fun convertToDatabaseColumn(attribute: SecureHash?): String? = attribute?.toString()
// override fun convertToEntityAttribute(dbData: String?): SecureHash? = dbData?.let { SecureHash.parse(it) }
//}

View File

@ -42,9 +42,7 @@
<column name="request_id" type="NVARCHAR(64)"> <column name="request_id" type="NVARCHAR(64)">
<constraints nullable="false"/> <constraints nullable="false"/>
</column> </column>
<column name="legal_name" type="NVARCHAR(256)"> <column name="legal_name" type="NVARCHAR(256)"/>
<constraints nullable="false"/>
</column>
<column name="modified_at" type="TIMESTAMP"> <column name="modified_at" type="TIMESTAMP">
<constraints nullable="false"/> <constraints nullable="false"/>
</column> </column>

View File

@ -17,6 +17,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.CertRole import net.corda.core.internal.CertRole
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.createDevNodeCaCertPath import net.corda.testing.internal.createDevNodeCaCertPath
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
@ -35,7 +36,7 @@ abstract class TestBase {
protected fun certificateSigningRequest( protected fun certificateSigningRequest(
requestId: String = SecureHash.randomSHA256().toString(), requestId: String = SecureHash.randomSHA256().toString(),
status: RequestStatus = RequestStatus.NEW, status: RequestStatus = RequestStatus.NEW,
legalName: String = "TestLegalName", legalName: CordaX500Name = ALICE_NAME,
publicKeyHash: SecureHash = SecureHash.randomSHA256(), publicKeyHash: SecureHash = SecureHash.randomSHA256(),
remark: String = "Test remark", remark: String = "Test remark",
request: PKCS10CertificationRequest = mock(), request: PKCS10CertificationRequest = mock(),

View File

@ -20,6 +20,8 @@ import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -89,7 +91,7 @@ class JiraCsrHandlerTest : TestBase() {
fun `create tickets`() { fun `create tickets`() {
val csr = certificateSigningRequest( val csr = certificateSigningRequest(
requestId = requestId, requestId = requestId,
legalName = "name", legalName = ALICE_NAME,
status = RequestStatus.NEW, status = RequestStatus.NEW,
request = pkcS10CertificationRequest) request = pkcS10CertificationRequest)
whenever(certificationRequestStorage.getRequests(RequestStatus.NEW)).thenReturn(listOf(csr)) whenever(certificationRequestStorage.getRequests(RequestStatus.NEW)).thenReturn(listOf(csr))
@ -105,8 +107,8 @@ class JiraCsrHandlerTest : TestBase() {
fun `sync tickets status`() { fun `sync tickets status`() {
val id1 = SecureHash.randomSHA256().toString() val id1 = SecureHash.randomSHA256().toString()
val id2 = SecureHash.randomSHA256().toString() val id2 = SecureHash.randomSHA256().toString()
val csr1 = CertificateSigningRequest(id1, "name1", SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, "Test", null) val csr1 = CertificateSigningRequest(id1, ALICE_NAME, SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, "Test", null)
val csr2 = CertificateSigningRequest(id2, "name2", SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, "Test", null) val csr2 = CertificateSigningRequest(id2, BOB_NAME, SecureHash.randomSHA256(), RequestStatus.NEW, pkcS10CertificationRequest, null, "Test", null)
val requests = mutableMapOf(id1 to csr1, id2 to csr2) val requests = mutableMapOf(id1 to csr1, id2 to csr2)