Initial commit: DashCaddy v1.0

Full codebase including API server (32 modules + routes), dashboard frontend,
DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
2026-03-05 02:26:12 -08:00
commit f61e85d9a7
337 changed files with 75282 additions and 0 deletions

281
ca/README.md Normal file
View File

@@ -0,0 +1,281 @@
# DashCA - Certificate Authority Distribution
A self-hosted landing page for distributing your root CA certificate with one-click installation across all major platforms.
## Quick Start
### Regenerate All Certificate Formats
```bash
cd scripts
bash generate-all.sh
```
This will:
1. Copy root.crt and intermediate.crt from Caddy PKI
2. Generate root.der (DER format for Windows)
3. Generate root.mobileconfig (Apple profile for iOS/macOS)
4. Extract certificate metadata to cert-info.json
### Deploy to Production
```bash
# Copy all files to production directory
cp -r e:/CaddyCerts/sites/ca/* C:/caddy/sites/ca/
```
Or deploy via the dashboard app selector (preferred method).
## File Structure
```
ca/
├── index.html # Landing page with OS detection
├── root.crt # Root CA certificate (PEM format)
├── root.der # Root CA certificate (DER format)
├── root.mobileconfig # Apple configuration profile
├── intermediate.crt # Intermediate CA certificate
├── cert-info.json # Certificate metadata (auto-generated)
├── scripts/
│ ├── install.ps1 # Windows PowerShell installer
│ ├── install.sh # Linux/macOS shell installer
│ ├── generate-cert-info.js # Extract certificate metadata
│ ├── generate-mobileconfig.js # Generate Apple profile
│ └── generate-all.sh # Wrapper script to regenerate all
└── assets/
└── (icons, logos, etc.)
```
## Certificate Information
**Source:** Caddy's built-in PKI at `C:/caddy/certs/pki/authorities/local/`
- **Name:** Sami Home Network Root CA
- **Algorithm:** ECDSA P-256 with SHA-256
- **Valid Until:** Dec 22, 2034
- **Fingerprint:** `08:98:A5:63:F5:A1:A2:58:5F:02:D7:A8:A2:54:87:E6:BC:33:96:21:29:0E`
## Installation Scripts
### Windows (install.ps1)
Features:
- Requires Administrator privileges
- Downloads certificate from ca.sami
- Verifies SHA-256 fingerprint
- Installs to LocalMachine\Root store
- Checks for existing installation
**One-liner:**
```powershell
irm https://ca.sami/install.ps1 | iex
```
### Linux/macOS (install.sh)
Features:
- Requires sudo/root
- Auto-detects OS (Debian, RedHat, Arch, macOS)
- Platform-specific installation commands
- Fingerprint verification with OpenSSL
- Checks for existing installation
**One-liner:**
```bash
curl -fsSL https://ca.sami/install.sh | sudo bash
```
### Apple Devices (root.mobileconfig)
Features:
- Works on both iOS and macOS
- XML configuration profile format
- Contains base64-encoded certificate
- Unique UUIDs per generation
- User must manually trust after installation (iOS)
**Installation:**
1. Download root.mobileconfig
2. iOS: Settings prompts automatically
3. macOS: System Settings → Profiles → Install
4. iOS: Enable trust in Certificate Trust Settings
## Landing Page Features
The landing page (`index.html`) includes:
- **OS Detection:** Automatically detects Windows, macOS, Linux, iOS, Android
- **Certificate Info Display:** Shows name, fingerprint, expiration, algorithm
- **QR Code:** For easy mobile access (powered by qrcodejs library)
- **Download Links:** All certificate formats and installation scripts
- **Platform Tabs:** Detailed instructions for each operating system
- **Copy-to-Clipboard:** For fingerprint and command-line scripts
- **DashCaddy Theme:** Dark mode with Sami Grotesk font
**API Integration:**
- Loads certificate info from `/api/ca/info` endpoint
- Falls back to static info if API unavailable
## Development Workflow
1. **Edit Files:** Make changes in `e:/CaddyCerts/sites/ca/`
2. **Test Locally:** Open `index.html` in browser (file:// protocol works)
3. **Regenerate Certificates:** Run `scripts/generate-all.sh` if CA renewed
4. **Deploy:** Copy to production or use dashboard deployment
5. **Verify:** Visit https://ca.sami and test on target platforms
## Updating After CA Renewal
When Caddy regenerates its CA certificate (every ~10 years):
### 1. Regenerate Certificate Formats
```bash
cd e:/CaddyCerts/sites/ca/scripts
bash generate-all.sh
```
### 2. Update Fingerprints in Scripts
The new fingerprint will be in `cert-info.json`. Update these files:
**install.ps1** (line 17):
```powershell
$ExpectedFingerprint = "NEW:FIN:GER:PRINT:HERE"
```
**install.sh** (line 13):
```bash
EXPECTED_FP="NEW:FIN:GER:PRINT:HERE"
```
### 3. Deploy to Production
```bash
cp -r e:/CaddyCerts/sites/ca/* C:/caddy/sites/ca/
```
### 4. Notify Users
- Add banner to dashboard
- Send notification via configured channels
- Update documentation with new expiration date
## API Endpoints
DashCA integrates with DashCaddy API:
### GET /api/ca/info
Returns certificate metadata:
```json
{
"success": true,
"certificate": {
"name": "Sami Home Network Root CA",
"fingerprint": "08:98:A5:...",
"validFrom": "Feb 12 07:44:51 2025 GMT",
"validUntil": "Dec 22 07:44:51 2034 GMT",
"daysUntilExpiration": 3235,
"algorithm": "ECDSA P-256 with SHA-256",
"serialNumber": "c1:dc:48:...",
"downloadUrl": "https://ca.sami/root.crt"
}
}
```
### GET /api/health/ca
Returns CA expiration health status:
```json
{
"status": "healthy",
"message": "CA certificate valid for 3235 days",
"daysUntilExpiration": 3235,
"expiresAt": "Dec 22 07:44:51 2034 GMT"
}
```
**Status values:**
- `healthy`: >90 days remaining
- `warning`: 30-90 days
- `critical`: <30 days or expired
- `error`: Certificate not found or error reading
## Troubleshooting
### Certificate Not Found Error
**Symptom:** Scripts fail with "certificate not found"
**Cause:** Caddy hasn't generated the local CA yet
**Solution:** Visit any *.sami domain to trigger CA generation
### Fingerprint Mismatch
**Symptom:** Install scripts reject certificate with fingerprint mismatch
**Cause:** CA was renewed but scripts not updated
**Solution:** Run `generate-all.sh` and update fingerprints in install scripts
### iOS Profile Won't Install
**Symptom:** .mobileconfig shows error when installing
**Cause:** Invalid XML or missing UUIDs
**Solution:** Regenerate with `node generate-mobileconfig.js`
### Android Shows "Not Trusted"
**Symptom:** Certificate installs but sites still show warnings
**Cause:** Android installs as "user" certificate; some apps don't trust user CAs
**Solution:** This is by design. System CA installation requires root access.
### Landing Page Shows "Loading..."
**Symptom:** Certificate info stuck on loading state
**Cause:** API endpoint not accessible
**Solution:** Check that dashcaddy-api server is running and `/api/ca/info` responds
## Testing Checklist
Before deploying to production:
- [ ] All certificate formats generated successfully
- [ ] Landing page loads correctly in browser
- [ ] OS detection works (test multiple user agents)
- [ ] QR code renders and scans correctly
- [ ] Download links work for all file types
- [ ] API endpoint returns valid certificate info
- [ ] Copy-to-clipboard buttons work
- [ ] Platform instruction tabs function correctly
- [ ] Responsive design works on mobile viewport
- [ ] HTTPS access works after deployment
## Security Notes
- **Private Key:** NEVER serve the CA private key (`root.key`). Only public certificates are safe to distribute.
- **Fingerprint Verification:** Install scripts verify fingerprint to prevent MITM attacks
- **Access Control:** ca.sami should only be accessible on your Tailnet/internal network
- **HTTPS Enforcement:** The page itself uses HTTPS (via Caddy's internal CA) to protect the distribution
- **No Auto-Execution:** All installation methods require explicit user action
## Contributing
When adding features to DashCA:
1. Test on multiple platforms before committing
2. Update this README with new features
3. Add relevant sections to troubleshooting guide
4. Update CLAUDE.md if deployment process changes
5. Ensure backward compatibility with existing certificates
## Resources
- **Caddy PKI Documentation:** https://caddyserver.com/docs/caddyfile/directives/tls#pki
- **mobileconfig Format:** https://developer.apple.com/documentation/devicemanagement
- **OpenSSL Certificate Commands:** https://www.openssl.org/docs/man1.1.1/man1/x509.html
- **QR Code Library:** https://github.com/davidshimjs/qrcodejs
---
**Part of the DashCaddy project** - Unified management for Docker + Caddy + DNS

11
ca/cert-info.json Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "Sami Home Network Root CA",
"fingerprint": "08:98:A5:63:F5:A1:A2:58:5F:02:D7:A8:A2:54:87:E6:BC:33:96:9F:9B:5D:B0:53:62:20:7F:AF:96:21:29:0E",
"validFrom": "Feb 12 07:44:51 2025 GMT",
"validUntil": "Dec 22 07:44:51 2034 GMT",
"daysUntilExpiration": 3235,
"algorithm": "ECDSA P-256 with SHA-256",
"issuer": "Sami Home Network Root CA",
"serialNumber": "C1DC482220B562C06853903A8956D052",
"generatedAt": "2026-02-11T10:43:32.863Z"
}

1284
ca/index.html Normal file

File diff suppressed because it is too large Load Diff

12
ca/intermediate.crt Normal file
View File

@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBtTCCAVugAwIBAgIRAIyx9ujLhds2Wffi6rROHOYwCgYIKoZIzj0EAwIwJDEi
MCAGA1UEAxMZU2FtaSBIb21lIE5ldHdvcmsgUm9vdCBDQTAeFw0yNjAyMTAxMTMx
MjBaFw0yNjAyMTcxMTMxMjBaMCwxKjAoBgNVBAMTIVNhbWkgSG9tZSBOZXR3b3Jr
IEludGVybWVkaWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL3XMHS8
bbGgHsGojPWIgDqHH65nxm/yvfrA/w5rXe1QNZ0oQfXdhUODuu1oTjdQiGSOxp5J
N7+r73DIIjDoO1SjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/
AgEAMB0GA1UdDgQWBBRvN+rmvteWGd3Gj1ek/5lJWq5MXzAfBgNVHSMEGDAWgBQ1
JUJhev790of0c/LsH+PAvsy4iTAKBggqhkjOPQQDAgNIADBFAiEAvWR3KVBGMsWp
OEyqcRAmI5kDvfE/zC8bf3IZru5pGFsCIEvil49Fg2ifB8+w5c2T0wjllpsBOUUy
HjpIXBIn9ix7
-----END CERTIFICATE-----

11
ca/root.crt Normal file
View File

@@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBjTCCATKgAwIBAgIRAMHcSCIgtWLAaFOQOolW0FIwCgYIKoZIzj0EAwIwJDEi
MCAGA1UEAxMZU2FtaSBIb21lIE5ldHdvcmsgUm9vdCBDQTAeFw0yNTAyMTIwNzQ0
NTFaFw0zNDEyMjIwNzQ0NTFaMCQxIjAgBgNVBAMTGVNhbWkgSG9tZSBOZXR3b3Jr
IFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATs8K5hvh7qC77kdFgk
wyIu6SvzEtrK416lLkQkC+E79xIwGRKsZ7T/gd+0Bk0NMUZBxLww4F2Rl/kt3eGu
49rSo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNV
HQ4EFgQUNSVCYXr+/dKH9HPy7B/jwL7MuIkwCgYIKoZIzj0EAwIDSQAwRgIhAJE5
d02KdZA6V79f4qNfmy3tJMmnL4MA2MHhDQ5qqZyqAiEA2UisGjAXYV3GAGo1d+8C
yam9Y42t1K8Fx5q5iy+bs8w=
-----END CERTIFICATE-----

BIN
ca/root.der Normal file

Binary file not shown.

45
ca/root.mobileconfig Normal file
View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadCertificateFileName</key>
<string>root.crt</string>
<key>PayloadContent</key>
<data>
MIIBjTCCATKgAwIBAgIRAMHcSCIgtWLAaFOQOolW0FIwCgYIKoZIzj0EAwIwJDEiMCAGA1UEAxMZU2FtaSBIb21lIE5ldHdvcmsgUm9vdCBDQTAeFw0yNTAyMTIwNzQ0NTFaFw0zNDEyMjIwNzQ0NTFaMCQxIjAgBgNVBAMTGVNhbWkgSG9tZSBOZXR3b3JrIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATs8K5hvh7qC77kdFgkwyIu6SvzEtrK416lLkQkC+E79xIwGRKsZ7T/gd+0Bk0NMUZBxLww4F2Rl/kt3eGu49rSo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUNSVCYXr+/dKH9HPy7B/jwL7MuIkwCgYIKoZIzj0EAwIDSQAwRgIhAJE5d02KdZA6V79f4qNfmy3tJMmnL4MA2MHhDQ5qqZyqAiEA2UisGjAXYV3GAGo1d+8Cyam9Y42t1K8Fx5q5iy+bs8w=
</data>
<key>PayloadDescription</key>
<string>Root CA certificate for Sami Home Network</string>
<key>PayloadDisplayName</key>
<string>Sami Home Network Root CA</string>
<key>PayloadIdentifier</key>
<string>com.sami-home.ca.root-ca</string>
<key>PayloadType</key>
<string>com.apple.security.root</string>
<key>PayloadUUID</key>
<string>059F6B88-E62A-4219-90D5-7FABBE83540A</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key>
<string>Install the Sami Home Network Root CA to trust locally-issued certificates for *.sami domains.</string>
<key>PayloadDisplayName</key>
<string>Sami Home Network Root CA</string>
<key>PayloadIdentifier</key>
<string>com.sami-home.ca</string>
<key>PayloadOrganization</key>
<string>Sami Home Network</string>
<key>PayloadRemovalDisallowed</key>
<false/>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>AF495D1C-16AF-44A7-8C6C-173CC8E82FC3</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

View File

@@ -0,0 +1,50 @@
#!/bin/bash
set -e
# DashCA Certificate Generation Script
# This script generates all required certificate formats
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CA_DIR="$(dirname "$SCRIPT_DIR")"
CADDY_CERT_DIR="C:/caddy/certs/pki/authorities/local"
echo "======================================"
echo "DashCA Certificate Format Generator"
echo "======================================"
echo ""
# Step 1: Copy certificates from Caddy
echo "[1/4] Copying certificates from Caddy PKI..."
if [ ! -f "$CADDY_CERT_DIR/root.crt" ]; then
echo "ERROR: Root certificate not found at $CADDY_CERT_DIR/root.crt"
exit 1
fi
cp "$CADDY_CERT_DIR/root.crt" "$CA_DIR/"
cp "$CADDY_CERT_DIR/intermediate.crt" "$CA_DIR/" 2>/dev/null || echo " (Intermediate certificate not found, skipping)"
echo " ✓ Certificates copied"
# Step 2: Generate DER format
echo "[2/4] Generating DER format..."
openssl x509 -in "$CA_DIR/root.crt" -outform DER -out "$CA_DIR/root.der"
echo " ✓ DER format generated: root.der"
# Step 3: Generate certificate info JSON
echo "[3/4] Extracting certificate metadata..."
node "$SCRIPT_DIR/generate-cert-info.js"
# Step 4: Generate Apple mobileconfig
echo "[4/4] Generating Apple mobile configuration profile..."
node "$SCRIPT_DIR/generate-mobileconfig.js"
echo ""
echo "======================================"
echo "✓ All certificate formats generated!"
echo "======================================"
echo ""
echo "Files created in: $CA_DIR"
ls -lh "$CA_DIR"/*.{crt,der,mobileconfig,json} 2>/dev/null || echo "Files created successfully"
echo ""
echo "To deploy to production:"
echo " cp -r $CA_DIR/* C:/caddy/sites/ca/"
echo ""

View File

@@ -0,0 +1,75 @@
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const CERT_PATH = path.join(__dirname, '../root.crt');
const OUTPUT_PATH = path.join(__dirname, '../cert-info.json');
function extractCertInfo() {
try {
console.log('Extracting certificate information from:', CERT_PATH);
// Extract SHA-256 fingerprint
const fingerprint = execSync(`openssl x509 -in "${CERT_PATH}" -noout -fingerprint -sha256`)
.toString()
.trim()
.split('=')[1];
// Extract validity dates
const dates = execSync(`openssl x509 -in "${CERT_PATH}" -noout -dates`).toString();
const notBefore = dates.match(/notBefore=(.*)/)[1].trim();
const notAfter = dates.match(/notAfter=(.*)/)[1].trim();
// Extract subject
const subject = execSync(`openssl x509 -in "${CERT_PATH}" -noout -subject`)
.toString()
.trim()
.split('CN = ')[1] || execSync(`openssl x509 -in "${CERT_PATH}" -noout -subject`)
.toString()
.trim()
.split('CN=')[1];
// Extract serial number
const serialNumber = execSync(`openssl x509 -in "${CERT_PATH}" -noout -serial`)
.toString()
.trim()
.split('=')[1];
// Calculate days until expiration
const expirationDate = new Date(notAfter);
const today = new Date();
const daysUntilExpiration = Math.floor((expirationDate - today) / (1000 * 60 * 60 * 24));
const certInfo = {
name: subject,
fingerprint: fingerprint,
validFrom: notBefore,
validUntil: notAfter,
daysUntilExpiration: daysUntilExpiration,
algorithm: 'ECDSA P-256 with SHA-256',
issuer: subject, // Self-signed root CA
serialNumber: serialNumber,
generatedAt: new Date().toISOString()
};
fs.writeFileSync(OUTPUT_PATH, JSON.stringify(certInfo, null, 2));
console.log('✓ Certificate information extracted successfully!');
console.log(' Output:', OUTPUT_PATH);
console.log(' Name:', certInfo.name);
console.log(' Fingerprint:', certInfo.fingerprint);
console.log(' Valid until:', certInfo.validUntil);
console.log(' Days until expiration:', certInfo.daysUntilExpiration);
return certInfo;
} catch (error) {
console.error('Error extracting certificate information:', error.message);
process.exit(1);
}
}
// Run if called directly
if (require.main === module) {
extractCertInfo();
}
module.exports = { extractCertInfo };

View File

@@ -0,0 +1,105 @@
const fs = require('fs');
const crypto = require('crypto');
const path = require('path');
const CERT_PATH = path.join(__dirname, '../root.crt');
const OUTPUT_PATH = path.join(__dirname, '../root.mobileconfig');
function generateUUID() {
return crypto.randomUUID().toUpperCase();
}
function generateMobileConfig() {
try {
console.log('Generating Apple mobile configuration profile...');
console.log('Reading certificate from:', CERT_PATH);
// Read certificate
const certPem = fs.readFileSync(CERT_PATH, 'utf8');
// Extract base64 content (remove PEM headers and newlines)
const certBase64 = certPem
.replace('-----BEGIN CERTIFICATE-----', '')
.replace('-----END CERTIFICATE-----', '')
.replace(/\s/g, '');
// Generate UUIDs for profile and payload
const profileUUID = generateUUID();
const payloadUUID = generateUUID();
const mobileconfig = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadCertificateFileName</key>
<string>root.crt</string>
<key>PayloadContent</key>
<data>
${certBase64}
</data>
<key>PayloadDescription</key>
<string>Root CA certificate for Sami Home Network</string>
<key>PayloadDisplayName</key>
<string>Sami Home Network Root CA</string>
<key>PayloadIdentifier</key>
<string>com.sami-home.ca.root-ca</string>
<key>PayloadType</key>
<string>com.apple.security.root</string>
<key>PayloadUUID</key>
<string>${payloadUUID}</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key>
<string>Install the Sami Home Network Root CA to trust locally-issued certificates for *.sami domains.</string>
<key>PayloadDisplayName</key>
<string>Sami Home Network Root CA</string>
<key>PayloadIdentifier</key>
<string>com.sami-home.ca</string>
<key>PayloadOrganization</key>
<string>Sami Home Network</string>
<key>PayloadRemovalDisallowed</key>
<false/>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>${profileUUID}</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
`;
fs.writeFileSync(OUTPUT_PATH, mobileconfig);
console.log('✓ Mobile configuration profile generated successfully!');
console.log(' Output:', OUTPUT_PATH);
console.log(' Profile UUID:', profileUUID);
console.log(' Payload UUID:', payloadUUID);
console.log('\nTo install on iOS:');
console.log(' 1. Download root.mobileconfig to your device');
console.log(' 2. Open Settings app (it should prompt automatically)');
console.log(' 3. Tap "Install Profile" and follow the prompts');
console.log(' 4. Go to Settings > General > About > Certificate Trust Settings');
console.log(' 5. Enable full trust for "Sami Home Network Root CA"');
console.log('\nTo install on macOS:');
console.log(' 1. Download root.mobileconfig');
console.log(' 2. Open System Settings > Privacy & Security > Profiles');
console.log(' 3. Click the profile and click Install');
return { profileUUID, payloadUUID };
} catch (error) {
console.error('Error generating mobile configuration profile:', error.message);
process.exit(1);
}
}
// Run if called directly
if (require.main === module) {
generateMobileConfig();
}
module.exports = { generateMobileConfig };

132
ca/scripts/install.ps1 Normal file
View File

@@ -0,0 +1,132 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Installs the Sami Home Network Root CA certificate to the Trusted Root Certification Authorities store.
.DESCRIPTION
This script downloads the root CA certificate from ca.sami, verifies its fingerprint,
and installs it to the local machine's trusted root store. This allows all *.sami domains
to be trusted system-wide without browser warnings.
.NOTES
Requires Administrator privileges.
For use with DashCA - https://ca.sami
#>
$ErrorActionPreference = "Stop"
# Configuration
$CertUrl = "https://ca.sami/root.crt"
$ExpectedFingerprint = "0898A563F5A1A2585F02D7A8A25487E6BC33969F9B5DB053622 07FAF9621290E"
$TempFile = "$env:TEMP\sami-root-ca.crt"
# Colors
$Red = [System.ConsoleColor]::Red
$Green = [System.ConsoleColor]::Green
$Cyan = [System.ConsoleColor]::Cyan
$Yellow = [System.ConsoleColor]::Yellow
Write-Host ""
Write-Host "========================================" -ForegroundColor $Cyan
Write-Host " DashCA Installer" -ForegroundColor $Cyan
Write-Host " Sami Home Network Root CA" -ForegroundColor $Cyan
Write-Host "========================================" -ForegroundColor $Cyan
Write-Host ""
# Step 1: Download certificate
Write-Host "[1/4] Downloading certificate from $CertUrl..." -ForegroundColor $Cyan
try {
$ProgressPreference = 'SilentlyContinue' # Disable progress bar for faster download
Invoke-WebRequest -Uri $CertUrl -OutFile $TempFile -UseBasicParsing -ErrorAction Stop
Write-Host " ✓ Certificate downloaded" -ForegroundColor $Green
} catch {
Write-Host " ✗ Failed to download certificate" -ForegroundColor $Red
Write-Host " Error: $_" -ForegroundColor $Red
Write-Host ""
Write-Host "Troubleshooting:" -ForegroundColor $Yellow
Write-Host " - Ensure you are on the Tailnet/network where ca.sami is accessible" -ForegroundColor $Yellow
Write-Host " - Try accessing https://ca.sami in your browser first" -ForegroundColor $Yellow
exit 1
}
# Step 2: Verify fingerprint
Write-Host "[2/4] Verifying certificate fingerprint..." -ForegroundColor $Cyan
try {
$Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($TempFile)
$Fingerprint = $Cert.Thumbprint
$NormalizedExpected = $ExpectedFingerprint -replace '[:\s]', ''
$NormalizedActual = $Fingerprint -replace '[:\s]', ''
if ($NormalizedActual -ne $NormalizedExpected) {
Write-Host " ✗ Fingerprint mismatch!" -ForegroundColor $Red
Write-Host " Expected: $ExpectedFingerprint" -ForegroundColor $Yellow
Write-Host " Got: $Fingerprint" -ForegroundColor $Red
Remove-Item $TempFile -Force
Write-Host ""
Write-Host "SECURITY WARNING: The downloaded certificate does not match the expected fingerprint." -ForegroundColor $Red
Write-Host "This could indicate a man-in-the-middle attack or certificate renewal." -ForegroundColor $Red
Write-Host "Please verify with your network administrator before proceeding." -ForegroundColor $Red
exit 1
}
Write-Host " ✓ Fingerprint verified: $Fingerprint" -ForegroundColor $Green
} catch {
Write-Host " ✗ Failed to verify fingerprint" -ForegroundColor $Red
Write-Host " Error: $_" -ForegroundColor $Red
Remove-Item $TempFile -Force -ErrorAction SilentlyContinue
exit 1
}
# Step 3: Check if already installed
Write-Host "[3/4] Checking for existing certificate..." -ForegroundColor $Cyan
$ExistingCert = Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object { $_.Thumbprint -eq $Fingerprint }
if ($ExistingCert) {
Write-Host " Certificate already installed" -ForegroundColor $Yellow
Write-Host " Subject: $($ExistingCert.Subject)" -ForegroundColor $Yellow
Write-Host " Not After: $($ExistingCert.NotAfter)" -ForegroundColor $Yellow
Remove-Item $TempFile -Force
Write-Host ""
Write-Host "The Sami Home Network Root CA is already trusted on this system." -ForegroundColor $Green
Write-Host "No further action needed!" -ForegroundColor $Green
Write-Host ""
exit 0
}
Write-Host " ✓ Certificate not yet installed, proceeding..." -ForegroundColor $Green
# Step 4: Install certificate
Write-Host "[4/4] Installing certificate to Trusted Root store..." -ForegroundColor $Cyan
try {
$ImportedCert = Import-Certificate -FilePath $TempFile -CertStoreLocation Cert:\LocalMachine\Root -ErrorAction Stop
Write-Host " ✓ Certificate installed successfully" -ForegroundColor $Green
Write-Host " Subject: $($ImportedCert.Subject)" -ForegroundColor $Green
Write-Host " Thumbprint: $($ImportedCert.Thumbprint)" -ForegroundColor $Green
} catch {
Write-Host " ✗ Failed to install certificate" -ForegroundColor $Red
Write-Host " Error: $_" -ForegroundColor $Red
Remove-Item $TempFile -Force -ErrorAction SilentlyContinue
Write-Host ""
Write-Host "Installation failed. Please ensure you are running as Administrator." -ForegroundColor $Red
exit 1
}
# Cleanup
Remove-Item $TempFile -Force -ErrorAction SilentlyContinue
Write-Host ""
Write-Host "========================================" -ForegroundColor $Green
Write-Host " SUCCESS!" -ForegroundColor $Green
Write-Host "========================================" -ForegroundColor $Green
Write-Host ""
Write-Host "The Sami Home Network Root CA has been installed to your Trusted Root store." -ForegroundColor $Green
Write-Host ""
Write-Host "What's next:" -ForegroundColor $Cyan
Write-Host " ✓ All *.sami domains will now be trusted system-wide" -ForegroundColor $Green
Write-Host " ✓ Browsers (Edge, Chrome, Firefox) will no longer show security warnings" -ForegroundColor $Green
Write-Host " ✓ Applications will trust HTTPS connections to your local services" -ForegroundColor $Green
Write-Host ""
Write-Host "Test it out:" -ForegroundColor $Cyan
Write-Host " Visit https://status.sami or any other *.sami service" -ForegroundColor $Yellow
Write-Host " The connection should show as secure with no warnings" -ForegroundColor $Yellow
Write-Host ""

220
ca/scripts/install.sh Normal file
View File

@@ -0,0 +1,220 @@
#!/bin/bash
#
# DashCA Installer - Sami Home Network Root CA
# Installs the root CA certificate system-wide on Linux and macOS
#
# Usage: curl -fsSL https://ca.sami/install.sh | sudo bash
#
set -e
# Configuration
CERT_URL="https://ca.sami/root.crt"
EXPECTED_FP="08:98:A5:63:F5:A1:A2:58:5F:02:D7:A8:A2:54:87:E6:BC:33:96:9F:9B:5D:B0:53:62:20:7F:AF:96:21:29:0E"
CERT_NAME="Sami_Home_Network_Root_CA"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo ""
echo -e "${CYAN}========================================${NC}"
echo -e "${CYAN} DashCA Installer${NC}"
echo -e "${CYAN} Sami Home Network Root CA${NC}"
echo -e "${CYAN}========================================${NC}"
echo ""
# Check for root/sudo
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}✗ This script requires root privileges${NC}"
echo ""
echo "Please run with sudo:"
echo -e " ${YELLOW}curl -fsSL https://ca.sami/install.sh | sudo bash${NC}"
echo ""
echo "Or download first, then run:"
echo -e " ${YELLOW}curl -o install.sh https://ca.sami/install.sh${NC}"
echo -e " ${YELLOW}sudo bash install.sh${NC}"
echo ""
exit 1
fi
# Detect OS
echo -e "${CYAN}[1/6] Detecting operating system...${NC}"
if [[ "$OSTYPE" == "darwin"* ]]; then
OS="macos"
OS_NAME="macOS"
elif [[ -f /etc/os-release ]]; then
. /etc/os-release
if [[ "$ID" == "debian" ]] || [[ "$ID" == "ubuntu" ]] || [[ "$ID_LIKE" == *"debian"* ]]; then
OS="debian"
OS_NAME="Debian/Ubuntu"
elif [[ "$ID" == "fedora" ]] || [[ "$ID" == "rhel" ]] || [[ "$ID" == "centos" ]] || [[ "$ID_LIKE" == *"fedora"* ]] || [[ "$ID_LIKE" == *"rhel"* ]]; then
OS="redhat"
OS_NAME="RedHat/CentOS/Fedora"
elif [[ "$ID" == "arch" ]] || [[ "$ID_LIKE" == *"arch"* ]]; then
OS="arch"
OS_NAME="Arch Linux"
else
OS="unknown"
OS_NAME="Unknown Linux"
fi
elif [[ -f /etc/redhat-release ]]; then
OS="redhat"
OS_NAME="RedHat/CentOS"
elif [[ -f /etc/arch-release ]]; then
OS="arch"
OS_NAME="Arch Linux"
else
OS="unknown"
OS_NAME="Unknown"
fi
if [[ "$OS" == "unknown" ]]; then
echo -e "${RED} ✗ Unsupported operating system${NC}"
echo ""
echo "This script supports:"
echo " - Debian/Ubuntu"
echo " - RedHat/CentOS/Fedora"
echo " - Arch Linux"
echo " - macOS"
echo ""
echo "For manual installation, download the certificate:"
echo -e " ${YELLOW}curl -O $CERT_URL${NC}"
echo ""
exit 1
fi
echo -e "${GREEN} ✓ Detected: $OS_NAME${NC}"
# Download certificate
echo -e "${CYAN}[2/6] Downloading certificate from $CERT_URL...${NC}"
TEMP_CERT=$(mktemp)
if ! curl -fsSL "$CERT_URL" -o "$TEMP_CERT"; then
echo -e "${RED} ✗ Failed to download certificate${NC}"
echo ""
echo -e "${YELLOW}Troubleshooting:${NC}"
echo " - Ensure you are on the Tailnet/network where ca.sami is accessible"
echo " - Try accessing https://ca.sami in your browser first"
echo " - Check your network connection"
rm -f "$TEMP_CERT"
exit 1
fi
echo -e "${GREEN} ✓ Certificate downloaded${NC}"
# Verify fingerprint
echo -e "${CYAN}[3/6] Verifying certificate fingerprint...${NC}"
if ! command -v openssl &> /dev/null; then
echo -e "${RED} ✗ OpenSSL not found${NC}"
echo "Please install OpenSSL to verify certificate fingerprint"
rm -f "$TEMP_CERT"
exit 1
fi
ACTUAL_FP=$(openssl x509 -in "$TEMP_CERT" -noout -fingerprint -sha256 | cut -d= -f2)
if [[ "$ACTUAL_FP" != "$EXPECTED_FP" ]]; then
echo -e "${RED} ✗ Fingerprint mismatch!${NC}"
echo -e "${YELLOW} Expected: $EXPECTED_FP${NC}"
echo -e "${RED} Got: $ACTUAL_FP${NC}"
rm -f "$TEMP_CERT"
echo ""
echo -e "${RED}SECURITY WARNING: The downloaded certificate does not match the expected fingerprint.${NC}"
echo -e "${RED}This could indicate a man-in-the-middle attack or certificate renewal.${NC}"
echo -e "${RED}Please verify with your network administrator before proceeding.${NC}"
echo ""
exit 1
fi
echo -e "${GREEN} ✓ Fingerprint verified${NC}"
# Extract certificate details
echo -e "${CYAN}[4/6] Extracting certificate information...${NC}"
CERT_SUBJECT=$(openssl x509 -in "$TEMP_CERT" -noout -subject | sed 's/subject=//')
CERT_NOT_AFTER=$(openssl x509 -in "$TEMP_CERT" -noout -enddate | sed 's/notAfter=//')
echo -e "${GREEN} ✓ Subject: $CERT_SUBJECT${NC}"
echo -e "${GREEN} ✓ Valid until: $CERT_NOT_AFTER${NC}"
# Check if already installed
echo -e "${CYAN}[5/6] Checking for existing installation...${NC}"
ALREADY_INSTALLED=false
case "$OS" in
debian)
if [[ -f "/usr/local/share/ca-certificates/${CERT_NAME}.crt" ]]; then
ALREADY_INSTALLED=true
fi
;;
redhat)
if [[ -f "/etc/pki/ca-trust/source/anchors/${CERT_NAME}.crt" ]]; then
ALREADY_INSTALLED=true
fi
;;
arch)
if [[ -f "/etc/ca-certificates/trust-source/anchors/${CERT_NAME}.crt" ]]; then
ALREADY_INSTALLED=true
fi
;;
macos)
if security find-certificate -a -c "$CERT_SUBJECT" /Library/Keychains/System.keychain &>/dev/null; then
ALREADY_INSTALLED=true
fi
;;
esac
if [[ "$ALREADY_INSTALLED" == "true" ]]; then
echo -e "${YELLOW} Certificate already installed${NC}"
rm -f "$TEMP_CERT"
echo ""
echo -e "${GREEN}The Sami Home Network Root CA is already trusted on this system.${NC}"
echo -e "${GREEN}No further action needed!${NC}"
echo ""
exit 0
fi
echo -e "${GREEN} ✓ Certificate not yet installed, proceeding...${NC}"
# Install based on OS
echo -e "${CYAN}[6/6] Installing certificate...${NC}"
case "$OS" in
debian)
cp "$TEMP_CERT" "/usr/local/share/ca-certificates/${CERT_NAME}.crt"
update-ca-certificates
echo -e "${GREEN} ✓ Certificate installed via update-ca-certificates${NC}"
;;
redhat)
cp "$TEMP_CERT" "/etc/pki/ca-trust/source/anchors/${CERT_NAME}.crt"
update-ca-trust
echo -e "${GREEN} ✓ Certificate installed via update-ca-trust${NC}"
;;
arch)
cp "$TEMP_CERT" "/etc/ca-certificates/trust-source/anchors/${CERT_NAME}.crt"
trust extract-compat
echo -e "${GREEN} ✓ Certificate installed via trust extract-compat${NC}"
;;
macos)
security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$TEMP_CERT"
echo -e "${GREEN} ✓ Certificate installed to System Keychain${NC}"
;;
esac
# Cleanup
rm -f "$TEMP_CERT"
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} SUCCESS!${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo -e "${GREEN}The Sami Home Network Root CA has been installed system-wide.${NC}"
echo ""
echo -e "${CYAN}What's next:${NC}"
echo -e " ${GREEN}${NC} All *.sami domains will now be trusted"
echo -e " ${GREEN}${NC} Browsers will no longer show security warnings"
echo -e " ${GREEN}${NC} Applications will trust HTTPS connections to your local services"
echo ""
echo -e "${CYAN}Test it out:${NC}"
echo -e " ${YELLOW}Visit https://status.sami or any other *.sami service${NC}"
echo -e " ${YELLOW}The connection should show as secure with no warnings${NC}"
echo ""