diff --git a/generate-cert.js b/generate-cert.js index 1022fe2..45a2fc9 100644 --- a/generate-cert.js +++ b/generate-cert.js @@ -1,5 +1,6 @@ import { execSync } from 'node:child_process'; import fs from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -12,10 +13,50 @@ const certPath = path.join(certsDir, 'cert.pem'); fs.mkdirSync(certsDir, { recursive: true }); -// ECDSA P-256 keeps pairing QR codes much smaller than RSA-2048 -const subject = '/CN=localhost/O=JTL POS Sync/C=DE'; -const san = 'subjectAltName=DNS:localhost,IP:127.0.0.1,IP:0.0.0.0'; +function isIp(value) { + return /^(?:\d{1,3}\.){3}\d{1,3}$/.test(value) || value.includes(':'); +} +function localIpv4s() { + const ips = []; + for (const entries of Object.values(os.networkInterfaces())) { + for (const entry of entries || []) { + if (entry.family !== 'IPv4' || entry.internal) continue; + // Skip link-local / docker / libvirt bridge noise by default — keep LAN + extras via args + if (entry.address.startsWith('169.254.')) continue; + if (entry.address.startsWith('172.17.')) continue; + if (entry.address.startsWith('192.168.122.')) continue; + ips.push(entry.address); + } + } + return ips; +} + +const dnsNames = new Set(['localhost']); +const ipAddrs = new Set(['127.0.0.1', '0.0.0.0']); + +for (const ip of localIpv4s()) { + ipAddrs.add(ip); +} + +const extras = [ + ...(process.env.CERT_SAN || '').split(/[,\s]+/).filter(Boolean), + ...process.argv.slice(2), +]; +for (const value of extras) { + if (isIp(value)) ipAddrs.add(value); + else dnsNames.add(value); +} + +const sanParts = [ + ...[...dnsNames].map((name) => `DNS:${name}`), + ...[...ipAddrs].map((ip) => `IP:${ip}`), +]; +const san = `subjectAltName=${sanParts.join(',')}`; +const cn = [...dnsNames][0] || 'localhost'; +const subject = `/CN=${cn}/O=JTL POS Sync/C=DE`; + +// ECDSA P-256 keeps pairing QR codes much smaller than RSA-2048 execSync( `openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout "${keyPath}" -out "${certPath}" -days 3650 -subj "${subject}" -addext "${san}"`, { stdio: 'inherit' } @@ -34,5 +75,6 @@ const serial = execSync(`openssl x509 -in "${certPath}" -noout -serial`, { logger.success(`Wrote ${keyPath}`); logger.success(`Wrote ${certPath}`); +logger.info(`SAN: ${sanParts.join(', ')}`); logger.info(`Fingerprint: ${sha1.replace(/:/g, '')}`); logger.info(`Serial: ${serial}`); diff --git a/jtlsrv-cpp/README.md b/jtlsrv-cpp/README.md index 096c39d..5f2339f 100644 --- a/jtlsrv-cpp/README.md +++ b/jtlsrv-cpp/README.md @@ -79,17 +79,25 @@ The server starts without MSSQL if `MSSQL_USER` is unset or the connection fails ## TLS certificates -Place a certificate and key at `certs/cert.pem` and `certs/key.pem` (relative to the working directory when you run the binary). +From the repo root (preferred — picks up LAN IPs automatically): + +```bash +npm run cert +# optional extras: +npm run cert -- 192.168.188.22 sync.quixpos.com +``` + +Or manually: ```bash mkdir -p certs -openssl req -x509 -newkey rsa:2048 -nodes \ +openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \ -keyout certs/key.pem -out certs/cert.pem -days 3650 \ -subj '/CN=localhost/O=JTL POS Sync/C=DE' \ - -addext 'subjectAltName=DNS:localhost,IP:127.0.0.1,IP:0.0.0.0' + -addext 'subjectAltName=DNS:localhost,DNS:sync.quixpos.com,IP:127.0.0.1,IP:0.0.0.0,IP:192.168.188.22' ``` -On startup the server prints the pairing code and whether MSSQL connected. +Place `certs/cert.pem` and `certs/key.pem` relative to the working directory when you run the binary. The browser hostname check requires the address you open (`192.168.x.x` or a DNS name) to appear in the certificate SAN — trusting a CA alone is not enough. ## API endpoints