A secure, containerized PKI certificate management system for creating and managing SSL/TLS certificates with a modern web interface.
- Secure Certificate Management: Generate and manage X.509 certificates with industry-standard encryption
- Multiple Root CAs: Support for multiple Certificate Authorities with hierarchical trust models
- Modern Web UI: Built with React, TypeScript, and Shadcn UI for an intuitive user experience
- Secure Key Storage: AES-256-GCM encryption for private keys at rest
- Multiple Export Formats: Export certificates as PEM bundles or PKCS#12/PFX
- Flexible Database: Support for both SQLite (embedded) and PostgreSQL
- Docker Support: Runs in a single container for easy deployment
- Authentication: JWT-based authentication with bcrypt password hashing
# Build and start the service with SQLite
docker-compose up -d ocm
# Access the web interface
open http://localhost:8000To use PostgreSQL instead:
-
Edit
docker-compose.yml:- Comment out the SQLite configuration lines
- Uncomment the PostgreSQL configuration lines
- Uncomment the
depends_onsection
-
Start the services:
# Build and start both OCM and PostgreSQL
docker-compose --profile postgres up -d
# Access the web interface
open http://localhost:8000# Build the image
docker build -t ocm:latest .
# Run with SQLite
docker run -d \
-p 8000:8000 \
-v ocm-data:/app/data \
--name ocm \
ocm:latest- Go 1.22 or higher
- Node.js 20 or higher
- Make (optional, for convenience commands)
# Install Go dependencies
go mod download
# Create data directory
mkdir -p data
# Run the backend
go run ./cmd/ocm# Navigate to frontend directory
cd frontend
# Install dependencies
npm install
# Run development server
npm run devThe frontend dev server will proxy API requests to the backend at http://localhost:8000.
-
Initial Access: Navigate to
http://localhost:8000 -
Setup Wizard: On first run, you'll be guided through initial setup
-
Create Admin Account: Choose a username and strong password
-
Master Key: The system will generate a master encryption key
- CRITICAL: Save this key securely! It encrypts all private keys in the database
- If lost, you cannot decrypt your certificates
-
Complete Setup: You'll be automatically logged in to the dashboard
-
Navigate to Certificate Authorities
-
Click Create Root CA
-
Fill in the details:
- Friendly Name: A name for easy identification
- Common Name: The CA's common name (e.g., "My Organization Root CA")
- Organization: Your organization name
- Country: Two-letter country code (e.g., "US")
-
Click Create
The CA will be generated with:
-
Navigate to Certificates
-
Click Create Certificate
-
Select the Certificate Authority to sign the certificate
-
Enter the Common Name (e.g.,
example.com) -
Add Subject Alternative Names (optional):
- DNS names:
www.example.com,*.example.com - IP addresses: 192.168.1.1
- DNS names:
-
Click Create
The certificate will be generated with:
NOTE: If you need to create multiple similar certificates, use the Clone feature. Most of the fields will be inherited from the cloned certificate, and you can quickly edit only those fields that need to be changed.
From the Certificates page, find your certificate. Click Export:
- Select Export PEM for:
- Linux/Unix systems (Nginx, Apache, HAProxy)
- Includes certificate, CA chain, and private key in a single file
- Select Export PEM (Split Files) for:
- Linux/Unix systems requiring separate files
- Exports as a ZIP archive containing separate certificate and private key files
- Useful for applications that require distinct cert/key files
- Select Export PFX/PKCS#12 for:
- Windows systems (IIS, Microsoft services)
- Java applications (Tomcat, Java KeyStore)
- Password-protected PKCS#12 format
The application can be configured in three ways, with the following priority (highest to lowest):
- Command line flags (highest priority)
- Environment variables (
OCM_*) - Configuration file (
config.yaml, lowest priority)
All configuration options can be set via command line flags. Use --help to see all available options:
./ocm --help
./ocm --version# Use a custom configuration file
./ocm --config /etc/ocm/config.yaml
# Development mode with debug logging
./ocm --server.port 9090 --log.level debug
# Production with PostgreSQL
./ocm --db.type postgres \
--db.postgres.host db.example.com \
--db.postgres.database ocm \
--db.postgres.user ocm \
--db.postgres.password secret
# Enable TLS/HTTPS
./ocm --server.tls-enabled \
--server.tls-cert /path/to/cert.pem \
--server.tls-key /path/to/key.pem
# Testing with isolated database
./ocm --db.sqlite.path /tmp/test.db \
--server.port 8888 \
--log.level debugGeneral:
-c, --config- Path to configuration file (default:config.yaml)-v, --version- Print version and exit
Server:
--server.port- HTTP server port (default: 8000)--server.host- Bind address (default: 0.0.0.0)--server.read-timeout- Read timeout (e.g., 30s)--server.write-timeout- Write timeout (e.g., 30s)--server.tls-enabled- Enable HTTPS--server.tls-cert- Path to TLS certificate--server.tls-key- Path to TLS key
Database:
--db.type- Database type (sqlite or postgres)--db.sqlite.path- SQLite database file path--db.postgres.host- PostgreSQL host--db.postgres.port- PostgreSQL port (default: 5432)--db.postgres.database- PostgreSQL database name--db.postgres.user- PostgreSQL username--db.postgres.password- PostgreSQL password--db.postgres.ssl-mode- SSL mode (disable, require, verify-ca, verify-full)--db.postgres.max-open-conns- Max open connections (default: 25)--db.postgres.max-idle-conns- Max idle connections (default: 5)
JWT:
--jwt.secret- JWT signing secret (auto-generated if not provided)--jwt.expiration- Token expiration (e.g., 24h)--jwt.issuer- JWT issuer (default: ocm)
Cryptography:
--crypto.default-algorithm- Default algorithm (rsa or ecdsa)--crypto.default-ca-validity- CA validity period (e.g., 87600h = 10 years)--crypto.default-cert-validity- Certificate validity (e.g., 8760h = 1 year)--crypto.default-rsa-bits- RSA key size (2048 or 4096)--crypto.default-ec-curve- EC curve (P256 or P384)
Logging:
-l, --log.level- Log level (debug, info, warn, error)--log.format- Log format (json or console)--log.output- Log output (stdout or file path)
Security:
--security.cors-enabled- Enable CORS--security.cors-origins- CORS allowed origins (repeatable flag)--security.rate-limit-enabled- Enable rate limiting--security.rate-limit-requests- Requests per window (default: 100)--security.rate-limit-window- Window duration (e.g., 1m)
The application is configured via config.yaml:
server:
port: 8000 # HTTP server port
host: 0.0.0.0 # Bind address
read_timeout: 30s
write_timeout: 30s
tls_enabled: false # Enable HTTPS (requires cert/key)
database:
type: sqlite # sqlite or postgres
sqlite:
path: ./data/ocm.db
postgres:
host: localhost
port: 5432
database: ocm
user: ocm
password: ""
ssl_mode: disable
jwt:
secret: "" # Auto-generated on first run
expiration: 24h
issuer: ocm
crypto:
default_ca_validity: 87600h # 10 years
default_cert_validity: 8760h # 1 year
default_algorithm: rsa # rsa or ecdsa
default_rsa_bits: 2048 # 2048 or 4096
default_ec_curve: P256 # P256 or P384
logging:
level: info # debug, info, warn, error
format: json # json or console
output: stdout
security:
cors_enabled: true
cors_origins:
- http://localhost:3000
- http://localhost:8000
rate_limit_enabled: true
rate_limit_requests: 100
rate_limit_window: 1mConfiguration can be overridden with environment variables (takes precedence over config file but not command line flags):
export OCM_SERVER_PORT=9000
export OCM_DB_TYPE=postgres
export OCM_DB_POSTGRES_HOST=db.example.com
export OCM_DB_POSTGRES_PASSWORD=secure_password
export OCM_LOG_LEVEL=debug
./ocmEnvironment variables use the OCM_ prefix followed by the config path in uppercase with underscores (e.g., OCM_SERVER_PORT, OCM_DB_POSTGRES_HOST).
When running in Docker, you can pass flags to the container or use environment variables:
# Using command line flags
docker run -d \
-p 9090:9090 \
-v ocm-data:/app/data \
ocm:latest \
--server.port 9090 \
--log.level debug
# Using environment variables
docker run -d \
-p 8000:8000 \
-e OCM_SERVER_PORT=8000 \
-e OCM_LOG_LEVEL=debug \
-v ocm-data:/app/data \
ocm:latest- Backup Immediately: Save the master key displayed during setup
- Secure Storage: Store in a password manager or secure vault
- Never Commit: Do not commit the master key to version control
- Disaster Recovery: Keep an offline backup in a secure location
- Minimum 8 characters
- Must contain at least one letter
- Must contain at least one number
- Use HTTPS in Production: Enable TLS in the configuration
- Firewall Rules: Restrict access to trusted networks
- Reverse Proxy: Consider using Nginx or Traefik with SSL termination
- Regular Updates: Keep the application and dependencies updated
- SQLite: Secure the database file with appropriate file permissions
- PostgreSQL: Use strong passwords and SSL connections
- Backups: Regularly backup both the database and master key
The application exposes a RESTful API:
GET /api/v1/setup/status- Check if setup is completePOST /api/v1/setup- Perform initial setup
POST /api/v1/auth/login- Login and get JWT tokenGET /api/v1/auth/me- Get current user info (authenticated)
GET /api/v1/authorities- List all CAsGET /api/v1/authorities/:id- Get specific CAPOST /api/v1/authorities- Create Root CAPOST /api/v1/authorities/import- Import existing CAPOST /api/v1/authorities/:id/export- Export CA certificate and keyDELETE /api/v1/authorities/:id- Delete CA
GET /api/v1/certificates- List all certificatesGET /api/v1/certificates/:id- Get specific certificatePOST /api/v1/certificates- Create certificatePOST /api/v1/certificates/:id/export- Export certificatePUT /api/v1/certificates/:id/revoke- Revoke certificateDELETE /api/v1/certificates/:id- Delete certificate
All authenticated endpoints require Authorization: Bearer <token> header.
# Build binary
go build -o ocm ./cmd/ocm
# Run
./ocmcd frontend
# Build production assets
npm run build
# Output in frontend/dist/# Build
docker build -t ocm:latest .
# Run
docker run -p 8000:8000 -v ocm-data:/app/data ocm:latestFor building Docker images that support both AMD64 (x86_64) and ARM64 architectures:
- Docker Desktop (Mac/Windows) or Docker Engine 19.03+ with buildx plugin
- The
makecommand
# Build multi-arch images (both amd64 and arm64)
make release VERSION=1.0.0Important: Multi-architecture builds cannot be loaded into the local Docker daemon directly. The release target builds the images and prepares them for pushing to a registry like Docker Hub.
This command will:
- Set up Docker Buildx (if not already configured)
- Build the frontend assets
- Build Docker images for both
linux/amd64andlinux/arm64platforms - Create a multi-platform manifest list
To build and load a single-architecture image for local testing:
# Load AMD64 image locally (for Intel/AMD processors)
make release-local VERSION=1.0.0 ARCH=amd64
# Load ARM64 image locally (for Apple Silicon)
make release-local VERSION=1.0.0 ARCH=arm64
# Default is amd64 if ARCH not specified
make release-local VERSION=1.0.0This will load the image as ocm:latest and ocm:1.0.0-amd64 (or ocm:1.0.0-arm64) in your local Docker.
The release targets automatically set up Docker Buildx, but you can also run it manually:
make buildx-setupThis creates a builder instance named ocm-builder that supports multi-platform builds.
After building the multi-arch release, push directly to Docker Hub:
# Build and push multi-arch images in one command
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag yourusername/ocm:1.0.0 \
--tag yourusername/ocm:latest \
--push \
.This creates a single manifest that references both AMD64 and ARM64 images. Docker will automatically pull the correct architecture when users run your image.
To test a specific architecture locally:
# Run AMD64 image
docker run --platform linux/amd64 -p 8000:8000 ocm:latest
# Run ARM64 image
docker run --platform linux/arm64 -p 8000:8000 ocm:latestmake build # Build Go binary
make build-all # Build everything (frontend + backend + docker image)
make test # Run tests
make clean # Clean build artifacts
make docker-build # Build Docker image (single architecture)
make release # Build multi-arch images (amd64+arm64, for pushing)
make release-local # Build and load single-arch image locally
make buildx-setup # Setup Docker Buildx for multi-platform builds
make frontend-build # Build frontend only
make frontend-dev # Run frontend dev server
make deps # Install Go dependencies
make frontend-deps # Install frontend dependenciesCheck that no users exist in the database:
# SQLite
sqlite3 data/ocm.db "SELECT COUNT(*) FROM users;"
# If users exist but you need to reset
rm data/ocm.db- Verify credentials are correct
- Check logs for authentication errors
- Ensure JWT secret is properly configured
PostgreSQL:
- Verify connection settings in config.yaml
- Ensure PostgreSQL is running and accessible
- Check firewall rules
SQLite:
- Verify the data directory exists and is writable
- Check file permissions
- Ensure frontend was built:
cd frontend && npm run build - Check that static files are in
./static/directory - Verify the backend is serving static files correctly
- Verify the certificate exists
- Check that the master key is correctly configured
- Review logs for encryption errors
See LICENSE file for details.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
For issues, questions, or feature requests, please open an issue.
Future enhancements which may be implemented:
- Intermediate CAs (3-tier hierarchy)
- Certificate Revocation Lists (CRL)
- Full RBAC with granular permissions
- Certificate renewal workflows
- ACME protocol support
- HSM integration
- Audit logging
- Webhook notifications
- Bulk operations
This application manages cryptographic material and should be deployed in a secure environment. Always use HTTPS in production, secure your master key, and follow security best practices.








