Skip to content

Commit

Permalink
SQC-352 SQC-353 Create cert upload command for client side mtls certi…
Browse files Browse the repository at this point in the history
…ficates and ca chain certificates
  • Loading branch information
Adrian Gracia committed Jan 14, 2025
1 parent 9989022 commit 545288b
Show file tree
Hide file tree
Showing 8 changed files with 1,109 additions and 5 deletions.
14 changes: 14 additions & 0 deletions .changeset/hip-cameras-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"wrangler": minor
---

feat: implement the `wrangler cert upload` command

This command allows users to upload a mTLS certificate/private key or certificate-authority certificate chain.

For uploading mTLS certificate, run:

- `wrangler cert upload mtls-certificate --cert cert.pem --key key.pem --name MY_CERT`

For uploading CA certificate chain, run:
- `wrangler cert upload certificate-authority --ca-cert server-ca.pem --name SERVER_CA`
176 changes: 176 additions & 0 deletions packages/wrangler/e2e/cert.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { describe, expect, it } from "vitest";
import { WranglerE2ETestHelper } from "./helpers/e2e-wrangler-test";
import { normalizeOutput } from "./helpers/normalize";

import forge from 'node-forge';

// Generate X509 self signed root key pair and certificate
function generateRootCertificate() {
const rootKeys = forge.pki.rsa.generateKeyPair(2048);
const rootCert = forge.pki.createCertificate();
rootCert.publicKey = rootKeys.publicKey;
rootCert.serialNumber = '01';
rootCert.validity.notBefore = new Date();
rootCert.validity.notAfter = new Date();
rootCert.validity.notAfter.setFullYear(rootCert.validity.notBefore.getFullYear() + 10); // 10 years validity

const rootAttrs = [
{ name: 'commonName', value: 'Root CA' },
{ name: 'countryName', value: 'US' },
{ shortName: 'ST', value: 'California' },
{ name: 'organizationName', value: 'Localhost Root CA' }
];
rootCert.setSubject(rootAttrs);
rootCert.setIssuer(rootAttrs); // Self-signed

rootCert.sign(rootKeys.privateKey, forge.md.sha256.create());

return { certificate: rootCert, privateKey: rootKeys.privateKey };
}

// Generate X509 leaf certificate signed by the root
function generateLeafCertificate(rootCert: forge.pki.Certificate, rootKey: forge.pki.PrivateKey) {
const leafKeys = forge.pki.rsa.generateKeyPair(2048);
const leafCert = forge.pki.createCertificate();
leafCert.publicKey = leafKeys.publicKey;
leafCert.serialNumber = '02';
leafCert.validity.notBefore = new Date();
leafCert.validity.notAfter = new Date();
leafCert.validity.notAfter.setFullYear(2034, 10, 18);

const leafAttrs = [
{ name: 'commonName', value: 'example.org' },
{ name: 'countryName', value: 'US' },
{ shortName: 'ST', value: 'California' },
{ name: 'organizationName', value: 'Example Inc' }
];
leafCert.setSubject(leafAttrs);
leafCert.setIssuer(rootCert.subject.attributes); // Signed by root

leafCert.sign(rootKey, forge.md.sha256.create()); // Signed using root's private key

const pemLeafCert = forge.pki.certificateToPem(leafCert);
const pemLeafKey = forge.pki.privateKeyToPem(leafKeys.privateKey);

return { certificate: pemLeafCert, privateKey: pemLeafKey };
}

// Generate self signed X509 CA root certificate
function generateRootCaCert() {
// Create a key pair (private and public keys)
const keyPair = forge.pki.rsa.generateKeyPair(2048);

// Create a new X.509 certificate
const cert = forge.pki.createCertificate();

// Set certificate fields
cert.publicKey = keyPair.publicKey;
cert.serialNumber = '01';
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(2034, 10, 18);

// Add issuer and subject fields (for a root CA, they are the same)
const attrs = [
{ name: 'commonName', value: 'Localhost CA' },
{ name: 'countryName', value: 'US' },
{ shortName: 'ST', value: 'California' },
{ name: 'localityName', value: 'San Francisco' },
{ name: 'organizationName', value: 'Localhost' },
{ shortName: 'OU', value: 'SSL Department' }
];
cert.setSubject(attrs);
cert.setIssuer(attrs);

// Add basic constraints and key usage extensions
cert.setExtensions([
{
name: 'basicConstraints',
cA: true,
},
{
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
cRLSign: true,
}
]);

// Self-sign the certificate with the private key
cert.sign(keyPair.privateKey, forge.md.sha256.create());

// Convert the certificate and private key to PEM format
const pemCert = forge.pki.certificateToPem(cert);
const pemPrivateKey = forge.pki.privateKeyToPem(keyPair.privateKey);

return { certificate: pemCert, privateKey: pemPrivateKey };
}

