Certificate Revocation Request - Persistence Layer (#507)

* Certificate Revocation Request - Persistence Layer

* Addressing review comments

* Addressing review comments

* Adding comments to the requestId field
This commit is contained in:
Michal Kit 2018-03-07 11:13:34 +00:00 committed by GitHub
parent 1ce4805c6d
commit 6bed95c02b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 556 additions and 9 deletions

View File

@ -0,0 +1,69 @@
package com.r3.corda.networkmanage.common.persistence
import java.math.BigInteger
import java.security.cert.CRLReason
import java.time.Instant
data class CertificateRevocationRequestData(val requestId: String, // This is a uniquely generated string
val certificateSerialNumber: BigInteger,
val revocationTime: Instant?,
val legalName: String,
val status: RequestStatus,
val reason: CRLReason,
val reporter: String) // Username of the reporter
/**
* Interface for managing certificate revocation requests persistence
*/
interface CertificateRevocationRequestStorage {
/**
* Creates a new revocation request for the given [certificateSerialNumber].
* The newly created revocation request has the [RequestStatus.NEW] status.
* If the revocation request with the [certificateSerialNumber] already exists and has status
* [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
*
* @return identifier of the newly created (or existing) revocation request.
*/
fun saveRevocationRequest(certificateSerialNumber: BigInteger, reason: CRLReason, reporter: String): String
/**
* Retrieves the revocation request with the given [requestId]
*
* @param requestId revocation request identifier
*
* @return CertificateRevocationRequest matching the specified identifier. Or null if it doesn't exist.
*/
fun getRevocationRequest(requestId: String): CertificateRevocationRequestData?
/**
* Retrieves all the revocation requests with the specified revocation request status.
*
* @param revocationStatus revocation request status of the returned revocation requests.
*
* @return list of certificate revocation requests that match the revocation request status.
*/
fun getRevocationRequests(revocationStatus: RequestStatus): List<CertificateRevocationRequestData>
/**
* Changes the revocation request status to [RequestStatus.APPROVED].
*
* @param requestId revocation request identifier
* @param approvedBy who is approving it
*/
fun approveRevocationRequest(requestId: String, approvedBy: String)
/**
* Changes the revocation request status to [RequestStatus.REJECTED].
*
* @param requestId revocation request identifier
* @param rejectedBy who is rejecting it
* @param reason description of the reason of this rejection.
*/
fun rejectRevocationRequest(requestId: String, rejectedBy: String, reason: String)
}

View File

@ -76,10 +76,11 @@ interface CertificateSigningRequestStorage {
/**
* Store certificate path with [requestId], this will store the encoded [CertPath] and transit request status to [RequestStatus.DONE].
* @param requestId id of the certificate signing request
* @param certPath chain of certificates starting with the one generated in response to the CSR up to the root.
* @param signedBy authority (its identifier) signing this request.
* @throws IllegalArgumentException if request is not found or not in Approved state.
*/
fun putCertificatePath(requestId: String, certificates: CertPath, signedBy: String)
fun putCertificatePath(requestId: String, certPath: CertPath, signedBy: String)
}
sealed class CertificateResponse {

View File

@ -58,6 +58,7 @@ sealed class NetworkManagementSchemaServices {
mappedTypes = listOf(
CertificateSigningRequestEntity::class.java,
CertificateDataEntity::class.java,
CertificateRevocationRequestEntity::class.java,
NodeInfoEntity::class.java,
NetworkParametersEntity::class.java,
NetworkMapEntity::class.java)) {

View File

@ -0,0 +1,108 @@
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 net.corda.core.crypto.SecureHash
import net.corda.nodeapi.internal.persistence.CordaPersistence
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 {
return database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
// Check if there is an entry for the given certificate serial number
val revocation = singleRequestWhere(CertificateRevocationRequestEntity::class.java) { builder, path ->
val serialNumberEqual = builder.equal(path.get<BigInteger>(CertificateRevocationRequestEntity::certificateSerialNumber.name), certificateSerialNumber)
val statusNotEqualRejected = builder.notEqual(path.get<RequestStatus>(CertificateRevocationRequestEntity::status.name), RequestStatus.REJECTED)
builder.and(serialNumberEqual, statusNotEqualRejected)
}
if (revocation != null) {
revocation.requestId
} else {
val certificateData = singleRequestWhere(CertificateDataEntity::class.java) { builder, path ->
val serialNumberEqual = builder.equal(path.get<BigInteger>(CertificateDataEntity::certificateSerialNumber.name), certificateSerialNumber)
val statusEqualValid = builder.equal(path.get<CertificateStatus>(CertificateDataEntity::certificateStatus.name), CertificateStatus.VALID)
builder.and(serialNumberEqual, statusEqualValid)
}
requireNotNull(certificateData) { "The certificate with the given serial number cannot be found." }
val requestId = SecureHash.randomSHA256().toString()
session.save(CertificateRevocationRequestEntity(
certificateSerialNumber = certificateSerialNumber,
revocationReason = reason,
requestId = requestId,
modifiedBy = reporter,
certificateData = certificateData!!,
reporter = reporter,
legalName = certificateData.legalName()
))
requestId
}
}
}
override fun getRevocationRequest(requestId: String): CertificateRevocationRequestData? = database.transaction {
getRevocationRequestEntity(requestId)?.toCertificateRevocationRequest()
}
override fun getRevocationRequests(revocationStatus: RequestStatus): List<CertificateRevocationRequestData> {
return database.transaction {
val builder = session.criteriaBuilder
val query = builder.createQuery(CertificateRevocationRequestEntity::class.java).run {
from(CertificateRevocationRequestEntity::class.java).run {
where(builder.equal(get<RequestStatus>(CertificateRevocationRequestEntity::status.name), revocationStatus))
}
}
session.createQuery(query).resultList.map { it.toCertificateRevocationRequest() }
}
}
override fun approveRevocationRequest(requestId: String, approvedBy: String) {
database.transaction {
val revocation = getRevocationRequestEntity(requestId)
if (revocation == null) {
throw NoSuchElementException("Error while approving! Certificate revocation id=$id does not exist")
} else {
session.merge(revocation.copy(
status = RequestStatus.APPROVED,
modifiedAt = Instant.now(),
modifiedBy = approvedBy
))
}
}
}
override fun rejectRevocationRequest(requestId: String, rejectedBy: String, reason: String) {
database.transaction {
val revocation = getRevocationRequestEntity(requestId)
if (revocation == null) {
throw NoSuchElementException("Error while rejecting! Certificate revocation id=$id does not exist")
} else {
session.merge(revocation.copy(
status = RequestStatus.REJECTED,
modifiedAt = Instant.now(),
modifiedBy = rejectedBy,
remark = reason
))
}
}
}
private fun getRevocationRequestEntity(requestId: String): CertificateRevocationRequestEntity? = database.transaction {
singleRequestWhere(CertificateRevocationRequestEntity::class.java) { builder, path ->
builder.equal(path.get<String>(CertificateRevocationRequestEntity::requestId.name), requestId)
}
}
private fun CertificateRevocationRequestEntity.toCertificateRevocationRequest(): CertificateRevocationRequestData {
return CertificateRevocationRequestData(
requestId,
certificateSerialNumber,
if (status == RequestStatus.DONE) modifiedAt else null,
legalName,
status,
revocationReason,
reporter)
}
}

View File

@ -18,6 +18,7 @@ import net.corda.core.crypto.Crypto.toSupportedPublicKey
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.CertRole
import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
import net.corda.nodeapi.internal.persistence.TransactionIsolationLevel
@ -35,7 +36,7 @@ class PersistentCertificateSigningRequestStorage(private val database: CordaPers
private val allowedCertRoles = setOf(CertRole.NODE_CA, CertRole.SERVICE_IDENTITY)
}
override fun putCertificatePath(requestId: String, certificates: CertPath, signedBy: String) {
override fun putCertificatePath(requestId: String, certPath: CertPath, signedBy: String) {
return database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
val request = singleRequestWhere(CertificateSigningRequestEntity::class.java) { builder, path ->
val requestIdEq = builder.equal(path.get<String>(CertificateSigningRequestEntity::requestId.name), requestId)
@ -50,8 +51,9 @@ class PersistentCertificateSigningRequestStorage(private val database: CordaPers
session.merge(certificateSigningRequest)
val certificateDataEntity = CertificateDataEntity(
certificateStatus = CertificateStatus.VALID,
certificatePathBytes = certificates.encoded,
certificateSigningRequest = certificateSigningRequest)
certificatePathBytes = certPath.encoded,
certificateSigningRequest = certificateSigningRequest,
certificateSerialNumber = certPath.x509Certificates.first().serialNumber)
session.persist(certificateDataEntity)
}
}

View File

@ -0,0 +1,80 @@
package com.r3.corda.networkmanage.common.persistence.entity
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import org.hibernate.envers.Audited
import java.math.BigInteger
import java.security.cert.CRLReason
import java.time.Instant
import javax.persistence.*
@Entity
@Table(name = "certificate_revocation_request")
class CertificateRevocationRequestEntity(
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
val id: Long? = null,
@Column(name = "request_id", length = 256, nullable = false, unique = true)
val requestId: String,
@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "certificate_data")
val certificateData: CertificateDataEntity,
@Column(name = "certificate_serial_number", nullable = false)
val certificateSerialNumber: BigInteger,
@Column(name = "legal_name", length = 256, nullable = false)
val legalName: String,
@Audited
@Column(name = "status", nullable = false)
@Enumerated(EnumType.STRING)
val status: RequestStatus = RequestStatus.NEW,
@Column(name = "reporter", nullable = false, length = 512)
val reporter: String,
@Audited
@Column(name = "modified_by", nullable = false, length = 512)
val modifiedBy: String,
@Audited
@Column(name = "modified_at", nullable = false)
val modifiedAt: Instant = Instant.now(),
@Audited
@Column(name = "revocation_reason", nullable = false)
@Enumerated(EnumType.STRING)
val revocationReason: CRLReason,
@Audited
@Column(name = "remark", length = 256)
val remark: String? = null
) {
fun copy(id: Long? = this.id,
requestId: String = this.requestId,
certificateData: CertificateDataEntity = this.certificateData,
certificateSerialNumber: BigInteger = this.certificateSerialNumber,
status: RequestStatus = this.status,
legalName: String = this.legalName,
reporter: String = this.reporter,
modifiedBy: String = this.modifiedBy,
modifiedAt: Instant = this.modifiedAt,
revocationReason: CRLReason = this.revocationReason,
remark: String? = this.remark): CertificateRevocationRequestEntity {
return CertificateRevocationRequestEntity(
id = id,
requestId = requestId,
certificateData = certificateData,
certificateSerialNumber = certificateSerialNumber,
status = status,
legalName = legalName,
reporter = reporter,
modifiedBy = modifiedBy,
modifiedAt = modifiedAt,
revocationReason = revocationReason,
remark = remark
)
}
}

View File

@ -18,7 +18,9 @@ import com.r3.corda.networkmanage.common.utils.buildCertPath
import net.corda.core.crypto.SecureHash
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.hibernate.envers.Audited
import java.math.BigInteger
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.time.Instant
import javax.persistence.*
@ -114,7 +116,10 @@ class CertificateDataEntity(
@OneToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "certificate_signing_request", foreignKey = ForeignKey(name = "FK__cert_data__cert_sign_req"))
val certificateSigningRequest: CertificateSigningRequestEntity
val certificateSigningRequest: CertificateSigningRequestEntity,
@Column(name = "certificate_serial_number", unique = true)
val certificateSerialNumber: BigInteger
) {
fun toCertificateData(): CertificateData {
return CertificateData(
@ -123,5 +128,9 @@ class CertificateDataEntity(
)
}
fun legalName(): String {
return (toCertificatePath().certificates.first() as X509Certificate).subjectX500Principal.name
}
private fun toCertificatePath(): CertPath = buildCertPath(certificatePathBytes)
}

View File

@ -0,0 +1,49 @@
package com.r3.corda.networkmanage.common.persistence.migration
import com.r3.corda.networkmanage.common.utils.buildCertPath
import liquibase.change.custom.CustomTaskChange
import liquibase.database.Database
import liquibase.database.jvm.JdbcConnection
import liquibase.exception.ValidationErrors
import liquibase.resource.ResourceAccessor
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.math.BigDecimal
import java.security.Security
import java.security.cert.X509Certificate
import java.sql.ResultSet
class CertificateDataSerialNumber : CustomTaskChange {
override fun validate(database: Database?): ValidationErrors? {
return null
}
override fun setUp() {
// Do nothing
}
override fun setFileOpener(resourceAccessor: ResourceAccessor?) {
// Do nothing
}
override fun getConfirmationMessage(): String {
return "Certificate data serial numbers have been extracted and persisted."
}
override fun execute(database: Database?) {
Security.addProvider(BouncyCastleProvider())
val jdbcConnection = database!!.connection as JdbcConnection
jdbcConnection.autoCommit = true
val statement = jdbcConnection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE)
val resultSet = statement.executeQuery("SELECT certificate_path_bytes, certificate_serial_number FROM certificate_data")
while (resultSet.next()) {
val blob = resultSet.getBlob(1)
val certPath = buildCertPath(blob.getBytes(1, blob.length().toInt()))
blob.free()
val serialNumber = (certPath.certificates.first() as X509Certificate).serialNumber
resultSet.updateBigDecimal(2, BigDecimal(serialNumber))
resultSet.updateRow()
}
statement.close()
}
}

