diff --git a/API.md b/API.md
new file mode 100644
index 0000000..369833c
--- /dev/null
+++ b/API.md
@@ -0,0 +1,184 @@
+# Certificate Management API for OCSP Server
+
+This API allows you to add, revoke, and query certificates in the database used by the OCSP server.
+
+## Configuration
+
+To enable the API, add the following parameters to your `config.toml` file:
+
+```toml
+# API Configuration
+enable_api = true # Enable the API
+api_keys = ["your-secure-api-key"] # List of valid API keys
+```
+
+## Generating Secure API Keys
+
+API keys should be random, hard to guess, and unique. Here are different methods to generate secure API keys:
+
+### Using OpenSSL
+
+The simplest way to generate a secure API key is using OpenSSL:
+
+```bash
+# Generate a 32-byte random hexadecimal string
+openssl rand -hex 32
+```
+
+Example output: `a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6`
+
+After generating an API key, add it to the `api_keys` array in your `config.toml` file.
+
+## Authentication
+
+All API requests must include an `X-API-Key` header with a valid API key.
+
+## Finding Certificate Numbers
+
+To use this API, you need the certificate number in the correct format (with `0x` prefix and lowercase hexadecimal digits). You can extract a certificate's serial number and format it properly using this command:
+
+```bash
+openssl x509 -in your_certificate.pem -serial -noout | awk -F= '{print "0x" tolower($2)}'
+```
+
+This will output the certificate number in the exact format required by the API endpoints (e.g., `0x3b6ea97e1bf7699397e2109846e4f356be982542`).
+
+## Endpoints
+
+### Health Check
+```
+GET /api/health
+```
+Returns "OK" if the service is available.
+
+### Add a Certificate
+```
+POST /api/certificates
+```
+
+**Request Body:**
+```json
+{
+ "cert_num": "0x123456789ABCDEF"
+}
+```
+
+**Example Response:**
+```json
+{
+ "cert_num": "0x123456789ABCDEF",
+ "status": "Valid",
+ "message": "Certificate added successfully"
+}
+```
+
+### Revoke a Certificate
+```
+POST /api/certificates/revoke
+```
+
+**Request Body:**
+```json
+{
+ "cert_num": "0x123456789ABCDEF",
+ "reason": "key_compromise",
+ "revocation_time": "2025-03-18T12:00:00" // Optional, uses the current time if not provided
+}
+```
+
+Valid revocation reasons are:
+- `unspecified`
+- `key_compromise`
+- `ca_compromise`
+- `affiliation_changed`
+- `superseded`
+- `cessation_of_operation`
+- `certificate_hold`
+- `privilege_withdrawn`
+- `aa_compromise`
+
+**Example Response:**
+```json
+{
+ "cert_num": "0x123456789ABCDEF",
+ "status": "Revoked",
+ "message": "Certificate revoked successfully"
+}
+```
+
+### Get a Certificate's Status
+```
+GET /api/certificates/{cert_num}
+```
+
+**Example Response:**
+```json
+{
+ "cert_num": "0x123456789ABCDEF",
+ "status": "Valid",
+ "message": "Certificate status retrieved: Valid"
+}
+```
+
+### List All Certificates
+```
+GET /api/certificates
+```
+
+Optional parameters:
+- `status`: Filter by status (`Valid`, `Revoked`, or `all`)
+ - If no `status` parameter is provided or `status=all` is used, all certificates will be returned
+ - Use `status=Valid` to return only valid certificates
+ - Use `status=Revoked` to return only revoked certificates
+
+**Example Response:**
+```json
+[
+ {
+ "cert_num": "0x123456789ABCDEF",
+ "status": "Valid",
+ "message": ""
+ },
+ {
+ "cert_num": "0x987654321FEDCBA",
+ "status": "Revoked",
+ "message": ""
+ }
+]
+```
+
+## Usage Examples with cURL
+
+### Add a Certificate
+```bash
+curl -X POST http://localhost:9000/api/certificates \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: your-api-key" \
+ -d '{"cert_num": "0x123456789ABCDEF"}'
+```
+
+### Revoke a Certificate
+```bash
+curl -X POST http://localhost:9000/api/certificates/revoke \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: your-api-key" \
+ -d '{"cert_num": "0x123456789ABCDEF", "reason": "key_compromise"}'
+```
+
+### Get a Certificate's Status
+```bash
+curl -X GET http://localhost:9000/api/certificates/0x123456789ABCDEF \
+ -H "X-API-Key: your-api-key"
+```
+
+### List All Valid Certificates
+```bash
+curl -X GET "http://localhost:9000/api/certificates?status=Valid" \
+ -H "X-API-Key: your-api-key"
+```
+
+### List All Certificates (Explicitly)
+```bash
+curl -X GET "http://localhost:9000/api/certificates?status=all" \
+ -H "X-API-Key: your-api-key"
+```
diff --git a/Cargo.lock b/Cargo.lock
index a2bc5e7..fb6e387 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -93,9 +93,9 @@ dependencies = [
[[package]]
name = "asn1-rs"
-version = "0.7.0"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "607495ec7113b178fbba7a6166a27f99e774359ef4823adbefd756b5b81d7970"
+checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60"
dependencies = [
"asn1-rs-derive",
"asn1-rs-impl",
@@ -223,30 +223,6 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
-[[package]]
-name = "block-buffer"
-version = "0.10.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
-name = "btoi"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad"
-dependencies = [
- "num-traits",
-]
-
-[[package]]
-name = "bufstream"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
-
[[package]]
name = "bumpalo"
version = "3.17.0"
@@ -273,12 +249,10 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
-version = "1.2.16"
+version = "1.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
+checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
dependencies = [
- "jobserver",
- "libc",
"shlex",
]
@@ -298,15 +272,16 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
+ "serde",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "clap"
-version = "4.5.32"
+version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83"
+checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
dependencies = [
"clap_builder",
"clap_derive",
@@ -314,9 +289,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.32"
+version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
+checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
dependencies = [
"anstream",
"anstyle",
@@ -342,15 +317,6 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
-[[package]]
-name = "cmake"
-version = "0.1.54"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
-dependencies = [
- "cc",
-]
-
[[package]]
name = "colorchoice"
version = "1.0.3"
@@ -386,46 +352,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
-name = "cpufeatures"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "crc32fast"
-version = "1.4.2"
+name = "darling"
+version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
+checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
- "cfg-if",
+ "darling_core",
+ "darling_macro",
]
[[package]]
-name = "crossbeam-queue"
-version = "0.3.12"
+name = "darling_core"
+version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
+checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
dependencies = [
- "crossbeam-utils",
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
]
[[package]]
-name = "crossbeam-utils"
-version = "0.8.21"
+name = "darling_macro"
+version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
-
-[[package]]
-name = "crypto-common"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
- "generic-array",
- "typenum",
+ "darling_core",
+ "quote",
+ "syn",
]
[[package]]
@@ -450,24 +408,13 @@ dependencies = [
[[package]]
name = "deranged"
-version = "0.3.11"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [
"powerfmt",
]
-[[package]]
-name = "derive_utils"
-version = "0.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccfae181bab5ab6c5478b2ccb69e4c68a02f8c3ec72f6616bfec9dbc599d2ee0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "devise"
version = "0.4.2"
@@ -502,13 +449,45 @@ dependencies = [
]
[[package]]
-name = "digest"
-version = "0.10.7"
+name = "diesel"
+version = "2.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+checksum = "34d3950690ba3a6910126162b47e775e203006d4242a15de912bec6c0a695153"
dependencies = [
- "block-buffer",
- "crypto-common",
+ "bitflags",
+ "byteorder",
+ "chrono",
+ "diesel_derives",
+ "itoa",
+ "libsqlite3-sys",
+ "mysqlclient-sys",
+ "percent-encoding",
+ "pq-sys",
+ "r2d2",
+ "time",
+ "url",
+]
+
+[[package]]
+name = "diesel_derives"
+version = "2.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a93958254b70bea63b4187ff73d10180599d9d8d177071b7f91e6da4e0c0ad55"
+dependencies = [
+ "diesel_table_macro_syntax",
+ "dsl_auto_type",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "diesel_table_macro_syntax"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25"
+dependencies = [
+ "syn",
]
[[package]]
@@ -522,6 +501,26 @@ dependencies = [
"syn",
]
+[[package]]
+name = "downcast"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
+
+[[package]]
+name = "dsl_auto_type"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139ae9aca7527f85f26dd76483eb38533fd84bd571065da1739656ef71c5ff5b"
+dependencies = [
+ "darling",
+ "either",
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "either"
version = "1.15.0"
@@ -545,9 +544,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
-version = "0.3.10"
+version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
+checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [
"libc",
"windows-sys 0.59.0",
@@ -574,21 +573,25 @@ dependencies = [
]
[[package]]
-name = "flate2"
-version = "1.1.0"
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
- "crc32fast",
- "libz-sys",
- "miniz_oxide",
+ "foreign-types-shared",
]
[[package]]
-name = "fnv"
-version = "1.0.7"
+name = "foreign-types-shared"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
@@ -599,6 +602,12 @@ dependencies = [
"percent-encoding",
]
+[[package]]
+name = "fragile"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619"
+
[[package]]
name = "futures"
version = "0.3.31"
@@ -677,16 +686,6 @@ dependencies = [
"windows",
]
-[[package]]
-name = "generic-array"
-version = "0.14.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
-dependencies = [
- "typenum",
- "version_check",
-]
-
[[package]]
name = "getrandom"
version = "0.2.15"
@@ -700,14 +699,14 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.3.1"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
+checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"libc",
- "wasi 0.13.3+wasi-0.2.2",
- "windows-targets 0.52.6",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
]
[[package]]
@@ -842,14 +841,15 @@ dependencies = [
[[package]]
name = "iana-time-zone"
-version = "0.1.61"
+version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
+checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
+ "log",
"wasm-bindgen",
"windows-core",
]
@@ -904,9 +904,9 @@ dependencies = [
[[package]]
name = "icu_locid_transform_data"
-version = "1.5.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
+checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d"
[[package]]
name = "icu_normalizer"
@@ -928,9 +928,9 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
-version = "1.5.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7"
[[package]]
name = "icu_properties"
@@ -949,9 +949,9 @@ dependencies = [
[[package]]
name = "icu_properties_data"
-version = "1.5.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2"
[[package]]
name = "icu_provider"
@@ -981,6 +981,12 @@ dependencies = [
"syn",
]
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
[[package]]
name = "idna"
version = "1.0.3"
@@ -1004,9 +1010,9 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.8.0"
+version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
+checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown",
@@ -1019,15 +1025,6 @@ version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
-[[package]]
-name = "io-enum"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d197db2f7ebf90507296df3aebaf65d69f5dce8559d8dbd82776a6cadab61bbf"
-dependencies = [
- "derive_utils",
-]
-
[[package]]
name = "is-terminal"
version = "0.4.16"
@@ -1051,15 +1048,6 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
-[[package]]
-name = "jobserver"
-version = "0.1.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "js-sys"
version = "0.3.77"
@@ -1083,12 +1071,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
-name = "libz-sys"
-version = "1.1.22"
+name = "libsqlite3-sys"
+version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d"
+checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7"
dependencies = [
- "cc",
"pkg-config",
"vcpkg",
]
@@ -1117,9 +1104,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.26"
+version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "loom"
@@ -1136,12 +1123,6 @@ dependencies = [
"tracing-subscriber",
]
-[[package]]
-name = "lru"
-version = "0.12.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
-
[[package]]
name = "matchers"
version = "0.1.0"
@@ -1171,9 +1152,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
-version = "0.8.5"
+version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
+checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
dependencies = [
"adler2",
]
@@ -1189,6 +1170,33 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "mockall"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43766c2b5203b10de348ffe19f7e54564b64f3d6018ff7648d1e2d6d3a0f0a48"
+dependencies = [
+ "cfg-if",
+ "downcast",
+ "fragile",
+ "lazy_static",
+ "mockall_derive",
+ "predicates",
+ "predicates-tree",
+]
+
+[[package]]
+name = "mockall_derive"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af7cbce79ec385a1d4f54baa90a76401eb15d9cab93685f62e7e9f942aa00ae2"
+dependencies = [
+ "cfg-if",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "multer"
version = "3.1.0"
@@ -1209,65 +1217,13 @@ dependencies = [
]
[[package]]
-name = "mysql"
-version = "26.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64453aedc258ac8c720b46c8264302fad39cef6c02483f68adbad4bcd22d6fab"
-dependencies = [
- "bufstream",
- "bytes",
- "crossbeam-queue",
- "flate2",
- "io-enum",
- "libc",
- "lru",
- "mysql_common",
- "named_pipe",
- "pem",
- "percent-encoding",
- "socket2",
- "twox-hash",
- "url",
-]
-
-[[package]]
-name = "mysql_common"
-version = "0.34.1"
+name = "mysqlclient-sys"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34a9141e735d5bb02414a7ac03add09522466d4db65bdd827069f76ae0850e58"
+checksum = "8f29e21174d84e2622ceb7b0146a9187d36458a3a9ee9a66c9cac22e96493ef9"
dependencies = [
- "base64",
- "bitflags",
- "btoi",
- "byteorder",
- "bytes",
- "cc",
- "cmake",
- "crc32fast",
- "flate2",
- "lazy_static",
- "num-bigint",
- "num-traits",
- "rand",
- "regex",
- "saturating",
- "serde",
- "serde_json",
- "sha1",
- "sha2",
- "subprocess",
- "thiserror 1.0.69",
- "uuid",
- "zstd",
-]
-
-[[package]]
-name = "named_pipe"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad9c443cce91fc3e12f017290db75dde490d685cdaaf508d7159d7cf41f0eb2b"
-dependencies = [
- "winapi",
+ "pkg-config",
+ "vcpkg",
]
[[package]]
@@ -1360,18 +1316,24 @@ dependencies = [
[[package]]
name = "ocsp-server"
-version = "0.4.1"
+version = "0.5.0"
dependencies = [
+ "async-trait",
"chrono",
"clap",
"config-file",
+ "diesel",
"hex",
- "mysql",
+ "log",
+ "mockall",
"ocsp",
+ "openssl",
"pem",
+ "r2d2",
"ring",
"rocket",
"serde",
+ "tokio",
"x509-parser",
"zeroize",
]
@@ -1387,9 +1349,57 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.21.1"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "openssl"
+version = "0.10.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-src"
+version = "300.4.2+3.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "168ce4e058f975fe43e89d9ccf78ca668601887ae736090aacc23ae353c298e2"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
+checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07"
+dependencies = [
+ "cc",
+ "libc",
+ "openssl-src",
+ "pkg-config",
+ "vcpkg",
+]
[[package]]
name = "overload"
@@ -1512,6 +1522,42 @@ dependencies = [
"zerocopy",
]
+[[package]]
+name = "pq-sys"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41c852911b98f5981956037b2ca976660612e548986c30af075e753107bc3400"
+dependencies = [
+ "libc",
+ "vcpkg",
+]
+
+[[package]]
+name = "predicates"
+version = "3.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573"
+dependencies = [
+ "anstyle",
+ "predicates-core",
+]
+
+[[package]]
+name = "predicates-core"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa"
+
+[[package]]
+name = "predicates-tree"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c"
+dependencies = [
+ "predicates-core",
+ "termtree",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.94"
@@ -1543,6 +1589,23 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "r-efi"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
+
+[[package]]
+name = "r2d2"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93"
+dependencies = [
+ "log",
+ "parking_lot",
+ "scheduled-thread-pool",
+]
+
[[package]]
name = "rand"
version = "0.8.5"
@@ -1575,9 +1638,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.5.10"
+version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
+checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
dependencies = [
"bitflags",
]
@@ -1686,6 +1749,7 @@ dependencies = [
"rocket_codegen",
"rocket_http",
"serde",
+ "serde_json",
"state",
"tempfile",
"time",
@@ -1758,9 +1822,9 @@ dependencies = [
[[package]]
name = "rustix"
-version = "1.0.2"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825"
+checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
"bitflags",
"errno",
@@ -1782,10 +1846,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
-name = "saturating"
-version = "0.1.0"
+name = "scheduled-thread-pool"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71"
+checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19"
+dependencies = [
+ "parking_lot",
+]
[[package]]
name = "scoped-tls"
@@ -1840,28 +1907,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "sha1"
-version = "0.10.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
-]
-
-[[package]]
-name = "sha2"
-version = "0.10.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
-]
-
[[package]]
name = "sharded-slab"
version = "0.1.7"
@@ -1897,15 +1942,15 @@ dependencies = [
[[package]]
name = "smallvec"
-version = "1.14.0"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
+checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
[[package]]
name = "socket2"
-version = "0.5.8"
+version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
+checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
dependencies = [
"libc",
"windows-sys 0.52.0",
@@ -1947,16 +1992,6 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
-[[package]]
-name = "subprocess"
-version = "0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086"
-dependencies = [
- "libc",
- "winapi",
-]
-
[[package]]
name = "syn"
version = "2.0.100"
@@ -1981,17 +2016,23 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.19.0"
+version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600"
+checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
dependencies = [
"fastrand",
- "getrandom 0.3.1",
+ "getrandom 0.3.2",
"once_cell",
"rustix",
"windows-sys 0.59.0",
]
+[[package]]
+name = "termtree"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
+
[[package]]
name = "thiserror"
version = "1.0.69"
@@ -2044,9 +2085,9 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.39"
+version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
+checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [
"deranged",
"itoa",
@@ -2059,15 +2100,15 @@ dependencies = [
[[package]]
name = "time-core"
-version = "0.1.3"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
+checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
-version = "0.2.20"
+version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
+checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [
"num-conv",
"time-core",
@@ -2085,9 +2126,9 @@ dependencies = [
[[package]]
name = "tokio"
-version = "1.44.1"
+version = "1.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
+checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
dependencies = [
"backtrace",
"bytes",
@@ -2261,18 +2302,6 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
-[[package]]
-name = "twox-hash"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908"
-
-[[package]]
-name = "typenum"
-version = "1.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
-
[[package]]
name = "ubyte"
version = "0.10.4"
@@ -2339,12 +2368,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
-[[package]]
-name = "uuid"
-version = "1.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
-
[[package]]
name = "valuable"
version = "0.1.1"
@@ -2380,9 +2403,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
-version = "0.13.3+wasi-0.2.2"
+version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
@@ -2478,18 +2501,62 @@ dependencies = [
[[package]]
name = "windows-core"
-version = "0.52.0"
+version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [
- "windows-targets 0.52.6",
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "windows-link"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
+checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
+
+[[package]]
+name = "windows-result"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
+dependencies = [
+ "windows-link",
+]
[[package]]
name = "windows-sys"
@@ -2641,9 +2708,9 @@ dependencies = [
[[package]]
name = "wit-bindgen-rt"
-version = "0.33.0"
+version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
@@ -2718,18 +2785,18 @@ dependencies = [
[[package]]
name = "zerocopy"
-version = "0.8.23"
+version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
+checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.23"
+version = "0.8.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
+checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
dependencies = [
"proc-macro2",
"quote",
@@ -2798,31 +2865,3 @@ dependencies = [
"quote",
"syn",
]
-
-[[package]]
-name = "zstd"
-version = "0.13.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
-dependencies = [
- "zstd-safe",
-]
-
-[[package]]
-name = "zstd-safe"
-version = "7.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722"
-dependencies = [
- "zstd-sys",
-]
-
-[[package]]
-name = "zstd-sys"
-version = "2.0.14+zstd.1.5.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5"
-dependencies = [
- "cc",
- "pkg-config",
-]
diff --git a/Cargo.toml b/Cargo.toml
index 4dc267d..a4f4573 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,10 +2,10 @@
name = "ocsp-server"
authors = ["DorianCoding <108593662+DorianCoding@users.noreply.github.com>"]
description = "OCSP server, listening for requests to give responses."
-version = "0.4.1"
+version = "0.5.0"
edition = "2024"
-license = "GPL-3.0-only"
rust-version = "1.85"
+license = "GPL-3.0-only"
repository = "https://github.com/DorianCoding/OCSP_server"
keywords = ["ocsp", "server", "ocsp-response"]
categories = ["caching", "cryptography"]
@@ -17,22 +17,38 @@ exclude = [
[dependencies]
clap = { version = "4.5.32", features = ["derive", "cargo"] }
-chrono = { version = "~0.4.40", default-features = false, features = ["std"]}
+chrono = { version = "~0.4.31", default-features = false, features = ["std", "serde"]}
config-file = "~0.2.3"
-hex = "~0.4.3"
-mysql = { version = "~26.0.0", default-features = false, features = ["minimal"]}
+serde = "~1.0.219"
+diesel = { version = "2.2.8", features = ["sqlite", "r2d2", "chrono"] }
+r2d2 = "0.8.10"
ocsp = {git = "https://github.com/DorianCoding/ocsp-rs.git", tag="1.0.0" }
-#ocsp = {git = "https://github.com/maicallist/ocsp-rs.git" }
pem = "3.0.5"
-#openssl-sys = { version = "~0.9.103", features = ["vendored" ]}
+openssl = { version = "0.10.71", features = ["vendored"] }
ring = "0.17.14"
-rocket = "~0.5.1"
-serde = "~1.0.219"
x509-parser = "~0.17.0"
+hex = "~0.4.3"
zeroize = { version = "~1.8.1", features = ["std", "zeroize_derive"] }
+log = "0.4.20"
+async-trait = "0.1.79"
+tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros", "fs", "io-util", "time"] }
+
+[dependencies.rocket]
+version = "0.5.1"
+features = ["json"]
+[dev-dependencies]
+mockall = "0.12.1"
+[features]
+api=[]
+mysql = ["diesel/mysql"]
+postgres = ["diesel/postgres"]
+default = ["api","mysql","postgres"]
[profile.release]
strip = "symbols"
+lto = true
+codegen-units = 1
+opt-level = 3
[lints.rust]
unsafe_code = "deny"
diff --git a/README.md b/README.md
index f82ae9a..a42a762 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
----
-This software implements a OCSP responder in Rust, fetching certificate status in a Mysql/MariaDB database. Unlike the Python implementation, it **does implement its own TCP listener** on a user-selected port.
+This software implements a OCSP responder in Rust, fetching certificate status in a Mysql/MariaDB database. Unlike the Python implementation, it **does implement its own TCP listener** on a user-selected port.
*It will answer to any **GET or POST** requests on any URL*.
## Requirements
- A CA certificate (self-signed allowed) and/or an intermediate CA that will sign leaf certificates.
@@ -30,16 +30,23 @@ The config file should contain the following informations :
#Config file, all fields are compulsory
cachedays = 3 #Number of days a response is valid once created (only for valid certificates)
dbip = "127.0.0.1" #Optional. IP to connect to MySql database. If absent, use of unix socket.
-timeout = 5 #Optional timeout, default 5s
+db_type = "mysql" # Can be "mysql" or "postgres" or "sqlite"
dbuser = "cert" #Username to connect to MySql database
-port = 9000 #Port to listen to, from 1 to 65535. Cannot use a port already used by another service (privileged ports allowed if used as root or as a service). By default 9000
+dbport = 3306 # Optional: Default 3306 for MySQL, 5432 for PostgreSQL
dbname = "certs" #Name to connect to MySql data
dbpassword = "certdata" #Password to connect to cert data
+port = 9000 #Port to listen to, from 1 to 65535. Cannot use a port already used by another service (privileged ports allowed if used as root or as a service). By default 9000
+listen_ip = "0.0.0.0" # Optional: IP address to listen on (default: 127.0.0.1)
+timeout = 5 #Optional timeout, default 5s
cachefolder = "cache/" #Folder to cache data (relative or absolute, will be created if not present)
itcert = "/var/public_files/it_cert.crt" #Path to intermediate certificate as PEM format
+itkey = "/var/private_files/it_privkey.pem" #Path to intermediate private key, keep it secret (PKCS#8 format, only RSA keys supported so far)
revocextended = true #Optional, if you want to enable EXTENDED_REVOCATION
caching = true #Optional, enable caching or enable nonce response.
-itkey = "/var/private_files/it_privkey.pem" #Path to intermediate private key, keep it secret (PKCS#8 format, only RSA keys supported so far)
+create_table = true # Optional: Creates the table if it doesn't exist
+table_name = "custom_certs" # Optional: Custom table name (default is list_certs for MySQL, ocsp_list_certs for PostgreSQL)
+enable_api = true # Optional: Enable the certificate management API
+api_keys = ["secure-api-key-1", "secure-api-key-2"] # Optional: List of valid API keys for authentication
```
> [!CAUTION]
@@ -168,15 +175,15 @@ OCSP Response Data:
> OCSP Server - OCSP responder in Rust
> Copyright (C) 2023 DorianCoding
->
+>
> This program is free software: you can redistribute it and/or modify
> it under the terms of the GNU General Public License as published by
> the Free Software Foundation, under version 3 of the License only.
->
+>
> This program is distributed in the hope that it will be useful,
> but WITHOUT ANY WARRANTY; without even the implied warranty of
> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> GNU General Public License for more details.
->
+>
> You should have received a copy of the GNU General Public License
> along with this program. If not, see .
diff --git a/config.toml b/config.toml
index f15795c..108820b 100644
--- a/config.toml
+++ b/config.toml
@@ -1,11 +1,20 @@
#Config file, all fields are compulsory
cachedays = 3
+db_type = "mysql" # Can be "mysql" or "postgres" or "sqlite"
dbip = "127.0.0.1"
+dbport = 3306 # Optional: Default 3306 for MySQL, 5432 for PostgreSQL
port = 9000
+listen_ip = "0.0.0.0" # Optional: IP address to listen on (default: 127.0.0.1)
timeout = 10
-dbuser = "cert"
-dbpassword = "cert"
+dbuser = "ocsp"
+dbpassword = "ocsp"
dbname = "certs"
cachefolder = "cache/"
itcert = "test_files/cert.pem"
-itkey = "test_files/keyp8.pk8"
+itkey = "test_files/keyp8.pk8" # Supports PKCS#8, PEM PKCS#1 (RSA) formats
+revocextended = false # Optional, if you want to enable EXTENDED_REVOCATION
+caching = false # Optional, enable caching or enable nonce response.
+create_table = true # Optional: Creates the table if it doesn't exist
+table_name = "custom_certs" # Optional: Custom table name (default is list_certs for MySQL, ocsp_list_certs for PostgreSQL)
+enable_api = true # Optional: Enable the certificate management API
+api_keys = ["secure-api-key-1", "secure-api-key-2"] # Optional: List of valid API keys for authentication
diff --git a/shell.nix b/shell.nix
index e9e5989..29bba71 100644
--- a/shell.nix
+++ b/shell.nix
@@ -5,12 +5,19 @@ pkgs.mkShell {
rustc
cargo
cargo-audit
+ clippy
rustfmt
rust-analyzer
+ git
+ openssl
+ libmysqlclient
+ sqlite
+ postgresql
+ pkg-config
];
shellHook = ''
- rustfmt --edition 2024 src/main.rs
+ rustfmt --edition 2024 src/*.rs
cargo audit
'';
diff --git a/src/api.rs b/src/api.rs
new file mode 100644
index 0000000..76af458
--- /dev/null
+++ b/src/api.rs
@@ -0,0 +1,209 @@
+use crate::database::Database;
+use crate::r#struct::{ApiKey, CertificateRequest, CertificateResponse, RevocationRequest};
+use chrono::Utc;
+use log::{error, info, warn};
+use rocket::http::Status;
+use rocket::request::{FromRequest, Outcome, Request};
+use rocket::serde::json::Json;
+use rocket::{get, post, routes, State};
+use std::sync::Arc;
+
+pub fn api_routes() -> Vec {
+ routes![
+ health_check,
+ add_certificate,
+ revoke_certificate,
+ get_certificate_status,
+ list_certificates
+ ]
+}
+
+/// API key authentication guard for secure endpoints
+#[rocket::async_trait]
+impl<'r> FromRequest<'r> for ApiKey {
+ type Error = ();
+
+ async fn from_request(request: &'r Request<'_>) -> Outcome {
+ // Get API key from header
+ let api_key = request.headers().get_one("X-API-Key");
+
+ match api_key {
+ Some(key) => {
+ let config = request.rocket().state::>();
+
+ match config {
+ Some(config) => {
+ if let Some(valid_keys) = &config.api_keys {
+ if valid_keys.contains(&key.to_string()) {
+ Outcome::Success(ApiKey(key.to_string()))
+ } else {
+ warn!("Invalid API key attempted: {}", key);
+ Outcome::Error((Status::Unauthorized, ()))
+ }
+ } else {
+ // If no API keys are configured, reject all requests
+ warn!("API keys not configured but API endpoint accessed");
+ Outcome::Error((Status::Unauthorized, ()))
+ }
+ }
+ None => Outcome::Error((Status::InternalServerError, ())),
+ }
+ }
+ None => Outcome::Error((Status::Unauthorized, ())),
+ }
+ }
+}
+
+#[get("/health")]
+fn health_check() -> &'static str {
+ "OK"
+}
+
+/// Add a new certificate to the database
+#[post("/certificates", data = "")]
+async fn add_certificate(
+ _api_key: ApiKey,
+ cert_request: Json,
+ db: &State>,
+) -> Result, Status> {
+ let cert = cert_request.into_inner();
+
+ // Validate certificate number format (should start with 0x)
+ if !cert.cert_num.starts_with("0x") {
+ return Err(Status::BadRequest);
+ }
+
+ match db.add_certificate(&cert.cert_num).await {
+ Ok(_) => {
+ info!("Certificate added successfully: {}", cert.cert_num);
+ Ok(Json(CertificateResponse {
+ cert_num: cert.cert_num,
+ status: "Valid".to_string(),
+ message: "Certificate added successfully".to_string(),
+ }))
+ }
+ Err(e) => {
+ error!("Failed to add certificate: {}", e);
+ Err(Status::InternalServerError)
+ }
+ }
+}
+
+/// Revoke a certificate
+#[post("/certificates/revoke", data = "")]
+async fn revoke_certificate(
+ _api_key: ApiKey,
+ revoke_request: Json,
+ db: &State>,
+) -> Result, Status> {
+ let request = revoke_request.into_inner();
+
+ // Validate certificate number format
+ if !request.cert_num.starts_with("0x") {
+ return Err(Status::BadRequest);
+ }
+
+ // Validate revocation reason
+ let valid_reasons = [
+ "unspecified",
+ "key_compromise",
+ "ca_compromise",
+ "affiliation_changed",
+ "superseded",
+ "cessation_of_operation",
+ "certificate_hold",
+ "privilege_withdrawn",
+ "aa_compromise",
+ ];
+
+ if !valid_reasons.contains(&request.reason.as_str()) {
+ return Err(Status::BadRequest);
+ }
+
+ // Use current time if not provided
+ let revocation_time = request
+ .revocation_time
+ .unwrap_or_else(|| Utc::now().naive_utc());
+
+ match db
+ .revoke_certificate(&request.cert_num, revocation_time, &request.reason)
+ .await
+ {
+ Ok(_) => {
+ info!("Certificate revoked successfully: {}", request.cert_num);
+ Ok(Json(CertificateResponse {
+ cert_num: request.cert_num,
+ status: "Revoked".to_string(),
+ message: "Certificate revoked successfully".to_string(),
+ }))
+ }
+ Err(e) => {
+ error!("Failed to revoke certificate: {}", e);
+ Err(Status::InternalServerError)
+ }
+ }
+}
+
+/// Get the status of a specific certificate
+#[get("/certificates/")]
+async fn get_certificate_status(
+ _api_key: ApiKey,
+ cert_num: String,
+ db: &State>,
+) -> Result, Status> {
+ let cert_num = if !cert_num.starts_with("0x") {
+ format!("0x{}", cert_num)
+ } else {
+ cert_num
+ };
+
+ match db.get_certificate_status(&cert_num).await {
+ Ok(cert_info) => Ok(Json(CertificateResponse {
+ cert_num: cert_num.clone(),
+ status: cert_info.status.clone(),
+ message: format!("Certificate status retrieved: {}", cert_info.status),
+ })),
+ Err(_) => Err(Status::NotFound),
+ }
+}
+
+/// List all certificates or filter by status
+#[get("/certificates?")]
+async fn list_certificates(
+ _api_key: ApiKey,
+ status: Option,
+ db: &State>,
+) -> Result>, Status> {
+ let liststatus = ["Valid","revoked","All"];
+ let filtered_status = match status {
+ Some(d) if liststatus.iter().any(|p| *p == d.as_str()) => {
+ if d == "All" {
+ Some(d)
+ } else {
+ None
+ }
+ },
+ _ => {
+ return Err(Status::BadRequest);
+ }
+ };
+
+ match db.list_certificates(filtered_status).await {
+ Ok(certs) => {
+ let response = certs
+ .into_iter()
+ .map(|cert| CertificateResponse {
+ cert_num: cert.cert_num,
+ status: cert.status,
+ message: String::new(),
+ })
+ .collect();
+
+ Ok(Json(response))
+ }
+ Err(e) => {
+ error!("Failed to list certificates: {}", e);
+ Err(Status::InternalServerError)
+ }
+ }
+}
diff --git a/src/database.rs b/src/database.rs
new file mode 100644
index 0000000..1a14dee
--- /dev/null
+++ b/src/database.rs
@@ -0,0 +1,1237 @@
+use crate::r#struct::{
+ CertRecord, CertificateResponse, Certinfo, Config, DEFAULT_SQLITE_TABLE,
+};
+#[cfg(feature="mysql")]
+use crate::r#struct::DEFAULT_MYSQL_PORT;
+#[cfg(feature="mysql")]
+use crate::r#struct::DEFAULT_MYSQL_TABLE;
+#[cfg(feature="postgres")]
+use crate::r#struct::DEFAULT_POSTGRES_PORT;
+#[cfg(feature="postgres")]
+use crate::r#struct::DEFAULT_POSTGRES_TABLE;
+use async_trait::async_trait;
+use chrono::{Datelike, NaiveDateTime, Timelike};
+use diesel::prelude::*;
+use diesel::r2d2::{ConnectionManager, Pool};
+use diesel::sql_types;
+use diesel::SqliteConnection;
+#[cfg(feature = "mysql")]
+use diesel::MysqlConnection;
+#[cfg(feature = "postgres")]
+use diesel::PgConnection;
+#[cfg(feature = "postgres")]
+use crate::BoolResult;
+use log::{debug, info, warn};
+use ocsp::common::asn1::GeneralizedTime;
+use ocsp::response::{CertStatus as OcspCertStatus, CertStatusCode, CrlReason, RevokedInfo};
+use std::error::Error;
+use std::path::Path;
+use std::sync::Arc;
+use std::time::Duration;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum DatabaseType {
+ MySQL,
+ PostgreSQL,
+ SQLite,
+}
+
+impl DatabaseType {
+ pub fn from_string(s: &str) -> Self {
+ match s.to_lowercase().as_str() {
+ "mysql" | "MySql" => DatabaseType::MySQL,
+ "postgres" | "postgresql" => DatabaseType::PostgreSQL,
+ _ => DatabaseType::SQLite,
+ }
+ }
+ #[cfg(any(feature = "mysql",feature="postgres"))]
+ fn default_port(&self) -> u16 {
+ match self {
+ DatabaseType::MySQL => DEFAULT_MYSQL_PORT,
+ DatabaseType::PostgreSQL => DEFAULT_POSTGRES_PORT,
+ DatabaseType::SQLite => 0,
+ }
+ }
+
+ pub fn default_table_name(&self) -> &'static str {
+ match self {
+ #[cfg(feature = "mysql")]
+ DatabaseType::MySQL => DEFAULT_MYSQL_TABLE,
+ #[cfg(feature = "postgres")]
+ DatabaseType::PostgreSQL => DEFAULT_POSTGRES_TABLE,
+ _ => DEFAULT_SQLITE_TABLE,
+ }
+ }
+}
+
+#[async_trait]
+pub trait Database: Send + Sync {
+ async fn check_cert(
+ &self,
+ certnum: &str,
+ revoked: bool,
+ ) -> Result>;
+
+ fn create_tables_if_needed(&self) -> Result<(), Box>;
+
+ async fn add_certificate(&self, cert_num: &str) -> Result<(), Box>;
+
+ async fn revoke_certificate(
+ &self,
+ cert_num: &str,
+ revocation_time: NaiveDateTime,
+ reason: &str,
+ ) -> Result<(), Box>;
+
+ async fn get_certificate_status(
+ &self,
+ cert_num: &str,
+ ) -> Result>;
+
+ async fn list_certificates(
+ &self,
+ status: Option,
+ ) -> Result, Box>;
+}
+
+enum DatabaseConnection {
+ #[cfg(feature = "mysql")]
+ MySQL(Pool>),
+ #[cfg(feature = "postgres")]
+ PostgreSQL(Pool>),
+ SQLite(Pool>),
+}
+
+pub struct DieselDatabase {
+ connection: DatabaseConnection,
+ config: Arc,
+ table_name: String,
+}
+
+impl DieselDatabase {
+ pub fn new(config: Arc) -> Result> {
+ let db_type = DatabaseType::from_string(&config.db_type);
+ let table_name = config
+ .table_name
+ .clone()
+ .unwrap_or_else(|| db_type.default_table_name().to_string());
+
+ let connection = match db_type {
+ #[cfg(feature="mysql")]
+ DatabaseType::MySQL => {
+ let dbport = config.dbport.unwrap_or_else(|| db_type.default_port());
+ let database_url = match &config.dbip {
+ Some(host) => format!(
+ "mysql://{}:{}@{}:{}/{}",
+ config.dbuser, config.dbpassword, host, dbport, config.dbname
+ ),
+ None => format!(
+ "mysql://{}:{}@localhost/{}",
+ config.dbuser, config.dbpassword, config.dbname
+ ),
+ };
+
+ let manager = ConnectionManager::::new(database_url);
+ let pool = Pool::builder()
+ .max_size(15)
+ .connection_timeout(Duration::from_secs(config.time as u64))
+ .build(manager)?;
+
+ DatabaseConnection::MySQL(pool)
+ }
+ #[cfg(feature="postgres")]
+ DatabaseType::PostgreSQL => {
+ let dbport = config.dbport.unwrap_or_else(|| db_type.default_port());
+ let database_url = match &config.dbip {
+ Some(host) => format!(
+ "postgres://{}:{}@{}:{}/{}",
+ config.dbuser, config.dbpassword, host, dbport, config.dbname
+ ),
+ None => format!(
+ "postgres://{}:{}@localhost/{}",
+ config.dbuser, config.dbpassword, config.dbname
+ ),
+ };
+
+ let manager = ConnectionManager::::new(database_url);
+ let pool = Pool::builder()
+ .max_size(15)
+ .connection_timeout(Duration::from_secs(config.time as u64))
+ .build(manager)?;
+
+ DatabaseConnection::PostgreSQL(pool)
+ }
+ _ => {
+ let db_path = &config.dbname;
+
+ if let Some(parent) = Path::new(db_path).parent() {
+ if !parent.exists() {
+ std::fs::create_dir_all(parent)?;
+ }
+ }
+
+ let database_url = format!("sqlite://{}", db_path);
+
+ let manager = ConnectionManager::::new(database_url);
+ let pool = Pool::builder()
+ .max_size(1)
+ .connection_timeout(Duration::from_secs(config.time as u64))
+ .build(manager)?;
+
+ DatabaseConnection::SQLite(pool)
+ }
+ };
+
+ Ok(Self {
+ connection,
+ config,
+ table_name,
+ })
+ }
+ #[cfg(feature="mysql")]
+ async fn check_cert_mysql(
+ &self,
+ pool: &Pool>,
+ certnum: &str,
+ revoked: bool,
+ ) -> Result> {
+ let table_name = self.table_name.clone();
+ let cert_num = certnum.to_string();
+ let connection_manager = pool.clone();
+
+ let result = tokio::task::spawn_blocking(move || -> Result> {
+ let mut conn = connection_manager.get()?;
+
+ let query = format!(
+ "SELECT cert_num, revocation_time, revocation_reason, status FROM {} WHERE cert_num = ?",
+ table_name
+ );
+
+ let results = diesel::sql_query(query)
+ .bind::(&cert_num)
+ .load::(&mut conn)?;
+
+ if results.is_empty() {
+ warn!("Entry not found for cert {}", cert_num);
+ if !revoked {
+ Ok(OcspCertStatus::new(CertStatusCode::Unknown, None))
+ } else {
+ Ok(OcspCertStatus::new(
+ CertStatusCode::Revoked,
+ Some(RevokedInfo::new(
+ GeneralizedTime::new(1970, 1, 1, 0, 0, 0).unwrap(),
+ Some(CrlReason::OcspRevokeCertHold),
+ )),
+ ))
+ }
+ } else {
+ let record = &results[0];
+ debug!("Entry found for cert {}, status {}", cert_num, record.status);
+
+ if record.status == "Revoked" {
+ let time = GeneralizedTime::now();
+
+ let time = if let Some(rt) = record.revocation_time {
+ GeneralizedTime::new(
+ rt.year(),
+ rt.month(),
+ rt.day(),
+ rt.hour(),
+ rt.minute(),
+ rt.second(),
+ ).unwrap_or(time)
+ } else {
+ time
+ };
+
+ let motif = record.revocation_reason.clone().unwrap_or_default();
+ let motif: CrlReason = match motif.as_str() {
+ "key_compromise" => CrlReason::OcspRevokeKeyCompromise,
+ "ca_compromise" => CrlReason::OcspRevokeCaCompromise,
+ "affiliation_changed" => CrlReason::OcspRevokeAffChanged,
+ "superseded" => CrlReason::OcspRevokeSuperseded,
+ "cessation_of_operation" => CrlReason::OcspRevokeCessOperation,
+ "certificate_hold" => CrlReason::OcspRevokeCertHold,
+ "privilege_withdrawn" => CrlReason::OcspRevokePrivWithdrawn,
+ "aa_compromise" => CrlReason::OcspRevokeAaCompromise,
+ _ => CrlReason::OcspRevokeUnspecified,
+ };
+
+ Ok(OcspCertStatus::new(
+ CertStatusCode::Revoked,
+ Some(RevokedInfo::new(time, Some(motif))),
+ ))
+ } else {
+ Ok(OcspCertStatus::new(CertStatusCode::Good, None))
+ }
+ }
+ }).await??;
+
+ Ok(result)
+ }
+ #[cfg(feature="postgres")]
+ async fn check_cert_postgres(
+ &self,
+ pool: &Pool>,
+ certnum: &str,
+ revoked: bool,
+ ) -> Result> {
+ let table_name = self.table_name.clone();
+ let cert_num = certnum.to_string();
+ let connection_manager = pool.clone();
+
+ let result = tokio::task::spawn_blocking(move || -> Result> {
+ let mut conn = connection_manager.get()?;
+
+ // Using text SQL query with explicit column names and types
+ let query = format!(
+ "SELECT cert_num, revocation_time, revocation_reason, status FROM {} WHERE cert_num = $1",
+ table_name
+ );
+
+ let results = diesel::sql_query(query)
+ .bind::(&cert_num)
+ .load::(&mut conn)?;
+
+ if results.is_empty() {
+ warn!("Entry not found for cert {} in PostgreSQL", cert_num);
+ if !revoked {
+ Ok(OcspCertStatus::new(CertStatusCode::Unknown, None))
+ } else {
+ Ok(OcspCertStatus::new(
+ CertStatusCode::Revoked,
+ Some(RevokedInfo::new(
+ GeneralizedTime::new(1970, 1, 1, 0, 0, 0).unwrap(),
+ Some(CrlReason::OcspRevokeCertHold),
+ )),
+ ))
+ }
+ } else {
+ let record = &results[0];
+ debug!("Entry found for cert {}, status {}", cert_num, record.status);
+
+ if record.status == "Revoked" {
+ let time = GeneralizedTime::now();
+
+ let time = if let Some(rt) = record.revocation_time {
+ GeneralizedTime::new(
+ rt.year(),
+ rt.month(),
+ rt.day(),
+ rt.hour(),
+ rt.minute(),
+ rt.second(),
+ ).unwrap_or(time)
+ } else {
+ time
+ };
+
+ let motif = record.revocation_reason.clone().unwrap_or_default();
+ let motif: CrlReason = match motif.as_str() {
+ "key_compromise" => CrlReason::OcspRevokeKeyCompromise,
+ "ca_compromise" => CrlReason::OcspRevokeCaCompromise,
+ "affiliation_changed" => CrlReason::OcspRevokeAffChanged,
+ "superseded" => CrlReason::OcspRevokeSuperseded,
+ "cessation_of_operation" => CrlReason::OcspRevokeCessOperation,
+ "certificate_hold" => CrlReason::OcspRevokeCertHold,
+ "privilege_withdrawn" => CrlReason::OcspRevokePrivWithdrawn,
+ "aa_compromise" => CrlReason::OcspRevokeAaCompromise,
+ _ => CrlReason::OcspRevokeUnspecified,
+ };
+
+ Ok(OcspCertStatus::new(
+ CertStatusCode::Revoked,
+ Some(RevokedInfo::new(time, Some(motif))),
+ ))
+ } else {
+ Ok(OcspCertStatus::new(CertStatusCode::Good, None))
+ }
+ }
+ }).await??;
+
+ Ok(result)
+ }
+
+ async fn check_cert_sqlite(
+ &self,
+ pool: &Pool>,
+ certnum: &str,
+ revoked: bool,
+ ) -> Result> {
+ let table_name = self.table_name.clone();
+ let cert_num = certnum.to_string();
+ let connection_manager = pool.clone();
+
+ let result = tokio::task::spawn_blocking(move || -> Result> {
+ let mut conn = connection_manager.get()?;
+
+ // Using text SQL query with explicit column names and types
+ let query = format!(
+ "SELECT cert_num, revocation_time, revocation_reason, status FROM {} WHERE cert_num = ?",
+ table_name
+ );
+
+ let results = diesel::sql_query(query)
+ .bind::(&cert_num)
+ .load::(&mut conn)?;
+
+ if results.is_empty() {
+ warn!("Entry not found for cert {} in SQLite", cert_num);
+ if !revoked {
+ Ok(OcspCertStatus::new(CertStatusCode::Unknown, None))
+ } else {
+ Ok(OcspCertStatus::new(
+ CertStatusCode::Revoked,
+ Some(RevokedInfo::new(
+ GeneralizedTime::new(1970, 1, 1, 0, 0, 0).unwrap(),
+ Some(CrlReason::OcspRevokeCertHold),
+ )),
+ ))
+ }
+ } else {
+ let record = &results[0];
+ debug!("Entry found for cert {}, status {}", cert_num, record.status);
+
+ if record.status == "Revoked" {
+ let time = GeneralizedTime::now();
+
+ let time = if let Some(rt) = record.revocation_time {
+ GeneralizedTime::new(
+ rt.year(),
+ rt.month(),
+ rt.day(),
+ rt.hour(),
+ rt.minute(),
+ rt.second(),
+ ).unwrap_or(time)
+ } else {
+ time
+ };
+
+ let motif = record.revocation_reason.clone().unwrap_or_default();
+ let motif: CrlReason = match motif.as_str() {
+ "key_compromise" => CrlReason::OcspRevokeKeyCompromise,
+ "ca_compromise" => CrlReason::OcspRevokeCaCompromise,
+ "affiliation_changed" => CrlReason::OcspRevokeAffChanged,
+ "superseded" => CrlReason::OcspRevokeSuperseded,
+ "cessation_of_operation" => CrlReason::OcspRevokeCessOperation,
+ "certificate_hold" => CrlReason::OcspRevokeCertHold,
+ "privilege_withdrawn" => CrlReason::OcspRevokePrivWithdrawn,
+ "aa_compromise" => CrlReason::OcspRevokeAaCompromise,
+ _ => CrlReason::OcspRevokeUnspecified,
+ };
+
+ Ok(OcspCertStatus::new(
+ CertStatusCode::Revoked,
+ Some(RevokedInfo::new(time, Some(motif))),
+ ))
+ } else {
+ Ok(OcspCertStatus::new(CertStatusCode::Good, None))
+ }
+ }
+ }).await??;
+
+ Ok(result)
+ }
+ #[cfg(feature="mysql")]
+ fn create_tables_if_needed_mysql(
+ &self,
+ pool: &Pool>,
+ ) -> Result<(), Box> {
+ if !self.config.create_table {
+ return Ok(());
+ }
+
+ let mut conn = pool.get()?;
+ let query = format!("SHOW TABLES LIKE '{}'", self.table_name);
+
+ // For checking if table exists, we'll use execute instead of load for simpler handling
+ let exists: bool = diesel::sql_query(query)
+ .execute(&mut conn)
+ .map(|count| count > 0)?;
+
+ if exists {
+ info!("Table {} already exists in MySQL database", self.table_name);
+ return Ok(());
+ }
+
+ let create_table_query = format!(
+ "CREATE TABLE `{}` (
+ `cert_num` varchar(50) NOT NULL,
+ `revocation_time` datetime DEFAULT NULL,
+ `revocation_reason` enum('unspecified','key_compromise','ca_compromise','affiliation_changed','superseded','cessation_of_operation','certificate_hold','privilege_withdrawn','aa_compromise') DEFAULT NULL,
+ `status` enum('Valid','Revoked') NOT NULL DEFAULT 'Valid',
+ PRIMARY KEY (`cert_num`)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
+ self.table_name
+ );
+
+ diesel::sql_query(create_table_query).execute(&mut conn)?;
+
+ info!(
+ "Table {} created successfully in MySQL database",
+ self.table_name
+ );
+ Ok(())
+ }
+ #[cfg(feature="postgres")]
+ fn create_tables_if_needed_postgres(
+ &self,
+ pool: &Pool>,
+ ) -> Result<(), Box> {
+ if !self.config.create_table {
+ return Ok(());
+ }
+
+ let mut conn = pool.get()?;
+
+ let exists_query = format!(
+ "SELECT EXISTS (
+ SELECT FROM information_schema.tables
+ WHERE table_schema = 'public'
+ AND table_name = '{}'
+ ) as exists",
+ self.table_name
+ );
+
+ let exists_results = diesel::sql_query(exists_query).load::(&mut conn)?;
+
+ let exists = !exists_results.is_empty() && exists_results[0].exists;
+
+ if exists {
+ info!(
+ "Table {} already exists in PostgreSQL database",
+ self.table_name
+ );
+ return Ok(());
+ }
+
+ let types_exist_query = "SELECT EXISTS (
+ SELECT FROM pg_type
+ WHERE typname = 'cert_status'
+ ) as exists";
+
+ let types_exist_results =
+ diesel::sql_query(types_exist_query).load::(&mut conn)?;
+
+ let types_exist = !types_exist_results.is_empty() && types_exist_results[0].exists;
+
+ if !types_exist {
+ diesel::sql_query("CREATE TYPE cert_status AS ENUM ('Valid', 'Revoked');")
+ .execute(&mut conn)?;
+
+ diesel::sql_query(
+ "CREATE TYPE revocation_reason_enum AS ENUM (
+ 'unspecified',
+ 'key_compromise',
+ 'ca_compromise',
+ 'affiliation_changed',
+ 'superseded',
+ 'cessation_of_operation',
+ 'certificate_hold',
+ 'privilege_withdrawn',
+ 'aa_compromise'
+ );",
+ )
+ .execute(&mut conn)?;
+ }
+
+ diesel::sql_query(format!(
+ "CREATE TABLE {} (
+ cert_num VARCHAR(50) PRIMARY KEY,
+ revocation_time TIMESTAMP DEFAULT NULL,
+ revocation_reason revocation_reason_enum DEFAULT NULL,
+ status cert_status NOT NULL DEFAULT 'Valid'
+ );",
+ self.table_name
+ ))
+ .execute(&mut conn)?;
+
+ info!(
+ "Table {} created successfully in PostgreSQL database",
+ self.table_name
+ );
+ Ok(())
+ }
+
+ fn create_tables_if_needed_sqlite(
+ &self,
+ pool: &Pool>,
+ ) -> Result<(), Box> {
+ if !self.config.create_table {
+ return Ok(());
+ }
+
+ let mut conn = pool.get()?;
+
+ let exists_query = format!(
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='{}'",
+ self.table_name
+ );
+
+ let exists: bool = diesel::sql_query(exists_query)
+ .execute(&mut conn)
+ .map(|count| count > 0)?;
+
+ if exists {
+ info!(
+ "Table {} already exists in SQLite database",
+ self.table_name
+ );
+ return Ok(());
+ }
+
+ let create_table_query = format!(
+ "CREATE TABLE {} (
+ cert_num TEXT PRIMARY KEY,
+ revocation_time TIMESTAMP DEFAULT NULL,
+ revocation_reason TEXT DEFAULT NULL CHECK(
+ revocation_reason IS NULL OR
+ revocation_reason IN ('unspecified', 'key_compromise', 'ca_compromise',
+ 'affiliation_changed', 'superseded', 'cessation_of_operation',
+ 'certificate_hold', 'privilege_withdrawn', 'aa_compromise')
+ ),
+ status TEXT NOT NULL DEFAULT 'Valid' CHECK(status IN ('Valid', 'Revoked'))
+ )",
+ self.table_name
+ );
+
+ diesel::sql_query(create_table_query).execute(&mut conn)?;
+
+ info!(
+ "Table {} created successfully in SQLite database",
+ self.table_name
+ );
+ Ok(())
+ }
+ #[cfg(feature="mysql")]
+ async fn add_certificate_mysql(
+ &self,
+ pool: &Pool>,
+ cert_num: &str,
+ ) -> Result<(), Box> {
+ let table_name = self.table_name.clone();
+ let cert_num = cert_num.to_string();
+ let connection_manager = pool.clone();
+
+ tokio::task::spawn_blocking(move || -> Result<(), Box> {
+ let mut conn = connection_manager.get()?;
+
+ // Check if certificate already exists
+ let query = format!(
+ "SELECT COUNT(*) as count FROM {} WHERE cert_num = ?",
+ table_name
+ );
+
+ let exists: bool = diesel::sql_query(query.clone())
+ .bind::(&cert_num)
+ .execute(&mut conn)?
+ > 0;
+
+ if exists {
+ return Err("Certificate already exists".into());
+ }
+
+ // Insert new certificate
+ let insert_query = format!(
+ "INSERT INTO {} (cert_num, status) VALUES (?, 'Valid')",
+ table_name
+ );
+
+ diesel::sql_query(insert_query)
+ .bind::(&cert_num)
+ .execute(&mut conn)?;
+
+ Ok(())
+ })
+ .await?
+ }
+ #[cfg(feature="postgres")]
+ async fn add_certificate_postgres(
+ &self,
+ pool: &Pool>,
+ cert_num: &str,
+ ) -> Result<(), Box> {
+ let table_name = self.table_name.clone();
+ let cert_num = cert_num.to_string();
+ let connection_manager = pool.clone();
+
+ tokio::task::spawn_blocking(move || -> Result<(), Box> {
+ let mut conn = connection_manager.get()?;
+
+ let query = format!(
+ "SELECT EXISTS (SELECT 1 FROM {} WHERE cert_num = $1) as exists",
+ table_name
+ );
+
+ let exists_results = diesel::sql_query(query)
+ .bind::(&cert_num)
+ .load::(&mut conn)?;
+
+ let exists = !exists_results.is_empty() && exists_results[0].exists;
+
+ if exists {
+ return Err("Certificate already exists".into());
+ }
+
+ let insert_query = format!(
+ "INSERT INTO {} (cert_num, status) VALUES ($1, 'Valid')",
+ table_name
+ );
+
+ diesel::sql_query(insert_query)
+ .bind::(&cert_num)
+ .execute(&mut conn)?;
+
+ Ok(())
+ })
+ .await?
+ }
+
+ async fn add_certificate_sqlite(
+ &self,
+ pool: &Pool>,
+ cert_num: &str,
+ ) -> Result<(), Box> {
+ let table_name = self.table_name.clone();
+ let cert_num = cert_num.to_string();
+ let connection_manager = pool.clone();
+
+ tokio::task::spawn_blocking(move || -> Result<(), Box> {
+ let mut conn = connection_manager.get()?;
+
+ let query = format!(
+ "SELECT COUNT(*) as count FROM {} WHERE cert_num = ?",
+ table_name
+ );
+
+ let exists: bool = diesel::sql_query(query)
+ .bind::(&cert_num)
+ .execute(&mut conn)?
+ > 0;
+
+ if exists {
+ return Err("Certificate already exists".into());
+ }
+
+ let insert_query = format!(
+ "INSERT INTO {} (cert_num, status) VALUES (?, 'Valid')",
+ table_name
+ );
+
+ diesel::sql_query(insert_query)
+ .bind::(&cert_num)
+ .execute(&mut conn)?;
+
+ Ok(())
+ })
+ .await?
+ }
+ #[cfg(feature="mysql")]
+ async fn revoke_certificate_mysql(
+ &self,
+ pool: &Pool>,
+ cert_num: &str,
+ revocation_time: NaiveDateTime,
+ reason: &str,
+ ) -> Result<(), Box> {
+ let table_name = self.table_name.clone();
+ let cert_num = cert_num.to_string();
+ let reason = reason.to_string();
+ let connection_manager = pool.clone();
+
+ tokio::task::spawn_blocking(move || -> Result<(), Box> {
+ let mut conn = connection_manager.get()?;
+
+ // Check if certificate exists
+ let query = format!(
+ "SELECT COUNT(*) as count FROM {} WHERE cert_num = ?",
+ table_name
+ );
+
+ let exists: bool = diesel::sql_query(query.clone())
+ .bind::(&cert_num)
+ .execute(&mut conn)?
+ > 0;
+
+ if !exists {
+ return Err("Certificate does not exist".into());
+ }
+
+ // Update certificate status
+ let update_query = format!(
+ "UPDATE {} SET status = 'Revoked', revocation_time = ?, revocation_reason = ? WHERE cert_num = ?",
+ table_name
+ );
+
+ diesel::sql_query(update_query)
+ .bind::(&revocation_time)
+ .bind::(&reason)
+ .bind::(&cert_num)
+ .execute(&mut conn)?;
+
+ Ok(())
+ }).await?
+ }
+ #[cfg(feature="postgres")]
+ async fn revoke_certificate_postgres(
+ &self,
+ pool: &Pool>,
+ cert_num: &str,
+ revocation_time: NaiveDateTime,
+ reason: &str,
+ ) -> Result<(), Box> {
+ let table_name = self.table_name.clone();
+ let cert_num = cert_num.to_string();
+ let reason = reason.to_string();
+ let connection_manager = pool.clone();
+
+ tokio::task::spawn_blocking(move || -> Result<(), Box> {
+ let mut conn = connection_manager.get()?;
+
+ let query = format!(
+ "SELECT EXISTS (SELECT 1 FROM {} WHERE cert_num = $1) as exists",
+ table_name
+ );
+
+ let exists_results = diesel::sql_query(query)
+ .bind::(&cert_num)
+ .load::(&mut conn)?;
+
+ let exists = !exists_results.is_empty() && exists_results[0].exists;
+
+ if !exists {
+ return Err("Certificate does not exist".into());
+ }
+
+ // Update certificate status
+ // For PostgreSQL, we need to cast the text to the enum type
+ let update_query = format!(
+ "UPDATE {} SET status = 'Revoked', revocation_time = $1, revocation_reason = $2::revocation_reason_enum WHERE cert_num = $3",
+ table_name
+ );
+
+ diesel::sql_query(update_query)
+ .bind::(&revocation_time)
+ .bind::(&reason)
+ .bind::(&cert_num)
+ .execute(&mut conn)?;
+
+ Ok(())
+ }).await?
+ }
+
+ async fn revoke_certificate_sqlite(
+ &self,
+ pool: &Pool>,
+ cert_num: &str,
+ revocation_time: NaiveDateTime,
+ reason: &str,
+ ) -> Result<(), Box> {
+ let table_name = self.table_name.clone();
+ let cert_num = cert_num.to_string();
+ let reason = reason.to_string();
+ let connection_manager = pool.clone();
+
+ tokio::task::spawn_blocking(move || -> Result<(), Box> {
+ let mut conn = connection_manager.get()?;
+
+ let query = format!(
+ "SELECT COUNT(*) as count FROM {} WHERE cert_num = ?",
+ table_name
+ );
+
+ let exists: bool = diesel::sql_query(query)
+ .bind::(&cert_num)
+ .execute(&mut conn)?
+ > 0;
+
+ if !exists {
+ return Err("Certificate does not exist".into());
+ }
+
+ let update_query = format!(
+ "UPDATE {} SET status = 'Revoked', revocation_time = ?, revocation_reason = ? WHERE cert_num = ?",
+ table_name
+ );
+
+ diesel::sql_query(update_query)
+ .bind::(&revocation_time)
+ .bind::(&reason)
+ .bind::(&cert_num)
+ .execute(&mut conn)?;
+
+ Ok(())
+ }).await?
+ }
+ #[cfg(feature="mysql")]
+ async fn get_certificate_status_mysql(
+ &self,
+ pool: &Pool>,
+ cert_num: &str,
+ ) -> Result> {
+ let table_name = self.table_name.clone();
+ let cert_num = cert_num.to_string();
+ let connection_manager = pool.clone();
+
+ let result = tokio::task::spawn_blocking(move || -> Result> {
+ let mut conn = connection_manager.get()?;
+
+ // Query certificate status
+ let query = format!(
+ "SELECT cert_num, status, revocation_time, revocation_reason FROM {} WHERE cert_num = ?",
+ table_name
+ );
+
+ let records = diesel::sql_query(query)
+ .bind::(&cert_num)
+ .load::(&mut conn)?;
+
+ if records.is_empty() {
+ return Err("Certificate not found".into());
+ }
+
+ let record = &records[0];
+
+ Ok(Certinfo {
+ status: record.status.clone(),
+ revocation_time: record.revocation_time,
+ revocation_reason: record.revocation_reason.clone(),
+ })
+ }).await??;
+
+ Ok(result)
+ }
+ #[cfg(feature="postgres")]
+ async fn get_certificate_status_postgres(
+ &self,
+ pool: &Pool>,
+ cert_num: &str,
+ ) -> Result> {
+ let table_name = self.table_name.clone();
+ let cert_num = cert_num.to_string();
+ let connection_manager = pool.clone();
+
+ let result = tokio::task::spawn_blocking(move || -> Result> {
+ let mut conn = connection_manager.get()?;
+
+ // Query certificate status
+ let query = format!(
+ "SELECT cert_num, status, revocation_time, revocation_reason FROM {} WHERE cert_num = $1",
+ table_name
+ );
+
+ let records = diesel::sql_query(query)
+ .bind::(&cert_num)
+ .load::(&mut conn)?;
+
+ if records.is_empty() {
+ return Err("Certificate not found".into());
+ }
+
+ let record = &records[0];
+
+ Ok(Certinfo {
+ status: record.status.clone(),
+ revocation_time: record.revocation_time,
+ revocation_reason: record.revocation_reason.clone(),
+ })
+ }).await??;
+
+ Ok(result)
+ }
+
+ async fn get_certificate_status_sqlite(
+ &self,
+ pool: &Pool>,
+ cert_num: &str,
+ ) -> Result> {
+ let table_name = self.table_name.clone();
+ let cert_num = cert_num.to_string();
+ let connection_manager = pool.clone();
+
+ let result = tokio::task::spawn_blocking(move || -> Result> {
+ let mut conn = connection_manager.get()?;
+
+ let query = format!(
+ "SELECT cert_num, status, revocation_time, revocation_reason FROM {} WHERE cert_num = ?",
+ table_name
+ );
+
+ let records = diesel::sql_query(query)
+ .bind::(&cert_num)
+ .load::(&mut conn)?;
+
+ if records.is_empty() {
+ return Err("Certificate not found".into());
+ }
+
+ let record = &records[0];
+
+ Ok(Certinfo {
+ status: record.status.clone(),
+ revocation_time: record.revocation_time,
+ revocation_reason: record.revocation_reason.clone(),
+ })
+ }).await??;
+
+ Ok(result)
+ }
+ #[cfg(feature="mysql")]
+ async fn list_certificates_mysql(
+ &self,
+ pool: &Pool>,
+ status: Option,
+ ) -> Result, Box> {
+ let table_name = self.table_name.clone();
+ let status_filter = status.map(|s| s.to_string());
+ let connection_manager = pool.clone();
+
+ let result = tokio::task::spawn_blocking(
+ move || -> Result, Box> {
+ let mut conn = connection_manager.get()?;
+
+ // Build query based on status filter (MySQL)
+ let query = match &status_filter {
+ Some(_s) => format!(
+ "SELECT cert_num, status, revocation_time, revocation_reason FROM {} WHERE status = ?",
+ table_name
+ ),
+ None => format!("SELECT cert_num, status, revocation_time, revocation_reason FROM {}", table_name),
+ };
+
+ // Execute query with or without filter
+ let records: Vec = if let Some(s) = &status_filter {
+ diesel::sql_query(query)
+ .bind::(s)
+ .load::(&mut conn)?
+ } else {
+ diesel::sql_query(query).load::(&mut conn)?
+ };
+
+ // Convert to response format
+ let responses = records
+ .into_iter()
+ .map(|record| CertificateResponse {
+ cert_num: record.cert_num,
+ status: record.status,
+ message: String::new(),
+ })
+ .collect();
+
+ Ok(responses)
+ },
+ )
+ .await??;
+
+ Ok(result)
+ }
+ #[cfg(feature="postgres")]
+ async fn list_certificates_postgres(
+ &self,
+ pool: &Pool>,
+ status: Option,
+ ) -> Result, Box> {
+ let table_name = self.table_name.clone();
+ let status_filter = status.map(|s| s.to_string());
+ let connection_manager = pool.clone();
+
+ let result = tokio::task::spawn_blocking(
+ move || -> Result, Box> {
+ let mut conn = connection_manager.get()?;
+
+ // Build query based on status filter (PostgreSQL)
+ let query = match &status_filter {
+ Some(_s) => format!(
+ "SELECT cert_num, status, revocation_time, revocation_reason FROM {} WHERE status = $1::cert_status",
+ table_name
+ ),
+ None => format!("SELECT cert_num, status, revocation_time, revocation_reason FROM {}", table_name),
+ };
+
+ // Execute query with or without filter
+ let records: Vec = if let Some(s) = &status_filter {
+ diesel::sql_query(query)
+ .bind::(s)
+ .load::(&mut conn)?
+ } else {
+ diesel::sql_query(query).load::(&mut conn)?
+ };
+
+ // Convert to response format
+ let responses = records
+ .into_iter()
+ .map(|record| CertificateResponse {
+ cert_num: record.cert_num,
+ status: record.status,
+ message: String::new(),
+ })
+ .collect();
+
+ Ok(responses)
+ },
+ )
+ .await??;
+
+ Ok(result)
+ }
+
+ async fn list_certificates_sqlite(
+ &self,
+ pool: &Pool>,
+ status: Option,
+ ) -> Result, Box> {
+ let table_name = self.table_name.clone();
+ let status_filter = status.map(|s| s.to_string());
+ let connection_manager = pool.clone();
+
+ let result = tokio::task::spawn_blocking(
+ move || -> Result, Box> {
+ let mut conn = connection_manager.get()?;
+
+ let query = match &status_filter {
+ Some(_s) => format!(
+ "SELECT cert_num, status, revocation_time, revocation_reason FROM {} WHERE status = ?",
+ table_name
+ ),
+ None => format!("SELECT cert_num, status, revocation_time, revocation_reason FROM {}", table_name),
+ };
+
+ let records: Vec = if let Some(s) = &status_filter {
+ diesel::sql_query(query)
+ .bind::(s)
+ .load::(&mut conn)?
+ } else {
+ diesel::sql_query(query).load::(&mut conn)?
+ };
+
+ let responses = records
+ .into_iter()
+ .map(|record| CertificateResponse {
+ cert_num: record.cert_num,
+ status: record.status,
+ message: String::new(),
+ })
+ .collect();
+
+ Ok(responses)
+ },
+ )
+ .await??;
+
+ Ok(result)
+ }
+}
+
+#[async_trait]
+impl Database for DieselDatabase {
+ async fn check_cert(
+ &self,
+ certnum: &str,
+ revoked: bool,
+ ) -> Result> {
+ match &self.connection {
+ #[cfg(feature="mysql")]
+ DatabaseConnection::MySQL(pool) => self.check_cert_mysql(pool, certnum, revoked).await,
+ #[cfg(feature="postgres")]
+ DatabaseConnection::PostgreSQL(pool) => {
+ self.check_cert_postgres(pool, certnum, revoked).await
+ }
+ DatabaseConnection::SQLite(pool) => {
+ self.check_cert_sqlite(pool, certnum, revoked).await
+ }
+ }
+ }
+
+ fn create_tables_if_needed(&self) -> Result<(), Box> {
+ match &self.connection {
+ #[cfg(feature="mysql")]
+ DatabaseConnection::MySQL(pool) => self.create_tables_if_needed_mysql(pool),
+ #[cfg(feature="postgres")]
+ DatabaseConnection::PostgreSQL(pool) => self.create_tables_if_needed_postgres(pool),
+ DatabaseConnection::SQLite(pool) => self.create_tables_if_needed_sqlite(pool),
+ }
+ }
+
+ async fn add_certificate(&self, cert_num: &str) -> Result<(), Box> {
+ match &self.connection {
+ #[cfg(feature="mysql")]
+ DatabaseConnection::MySQL(pool) => self.add_certificate_mysql(pool, cert_num).await,
+ #[cfg(feature="postgres")]
+ DatabaseConnection::PostgreSQL(pool) => {
+ self.add_certificate_postgres(pool, cert_num).await
+ }
+ DatabaseConnection::SQLite(pool) => self.add_certificate_sqlite(pool, cert_num).await,
+ }
+ }
+
+ async fn revoke_certificate(
+ &self,
+ cert_num: &str,
+ revocation_time: NaiveDateTime,
+ reason: &str,
+ ) -> Result<(), Box> {
+ let now = chrono::Utc::now().naive_utc();
+ if revocation_time > now {
+ return Err("Revocation time cannot be in the future".into());
+ }
+
+ match &self.connection {
+ #[cfg(feature="mysql")]
+ DatabaseConnection::MySQL(pool) => {
+ self.revoke_certificate_mysql(pool, cert_num, revocation_time, reason)
+ .await
+ }
+ #[cfg(feature="postgres")]
+ DatabaseConnection::PostgreSQL(pool) => {
+ self.revoke_certificate_postgres(pool, cert_num, revocation_time, reason)
+ .await
+ }
+ DatabaseConnection::SQLite(pool) => {
+ self.revoke_certificate_sqlite(pool, cert_num, revocation_time, reason)
+ .await
+ }
+ }
+ }
+
+ async fn get_certificate_status(
+ &self,
+ cert_num: &str,
+ ) -> Result> {
+ match &self.connection {
+ #[cfg(feature="mysql")]
+ DatabaseConnection::MySQL(pool) => {
+ self.get_certificate_status_mysql(pool, cert_num).await
+ }
+ #[cfg(feature="postgres")]
+ DatabaseConnection::PostgreSQL(pool) => {
+ self.get_certificate_status_postgres(pool, cert_num).await
+ }
+ DatabaseConnection::SQLite(pool) => {
+ self.get_certificate_status_sqlite(pool, cert_num).await
+ }
+ }
+ }
+
+ async fn list_certificates(
+ &self,
+ status: Option,
+ ) -> Result, Box> {
+ match &self.connection {
+ #[cfg(feature="mysql")]
+ DatabaseConnection::MySQL(pool) => self.list_certificates_mysql(pool, status).await,
+ #[cfg(feature="postgres")]
+ DatabaseConnection::PostgreSQL(pool) => {
+ self.list_certificates_postgres(pool, status).await
+ }
+ DatabaseConnection::SQLite(pool) => self.list_certificates_sqlite(pool, status).await,
+ }
+ }
+}
+
+pub fn create_database(
+ config: Arc,
+) -> Result, Box> {
+ let db = DieselDatabase::new(config)?;
+ Ok(Box::new(db))
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 268c17d..e5a293c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,11 +1,11 @@
-#[macro_use]
extern crate rocket;
+
use chrono::{self, NaiveDateTime, Timelike};
use chrono::{DateTime, Datelike, FixedOffset};
-use clap::Parser;
+use clap::{CommandFactory, Parser};
use config_file::FromConfigFile;
-use mysql::prelude::Queryable;
-use mysql::*;
+use core::str;
+use log::{debug, error, info, trace, warn};
use ocsp::common::asn1::Bytes;
use ocsp::common::ocsp::{OcspExt, OcspExtI};
use ocsp::request::OcspRequest;
@@ -14,31 +14,61 @@ use ocsp::{
err::OcspError,
oid::{ALGO_SHA256_WITH_RSA_ENCRYPTION_DOT, OCSP_RESPONSE_BASIC_DOT},
response::{
- BasicResponse, CertStatus as OcspCertStatus, CertStatus, CertStatusCode, CrlReason,
- OcspRespStatus, OcspResponse, OneResp, ResponderId, ResponseBytes, ResponseData,
- RevokedInfo,
+ BasicResponse, CertStatus, CertStatusCode, OcspRespStatus, OcspResponse, OneResp,
+ ResponderId, ResponseBytes, ResponseData,
},
};
+use openssl::pkey::PKey;
+use openssl::rsa::Rsa;
use pem::parse;
use ring::digest::SHA1_FOR_LEGACY_USE_ONLY;
use ring::{rand, signature};
use rocket::State;
use rocket::http::ContentType;
use rocket::{Data, data::ToByteUnit};
-use r#struct::*;
+use rocket::{get, launch, post, routes};
use std::error::Error;
-use std::fs;
-use std::io;
+use std::fs::{self, File};
+use std::io::{self, Read};
use std::net::SocketAddr;
-use std::path::Path;
-use std::time::Duration;
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
+use r#struct::*;
use x509_parser::oid_registry::OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER;
use x509_parser::prelude::ParsedExtension;
use zeroize::Zeroize;
+//#[cfg(feature = "api")]
+mod api;
+mod database;
mod r#struct;
+#[cfg(test)]
+#[path = "./tests/test.rs"]
+mod test;
+use database::Database;
+#[derive(Parser, Debug)]
+#[clap(
+ author = crate_authors!("\n"),
+ before_help = "OCSP server, listening for requests to give responses.",
+ after_help = "This script is maintained on Github.",
+ help_template = "\
+ {name} {version}
+ Authors: {author-section}
+ {before-help}
+ About: {about-with-newline}
+ {usage-heading} {usage}
+
+ {all-args}{after-help}
+ "
+)]
+#[command(version, author, about, long_about = None)]
+struct Cli {
+ #[arg(default_value = "config.toml")]
+ config_path: PathBuf,
+}
+
fn signresponse(
issuer_hash: &[u8],
- private_key: &ring::rsa::KeyPair,
+ private_key: &ring::signature::RsaKeyPair,
response: Vec,
extensions: Option>,
cert: Option>,
@@ -61,95 +91,17 @@ fn signresponse(
let bytes = ResponseBytes::new_basic(resp_type, basic)?;
Ok(bytes)
}
+
fn signvalidresponse(bytes: ResponseBytes) -> Result, OcspError> {
let ocsp = OcspResponse::new_success(bytes);
ocsp.to_der()
}
+
fn signnonvalidresponse(motif: OcspRespStatus) -> Result, OcspError> {
let ocsp = OcspResponse::new_non_success(motif)?;
ocsp.to_der()
}
-fn checkcert(
- config: &State,
- certnum: &str,
- revoked: bool,
-) -> Result {
- // Let's select payments from database. Type inference should do the trick here.
- let opts = OptsBuilder::new()
- .user(Some(config.dbuser.as_str()))
- .read_timeout(Some(Duration::new(config.time as u64, 0)))
- .db_name(Some(config.dbname.as_str()))
- .pass(Some(config.dbpassword.as_str()));
- let opts = match &config.dbip {
- Some(string) => opts.ip_or_hostname(Some(string)),
- None => opts
- .prefer_socket(true)
- .socket(Some("/run/mysqld/mysqld.sock")),
- };
- let mut conn = Conn::new(opts)?;
- let status = conn.exec_map(
- "SELECT status, revocation_time, revocation_reason FROM list_certs WHERE cert_num=?",
- (String::from(certnum).into_bytes(),),
- |(status, revocation_time, revocation_reason)| Certinfo {
- status,
- revocation_time,
- revocation_reason,
- },
- )?;
- if status.is_empty() {
- warn!("Entry not found for cert {}", certnum);
- if !revoked {
- Ok(OcspCertStatus::new(CertStatusCode::Unknown, None))
- } else {
- Ok(OcspCertStatus::new(
- CertStatusCode::Revoked,
- Some(RevokedInfo::new(
- GeneralizedTime::new(1970, 1, 1, 0, 0, 0).unwrap(),
- Some(CrlReason::OcspRevokeCertHold),
- )),
- ))
- }
- } else {
- let statut = status[0].clone();
- debug!("Entry found for cert {}, status {}", certnum, statut.status);
- if statut.status == "Revoked" {
- let time = GeneralizedTime::now();
- let date = &statut.revocation_time;
- let timenew = match date {
- Some(mysql::Value::Date(year, month, day, hour, min, sec, _ms)) => {
- GeneralizedTime::new(
- i32::from(*year),
- u32::from(*month),
- u32::from(*day),
- u32::from(*hour),
- u32::from(*min),
- u32::from(*sec),
- )
- }
- _ => Ok(time),
- };
- let time = timenew.unwrap_or(time);
- let motif = statut.revocation_reason.unwrap_or_default();
- let motif: CrlReason = match motif.as_str() {
- "key_compromise" => CrlReason::OcspRevokeKeyCompromise,
- "ca_compromise" => CrlReason::OcspRevokeCaCompromise,
- "affiliation_changed" => CrlReason::OcspRevokeAffChanged,
- "superseded" => CrlReason::OcspRevokeSuperseded,
- "cessation_of_operation" => CrlReason::OcspRevokeCessOperation,
- "certificate_hold" => CrlReason::OcspRevokeCertHold,
- "privilege_withdrawn" => CrlReason::OcspRevokePrivWithdrawn,
- "aa_compromise" => CrlReason::OcspRevokeAaCompromise,
- _ => CrlReason::OcspRevokeUnspecified,
- };
- Ok(OcspCertStatus::new(
- CertStatusCode::Revoked,
- Some(RevokedInfo::new(time, Some(motif))),
- ))
- } else {
- Ok(OcspCertStatus::new(CertStatusCode::Good, None))
- }
- }
-}
+
fn createocspresponse(
cert: CertId,
cert_status: CertStatus,
@@ -183,7 +135,8 @@ fn createocspresponse(
one_resp_ext: extension,
})
}
-fn checkcache(state: &State, certname: &str) -> io::Result