describe("cert", () => {
const normalize = (str: string) =>
normalizeOutput(str, {
[process.env.CLOUDFLARE_ACCOUNT_ID as string]: "CLOUDFLARE_ACCOUNT_ID",
});
const helper = new WranglerE2ETestHelper();
// Generate root and leaf certificates
const { certificate: rootCert, privateKey: rootKey } = generateRootCertificate();
const { certificate: leafCert, privateKey: leafKey } = generateLeafCertificate(rootCert, rootKey);
const { certificate: caCert, privateKey: caCertKey } = generateRootCaCert();

it("upload mtls-certificate", async () => {
// locally generated certs/key
await helper.seed({"mtls_client_cert_file.pem": leafCert});
await helper.seed({"mtls_client_private_key_file.pem": leafKey});

const output = await helper.run(`wrangler cert upload mtls-certificate --name MTLS_CERTIFICATE_UPLOAD_CERT --cert mtls_client_cert_file.pem --key mtls_client_private_key_file.pem`);
expect(normalize(output.stdout)).toMatchInlineSnapshot(`
"Uploading mTLS Certificate MTLS_CERTIFICATE_UPLOAD_CERT...
Success! Uploaded mTLS Certificate MTLS_CERTIFICATE_UPLOAD_CERT
ID: 00000000-0000-0000-0000-000000000000
Issuer: CN=Root CA,O=Localhost Root CA,ST=California,C=US
Expires on 11/18/2034"
`);
});

it("upload certificate-authority", async () => {
await helper.seed({"ca_chain_cert.pem": caCert});

const output = await helper.run(`wrangler cert upload certificate-authority --name CERTIFICATE_AUTHORITY_UPLOAD_CERT --ca-cert ca_chain_cert.pem`);
expect(normalize(output.stdout)).toMatchInlineSnapshot(`
"Uploading CA Certificate CERTIFICATE_AUTHORITY_UPLOAD_CERT...
Success! Uploaded CA Certificate CERTIFICATE_AUTHORITY_UPLOAD_CERT
ID: 00000000-0000-0000-0000-000000000000
Issuer: CN=Localhost CA,OU=SSL Department,O=Localhost,L=San Francisco,ST=California,C=US
Expires on 11/18/2034"
`);
});

it("list cert", async () => {
const output = await helper.run(`wrangler cert list`);
let result = normalize(output.stdout);
expect(result).toContain("Name: MTLS_CERTIFICATE_UPLOAD_CERT");
expect(result).toContain('Name: CERTIFICATE_AUTHORITY_UPLOAD_CERT');
});

it("delete mtls cert", async () => {
const delete_mtls_cert_output = await helper.run(`wrangler cert delete --name MTLS_CERTIFICATE_UPLOAD_CERT`);
expect(normalize(delete_mtls_cert_output.stdout)).toMatchInlineSnapshot(
`
"? Are you sure you want to delete certificate 00000000-0000-0000-0000-000000000000 (MTLS_CERTIFICATE_UPLOAD_CERT)?
🤖 Using fallback value in non-interactive context: yes
Deleted certificate 00000000-0000-0000-0000-000000000000 (MTLS_CERTIFICATE_UPLOAD_CERT) successfully"
`
)
});

it("delete ca chain cert", async() => {
const delete_ca_cert_output = await helper.run(`wrangler cert delete --name CERTIFICATE_AUTHORITY_UPLOAD_CERT`);
expect(normalize(delete_ca_cert_output.stdout)).toMatchInlineSnapshot(
`
"? Are you sure you want to delete certificate 00000000-0000-0000-0000-000000000000 (CERTIFICATE_AUTHORITY_UPLOAD_CERT)?
🤖 Using fallback value in non-interactive context: yes
Deleted certificate 00000000-0000-0000-0000-000000000000 (CERTIFICATE_AUTHORITY_UPLOAD_CERT) successfully"
`
)
})
});
Loading

0 comments on commit 545288b

Please sign in to comment.