Skip to content

Commit 2048083

Browse files
committed
implement encrypted key logic
1 parent ff23066 commit 2048083

7 files changed

Lines changed: 53 additions & 4 deletions

File tree

.env.development

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ AUTH_URL="http://localhost:3000"
2424
# AUTH_EE_GOOGLE_CLIENT_SECRET=""
2525

2626
DATA_CACHE_DIR=${PWD}/.sourcebot # Path to the sourcebot cache dir (ex. ~/sourcebot/.sourcebot)
27+
SOURCEBOT_PUBLIC_KEY_PATH=${PWD}/public.pem
2728
# CONFIG_PATH=${PWD}/config.json # Path to the sourcebot config file (if one exists)
2829

2930
# Email

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,15 @@ ENV REDIS_DATA_DIR=$DATA_CACHE_DIR/redis
174174
ENV DATABASE_URL="postgresql://postgres@localhost:5432/sourcebot"
175175
ENV REDIS_URL="redis://localhost:6379"
176176
ENV SRC_TENANT_ENFORCEMENT_MODE=strict
177+
ENV SOURCEBOT_PUBLIC_KEY_PATH=/app/public.pem
177178

178179
# Valid values are: debug, info, warn, error
179180
ENV SOURCEBOT_LOG_LEVEL=info
180181

181182
# Sourcebot collects anonymous usage data using [PostHog](https://posthog.com/). Uncomment this line to disable.
182183
# ENV SOURCEBOT_TELEMETRY_DISABLED=1
183184

184-
COPY package.json yarn.lock* .yarnrc.yml ./
185+
COPY package.json yarn.lock* .yarnrc.yml public.pem ./
185186
COPY .yarn ./.yarn
186187

187188
# Configure zoekt

packages/crypto/src/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import crypto from 'crypto';
2+
import fs from 'fs';
23
import { SOURCEBOT_ENCRYPTION_KEY } from './environment';
34

45
const algorithm = 'aes-256-cbc';
@@ -63,3 +64,22 @@ export function decrypt(iv: string, encryptedText: string): string {
6364

6465
return decrypted;
6566
}
67+
68+
export function verifySignature(data: string, signature: string, publicKeyPath: string): boolean {
69+
try {
70+
if (!fs.existsSync(publicKeyPath)) {
71+
throw new Error(`Public key file not found at: ${publicKeyPath}`);
72+
}
73+
74+
const publicKey = fs.readFileSync(publicKeyPath, 'utf8');
75+
76+
const base64Signature = signature.replace(/-/g, '+').replace(/_/g, '/');
77+
const paddedSignature = base64Signature + '='.repeat((4 - base64Signature.length % 4) % 4);
78+
const signatureBuffer = Buffer.from(paddedSignature, 'base64');
79+
80+
return crypto.verify(null, Buffer.from(data, 'utf8'), publicKey, signatureBuffer);
81+
} catch (error) {
82+
console.error('Error verifying signature:', error);
83+
return false;
84+
}
85+
}

packages/web/src/app/[domain]/settings/license/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,13 @@ export default async function LicensePage({ params: { domain } }: LicensePagePro
109109
<div className="grid grid-cols-2 gap-4">
110110
<div className="text-sm text-muted-foreground">Expiry Date</div>
111111
<div className={`text-sm font-mono ${isExpired ? 'text-destructive' : ''}`}>
112-
{expiryDate.toLocaleDateString("en-US", {
112+
{expiryDate.toLocaleString("en-US", {
113113
hour: "2-digit",
114114
minute: "2-digit",
115115
month: "long",
116116
day: "numeric",
117-
year: "numeric"
117+
year: "numeric",
118+
timeZoneName: "short"
118119
})} {isExpired && '(Expired)'}
119120
</div>
120121
</div>

packages/web/src/env.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ export const env = createEnv({
5555

5656
DATA_CACHE_DIR: z.string(),
5757

58+
SOURCEBOT_PUBLIC_KEY_PATH: z.string().optional(),
59+
5860
// Email
5961
SMTP_CONNECTION_URL: z.string().url().optional(),
6062
EMAIL_FROM_ADDRESS: z.string().email().optional(),

packages/web/src/features/entitlements/server.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { base64Decode } from "@/lib/utils";
44
import { z } from "zod";
55
import { SOURCEBOT_SUPPORT_EMAIL } from "@/lib/constants";
66
import { createLogger } from "@sourcebot/logger";
7+
import { verifySignature } from "@sourcebot/crypto";
78

89
const logger = createLogger('entitlements');
910

@@ -15,6 +16,7 @@ const eeLicenseKeyPayloadSchema = z.object({
1516
seats: z.number(),
1617
// ISO 8601 date string
1718
expiryDate: z.string().datetime(),
19+
sig: z.string(),
1820
});
1921

2022
type LicenseKeyPayload = z.infer<typeof eeLicenseKeyPayloadSchema>;
@@ -23,7 +25,26 @@ const decodeLicenseKeyPayload = (payload: string): LicenseKeyPayload => {
2325
try {
2426
const decodedPayload = base64Decode(payload);
2527
const payloadJson = JSON.parse(decodedPayload);
26-
return eeLicenseKeyPayloadSchema.parse(payloadJson);
28+
const licenseData = eeLicenseKeyPayloadSchema.parse(payloadJson);
29+
30+
if (env.SOURCEBOT_PUBLIC_KEY_PATH) {
31+
const dataToVerify = JSON.stringify({
32+
expiryDate: licenseData.expiryDate,
33+
id: licenseData.id,
34+
seats: licenseData.seats
35+
});
36+
37+
const isSignatureValid = verifySignature(dataToVerify, licenseData.sig, env.SOURCEBOT_PUBLIC_KEY_PATH);
38+
if (!isSignatureValid) {
39+
logger.error('License key signature verification failed');
40+
process.exit(1);
41+
}
42+
} else {
43+
logger.error('No public key path provided, unable to verify license key signature');
44+
process.exit(1);
45+
}
46+
47+
return licenseData;
2748
} catch (error) {
2849
logger.error(`Failed to decode license key payload: ${error}`);
2950
process.exit(1);

public.pem

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MCowBQYDK2VwAyEAJ8fwB3wMcuNPput/El4bK2F8vt/algcGxC6MiJqrT+c=
3+
-----END PUBLIC KEY-----

0 commit comments

Comments
 (0)