Skip to content

Commit d842627

Browse files
committed
update website
1 parent b0e35b7 commit d842627

2 files changed

Lines changed: 68 additions & 34 deletions

File tree

website/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
},
1111
"dependencies": {
1212
"@hookform/resolvers": "^5.2.2",
13+
"@peculiar/x509": "^1.12.3",
1314
"@radix-ui/react-label": "^2.1.8",
1415
"@radix-ui/react-select": "^2.2.6",
1516
"@radix-ui/react-slot": "^1.2.4",
@@ -19,7 +20,6 @@
1920
"lucide-react": "^0.554.0",
2021
"next": "16.0.3",
2122
"next-themes": "^0.4.6",
22-
"node-forge": "^1.3.1",
2323
"react": "19.2.0",
2424
"react-dom": "19.2.0",
2525
"react-hook-form": "^7.66.1",
@@ -30,7 +30,6 @@
3030
"devDependencies": {
3131
"@tailwindcss/postcss": "^4",
3232
"@types/node": "^20",
33-
"@types/node-forge": "^1.3.14",
3433
"@types/react": "^19",
3534
"@types/react-dom": "^19",
3635
"babel-plugin-react-compiler": "1.0.0",

website/src/components/keyring-app.tsx

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useState, useEffect } from "react";
4-
import * as forge from "node-forge";
4+
import * as x509 from "@peculiar/x509";
55
import { useForm } from "react-hook-form";
66
import { zodResolver } from "@hookform/resolvers/zod";
77
import * as z from "zod";
@@ -53,16 +53,17 @@ const downloadFile = (content: string, filename: string) => {
5353
};
5454

5555
// Calculate certificate fingerprint (SHA-256)
56-
const getCertificateFingerprint = (cert: forge.pki.Certificate): string => {
57-
const der = forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes();
58-
const md = forge.md.sha256.create();
59-
md.update(der);
60-
const digest = md.digest().toHex();
61-
return digest.toUpperCase().match(/.{2}/g)?.join(':') || '';
56+
const getCertificateFingerprint = async (cert: x509.X509Certificate): Promise<string> => {
57+
const thumbprint = await cert.getThumbprint(crypto);
58+
const hex = Array.from(new Uint8Array(thumbprint))
59+
.map(b => b.toString(16).padStart(2, '0'))
60+
.join('')
61+
.toUpperCase();
62+
return hex.match(/.{2}/g)?.join(':') || '';
6263
};
6364

6465
// Get certificate serial number
65-
const getCertificateSerialNumber = (cert: forge.pki.Certificate): string => {
66+
const getCertificateSerialNumber = (cert: x509.X509Certificate): string => {
6667
return cert.serialNumber;
6768
};
6869

@@ -77,47 +78,81 @@ const parseCertFile = async (file: File): Promise<{ serialNumber: string, finger
7778
// Extract only the FIRST certificate from the chain
7879
const certMatch = text.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/);
7980
if (!certMatch) {
80-
throw new Error("No valid certificate found");
81+
console.error("No certificate block found in file");
82+
toast.error("No certificate found in file");
83+
return null;
8184
}
8285

8386
const firstCertPem = certMatch[0];
84-
const cert = forge.pki.certificateFromPem(firstCertPem);
8587

86-
// Validate this is a developer certificate (not a CA certificate)
87-
const basicConstraintsExt = cert.getExtension('basicConstraints');
88-
if (basicConstraintsExt && (basicConstraintsExt as any).cA === true) {
89-
throw new Error("This is a CA certificate, not a developer certificate. Please upload your developer certificate.");
88+
let cert: x509.X509Certificate;
89+
try {
90+
cert = new x509.X509Certificate(firstCertPem);
91+
} catch (parseError) {
92+
console.error("Failed to parse certificate:", parseError);
93+
toast.error("Invalid certificate format");
94+
return null;
95+
}
96+
97+
// Check if this is a CA certificate (warning only, not blocking)
98+
try {
99+
const basicConstraintsExt = cert.getExtension(x509.id_ce_basicConstraints);
100+
if (basicConstraintsExt) {
101+
const bc = new x509.BasicConstraintsExtension(basicConstraintsExt.value);
102+
if (bc.ca === true) {
103+
console.warn("Warning: This appears to be a CA certificate");
104+
toast.error("This is a CA certificate. Please upload your developer certificate (the first one in the chain).");
105+
return null;
106+
}
107+
}
108+
} catch (extError) {
109+
// Extension check failed, continue anyway
110+
console.warn("Could not check basicConstraints extension:", extError);
90111
}
91112

92113
// Extract fields
93-
const cn = cert.subject.getField('CN')?.value as string;
94-
const org = cert.subject.getField('O')?.value as string;
114+
const cn = cert.subject.split(',').find(s => s.trim().startsWith('CN='))?.split('=')[1]?.trim();
115+
const org = cert.subject.split(',').find(s => s.trim().startsWith('O='))?.split('=')[1]?.trim();
95116

96-
// Validate organization (optional check, can be removed if too strict)
117+
// Log organization info (warning only)
97118
if (org && org !== 'KernelSU Module Developers') {
98-
console.warn(`Warning: Certificate organization is "${org}", expected "KernelSU Module Developers"`);
119+
console.warn(`Certificate organization: "${org}" (expected "KernelSU Module Developers")`);
99120
}
100121

122+
// Get serial number and fingerprint
123+
const serialNumber = getCertificateSerialNumber(cert);
124+
const fingerprint = await getCertificateFingerprint(cert);
125+
126+
console.log("Certificate parsed successfully:");
127+
console.log("- Serial Number:", serialNumber);
128+
console.log("- CN:", cn);
129+
console.log("- Organization:", org);
130+
101131
return {
102-
serialNumber: getCertificateSerialNumber(cert),
103-
fingerprint: getCertificateFingerprint(cert),
132+
serialNumber,
133+
fingerprint,
104134
text: firstCertPem,
105135
cn
106136
};
107137
}
108138

109139
// Try to parse as CSR
110140
if (text.includes('BEGIN CERTIFICATE REQUEST')) {
141+
console.warn("File contains CSR, not certificate");
142+
toast.error("This is a certificate request (CSR), not a certificate. Please upload your issued certificate.");
111143
return {
112144
serialNumber: '',
113145
fingerprint: '',
114146
text: text
115147
};
116148
}
117149

118-
throw new Error("Invalid file format");
150+
console.error("File does not contain certificate or CSR");
151+
toast.error("Invalid file: No certificate found. Please upload a .pem or .crt file containing a certificate.");
152+
return null;
119153
} catch (e) {
120-
console.error(e);
154+
console.error("Error parsing certificate file:", e);
155+
toast.error(`Error reading file: ${(e as Error).message}`);
121156
return null;
122157
}
123158
};
@@ -491,20 +526,20 @@ function QueryForm({ t }: { t: typeof locales.en }) {
491526

492527
try {
493528
const text = await file.text();
494-
const cert = forge.pki.certificateFromPem(text);
529+
const cert = new x509.X509Certificate(text);
495530

496-
const cn = cert.subject.getField('CN')?.value as string || 'Unknown';
497-
const issuerCN = cert.issuer.getField('CN')?.value as string || 'Unknown';
531+
const cn = cert.subject.split(',').find(s => s.trim().startsWith('CN='))?.split('=')[1]?.trim() || 'Unknown';
532+
const issuerCN = cert.issuer.split(',').find(s => s.trim().startsWith('CN='))?.split('=')[1]?.trim() || 'Unknown';
498533
const now = new Date();
499534

500535
setResult({
501536
cn,
502-
fingerprint: getCertificateFingerprint(cert),
537+
fingerprint: await getCertificateFingerprint(cert),
503538
serialNumber: cert.serialNumber,
504539
issuer: issuerCN,
505-
validFrom: cert.validity.notBefore.toLocaleDateString(),
506-
validTo: cert.validity.notAfter.toLocaleDateString(),
507-
isValid: now >= cert.validity.notBefore && now <= cert.validity.notAfter
540+
validFrom: cert.notBefore.toLocaleDateString(),
541+
validTo: cert.notAfter.toLocaleDateString(),
542+
isValid: now >= cert.notBefore && now <= cert.notAfter
508543
});
509544
toast.success(t.query.found);
510545
} catch (e) {
@@ -588,10 +623,10 @@ function RevokeForm({ t }: { t: typeof locales.en }) {
588623
const result = await parseCertFile(file);
589624
if (result && result.serialNumber) {
590625
form.setValue("serial_number", result.serialNumber);
591-
toast.success(t.common.import_success);
592-
} else {
593-
toast.error(t.common.import_error);
626+
toast.success(`✅ ${t.common.import_success}\nSerial: ${result.serialNumber.substring(0, 16)}...`);
627+
console.log("Certificate imported:", result.cn || "Unknown CN");
594628
}
629+
// Error messages are already shown by parseCertFile
595630
e.target.value = "";
596631
};
597632

0 commit comments

Comments
 (0)