Skip to content

Commit

Permalink
frontend update
Browse files Browse the repository at this point in the history
  • Loading branch information
kayra1 committed Aug 23, 2024
1 parent 63839ab commit 4dcc321
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 39 deletions.
78 changes: 52 additions & 26 deletions ui/src/app/certificate_requests/components.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Dispatch, SetStateAction, useState, ChangeEvent } from "react"
import { Dispatch, SetStateAction, useState, ChangeEvent, useEffect } from "react"
import { useMutation, useQueryClient } from "react-query"
import { ConfirmationModalData } from "./row"
import { extractCert, csrMatchesCertificate } from "../utils"
import { csrMatchesCertificate, splitBundle, validateBundle } from "../utils"
import { postCertToID } from "../queries"
import { useCookies } from "react-cookie"

Expand Down Expand Up @@ -33,27 +33,43 @@ export function ConfirmationModal({ modalData, setModalData }: ConfirmationModal
}

function SubmitCertificate({ existingCSRText, existingCertText, certText, onClickFunc }: { existingCSRText: string, existingCertText: string, certText: string, onClickFunc: any }) {
let certIsValid = false
let certMatchesCSR = false
try {
extractCert(certText)
certIsValid = true
if (csrMatchesCertificate(existingCSRText, certText)) {
certMatchesCSR = true
const [validationErrorText, setValidationErrorText] = useState<string>("")
useEffect(() => {
const validateCertificate = async () => {
try {
const certs = splitBundle(certText)
if (certs.length < 2) {
setValidationErrorText("bundle with 2 certificates required")
return
}
if (!csrMatchesCertificate(existingCSRText, certs[0])) {
setValidationErrorText("Certificate does not match request")
return
}
let a = await validateBundle(certText)
if (await validateBundle(certText)) {
setValidationErrorText("Bundle validation failed: " + a)
return
}
}
catch {
setValidationErrorText("A certificate is invalid")
return
}
setValidationErrorText("")
}
}
catch { }
validateCertificate()
}, [existingCSRText, existingCertText, certText])

const validationComponent = certText == "" ?
<></> :
!certIsValid ?
<div><i className="p-icon--error"></i> Invalid Certificate</div> :
existingCertText == certText ?
<div><i className="p-icon--error"></i> Certificate is identical to the one uploaded</div> :
!certMatchesCSR ?
<div><i className="p-icon--error"></i> Certificate does not match the request</div> :
<div><i className="p-icon--success"></i> Valid Certificate</div>
const buttonComponent = certIsValid && certMatchesCSR && existingCertText != certText ? <button className="p-button--positive" name="submit" onClick={onClickFunc} >Submit</button> : <button className="p-button--positive" name="submit" disabled={true} onClick={onClickFunc} >Submit</button>
const validationComponent = certText != "" && validationErrorText == "" ? (
<div><i className="p-icon--success"></i> Valid Certificate</div>
) : (
<div><i className="p-icon--error"></i> {validationErrorText}</div>)
const buttonComponent = validationErrorText == "" ? (
<button className="p-button--positive" name="submit" onClick={onClickFunc} >Submit</button>
) : (
<button className="p-button--positive" name="submit" disabled={true} onClick={onClickFunc} >Submit</button>
)
return (
<>
{validationComponent}
Expand All @@ -70,11 +86,17 @@ interface SubmitCertificateModalProps {
}
export function SubmitCertificateModal({ id, csr, cert, setFormOpen }: SubmitCertificateModalProps) {
const [cookies, setCookie, removeCookie] = useCookies(['user_token']);
const [errorText, setErrorText] = useState<string>("")
const queryClient = useQueryClient()
const mutation = useMutation(postCertToID, {
onSuccess: () => {
queryClient.invalidateQueries('csrs')
setErrorText("")
setFormOpen(false)
},
onError: (e: Error) => {
setErrorText(e.message)
}
})
const [certificatePEMString, setCertificatePEMString] = useState<string>("")
const handleTextChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
Expand All @@ -94,10 +116,6 @@ export function SubmitCertificateModal({ id, csr, cert, setFormOpen }: SubmitCer
reader.readAsText(file);
}
};
const handleSubmit = () => {
mutation.mutate({ id: id, authToken: cookies.user_token, cert: certificatePEMString })
setFormOpen(false)
}
return (
<div className="p-modal" id="modal">
<section className="p-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-describedby="modal-description">
Expand All @@ -118,7 +136,15 @@ export function SubmitCertificateModal({ id, csr, cert, setFormOpen }: SubmitCer
</div>
</form>
<footer className="p-modal__footer">
<SubmitCertificate existingCSRText={csr.trim()} existingCertText={cert.trim()} certText={certificatePEMString.trim()} onClickFunc={handleSubmit} />
{errorText != "" &&
<div className="p-notification--negative">
<div className="p-notification__content">
<h5 className="p-notification__title">Error</h5>
<p className="p-notification__message">{errorText.split("error: ")}</p>
</div>
</div>
}
<SubmitCertificate existingCSRText={csr.trim()} existingCertText={cert.trim()} certText={certificatePEMString.trim()} onClickFunc={() => mutation.mutate({ id: id, authToken: cookies.user_token, cert: splitBundle(certificatePEMString).join("\n") })} />
<button className="u-no-margin--bottom" aria-controls="modal" onMouseDown={() => setFormOpen(false)}>Cancel</button>
</footer>
</section>
Expand Down
17 changes: 5 additions & 12 deletions ui/src/app/certificate_requests/row.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, Dispatch, SetStateAction, useEffect, useRef } from "react"
import { UseMutationResult, useMutation, useQueryClient } from "react-query"
import { extractCSR, extractCert } from "../utils"
import { extractCSR, extractCert, splitBundle } from "../utils"
import { RequiredCSRParams, deleteCSR, rejectCSR, revokeCertificate } from "../queries"
import { ConfirmationModal, SubmitCertificateModal, SuccessNotification } from "./components"
import "./../globals.scss"
Expand Down Expand Up @@ -31,7 +31,9 @@ export default function Row({ id, csr, certificate, ActionMenuExpanded, setActio
const [confirmationModalData, setConfirmationModalData] = useState<ConfirmationModalData>(null)

const csrObj = extractCSR(csr)
const certObj = extractCert(certificate)
const certs = splitBundle(certificate)
const clientCertificate = certs.at(0)
const certObj = clientCertificate ? extractCert(clientCertificate) : null

const queryClient = useQueryClient()
const deleteMutation = useMutation(deleteCSR, {
Expand Down Expand Up @@ -61,7 +63,7 @@ export default function Row({ id, csr, certificate, ActionMenuExpanded, setActio
const blob = new Blob([csr], { type: 'text/plain' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = "csr-" + id.toString() + ".pem"; // TODO: change this to <csr-commonname>.pem
link.download = "csr-" + (csrObj.commonName !== undefined ? csrObj.commonName : id.toString()) + ".pem";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
Expand All @@ -86,15 +88,6 @@ export default function Row({ id, csr, certificate, ActionMenuExpanded, setActio
})
}


const toggleActionMenu = () => {
if (ActionMenuExpanded == id) {
setActionMenuExpanded(0)
} else {
setActionMenuExpanded(id)
}
}

const getFieldDisplay = (label: string, field: string | undefined, compareField?: string | undefined) => {
const isMismatched = compareField !== undefined && compareField !== field;
return field ? (
Expand Down
24 changes: 23 additions & 1 deletion ui/src/app/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CertificationRequest, Certificate, Extensions } from "pkijs";
import { CertificationRequest, Certificate, Extensions, CertificateChainValidationEngine } from "pkijs";
import { fromBER } from "asn1js";
import * as pvutils from "pvutils";

Expand Down Expand Up @@ -275,4 +275,26 @@ export const passwordIsValid = (pw: string) => {
return true
}
return false
}

export const splitBundle = (bundle: string): string[] => {
const pemPattern = /-----BEGIN CERTIFICATE-----(?:.|\n)*?-----END CERTIFICATE-----/g;
const pemMatches = bundle.match(pemPattern);
return pemMatches ? pemMatches.map((e) => e.toString()) : [];
}

export const validateBundle = async (bundle: string) => {
const bundleList = splitBundle(bundle)
const extractedCerts = bundleList.map((cert) => loadCertificate(cert))
const rootCa = extractedCerts.at(-1)
if (rootCa == undefined) {
return "less than 2 certificates found."
}
const chainEngine = new CertificateChainValidationEngine({
certs: extractedCerts,
trustedCerts: [rootCa]
})
const result = await chainEngine.verify()
console.log(result)
return result.resultMessage
}

0 comments on commit 4dcc321

Please sign in to comment.