Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 56 additions & 5 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Rust
name: CI

on:
push:
Expand All @@ -8,12 +8,63 @@ on:

env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-D warnings"

jobs:
build:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install botan CLI
run: sudo apt-get update && sudo apt-get install -y botan
- name: Test
run: cargo test --workspace --all-features

feature-gate:
name: Feature Gate Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Check feature combinations
run: ./scripts/compile_feature_combinations.sh

lint:
name: Format & Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: rustfmt
run: cargo fmt --all --check
- name: clippy
run: cargo clippy --workspace --all-features --all-targets -- -D warnings

docs:
name: Docs
runs-on: ubuntu-latest
env:
RUSTDOCFLAGS: "-D warnings"
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: cargo doc
run: cargo doc --all-features --no-deps

msrv:
name: MSRV (1.85)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check
run: cargo check
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@1.85.0
- uses: Swatinem/rust-cache@v2
- name: cargo check
run: cargo check --all-features
76 changes: 76 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0]

This release focuses on correctness, security, and returning `Result` instead of panicking.

### Fixed

- DER-encode ECDSA signatures instead of using fixed-width format.
- Use and declare matching SHA-384 / SHA-512 signatures for P-384 and P-521 curves.
- Build Distinguished Names structurally to prevent RDN injection and panics.
- Set digital signature key usage correctly for non-RSA algorithms.
- Encode IP and email SANs using IPAddress and rfc822Name types instead of DNSName.
- Fix import of RSA private keys in PKCS#1 DER format.
- Return errors instead of panicking for invalid serial numbers or far-future validity dates.
- Fix panic when parsing certificates without extensions.

### Added

- Add certificate parsing functions from DER, PEM, or auto-detected bytes.
- Add `Certificate::fingerprint` to compute SHA-256 digests.
- Add Subject Key Identifier and Authority Key Identifier extensions to issued certificates.
- Add `max_path_length` to `CertificateParams` for path constraints.
- Add `KeyPair::encode_private_key_der` for PKCS#8 DER export.
- Add `KeyType` enum and helper methods for key algorithm inspection.
- Add validity bounds, duration, and remaining helper methods.
- Add `Display` implementations for key pairs, types, and signature algorithms.
- Add light log instrumentation for key and certificate operations.
- Add an integration test verifying end-to-end TLS echo round-trips.
- Expand CI checks with clippy, rustfmt, docs, and MSRV validation.

### Changed

- Rename and restructure public library APIs to return `Result` and improve parameter structures.
- Use random 20-byte CSPRNG serial numbers by default.
- Prevent RSA key generation under 2048 bits and strip private keys from debug output.
- Replace OpenSSL and Botan test dependencies with pure Rust integration tests and reduce dependencies.

## [0.1.2]

### Added

- Add Cargo features for individual cryptographic algorithms.
- Raise a compile error if no cryptographic algorithm features are enabled.
- Add a script and CI step to build different feature combinations.

### Changed

- Change P-256 public key DER serialization to use SEC1 point encoding.

## [0.1.1]

### Fixed

- Correctly propagate the `is_ca` flag to basic constraints in issued certificates.
- Fix signature algorithm OID encoding for issued certificates.
- Fix test execution under non-English locales and newer OpenSSL versions.

### Added

- Add `KeyPair::encode_private_key_pem` to export private keys in PEM format.
- Add `Certificate::new_self_signed_with_expiration` for custom validity dates.

## [0.1.0]

### Added

- Initial release implementing core X.509 certificate and key pair generation.
- Support key generation and management for RSA, ECDSA (P-256/P-384/P-521), and Ed25519.
- Support PEM/DER encoding for certificates and public/private keys.
- Add basic certificate extension support including Basic Constraints, Key Usage, and SAN.
37 changes: 17 additions & 20 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
[package]
name = "certkit"
version = "0.1.2"
version = "0.2.0"
edition = "2024"
rust-version = "1.85"
license = "MIT OR Apache-2.0"
description = "A pure Rust library for X.509 certificate management, creation, and validation, supporting RSA, ECDSA, and Ed25519 keys, with no OpenSSL or ring dependencies."
description = "A pure Rust library for X.509 certificate creation, parsing, and management, supporting RSA, ECDSA, and Ed25519 keys, with no OpenSSL or ring dependencies."
repository = "https://github.com/nacardin/certkit.git"
homepage = "https://github.com/nacardin/certkit"
documentation = "https://docs.rs/certkit"
Expand All @@ -13,33 +14,29 @@ categories = ["cryptography", "authentication"]
authors = ["Nick Cardin <nick@cardin.email>"]

[features]
default = ["rsa", "p256", "p384", "p521","ed25519"]
p521 = ["p384", "dep:p521", "ecdsa"] #For some reason p521 does not compile without p384...
ed25519 = ["ed25519-dalek"]
default = ["rsa", "p256", "p384", "p521", "ed25519"]
ed25519 = ["dep:ed25519-dalek"]