View File

@ -0,0 +1,73 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet id="Certificate Revocation Request" author="R3.Corda">
<createTable tableName="certificate_revocation_request">
<column name="id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="request_id" type="NVARCHAR(256)">
<constraints nullable="false" unique="true"/>
</column>
<column name="certificate_serial_number" type="NUMERIC(28)">
<constraints nullable="false"/>
</column>
<column name="legal_name" type="NVARCHAR(256)">
<constraints nullable="false"/>
</column>
<column name="status" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="reporter" type="NVARCHAR(512)">
<constraints nullable="false"/>
</column>
<column name="modified_by" type="NVARCHAR(512)">
<constraints nullable="false"/>
</column>
<column name="modified_at" type="TIMESTAMP">
<constraints nullable="false"/>
</column>
<column name="remark" type="NVARCHAR(256)"/>
<column name="revocation_reason" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="certificate_data" type="BIGINT">
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="certificate_revocation_request_AUD">
<column name="id" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="rev" type="INT">
<constraints nullable="false"/>
</column>
<column name="revtype" type="TINYINT"/>
<column name="revocation_reason" type="VARCHAR(255)"/>
<column name="modified_at" type="TIMESTAMP"/>
<column name="modified_by" type="NVARCHAR(256)"/>
<column name="remark" type="NVARCHAR(256)"/>
<column name="status" type="VARCHAR(255)"/>
</createTable>
<addColumn tableName="certificate_data">
<column name="certificate_serial_number" type="NUMERIC(28)"/>
</addColumn>
<addPrimaryKey columnNames="id" constraintName="certificate_revocation_request_pk" tableName="certificate_revocation_request"/>
<addPrimaryKey columnNames="id, rev" constraintName="certificate_revocation_request_AUD_pk" tableName="certificate_revocation_request_AUD"/>
<createIndex indexName="certificate_revocation_request_AUD_index" tableName="certificate_revocation_request_AUD">
<column name="rev"/>
</createIndex>
<addForeignKeyConstraint baseColumnNames="certificate_data"
baseTableName="certificate_revocation_request"
constraintName="cert_data__cert_rev_req_fk"
referencedColumnNames="id"
referencedTableName="certificate_data"/>
<addForeignKeyConstraint baseColumnNames="rev"
baseTableName="certificate_revocation_request_AUD"
constraintName="cert_rev_req__REVINFO_AUD_fk"
referencedColumnNames="rev"
referencedTableName="REVINFO"/>
</changeSet>
<changeSet id="Certificate Serial Number" author="R3.Corda" runAlways="false" failOnError="true">
<customChange class="com.r3.corda.networkmanage.common.persistence.migration.CertificateDataSerialNumber"/>
</changeSet>
</databaseChangeLog>

