Skip to content

Commit

Permalink
feat: Add certificate profile selection in CA issue certificate view
Browse files Browse the repository at this point in the history
  • Loading branch information
microshine committed May 30, 2024
1 parent db5a531 commit 37d3951
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 14 deletions.
25 changes: 19 additions & 6 deletions src/CaIssueCertificateView.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Box, Button, List, ListItem, Step, StepLabel, Stepper, TextField, Typography } from "@mui/material";
import { Box, Button, FormControl, InputLabel, List, ListItem, MenuItem, Select, Step, StepLabel, Stepper, TextField, Typography } from "@mui/material";
import * as React from 'react';
import { Pkcs10CertificateRequest, X509Certificate } from "@peculiar/x509";
import { Convert } from "pvtsutils";

import { CertificateDetails } from "./CertificateDetails";
import { useCaContext } from "./CaProvider";
import { CertificateProfile, useCaContext } from "./CaProvider";
import { useApplicationContext } from "./AppProvider";

export interface CaIssueCertificateViewProps {
Expand All @@ -21,6 +21,7 @@ export const CaIssueCertificateView: React.FC<CaIssueCertificateViewProps> = ()
const fileInputRef = React.useRef<HTMLInputElement>(null);
const [file, setFile] = React.useState<File | null>(null);
const [isDragOver, setIsDragOver] = React.useState(false);
const [profile, setProfile] = React.useState<CertificateProfile>('none');

const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
Expand Down Expand Up @@ -102,7 +103,8 @@ export const CaIssueCertificateView: React.FC<CaIssueCertificateViewProps> = ()
(async () => {
const issuedCert = await enrollCertificate(csr, {
subject: certName,
validity: certValidity
validity: certValidity,
profile,
});
setCert(issuedCert.toString());
setActiveStep(2);
Expand Down Expand Up @@ -153,7 +155,7 @@ export const CaIssueCertificateView: React.FC<CaIssueCertificateViewProps> = ()
</Stepper>
</Box>
{
activeStep === 0 &&
activeStep === 0 && // Import CSR
(
<Box sx={{ mt: 2 }}>
<Box>
Expand Down Expand Up @@ -193,7 +195,7 @@ export const CaIssueCertificateView: React.FC<CaIssueCertificateViewProps> = ()
)
}
{
activeStep === 1 &&
activeStep === 1 && // Issue Certificate
(
<Box>
<Typography variant='body2' paragraph>Please enter the certificate details below:</Typography>
Expand All @@ -204,6 +206,17 @@ export const CaIssueCertificateView: React.FC<CaIssueCertificateViewProps> = ()
<ListItem>
<TextField label='Validity (days)' value={certValidity} onChange={handleCertValidityChange} size='small' sx={{ width: '100%' }} />
</ListItem>
<ListItem>
<FormControl fullWidth size="small">
<InputLabel>Profile</InputLabel>
<Select value={profile} onChange={(e) => setProfile(e.target.value as CertificateProfile)} size='small'>
<MenuItem value="none"><em>No profile</em></MenuItem>
<MenuItem value="code_signing">Code Signing</MenuItem>
<MenuItem value="smime">S/MIME</MenuItem>
<MenuItem value="pdf_signing">PDF Document Signing</MenuItem>
</Select>
</FormControl>
</ListItem>
</List>
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 2, gap: 1 }}>
<Button onClick={handleBack}>Back</Button>
Expand All @@ -213,7 +226,7 @@ export const CaIssueCertificateView: React.FC<CaIssueCertificateViewProps> = ()
)
}
{
activeStep === 2 &&
activeStep === 2 && // Done
(
<Box>
<Typography variant='body2' paragraph>Certificate issued successfully.</Typography>
Expand Down
34 changes: 26 additions & 8 deletions src/CaProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,12 @@ export class CaDataBase {
}
}

export type CertificateProfile = 'none' | 'code_signing' | 'smime' | 'pdf_signing';

export interface CaEnrolParams {
subject: string;
validity: number;
profile: CertificateProfile;
}

export interface CaContextProps {
Expand Down Expand Up @@ -198,7 +201,8 @@ export const CaProvider: React.FC<CaProviderProps> = (params) => {
if (serial[0] === 0) {
serial[1] |= 0x80;
}
const cert = await x509.X509CertificateGenerator.create({

const certParams: x509.X509CertificateCreateParams = {
serialNumber: Convert.ToHex(serial),
subject: params.subject,
issuer: caCert.subject,
Expand All @@ -212,14 +216,28 @@ export const CaProvider: React.FC<CaProviderProps> = (params) => {
signingKey: value.key,
extensions: [
new x509.BasicConstraintsExtension(false, undefined, true),
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true),
await x509.AuthorityKeyIdentifierExtension.create(caCert),
await x509.SubjectKeyIdentifierExtension.create(req.publicKey),
new x509.ExtendedKeyUsageExtension([
x509.ExtendedKeyUsage.codeSigning,
])
],
});
};
const extensions = certParams.extensions || [];
switch (params.profile) {
case 'code_signing':
extensions.push(new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true));
extensions.push(new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage.codeSigning]));
break;
case 'smime':
extensions.push(new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.dataEncipherment | x509.KeyUsageFlags.keyEncipherment, true));
extensions.push(new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage.emailProtection, x509.ExtendedKeyUsage.clientAuth]));
break;
case 'pdf_signing':
extensions.push(new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true));
extensions.push(new x509.ExtendedKeyUsageExtension(['1.2.840.113583.1.1.10'])); // Adobe PDF
break;
default:
extensions.push(new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true));
break;
}

const cert = await x509.X509CertificateGenerator.create(certParams);

return cert;
},
Expand Down

0 comments on commit 37d3951

Please sign in to comment.