Skip to content

Commit

Permalink
chore: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbbreuer committed Mar 8, 2024
1 parent 2786e8d commit 1290366
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 8 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ Two things are true: Stacks OSS will always stay open-source, and we do love to

Our address: Stacks.js, 5710 Crescent Park #107, Playa Vista 90094, CA.

## Sponsors

We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.

- [JetBrains](https://www.jetbrains.com/)
- [The Solana Foundation](https://solana.com/)

## Credits

- [Chris Breuer](https://github.com/chrisbbreuer)
- [All Contributors](../../contributors)

## License

The MIT License (MIT). Please see [LICENSE](https://github.com/stacksjs/stacks/tree/main/LICENSE.md) for more information.
Expand Down
Binary file modified bun.lockb
Binary file not shown.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,14 @@
"dependencies": {
"@stacksjs/cli": "^0.59.11",
"@stacksjs/storage": "^0.59.11",
"c12": "^1.9.0"
"c12": "^1.9.0",
"node-forge": "^1.3.1"
},
"devDependencies": {
"@stacksjs/development": "^0.59.11",
"@types/bun": "^1.0.8",
"@types/node": "^20.11.24",
"@types/node-forge": "^1.3.11",
"bun-plugin-dts-auto": "^0.10.0",
"vitepress": "^1.0.0-rc.44"
},
Expand Down
2 changes: 0 additions & 2 deletions pkgx.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
dependencies:
bun.sh: ^1.0.30
mkcert.dev: ^1.4.4
mozilla.org/nss: ^3.92.0 # if you test locally using Firefox
35 changes: 35 additions & 0 deletions scripts/add-root-ca.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/bash

#/ Add the root CA to the system trust store
# This script is used to add the root CA to the system trust store.
#
# Usage:
#
# 1. Run the script - ./add-root-ca.sh
# 2. Enter the password when prompted
# 3. The root CA will be added to the system trust store
#/

CERT_PATH="$HOME/.stacks/ssl/keys/rootCA.crt"
DEST_PATH="/usr/local/share/ca-certificates/rootCA.crt"

if [[ "$OSTYPE" == "linux-gnu"# ]]; then
if [ -f "$DEST_PATH" ]; then
echo "$DEST_PATH already exists."
read -p "Do you want to overwrite it? [y/N] " -n 1 -r
echo # move to a new line
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
echo "Operation cancelled."
exit 1
fi
fi
sudo cp "$CERT_PATH" "$DEST_PATH"
sudo update-ca-certificates
elif [[ "$OSTYPE" == "darwin"* ]]; then
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$CERT_PATH"
elif [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" ]]; then
powershell.exe -Command "Import-Certificate -FilePath '$CERT_PATH' -CertStoreLocation Cert:\LocalMachine\Root"
else
echo "OS not supported"
fi
7 changes: 7 additions & 0 deletions scripts/generate-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { generateAndSaveCertificates, generateRootCA } from '../src/keys'

// Generate a root key and certificate (self-signed)
generateRootCA()

// Generate a keypair and create an X.509v3 certificate for the domain
generateAndSaveCertificates()
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { config } from './config'
export * from './keys'
export * from './start'
153 changes: 153 additions & 0 deletions src/keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import fs from 'node:fs'
import path from 'node:path'
import os from 'node:os'
import forge from 'node-forge'
import { log, italic } from '@stacksjs/cli'

// Function to generate and save keys, certificates, and CA bundle
export async function generateAndSaveCertificates({
commonName = 'stacks.localhost',
countryName = 'US',
stateName = 'California',
localityName = 'Playa Vista',
organizationName = 'Stacks',
organizationalUnitName = 'Test',
validityYears = 1,
} = {}) {
// Generate a root key and certificate (self-signed)
const rootKeys = forge.pki.rsa.generateKeyPair(2048)
const rootCert = forge.pki.createCertificate()
rootCert.publicKey = rootKeys.publicKey
rootCert.serialNumber = '01'
rootCert.validity.notBefore = new Date()
rootCert.validity.notAfter = new Date()
rootCert.validity.notAfter.setFullYear(rootCert.validity.notBefore.getFullYear() + validityYears)
const rootAttrs = [{
name: 'countryName',
value: countryName,
}, {
name: 'stateOrProvinceName',
value: stateName,
}, {
name: 'localityName',
value: localityName,
}, {
name: 'organizationName',
value: organizationName,
}, {
name: 'organizationalUnitName',
value: organizationalUnitName,
}, {
name: 'commonName',
value: 'Example Root CA',
}]
rootCert.setSubject(rootAttrs)
rootCert.setIssuer(rootAttrs) // Self-signed, so issuer is the same
rootCert.sign(rootKeys.privateKey)

// Generate a keypair and create an X.509v3 certificate for the domain
const keys = forge.pki.rsa.generateKeyPair(2048)
const cert = forge.pki.createCertificate()
cert.publicKey = keys.publicKey
cert.serialNumber = '01'
cert.validity.notBefore = new Date()
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + validityYears)
const attrs = [{
name: 'countryName',
value: countryName,
}, {
name: 'stateOrProvinceName',
value: stateName,
}, {
name: 'localityName',
value: localityName,
}, {
name: 'organizationName',
value: organizationName,
}, {
name: 'organizationalUnitName',
value: organizationalUnitName,
}, {
name: 'commonName',
value: commonName,
}]
cert.setSubject(attrs)
cert.setIssuer(rootAttrs) // Issued by the root CA
cert.sign(rootKeys.privateKey) // Signed with the root CA's private key

// Convert certificates and key to PEM format
const pemCert = forge.pki.certificateToPem(cert)
const pemRootCert = forge.pki.certificateToPem(rootCert)
const pemKey = forge.pki.privateKeyToPem(keys.privateKey)

// Bundle root certificate (and intermediates if any) into a CA bundle
const caBundle = pemRootCert // + pemIntermediateCert; if you have intermediates

// Define the directory to save the keys and certificates
const saveDir = path.join(os.homedir(), '.stacks', 'ssl', 'keys')
if (!fs.existsSync(saveDir))
fs.mkdirSync(saveDir, { recursive: true })

// Write the PEM-formatted files to disk
fs.writeFileSync(path.join(saveDir, `${commonName}.crt`), pemCert)
fs.writeFileSync(path.join(saveDir, 'rootCA.crt'), pemRootCert)
fs.writeFileSync(path.join(saveDir, `${commonName}.ca-bundle`), caBundle)
fs.writeFileSync(path.join(saveDir, `${commonName}.key`), pemKey)

log.success(`Certificates and private key have been generated and saved to ${saveDir}`)
}

export async function generateRootCA({
countryName = 'US',
stateName = 'California',
localityName = 'Playa Vista',
organizationName = 'Stacks',
organizationalUnitName = 'Test',
} = {}) {
// Generate a root key and certificate (self-signed)
const rootKeys = forge.pki.rsa.generateKeyPair(2048)
const rootCert = forge.pki.createCertificate()
rootCert.publicKey = rootKeys.publicKey
rootCert.serialNumber = '01'
rootCert.validity.notBefore = new Date()
rootCert.validity.notAfter = new Date()
rootCert.validity.notAfter.setFullYear(rootCert.validity.notBefore.getFullYear() + 1)
const rootAttrs = [{
name: 'countryName',
value: countryName,
}, {
name: 'stateOrProvinceName',
value: stateName,
}, {
name: 'localityName',
value: localityName,
}, {
name: 'organizationName',
value: organizationName,
}, {
name: 'organizationalUnitName',
value: organizationalUnitName,
}, {
name: 'commonName',
value: 'Example Root CA',
}]
rootCert.setSubject(rootAttrs)
rootCert.setIssuer(rootAttrs) // Self-signed, so issuer is the same
rootCert.sign(rootKeys.privateKey)

// Convert certificates and key to PEM format
const pemRootCert = forge.pki.certificateToPem(rootCert)
const pemRootKey = forge.pki.privateKeyToPem(rootKeys.privateKey)

// Define the directory to save the keys and certificates
const saveDir = path.join(os.homedir(), '.stacks', 'ssl', 'keys')
if (!fs.existsSync(saveDir))
fs.mkdirSync(saveDir, { recursive: true })

// Write the PEM-formatted files to disk
fs.writeFileSync(path.join(saveDir, 'rootCA.crt'), pemRootCert)
fs.writeFileSync(path.join(saveDir, 'rootCA.key'), pemRootKey)

log.success(`Root CA has been generated and saved to ${italic(saveDir)}`)
}
11 changes: 6 additions & 5 deletions src/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import * as https from 'node:https'
import * as fs from 'node:fs'
import type { Buffer } from 'node:buffer'
import { path } from '@stacksjs/path'
import { bold, dim, green, log, runCommand } from '@stacksjs/cli'
import { bold, dim, green, log } from '@stacksjs/cli'
import { version } from '../package.json'
import { generateAndSaveCertificates } from './keys'

export interface Option {
from?: string // domain to proxy from, defaults to localhost:3000
Expand Down Expand Up @@ -117,8 +118,8 @@ export async function ensureCertificates(option: Option): Promise<{ key: Buffer,
const keysPath = path.resolve(sslBasePath, 'keys')
await fs.promises.mkdir(keysPath, { recursive: true })

const keyPath = option.keyPath ?? path.resolve(keysPath, `${option.to}-key.pem`)
const certPath = option.certPath ?? path.resolve(keysPath, `${option.to}.pem`)
const keyPath = option.keyPath ?? path.resolve(keysPath, `${option.to}.key`)
const certPath = option.certPath ?? path.resolve(keysPath, `${option.to}.crt`)

let key: Buffer | undefined
let cert: Buffer | undefined
Expand All @@ -129,8 +130,8 @@ export async function ensureCertificates(option: Option): Promise<{ key: Buffer,
}
catch (error) {
log.info('A valid SSL key & certificate was not found, creating a self-signed certificate...')
await runCommand('mkcert -install', { cwd: keysPath })
await runCommand(`mkcert ${option.to}`, { cwd: keysPath })
await generateAndSaveCertificates()

key = await fs.promises.readFile(keyPath)
cert = await fs.promises.readFile(certPath)
}
Expand Down

0 comments on commit 1290366

Please sign in to comment.