[dependencies]
bon = "3"
const-oid = { version = "0.9.6", features = ["db"] }
rsa = { version = "0.9", optional = true }
der = "0.7"
ed25519-dalek = { version = "2", features = ["rand_core", "pkcs8", "pem"], optional = true }
log = "0.4"
p256 = { version = "0.13", features = ["ecdsa", "pkcs8"], optional = true }
p384 = { version = "0.13", features = ["ecdsa", "pkcs8"], optional = true }
p521 = { version = "0.13", features = ["ecdsa", "pkcs8"], optional = true }
ecdsa = { version = "0.16", features = ["verifying"], optional = true }
ed25519-dalek = { version = "2", features = ["rand_core", "pkcs8", "pem"], optional = true}
sha2 = { version = "0.10", default-features = false, features = ["oid"] }
rand_core = { version = "0.6", features = ["getrandom"]}
der = "0.7"
time = "0.3"
pem = "3"
x509-cert = "0.2.5"
pkcs8 = { version = "0.10.2", features = ["alloc", "pem"] }
rand = "0.9.1"
base64 = "0.22.1"
pkcs8 = { version = "0.10", features = ["alloc", "pem"] }
rand_core = { version = "0.6", features = ["getrandom"] }
rsa = { version = "0.9", optional = true }
sha1 = "0.10"
thiserror = "1.0"
regex = "1.7"
sha2 = { version = "0.10", default-features = false, features = ["oid"] }
thiserror = "2"
time = "0.3"
x509-cert = "0.2"

[dev-dependencies]
openssl = { version = "0.10" }
botan = { version = "0.11", features = ["vendored"] }
env_logger = "0.11"
rustls = "0.23"
51 changes: 47 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,69 @@ A high-level Rust library providing abstractions over certificates and keys. Thi
- Create intermediate CAs for certificate hierarchies
- Support for multiple key types:
- RSA
- ECDSA (P-256)
- ECDSA (P-256, P-384, P-521)
- Ed25519
- PEM and DER format support
- Modern Rust implementation with strong type safety
- Zero-copy parsing and serialization with `der` crate
- Type-safe parsing and serialization with `der` crate

## Usage

Add this to your `Cargo.toml`:

```toml
[dependencies]
certkit = "0.1.0"
certkit = "0.2"
```

## Cargo features

Each cryptographic algorithm is behind its own feature. All are enabled by default, so the default build is unchanged:

| Feature | Algorithm | Default |
|-----------|------------------|---------|
| `rsa` | RSA | yes |
| `p256` | ECDSA P-256 | yes |
| `p384` | ECDSA P-384 | yes |
| `p521` | ECDSA P-521 | yes |
| `ed25519` | Ed25519 | yes |

To pull in only the algorithms you need, disable the defaults and opt back in. For example, an ECDSA-only build that drops RSA (and its `num-bigint-dig` / `libm` dependency tree):

```toml
[dependencies]
certkit = { version = "0.2", default-features = false, features = ["p256", "p384"] }
```

At least one algorithm feature must be enabled; building with none is a compile error.

## Examples

[`tests/tls_echo.rs`](tests/tls_echo.rs) is a complete, runnable example that exercises the full PKI workflow:

1. Generate a **root CA** (self-signed)
2. Issue an **intermediate CA** signed by the root
3. Issue **server** and **client** end-entity certificates from the intermediate
4. Stand up an **mTLS echo server** with `rustls` and verify a successful round-trip

Run it with:

```sh
cargo test mtls_echo
```

## Key formats

| Standard | Supported | Notes |
|----------|-----------|-------|
| PKCS #1 | RSA only | Encoding/decoding RSA public and private keys; RSASSA-PKCS1-v1_5 signatures with SHA-256 |
| PKCS #8 | ✅ All | Primary private-key format for every algorithm (RSA, ECDSA, Ed25519). PEM and DER import/export |

## Dependencies

- `x509-cert`: X.509 certificate handling
- `der`: ASN.1 DER encoding/decoding
- `pkcs8`: Private key cryptography standard
- `pkcs8`: Public-Key Cryptography Standards #8
- `rsa`, `p256`, `ed25519-dalek`: Cryptographic algorithms
- `time`: Time handling for certificate validity
- `pem`: PEM format encoding/decoding
Expand Down
18 changes: 0 additions & 18 deletions compile_feature_combinations.sh

This file was deleted.

20 changes: 20 additions & 0 deletions scripts/compile_feature_combinations.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail

cargo check

cargo check --no-default-features --features rsa
cargo check --no-default-features --features p256
cargo check --no-default-features --features p384
cargo check --no-default-features --features p521
cargo check --no-default-features --features ed25519

cargo check --no-default-features --features rsa,p256
cargo check --no-default-features --features rsa,ed25519
cargo check --no-default-features --features p256,ed25519
cargo check --no-default-features --features p521,p256
cargo check --no-default-features --features rsa,p521
cargo check --no-default-features --features p384,p521
cargo check --no-default-features --features p521,ed25519

echo "All feature combinations checked successfully."
Loading