Skip to content

Commit

Permalink
feat(certificates): introduce UI for a new `Certificates -> Private K…
Browse files Browse the repository at this point in the history
…eys` utility

Private Keys is a new utility within the Digital Certificates bundle, enabling users to generate
RSA, DSA, EC, and Ed25519 private keys. These keys can later be used to create X.509 certificates.
Private keys can be optionally protected by a passphrase, which can be changed. They are stored in
an encrypted form within the database using the PKCS#8 AES algorithm (aes_256_cbc). Additionally,
private keys can be exported in PEM, PKCS#8, or PKCS#12 formats, with optional encryption.
  • Loading branch information
azasypkin committed Oct 20, 2023
1 parent 5c1c703 commit a9462dd
Show file tree
Hide file tree
Showing 16 changed files with 1,193 additions and 351 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ import {
} from '@elastic/eui';
import axios from 'axios';

import type { SelfSignedCertificate } from './self_signed_certificate';
import type { CertificateTemplate } from './certificate_template';
import type { AsyncData } from '../../../../model';
import { getApiUrl } from '../../../../model';
import { Downloader } from '../../../../tools/downloader';

export interface CertificateFormatModalProps {
certificate: SelfSignedCertificate;
template: CertificateTemplate;
onClose: () => void;
}

type GenerationResponse = {
value: { value: { certificate: number[] } };
value: { value: number[] };
};