View File

@ -15,5 +15,6 @@
<include file="migration/network-manager.changelog-signing-network-params.xml"/>
<include file="migration/network-manager.changelog-pub-key-move.xml"/>
<include file="migration/network-manager.changelog-modified-by-refactor.xml"/>
<include file="migration/network-manager.changelog-adding-crr.xml"/>
</databaseChangeLog>

View File

@ -11,15 +11,21 @@
package com.r3.corda.networkmanage
import com.nhaarman.mockito_kotlin.mock
import com.r3.corda.networkmanage.common.persistence.CertificateData
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequest
import com.r3.corda.networkmanage.common.persistence.CertificateStatus
import com.r3.corda.networkmanage.common.persistence.RequestStatus
import com.r3.corda.networkmanage.common.persistence.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.CertRole
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.createDevNodeCaCertPath
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.junit.Rule
import java.security.KeyPair
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import javax.security.auth.x500.X500Principal
abstract class TestBase {
@Rule
@ -55,4 +61,27 @@ abstract class TestBase {
certPath = certPath
)
}
private fun generateSignedCertPath(csr: PKCS10CertificationRequest, keyPair: KeyPair): CertPath {
return JcaPKCS10CertificationRequest(csr).run {
val (rootCa, intermediateCa, nodeCa) = createDevNodeCaCertPath(CordaX500Name.build(X500Principal(subject.encoded)), keyPair)
X509Utilities.buildCertPath(nodeCa.certificate, intermediateCa.certificate, rootCa.certificate)
}
}
protected fun createNodeCertificate(csrStorage: CertificateSigningRequestStorage, legalName: String = "LegalName"): X509Certificate {
val (csr, nodeKeyPair) = createRequest(legalName, certRole = CertRole.NODE_CA)
// Add request to DB.
val requestId = csrStorage.saveRequest(csr)
csrStorage.markRequestTicketCreated(requestId)
csrStorage.approveRequest(requestId, "Approver")
val certPath = generateSignedCertPath(csr, nodeKeyPair)
csrStorage.putCertificatePath(
requestId,
certPath,
CertificateSigningRequestStorage.DOORMAN_SIGNATURE
)
return certPath.x509Certificates.first()
}
}

