From 10c4269da042dca48802322cdb41ec3302094114 Mon Sep 17 00:00:00 2001 From: seb Date: Mon, 27 Jul 2026 02:16:26 +0200 Subject: [PATCH] u --- .env.example | 5 ---- generate-cert.js | 5 ++-- jtlsrv-cpp/src/main.cpp | 63 +++++++++++++++++++++++++++++++++++++---- server.js | 14 +++++++-- src/cert-meta.js | 12 ++++++++ src/jtl-server.js | 15 ++-------- 6 files changed, 86 insertions(+), 28 deletions(-) create mode 100644 src/cert-meta.js diff --git a/.env.example b/.env.example index 661b7f3..9d937a5 100644 --- a/.env.example +++ b/.env.example @@ -8,11 +8,6 @@ PAIRING_CODE=307018 LOG_FILE=logs/requests.log ORDER_LOG_FILE=logs/orders.log -# TLS certificate metadata returned during pairing -CERTIFICATE_FINGERPRINT=BC2114CF407A42724BEEF417960F76DCBF9DE879 -CERTIFICATE_SERIAL_NUMBER=00BFC8BEACDB981B165210EF111CB9D3 -SERVER_FINGERPRINT=39-6D-BD-DE-F3-5C-5A-EA-C2-19-CF-EB-A7-A9-58-2F-20-3F-20-F7-3D-E6-CA-8E-AE-FD-28-30-37-A6-45-AE - # Mandant MANDANT_ID=1 MANDANT_NAME=eB-Standard diff --git a/generate-cert.js b/generate-cert.js index 5d2bfed..1022fe2 100644 --- a/generate-cert.js +++ b/generate-cert.js @@ -34,6 +34,5 @@ const serial = execSync(`openssl x509 -in "${certPath}" -noout -serial`, { logger.success(`Wrote ${keyPath}`); logger.success(`Wrote ${certPath}`); -logger.info(`CERTIFICATE_FINGERPRINT=${sha1.replace(/:/g, '')}`); -logger.info(`CERTIFICATE_SERIAL_NUMBER=${serial}`); -logger.info(`SERVER_FINGERPRINT=${sha1.replace(/:/g, '-')}`); +logger.info(`Fingerprint: ${sha1.replace(/:/g, '')}`); +logger.info(`Serial: ${serial}`); diff --git a/jtlsrv-cpp/src/main.cpp b/jtlsrv-cpp/src/main.cpp index 17984d6..cb6b4ff 100644 --- a/jtlsrv-cpp/src/main.cpp +++ b/jtlsrv-cpp/src/main.cpp @@ -3,8 +3,13 @@ #include #include #include +#include #include +#include +#include +#include +#include #include "config.hpp" #include "log.hpp" @@ -29,12 +34,60 @@ static Router router; static PairingStore pairing_store; static RequestLog request_log; -static json build_config() { +static bool read_cert_metadata(const char* cert_path, + std::string& fingerprint, + std::string& serial, + std::string& server_fingerprint) { + FILE* fp = std::fopen(cert_path, "r"); + if (!fp) return false; + X509* cert = PEM_read_X509(fp, nullptr, nullptr, nullptr); + std::fclose(fp); + if (!cert) return false; + + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int md_len = 0; + if (X509_digest(cert, EVP_sha1(), md, &md_len) != 1) { + X509_free(cert); + return false; + } + + static const char* hex = "0123456789ABCDEF"; + fingerprint.clear(); + fingerprint.reserve(md_len * 2); + server_fingerprint.clear(); + server_fingerprint.reserve(md_len * 3 - 1); + for (unsigned int i = 0; i < md_len; ++i) { + fingerprint.push_back(hex[(md[i] >> 4) & 0xF]); + fingerprint.push_back(hex[md[i] & 0xF]); + if (i) server_fingerprint.push_back('-'); + server_fingerprint.push_back(hex[(md[i] >> 4) & 0xF]); + server_fingerprint.push_back(hex[md[i] & 0xF]); + } + + const ASN1_INTEGER* asn1_serial = X509_get0_serialNumber(cert); + BIGNUM* bn = ASN1_INTEGER_to_BN(asn1_serial, nullptr); + char* hex_serial = bn ? BN_bn2hex(bn) : nullptr; + bool ok = hex_serial != nullptr; + if (ok) { + serial = hex_serial; + for (char& c : serial) c = static_cast(std::toupper(static_cast(c))); + OPENSSL_free(hex_serial); + } + BN_free(bn); + X509_free(cert); + return ok; +} + +static json build_config(const char* cert_path) { + std::string fingerprint, serial, server_fingerprint; + if (!read_cert_metadata(cert_path, fingerprint, serial, server_fingerprint)) { + logc::error("failed to read certificate metadata from %s", cert_path); + } return { {"authToken", config::get("AUTH_TOKEN", "df40ad2067954646abb0499548a52241")}, - {"certificateFingerprint", config::get("CERTIFICATE_FINGERPRINT", "BC2114CF407A42724BEEF417960F76DCBF9DE879")}, - {"certificateSerialNumber", config::get("CERTIFICATE_SERIAL_NUMBER", "00BFC8BEACDB981B165210EF111CB9D3")}, - {"serverFingerprint", config::get("SERVER_FINGERPRINT", "39-6D-BD-DE-F3-5C-5A-EA-C2-19-CF-EB-A7-A9-58-2F-20-3F-20-F7-3D-E6-CA-8E-AE-FD-28-30-37-A6-45-AE")}, + {"certificateFingerprint", fingerprint}, + {"certificateSerialNumber", serial}, + {"serverFingerprint", server_fingerprint}, {"mandantId", config::get("MANDANT_ID", "1")}, {"mandantName", config::get("MANDANT_NAME", "eB-Standard")}, {"mandantDatabase", config::get("MANDANT_DATABASE", "eazybusiness")}, @@ -125,7 +178,7 @@ int main(int /*argc*/, char* argv[]) { loop = uv_default_loop(); - server_config = build_config(); + server_config = build_config(cert_path.c_str()); // Register routes router.add_route("GET", "/v1/client", handle_client); diff --git a/server.js b/server.js index 9eff0db..de6e70a 100644 --- a/server.js +++ b/server.js @@ -7,6 +7,7 @@ import 'dotenv/config'; import { connectDb, closeDb } from './src/db.js'; import { isDemoMode } from './src/demo/mode.js'; import { loadDemoCatalog } from './src/demo/store.js'; +import { readCertMetadata } from './src/cert-meta.js'; import { createJtlPosServer } from './src/jtl-server.js'; import { createPairingStore } from './src/pairing.js'; import { closeOrderLog } from './src/order-log.js'; @@ -59,11 +60,18 @@ function formatBody(buffer) { return `[binary ${buffer.length} bytes]`; } +const certPem = fs.readFileSync(certPath); +const keyPem = fs.readFileSync(keyPath); +const certMeta = readCertMetadata(certPem); + const pairingStore = createPairingStore(); pairingStore.setPairingCode(PAIRING_CODE, 'JTL-POS'); pairingStore.registerDevice(AUTH_TOKEN, 'JTL-POS'); -const jtlHandler = createJtlPosServer(pairingStore, { authToken: AUTH_TOKEN }); +const jtlHandler = createJtlPosServer(pairingStore, { + authToken: AUTH_TOKEN, + ...certMeta, +}); const loggedJtlHandler = async (req, res) => { const started = Date.now(); @@ -108,8 +116,8 @@ const loggedJtlHandler = async (req, res) => { const httpsServer = https.createServer( { - key: fs.readFileSync(keyPath), - cert: fs.readFileSync(certPath), + key: keyPem, + cert: certPem, }, loggedJtlHandler ); diff --git a/src/cert-meta.js b/src/cert-meta.js new file mode 100644 index 0000000..3daea11 --- /dev/null +++ b/src/cert-meta.js @@ -0,0 +1,12 @@ +import { X509Certificate } from 'node:crypto'; + +/** Derive pairing metadata from a PEM-encoded TLS certificate. */ +export function readCertMetadata(certPem) { + const x509 = new X509Certificate(certPem); + const sha1 = x509.fingerprint; // colon-separated uppercase hex + return { + certificateFingerprint: sha1.replace(/:/g, ''), + certificateSerialNumber: x509.serialNumber, + serverFingerprint: sha1.replace(/:/g, '-'), + }; +} diff --git a/src/jtl-server.js b/src/jtl-server.js index 7f3e44b..3116bae 100644 --- a/src/jtl-server.js +++ b/src/jtl-server.js @@ -4,18 +4,9 @@ import { normalizePath, readBody, sendJson } from './http.js'; function buildConfig(config = {}) { return { authToken: config.authToken || process.env.AUTH_TOKEN || 'df40ad2067954646abb0499548a52241', - certificateFingerprint: - config.certificateFingerprint || - process.env.CERTIFICATE_FINGERPRINT || - 'BC2114CF407A42724BEEF417960F76DCBF9DE879', - certificateSerialNumber: - config.certificateSerialNumber || - process.env.CERTIFICATE_SERIAL_NUMBER || - '00BFC8BEACDB981B165210EF111CB9D3', - serverFingerprint: - config.serverFingerprint || - process.env.SERVER_FINGERPRINT || - '39-6D-BD-DE-F3-5C-5A-EA-C2-19-CF-EB-A7-A9-58-2F-20-3F-20-F7-3D-E6-CA-8E-AE-FD-28-30-37-A6-45-AE', + certificateFingerprint: config.certificateFingerprint || '', + certificateSerialNumber: config.certificateSerialNumber || '', + serverFingerprint: config.serverFingerprint || '', mandantId: config.mandantId || process.env.MANDANT_ID || '1', mandantName: config.mandantName || process.env.MANDANT_NAME || 'eB-Standard', mandantDatabase: config.mandantDatabase || process.env.MANDANT_DATABASE || 'eazybusiness',