export function CertificateFormatModal({ certificate, onClose }: CertificateFormatModalProps) {
export function CertificateFormatModal({ template, onClose }: CertificateFormatModalProps) {
const [format, setFormat] = useState<string>('pkcs12');
const onFormatChange = useCallback((e: ChangeEvent<HTMLSelectElement>) => {
setFormat(e.target.value);
Expand Down Expand Up @@ -60,35 +60,22 @@ export function CertificateFormatModal({ certificate, onClose }: CertificateForm
type: 'certificates',
value: {
type: 'generateSelfSignedCertificate',
value: { templateName: certificate.name, format, passphrase: passphrase || null },
value: { templateName: template.name, format, passphrase: passphrase || null },
},
},
})
.then(
(response) => {
const content = new Uint8Array(response.data.value.value);
if (format === 'pem') {
Downloader.download(
`${certificate.name}.zip`,
new Uint8Array(response.data.value.value.certificate),
'application/zip',
);
Downloader.download(`${template.name}.zip`, content, 'application/zip');
} else if (format === 'pkcs8') {
Downloader.download(
`${certificate.name}.p8`,
new Uint8Array(response.data.value.value.certificate),
'application/pkcs8',
);
Downloader.download(`${template.name}.p8`, content, 'application/pkcs8');
} else {
Downloader.download(
`${certificate.name}.pfx`,
new Uint8Array(response.data.value.value.certificate),
'application/x-pkcs12',
);
Downloader.download(`${template.name}.pfx`, content, 'application/x-pkcs12');
}

setGeneratingStatus({ status: 'succeeded', data: undefined });
setFormat('');
setPassphrase('');

onClose();
},
Expand Down
178 changes: 178 additions & 0 deletions src/pages/workspace/utils/certificates/certificate_template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import type { PrivateKeyAlgorithm } from './private_key_alg';

export const CERTIFICATE_TEMPLATES_USER_DATA_NAMESPACE = 'selfSignedCertificates';

export type SerializedCertificateTemplates = Record<string, SerializedCertificateTemplate>;

export interface SerializedCertificateTemplate {
n: string;
cn?: string;
c?: string;
s?: string;
l?: string;
o?: string;
ou?: string;
ka: PrivateKeyAlgorithm;
sa: string;
nb: number;
na: number;
ca: boolean;
ku?: string[];
eku?: string[];
}

export interface CertificateTemplate {
name: string;
commonName?: string;
country?: string;
state?: string;
locality?: string;
organization?: string;
organizationalUnit?: string;
keyAlgorithm: PrivateKeyAlgorithm;
signatureAlgorithm: string;
notValidBefore: number;
notValidAfter: number;
isCA: boolean;
keyUsage?: string[];
extendedKeyUsage?: string[];
}

export function getDistinguishedNameString(template: CertificateTemplate) {
return [
template.country ? [`C=${template.country}`] : [],
template.state ? [`ST=${template.state}`] : [],
template.locality ? [`L=${template.locality}`] : [],
template.organization ? [`O=${template.organization}`] : [],
template.organizationalUnit ? [`OU=${template.organizationalUnit}`] : [],
template.commonName ? [`CN=${template.commonName}`] : [],
]
.flat()
.join(',');
}

export function certificateTypeString(template: CertificateTemplate) {
if (template.isCA) {
return 'Certification Authority';
}

return 'End Entity';
}

export function signatureAlgorithmString(template: CertificateTemplate) {
switch (template.signatureAlgorithm) {
case 'md5':
return template.signatureAlgorithm.toUpperCase();
case 'sha1':
case 'sha256':
case 'sha384':
case 'sha512':
return template.signatureAlgorithm.replace('sha', 'sha-').toUpperCase();
default:
return 'Ed25519';
}
}

export function deserializeCertificateTemplate(serializedTemplate: SerializedCertificateTemplate): CertificateTemplate {
const template: CertificateTemplate = {
name: serializedTemplate.n,
keyAlgorithm: serializedTemplate.ka,
signatureAlgorithm: serializedTemplate.sa,
notValidBefore: serializedTemplate.nb,
notValidAfter: serializedTemplate.na,
isCA: serializedTemplate.ca,
};

if (serializedTemplate.cn) {
template.commonName = serializedTemplate.cn;
}

if (serializedTemplate.c) {
template.country = serializedTemplate.c;
}

if (serializedTemplate.s) {
template.state = serializedTemplate.s;
}

if (serializedTemplate.l) {
template.locality = serializedTemplate.l;
}

if (serializedTemplate.o) {
template.organization = serializedTemplate.o;
}

if (serializedTemplate.ou) {
template.organizationalUnit = serializedTemplate.ou;
}

if (serializedTemplate.ku && serializedTemplate.ku.length > 0) {
template.keyUsage = serializedTemplate.ku;
}

if (serializedTemplate.eku && serializedTemplate.eku.length > 0) {
template.extendedKeyUsage = serializedTemplate.eku;
}

return template;
}

export function deserializeCertificateTemplates(
serializedTemplates: SerializedCertificateTemplates | null,
): CertificateTemplate[] {
if (!serializedTemplates) {
return [];
}

try {
return Object.values(serializedTemplates).map(deserializeCertificateTemplate);
} catch {
return [];
}
}

export function serializeCertificateTemplate(template: CertificateTemplate): SerializedCertificateTemplate {
const serializedCertificate: SerializedCertificateTemplate = {
n: template.name,
ka: template.keyAlgorithm,
sa: template.signatureAlgorithm,
nb: template.notValidBefore,
na: template.notValidAfter,
ca: template.isCA,
};

if (template.commonName) {
serializedCertificate.cn = template.commonName;
}

if (template.country) {
serializedCertificate.c = template.country;
}

if (template.state) {
serializedCertificate.s = template.state;
}

if (template.locality) {
serializedCertificate.l = template.locality;
}

if (template.organization) {
serializedCertificate.o = template.organization;
}

if (template.organizationalUnit) {
serializedCertificate.ou = template.organizationalUnit;
}

if (template.keyUsage && template.keyUsage.length > 0) {
serializedCertificate.ku = template.keyUsage;
}

if (template.extendedKeyUsage && template.extendedKeyUsage.length > 0) {
serializedCertificate.eku = template.extendedKeyUsage;
}

return serializedCertificate;
}
Loading

0 comments on commit a9462dd

Please sign in to comment.