@@ -7,7 +7,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
77import * as z from "zod" ;
88import { toast } from "sonner" ;
99import {
10- Copy , Loader2 , ShieldCheck , AlertTriangle , Search , Key ,
10+ Copy , Loader2 , AlertTriangle , Key ,
1111 Github , Globe , Download , Upload , FileKey
1212} from "lucide-react" ;
1313
@@ -194,10 +194,9 @@ export function KeyringApp() {
194194 { mounted ? (
195195 < Tabs defaultValue = "generate" className = "w-full" >
196196 < div className = "border-b px-6 py-2 bg-slate-50/50 dark:bg-slate-900/50" >
197- < TabsList className = "grid w-full grid-cols-4 bg-slate-200/50 dark:bg-slate-800/50" >
197+ < TabsList className = "grid w-full grid-cols-3 bg-slate-200/50 dark:bg-slate-800/50" >
198198 < TabsTrigger value = "generate" > { t . tabs . generate } </ TabsTrigger >
199199 < TabsTrigger value = "submit" > { t . tabs . submit } </ TabsTrigger >
200- < TabsTrigger value = "query" > { t . tabs . query } </ TabsTrigger >
201200 < TabsTrigger value = "revoke" > { t . tabs . revoke } </ TabsTrigger >
202201 </ TabsList >
203202 </ div >
@@ -209,9 +208,6 @@ export function KeyringApp() {
209208 < TabsContent value = "submit" forceMount className = "data-[state=inactive]:hidden" >
210209 < SubmitForm t = { t } initialPublicKey = { generatedPublicKey } />
211210 </ TabsContent >
212- < TabsContent value = "query" forceMount className = "data-[state=inactive]:hidden" >
213- < QueryForm t = { t } />
214- </ TabsContent >
215211 < TabsContent value = "revoke" forceMount className = "data-[state=inactive]:hidden" >
216212 < RevokeForm t = { t } />
217213 </ TabsContent >
@@ -468,152 +464,6 @@ function SubmitForm({ t, initialPublicKey }: { t: typeof locales.en; initialPubl
468464 ) ;
469465}
470466
471- interface QueryResult {
472- cn : string ;
473- fingerprint : string ;
474- serialNumber : string ;
475- issuer : string ;
476- validFrom : string ;
477- validTo : string ;
478- isValid : boolean ;
479- }
480-
481- function QueryForm ( { t } : { t : typeof locales . en } ) {
482- const [ fp , setFp ] = useState ( "" ) ;
483- const [ result , setResult ] = useState < QueryResult | null > ( null ) ;
484- const [ loading , setLoading ] = useState ( false ) ;
485-
486- const handleFileImport = async ( e : React . ChangeEvent < HTMLInputElement > ) => {
487- const file = e . target . files ?. [ 0 ] ;
488- if ( ! file ) return ;
489-
490- const result = await parseCertFile ( file ) ;
491- if ( result && result . fingerprint ) {
492- setFp ( result . fingerprint ) ;
493- toast . success ( t . common . import_success ) ;
494- } else {
495- toast . error ( t . common . import_error ) ;
496- }
497- e . target . value = "" ;
498- } ;
499-
500- const handleQuery = async ( ) => {
501- if ( ! fp ) return toast . error ( "Please enter fingerprint" ) ;
502- setLoading ( true ) ;
503- setResult ( null ) ;
504- try {
505- // Fetch certificate bundle from repository
506- const res = await fetch ( "https://raw.githubusercontent.com/KernelSU-Modules-Repo/developers/main/keyring/developers/" ) ;
507- if ( ! res . ok ) throw new Error ( "Failed to fetch certificate list" ) ;
508-
509- // For now, we'll provide a direct certificate lookup interface
510- // The actual implementation would iterate through available certificates
511- const cleanFp = fp . replace ( / [: \s] / g, "" ) . toUpperCase ( ) ;
512-
513- // Placeholder: In production, this would fetch and search actual certificates
514- toast . error ( t . query . not_found + " (Certificate directory not yet configured)" ) ;
515-
516- } catch ( e ) {
517- toast . error ( e instanceof Error ? e . message : String ( e ) ) ;
518- } finally {
519- setLoading ( false ) ;
520- }
521- } ;
522-
523- // Direct certificate verification from file
524- const handleVerifyCert = async ( e : React . ChangeEvent < HTMLInputElement > ) => {
525- const file = e . target . files ?. [ 0 ] ;
526- if ( ! file ) return ;
527-
528- try {
529- const text = await file . text ( ) ;
530- const cert = new x509 . X509Certificate ( text ) ;
531-
532- const cn = cert . subject . split ( ',' ) . find ( s => s . trim ( ) . startsWith ( 'CN=' ) ) ?. split ( '=' ) [ 1 ] ?. trim ( ) || 'Unknown' ;
533- const issuerCN = cert . issuer . split ( ',' ) . find ( s => s . trim ( ) . startsWith ( 'CN=' ) ) ?. split ( '=' ) [ 1 ] ?. trim ( ) || 'Unknown' ;
534- const now = new Date ( ) ;
535-
536- setResult ( {
537- cn,
538- fingerprint : await getCertificateFingerprint ( cert ) ,
539- serialNumber : cert . serialNumber ,
540- issuer : issuerCN ,
541- validFrom : cert . notBefore . toLocaleDateString ( ) ,
542- validTo : cert . notAfter . toLocaleDateString ( ) ,
543- isValid : now >= cert . notBefore && now <= cert . notAfter
544- } ) ;
545- toast . success ( t . query . found ) ;
546- } catch ( e ) {
547- toast . error ( e instanceof Error ? e . message : String ( e ) ) ;
548- }
549- e . target . value = "" ;
550- } ;
551-
552- return (
553- < div className = "space-y-6 animate-in fade-in slide-in-from-bottom-2" >
554- < div >
555- < h2 className = "text-lg font-semibold" > { t . query . title } </ h2 >
556- < p className = "text-sm text-slate-500" > { t . query . desc } </ p >
557- </ div >
558-
559- < div className = "space-y-2" >
560- < div className = "flex justify-between" >
561- < Label > { t . query . ph } </ Label >
562- < div className = "flex items-center gap-2" >
563- < Label htmlFor = "verify-cert" className = "cursor-pointer text-xs flex items-center gap-1 text-indigo-600 hover:bg-indigo-50 px-2 py-1 rounded" >
564- < FileKey className = "w-3 h-3" /> { t . common . import_file }
565- </ Label >
566- < Input id = "verify-cert" type = "file" className = "hidden" accept = ".pem,.cert,.crt" onChange = { handleVerifyCert } />
567- </ div >
568- </ div >
569- < div className = "flex gap-2" >
570- < Input value = { fp } onChange = { e => setFp ( e . target . value ) } placeholder = "AA:BB:CC:DD..." className = "font-mono" />
571- < Button onClick = { handleQuery } disabled = { loading } >
572- { loading ? < Loader2 className = "w-4 h-4 animate-spin" /> : < Search className = "w-4 h-4" /> }
573- </ Button >
574- </ div >
575- </ div >
576-
577- { result && (
578- < Card className = "bg-slate-50 dark:bg-slate-900" >
579- < CardContent className = "p-4 space-y-3 text-sm" >
580- < div className = "flex justify-between py-1 border-b" >
581- < span className = "text-slate-500" > Common Name (CN)</ span >
582- < span className = "font-medium" > { result . cn } </ span >
583- </ div >
584- < div className = "flex justify-between py-1 border-b" >
585- < span className = "text-slate-500" > Serial Number</ span >
586- < span className = "font-mono text-xs" > { result . serialNumber } </ span >
587- </ div >
588- < div className = "flex justify-between py-1 border-b" >
589- < span className = "text-slate-500" > Issuer</ span >
590- < span className = "font-medium" > { result . issuer } </ span >
591- </ div >
592- < div className = "flex justify-between py-1 border-b" >
593- < span className = "text-slate-500" > Valid From</ span >
594- < span > { result . validFrom } </ span >
595- </ div >
596- < div className = "flex justify-between py-1 border-b" >
597- < span className = "text-slate-500" > Valid To</ span >
598- < span > { result . validTo } </ span >
599- </ div >
600- < div className = "flex justify-between py-1 border-b" >
601- < span className = "text-slate-500" > Status</ span >
602- < span className = { result . isValid ? "text-green-600 font-medium" : "text-red-600 font-medium" } >
603- { result . isValid ? "Valid ✓" : "Expired ✗" }
604- </ span >
605- </ div >
606- < div className = "pt-2" >
607- < span className = "text-slate-500 block mb-1" > Fingerprint (SHA-256)</ span >
608- < code className = "block bg-slate-200 dark:bg-slate-800 p-2 rounded text-xs break-all" > { result . fingerprint } </ code >
609- </ div >
610- </ CardContent >
611- </ Card >
612- ) }
613- </ div >
614- ) ;
615- }
616-
617467function RevokeForm ( { t } : { t : typeof locales . en } ) {
618468 const form = useForm < z . infer < typeof revokeSchema > > ( { resolver : zodResolver ( revokeSchema ) } ) ;
619469
0 commit comments