View File

@ -0,0 +1,125 @@
package com.r3.corda.networkmanage.common.persistence
import com.r3.corda.networkmanage.TestBase
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.node.MockServices
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.math.BigInteger
import java.security.cert.CRLReason
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
class PersistentCertificateRevocationRequestStorageTest : TestBase() {
private lateinit var crrStorage: PersistentCertificateRevocationRequestStorage
private lateinit var csrStorage: PersistentCertificateSigningRequestStorage
private lateinit var persistence: CordaPersistence
companion object {
const val REPORTER = "TestReporter"
val REVOCATION_REASON = CRLReason.KEY_COMPROMISE
}
@Before
fun startDb() {
persistence = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(runMigration = true))
crrStorage = PersistentCertificateRevocationRequestStorage(persistence)
csrStorage = PersistentCertificateSigningRequestStorage(persistence)
}
@After
fun closeDb() {
persistence.close()
}
@Test
fun `Certificate revocation request is persisted correctly`() {
// given
val certificate = createNodeCertificate(csrStorage)
// when
val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER)
// then
assertNotNull(crrStorage.getRevocationRequest(requestId)).apply {
assertEquals(certificate.serialNumber, certificateSerialNumber)
assertEquals(REVOCATION_REASON, reason)
assertEquals(REPORTER, reporter)
}
}
@Test
fun `Retrieving a certificate revocation request succeeds`() {
// given
val certificate = createNodeCertificate(csrStorage)
val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER)
// when
val request = crrStorage.getRevocationRequest(requestId)
// then
assertNotNull(request)
}
@Test
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)
}
(11..15).forEach {
val requestId = crrStorage.saveRevocationRequest(createNodeCertificate(csrStorage, "LegalName" + it.toString()).serialNumber, REVOCATION_REASON, REPORTER)
crrStorage.approveRevocationRequest(requestId, "Approver")
}
// when
val result = crrStorage.getRevocationRequests(RequestStatus.APPROVED)
// then
assertEquals(5, result.size)
}
@Test
fun `Certificate revocation request is not persisted if a valid certificate cannot be found`() {
// given
// then
assertFailsWith(IllegalArgumentException::class) {
// when
crrStorage.saveRevocationRequest(BigInteger.TEN, REVOCATION_REASON, REPORTER)
}
}
@Test
fun `Approving a certificate revocation request changes its status`() {
// given
val certificate = createNodeCertificate(csrStorage)
val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER)
// when
crrStorage.approveRevocationRequest(requestId, "Approver")
// then
assertNotNull(crrStorage.getRevocationRequest(requestId)).apply {
assertEquals(RequestStatus.APPROVED, status)
}
}
@Test
fun `Rejecting a certificate revocation request changes its status`() {
// given
val certificate = createNodeCertificate(csrStorage)
val requestId = crrStorage.saveRevocationRequest(certificate.serialNumber, REVOCATION_REASON, REPORTER)
// when
crrStorage.rejectRevocationRequest(requestId, "Rejector", "No reason")
// then
assertNotNull(crrStorage.getRevocationRequest(requestId)).apply {
assertEquals(RequestStatus.REJECTED, status)
}
}
}