42043f3946
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled
Critical (5): - Reject empty JWT secret when authEnabled (server.nim) - Fix 2PC marking uncontacted participants as prepared/committed (disttxn.nim) - Fix Raft commit index calculation for even-sized clusters (raft.nim) - Fix REP/DISTTXN protocol auth bypass (server.nim) - Fix HTTP backup/restore path traversal (httpserver.nim) High (11): - Fix WAL write race with flush (lsm.nim) - Fix MVCC savepoint/rollback deep-copy writeSet (mvcc.nim) - Fix table mutation during deadlock iteration (mvcc.nim) - Fix LIMIT 0 returning all rows (executor.nim) - Fix COUNT(col) counting NULL values — 3 locations (executor.nim) - Fix EXISTS subquery lowering missing subqueryPlan (executor.nim) - Fix Raft appendEntries/applyCommitted array vs logical index (raft.nim) - Fix timing attacks on constantTimeCompare and SCRAM (auth.nim, scram.nim) - Fix B-tree leaf merge phantom separator key (btree.nim) - Fix SSL verifyPeer not applied to newContext (ssl.nim) - Fix sharding connectWithTimeout missing SO_ERROR check (sharding.nim) - Fix sync replication returning success on partial ack (replication.nim) - Fix WebSocket JWT expiration not validated (websocket.nim) Medium (13): - Fix writeSSTable partial file → tmp + atomic rename (lsm.nim) - Fix multi-CTE table loss (executor.nim) - Fix nl_to_sql DML restricted to superuser (executor.nim) - Fix unbounded plan cache — max 10000 (adaptive.nim) - Fix migration lock crash persistence — timestamp + stale detection (executor.nim) - Fix admin panel auth (httpserver.nim) - Fix MVCC unbounded txn tracking — prune in compactVersions (mvcc.nim) - Fix connection pool maxLifetime check (pool.nim) - Fix JWT JSON parser backslash escapes (auth.nim) - Fix substr(s, start) returning single char (udf.nim) - Fix loadSSTable minimum file-size check (lsm.nim) - Fix compaction mmap leak (compaction.nim) - Fix JSON injection in hybrid_search_filtered (executor.nim) Low (4): - Raft loadState logs error instead of silent discard - Replication healthCheck double-close fixed - Lexer readIdent double column counting fixed - WebSocket frame 32-bit overflow guard All 448 tests passing, 0 failures. Bump version to 1.1.7.
126 lines
3.9 KiB
Nim
126 lines
3.9 KiB
Nim
## TLS/SSL Wrapper — encrypted sockets using OpenSSL (Nim stdlib)
|
|
when not defined(ssl):
|
|
{.error: "BaraDB requires SSL support. Compile with -d:ssl".}
|
|
|
|
import std/os
|
|
import std/osproc
|
|
import std/strutils
|
|
import std/net
|
|
import std/asyncnet
|
|
|
|
type
|
|
TLSConfig* = object
|
|
certFile*: string
|
|
keyFile*: string
|
|
caFile*: string
|
|
verifyPeer*: bool
|
|
|
|
TLSContext* = ref object
|
|
sslCtx*: SslContext
|
|
config*: TLSConfig
|
|
|
|
proc newTLSConfig*(certFile: string, keyFile: string, caFile: string = "",
|
|
verifyPeer: bool = false): TLSConfig =
|
|
TLSConfig(
|
|
certFile: certFile, keyFile: keyFile,
|
|
caFile: caFile, verifyPeer: verifyPeer,
|
|
)
|
|
|
|
proc newTLSContext*(config: TLSConfig): TLSContext =
|
|
result = TLSContext(config: config)
|
|
if fileExists(config.certFile) and fileExists(config.keyFile):
|
|
result.sslCtx = newContext(
|
|
certFile = config.certFile,
|
|
keyFile = config.keyFile,
|
|
verifyMode = if config.verifyPeer: CVerifyPeer else: CVerifyNone,
|
|
)
|
|
else:
|
|
raise newException(IOError, "TLS certificate or key file not found: " &
|
|
config.certFile & ", " & config.keyFile)
|
|
|
|
proc wrapClient*(tls: TLSContext, socket: AsyncSocket) {.inline.} =
|
|
if tls.sslCtx != nil:
|
|
tls.sslCtx.wrapSocket(socket)
|
|
|
|
proc wrapServer*(tls: TLSContext, socket: AsyncSocket) {.inline.} =
|
|
if tls.sslCtx != nil:
|
|
tls.sslCtx.wrapConnectedSocket(socket, handshakeAsServer)
|
|
|
|
proc close*(tls: TLSContext) =
|
|
if tls.sslCtx != nil:
|
|
tls.sslCtx.destroyContext()
|
|
|
|
# TLS Certificate management
|
|
type
|
|
CertInfo* = object
|
|
subject*: string
|
|
issuer*: string
|
|
notBefore*: string
|
|
notAfter*: string
|
|
fingerprint*: string
|
|
keySize*: int
|
|
isSelfSigned*: bool
|
|
|
|
proc parseCertInfo*(certPath: string): CertInfo =
|
|
result = CertInfo()
|
|
if not fileExists(certPath):
|
|
return
|
|
let content = readFile(certPath)
|
|
result.subject = "Unknown"
|
|
result.issuer = "Unknown"
|
|
result.fingerprint = ""
|
|
for line in content.splitLines():
|
|
if line.startsWith("Subject:"):
|
|
result.subject = line[8..^1].strip()
|
|
elif line.startsWith("Issuer:"):
|
|
result.issuer = line[7..^1].strip()
|
|
result.isSelfSigned = result.subject == result.issuer
|
|
|
|
proc generateSelfSignedCert*(outputDir: string, commonName: string = "localhost"): (string, string) =
|
|
let certPath = outputDir / (commonName & ".crt")
|
|
let keyPath = outputDir / (commonName & ".key")
|
|
createDir(outputDir)
|
|
let cmd = "openssl req -x509 -newkey rsa:2048 -keyout " & quoteShell(keyPath) &
|
|
" -out " & quoteShell(certPath) & " -days 365 -nodes -subj " & quoteShell("/CN=" & commonName) & " 2>/dev/null"
|
|
if execShellCmd(cmd) == 0 and fileExists(certPath):
|
|
return (certPath, keyPath)
|
|
return ("", "")
|
|
|
|
proc certificateFingerprint*(certPath: string): string =
|
|
if not fileExists(certPath):
|
|
return ""
|
|
let cmd = "openssl x509 -in " & quoteShell(certPath) & " -fingerprint -noout 2>/dev/null"
|
|
let (output, _) = execCmdEx(cmd)
|
|
for line in output.splitLines():
|
|
if "Fingerprint=" in line:
|
|
let parts = line.split("Fingerprint=")
|
|
if parts.len > 1:
|
|
return parts[^1].strip()
|
|
return ""
|
|
|
|
proc isExpired*(certPath: string): bool =
|
|
if not fileExists(certPath):
|
|
return true
|
|
let cmd = "openssl x509 -in " & quoteShell(certPath) & " -checkend 0 2>/dev/null"
|
|
return execShellCmd(cmd) != 0
|
|
|
|
proc daysUntilExpiry*(certPath: string): int =
|
|
if not fileExists(certPath):
|
|
return -1
|
|
# Check if expires within 1 day
|
|
let cmd1 = "openssl x509 -in " & quoteShell(certPath) & " -checkend 86400 2>/dev/null"
|
|
if execShellCmd(cmd1) == 0:
|
|
# Check if expires within 30 days
|
|
let cmd30 = "openssl x509 -in " & quoteShell(certPath) & " -checkend 2592000 2>/dev/null"
|
|
if execShellCmd(cmd30) == 0:
|
|
return 365
|
|
return 30
|
|
return 1
|
|
|
|
proc validateCert*(certPath: string): seq[string] =
|
|
result = @[]
|
|
if not fileExists(certPath):
|
|
result.add("Certificate file not found: " & certPath)
|
|
if isExpired(certPath):
|
|
result.add("Certificate has expired")
|