Implement a client only solution

This commit contains a client only (aka semi-manual) solution for creating
client certificate key stores with a modern web browser.
main
Jan Dittberner 11 months ago
parent 4ecdedadf7
commit 2887f6d378

@ -1,102 +1,51 @@
# Browser PKCS#10 CSR generation PoC
# Browser based client certificate creation
This repository contains a small proof of concept implementation of browser
based PKCS#10 certificate signing request and PKCS#12 key store generation
using [node-forge](https://github.com/digitalbazaar/forge).
This repository contains an implementation to create a client certificate key store in a modern Web browser.
The backend is implemented in [Go](https://golang.org/) and utilizes openssl
for the signing operations. The instructions below have been tested on Debian
11 (Bullseye). Debian 10 works when you use a manual installation of Go.
The implementation uses the excellent [node-forge](https://github.com/digitalbazaar/forge) library.
[Bootstrap 5](https://getbootstrap.com/docs/5.2/) is used for styling the user interface.
This is meant as a replacement for the `<keygen>` tag that has been removed from browsers.
The following steps have been implemented:
- choose a common name for the subject of the certificate signing request
- generate an RSA key pair with a selectable size
- generate a certificate signing request (PKCS#10 CSR)
- paste the certificate signed by the certificate authority
- build a PKCS#12 (also known as .p12 or .pfx) key store file with a password chosen by the user. That file contains the
generated key pair, the client certificate, and the CA certificate chain
The implementation contains the [CAcert CA certificates](https://secure.cacert.org/index.php?id=3) for CA chain
building.
## Running
1. Install dependencies
```
sudo apt install git npm openssl golang-go
```shell
sudo apt install git npm
```
2. Clone the repository
```
git clone https://code.cacert.org/jandd/poc-browser-csr-generation.git
```shell
git clone https://code.cacert.org/cacert/browser-csr-generation.git
```
3. Get dependencies and build assets
```
cd poc-browser-csr-generation
```shell
cd browser-csr-generation
npm install --no-save --user gulp-cli
npm install
npm run build
```
3. Setup the example CA and a server certificate and key
```
./setup_example_ca.sh
openssl req -new -x509 -days 365 -subj "/CN=localhost" \
-addext subjectAltName=DNS:localhost -newkey rsa:3072 \
-nodes -out server.crt.pem -keyout server.key.pem
```
4. Run the Go based backend
```
go run main.go
```
Open https://localhost:8000/ in your browser.
5. Run gulp watch
You can run a
[gulp watch](https://gulpjs.com/docs/en/getting-started/watching-files/)
in a second terminal window to automatically publish changes to the files in
the `src` directory:
4. Open `public/index.html` in a Web browser
5. Run
```
npm run watch
```
## Translations
This PoC uses [go-i18n](https://github.com/nicksnyder/go-i18n/) for
internationalization (i18n) support.
The translation workflow needs the `go18n` binary which can be installed via
```
go get -u github.com/nicksnyder/go-i18n/v2/goi18n
```
To extract new messages from the code run
```
goi18n extract
```
Then use
```
goi18n merge active.*.toml
```
to create TOML files for translation as `translate.<locale>.toml`. After
translating the messages run
```
goi18n merge active.*.toml translate.*.toml
```
to merge the messages back into the active translation files. To add a new
language you need to add the language code to `main.go`'s i18n bundle loading
code
```
for _, lang := range []string{"en-US", "de-DE"} {
if _, err := bundle.LoadMessageFile(fmt.Sprintf("active.%s.toml", lang)); err != nil {
log.Panic(err)
}
}
```
to continuously update the `public/index.html` when changing `src/index.html`

@ -8,203 +8,441 @@
<link rel="stylesheet" href="../public/css/styles.min.css">
<meta name="theme-color" content="#ffffff">
<title>CSR generation in your browser</title>
<title>CAcert client certificate generation in your browser</title>
</head>
<body>
<div class="container">
<h1>CSR generation in your browser</h1>
<div class="row">
<div class="col-12">
<form id="csr-form">
<div class="form-group">
<label for="nameInput">Your name</label>
<input type="text" class="form-control" id="nameInput" aria-describedby="nameHelp" required
minlength="3">
<small id="nameHelp" class="form-text text-muted">Please input your name as it should be added to
your certificate</small>
</div>
<div class="form-group">
<label for="passwordInput">Password for your client certificate</label>
<input type="password" class="form-control" id="passwordInput" aria-describedby="nameHelp" required
minlength="8">
</div>
<fieldset class="form-group">
<legend>RSA Key Size</legend>
<div class="form-check">
<input class="form-check-input" type="radio" name="keySize" id="size3072" value="3072" checked>
<label class="form-check-label" for="size3072">3072 Bit</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="keySize" id="size2048" value="2048">
<label class="form-check-label" for="size2048">2048 Bit (most compatible, least secure)</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="keySize" id="size4096" value="4096">
<label class="form-check-label" for="size4096">4096 Bit</label>
<header class="sticky-top text-bg-dark py-2 mb-3" id="header">
<div class="container">
<svg id="CAcert-logo" width="510" height="116.25" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g id="b" transform="matrix(1.25,0,0,-1.25,0,116.25)"><g id="c" transform="scale(.1)"><path id="d" d="m2031.8 34.969c-56.31 0-107.84 6.4062-154.59 19.281-46.35 12.844-86.77 32.656-121.25 59.469-34.1 26.781-60.53 60.531-79.3 101.31-18.77 40.75-28.16 88.469-28.16 143.12 0 57.656 9.96 107.38 29.88 149.22 20.29 41.844 48.47 76.531 84.48 104.06 34.86 26.062 75.08 45.156 120.67 57.25 45.6 12.124 92.91 18.187 141.94 18.187 44.06 0 84.67-4.594 121.85-13.781 37.15-9.156 71.82-21.094 104-35.782v-169.03h-29.3c-8.05 6.594-17.83 14.281-29.31 23.094-11.11 8.844-24.91 17.469-41.38 25.937-15.7 8.032-32.95 14.688-51.72 19.782-18.78 5.531-40.61 8.25-65.51 8.25-55.17 0-97.69-16.875-127.58-50.625-29.49-33.438-44.25-78.907-44.25-136.56 0-59.468 15.12-104.62 45.4-135.41 30.66-30.875 73.94-46.281 129.88-46.281 26.05 0 49.42 2.781 70.11 8.25 21.06 5.875 38.5 12.687 52.3 20.375 13.01 7.344 24.51 15.062 34.47 23.125 9.96 8.093 19.15 15.969 27.59 23.687h29.3v-169.03c-32.55-14.688-66.66-26.219-102.29-34.688-35.25-8.8125-74.32-13.219-117.23-13.219" fill="#11568c"/><path
id="e"
d="m2981 330.9h-458.79c2.97-50.008 21.54-88.27 55.68-114.79 34.52-26.519 85.19-39.777 152-39.777 42.32 0 83.33 7.766 123.05 23.297 39.72 15.535 71.08 32.203 94.09 50.008h22.27v-164.23c-45.28-18.562-87.96-32.012-128.05-40.348-40.09-8.332-84.45-12.5-133.07-12.5-125.46 0-221.6 28.789-288.41 86.375-66.81 57.582-100.22 139.6-100.22 246.06 0 105.32 31.55 188.66 94.65 250.04 63.47 61.75 150.33 92.625 260.57 92.629 101.71-4e-3 178.17-26.332 229.39-78.992 51.22-52.277 76.84-127.67 76.84-226.17v-71.601m-199.33 119.91c-1.11 42.808-11.51 75.008-31.18 96.601-19.67 21.594-50.3 32.391-91.87 32.395-38.6-4e-3 -70.34-10.231-95.2-30.688-24.87-20.457-38.79-53.23-41.76-98.308h260.01"
fill="#11568c"/><path id="f"
d="m3514.5 477.77h-18.23c-8.74 2.953-22.79 5.172-42.17 6.656s-35.53 2.234-48.44 2.234c-29.26 0-55.09-1.859-77.5-5.562-22.42-3.703-46.55-10-72.39-18.891v-417.52h-205.16v623.5h205.16v-91.594c45.21 37.75 84.54 62.734 117.98 74.937 33.42 12.594 64.2 18.875 92.31 18.875 7.22 0 15.39-0.172 24.51-0.547s17.1-0.921 23.93-1.671v-190.42"
fill="#11568c"/><path id="g"
d="m3874.5 836.26-207.5-80.5v-94.75h-85.75v-133.5h85.75v-287c0-75.071 19.63-128.03 59-159 39.74-30.98 99.97-46.5 181-46.5 36.27 0 67.16 1.7188 92.25 5 25.08 2.9141 48.61 7.0313 71 12.5v135h-17.25c-6.95-3.645-19.25-7.633-37-12-17.37-4.375-31.45-6.504-42.25-6.5-26.24-8e-3 -46.36 3.461-60.25 10.75-13.51 7.652-23.1 17.988-28.5 30.75-5.79 12.754-8.87 27.211-9.25 43.25-0.39 16.035-0.5 34.746-0.5 56.25v217.5h195v133.5h-195v175.25h-0.75"
fill="#11568c"/><path id="h"
d="m439.12 20.273c-62.25 0-119.81 9.1875-172.69 27.562-52.5 18.375-97.688 45.75-135.56 82.125-37.875 36.375-67.312 81.75-88.312 136.12-20.625 54.375-30.938 117.19-30.938 188.44 0 66.375 9.9375 126.56 29.812 180.56 19.875 53.996 48.75 100.31 86.624 138.94 36.376 37.122 81.376 65.809 135 86.063 54 20.246 112.88 30.371 176.63 30.375 35.25-4e-3 66.933-2.066 95.062-6.188 28.496-3.753 54.746-8.816 78.75-15.187 25.121-7.129 47.809-15.191 68.062-24.188 20.622-8.628 38.622-16.691 54-24.187v-203.06h-24.75c-10.503 9-23.816 19.684-39.937 32.063-15.754 12.371-33.754 24.559-54 36.562-20.629 11.997-42.941 22.122-66.937 30.375-24.004 8.247-49.688 12.372-77.063 12.375-30.375-3e-3 -59.25-4.878-86.625-14.625-27.375-9.378-52.688-25.128-75.938-47.25-22.124-21.378-40.124-49.687-54-84.937-13.5-35.25-20.25-78-20.25-128.25 0-52.5 7.313-96.375 21.938-131.62 15-35.25 33.75-63 56.25-83.25 22.875-20.625 48.375-35.438 76.5-44.438 28.125-8.625 55.875-12.937 83.25-12.937 26.25 0 52.121 3.937 77.625 11.812 25.871 7.875 49.684 18.563 71.437 32.063 18.372 10.875 35.434 22.5 51.188 34.875 15.746 12.375 28.684 23.062 38.812 32.062h22.5v-200.25c-21.003-9.375-41.066-18.188-60.187-26.438-19.129-8.25-39.191-15.375-60.187-21.375-27.379-7.875-53.067-13.875-77.063-18-24.004-4.125-57-6.1875-99-6.1875"
fill="#11568c"/><path id="i"
d="m1672.2 45.082h-223.31l-57.94 169.31h-310.5l-57.94-169.31h-217.68l309.38 837.56h248.63l309.37-837.56m-333.56 322.88-102.94 300.38-102.94-300.38h205.88"
fill="#11568c"/><path
id="j"
d="m529.66 684.46c-36.738-1.871-77.344-32.203-81.344-73.883-4.417-45.98 17.786-71.976 51.626-89.816 16.921-8.922 36.476-11.504 56.164-8.313 19.683 3.192 38.996 12.778 52.886 32.239 5.774 8.136 3.856 19.41-4.281 25.183-8.137 5.774-19.41 3.856-25.184-4.281-8.914-12.488-18.597-15.906-29.214-17.629-10.618-1.723-22.473-1.027-33.497 4.785-22.046 11.621-34.374 29.988-32.992 54.609 1.391 24.7 26.168 40.575 49.614 41.09 23.449 0.52 45.949-10.675 53.894-41.804 0.942-6.871 5.746-12.473 12.344-14.606 6.594-2.137 13.851-0.488 18.637 4.531 4.781 5.02 6.226 12.403 3.777 18.887-11.832 46.344-52.047 69.836-89.66 69.008-0.879-0.02-1.887 0.043-2.77 0zm-503.69-11.332c-0.5546-0.07-1.0859-0.332-1.5117-0.504-0.125-0.055-0.3906-0.191-0.5039-0.254-0.0351-0.023-0.2187-0.226-0.25-0.25-0.0664-0.051-0.1953-0.199-0.2539-0.254-0.0547-0.055-0.1992-0.191-0.25-0.25-0.0234-0.031-0.2305-0.219-0.2539-0.254-0.0586-0.101-0.207-0.39-0.25-0.504-0.0156-0.035 0.0117-0.211 0-0.25-0.0352-0.117-0.2305-0.375-0.2539-0.504-0.0039-0.043 0.0078-0.207 0-0.25-0.4102-4.816 14.48-20.425 20.402-26.445 6.1016-6.211 56.524-46.558 84.871-65.73 28.527-19.297 94.066-54.223 110.81-62.461 16.594-8.16 68.145-29.715 102.25-40.043 58.672-17.77 118.95-28.031 177.3-32.488 58.348-4.454 114.86-2.961 165.97 3.023 102.22 11.965 184 38.824 222.38 85.879 19.191 23.527 29.098 61.598 7.305 85.375-23.711 25.871-78.68 46.996-82.102 47.097-0.191 0-0.582 0.012-0.754 0-0.113-0.011-0.402 0.016-0.504 0-0.051-0.011-0.203 0.012-0.254 0-0.047-0.015-0.207-0.238-0.25-0.253-0.043-0.016-0.211 0.019-0.254 0-0.117-0.055-0.398-0.18-0.503-0.25-0.032-0.028-0.219-0.227-0.25-0.254-0.028-0.028-0.227-0.223-0.254-0.25-0.067-0.098-0.196-0.395-0.25-0.504-0.043-0.117-0.223-0.375-0.254-0.504-0.016-0.086 0.011-0.41 0-0.504-4e-3 -0.047 4e-3 -0.203 0-0.254v-0.25c0.316-8.516 16.094-27.164 27.785-40.113 9.016-9.981 16.566-16.922 19.832-21.176 12.211-15.898 3.715-27.934-4.047-39.703-18.082-27.414-96.656-58.742-192.91-70.012-48.129-5.633-101.41-6.867-156.4-2.519-54.985 4.347-111.62 14.078-166.72 30.726-37.449 11.317-88.692 31.836-107.54 39.793-19.356 8.168-77.43 36.235-97.215 46.086-19.625 9.774-93.238 55.328-99.227 59.688-5.9882 4.359-21.301 9.871-25.688 9.32zm416.56-262.43c-1.821-0.109-3.649-0.5-5.04-1.008-0.453-0.175-1.101-0.535-1.511-0.754-0.199-0.113-0.567-0.382-0.754-0.503-0.184-0.126-0.582-0.368-0.754-0.504-0.227-0.188-0.555-0.551-0.758-0.754-0.199-0.211-0.578-0.532-0.754-0.758-0.129-0.172-0.387-0.574-0.504-0.754-0.3-0.496-0.539-1.203-0.757-1.766-0.254-0.711-0.629-1.707-0.754-2.515-1.094-8.11 4.429-21.043 16.371-36.266 23.273-29.68 22.093-53.344 22.414-80.844 0.316-27.5-17.395-56.957-41.051-84.621-27.258-31.871-58.082-59.094-86.109-80.883-27.844-21.644-82.547-55.171-100.01-68.714-17.219-13.352-30.551-21.672-33.242-29.211-0.153-0.4532-0.418-1.086-0.504-1.5118-0.063-0.3554 0.019-0.9257 0-1.2617-4e-3 -0.1992-0.012-0.5625 0-0.7539 0.015-0.1914-0.032-0.5703 0-0.7578 0.039-0.1797 0.195-0.5781 0.25-0.7539 0.043-0.1133 0.207-0.3906 0.254-0.5039 0.05-0.1094 0.191-0.3945 0.25-0.5039 0.129-0.211 0.347-0.5586 0.503-0.7539 0.043-0.0508 0.211-0.2071 0.254-0.2539 0.043-0.0469 0.204-0.2032 0.25-0.25 0.051-0.0469 0.204-0.2071 0.254-0.2539 0.625-0.5196 1.614-1.1211 2.52-1.5118 6.336-2.5625 19.394-1.9765 39.539 3.5274 15.758 4.3086 34.66 10.809 47.348 15.613 13.023 4.9375 74.964 38.465 109 63.152 33.785 24.508 71.421 62.141 87.437 83.172 15.879 20.848 41.262 53.235 43.32 118.37 1.371 43.528-8.191 72.75-54.902 99.731-19.402 11.207-33.578 15.898-42.562 15.363"
fill="#00be00" fill-rule="evenodd"/><path id="k"
d="m1298.3 33.055c-64.72 19.762-128.5 42.133-168.77 74.433-42.17 33.824-51.52 48.008-75.3 80.746-29.81 41.043-59.63 125.99-62.228 205.96-1.933 59.375 6.25 107.64 25.268 151.56 16.31 37.664 50.72 85.133 66.86 83.586 16.79-1.613 12.79-22.199 1.8-68.606-12.97-54.804-14.7-69.25-14.78-123.54-0.1-65.406 6-96.316 28.15-162.39 9.16-27.34 20.57-61.301 52.72-97.508 25.63-28.879 61.73-56.82 127.17-93.027 52.63-29.117 66.92-42.023 67.68-50.742 0.35-3.9531-3.72-6.789-10.93-6.9336-9.68-0.2031-23.7 2.211-37.64 6.4649zm219.34 469.26c-42.18 10.309-58.18 20.684-88.8 33.672-96.79 41.055-164.89 71.496-185.55 78.805-27.01 9.551-112.64 39.285-163.57 51.039-57.65 13.309-142.41 29.652-175.3 31.434-29.926 1.621-72.531-2.672-90.598-12.86-21.546-12.152-18.187-34.172 9.403-79.953 4.918-8.16 9.805-13.461 7.773-16.984-1.883-3.27-12.652-0.403-21.808 3.894-12.336 5.786-29.75 23.145-39.18 37.668-22.016 33.914-9.391 79.172 30.613 101.15 28.953 15.906 60.739 19.273 126.74 13.832 73.885-6.094 143.36-26.278 176.2-36.188 180.42-54.453 245.08-80.371 317.15-119.35 33.6-18.176 63.75-39.961 71.71-44.762 6.56-3.953 29.91-18.887 40.27-29.125 4.14-4.094 5.63-7.664 4.93-9.129-1.52-3.176-15.1-4.34-19.98-3.149zm-467.18 247.14c-13.3 5.957-22.54 18.926-29.18 31.254-4.52 8.375-7.28 21.266-6.57 35.387 0.71 14.113 1.87 25.601 10.46 43.691 11.5 24.199 28.57 41.508 51.45 52.16 14.78 6.883 17.58 7.863 36.23 7.906 18.8 0.04 24.33-3.253 34.76-9.316 13.03-7.57 19.42-18.258 23.87-28.062 4.41-9.68 6.72-16.102 6.87-33.622 0.14-17.671-4.96-37.812-21.27-63.027-8-12.375-21.5-24.558-41.88-36.012-14.21-7.988-43.3-9.964-64.74-0.359zm60.84 33.828c18.94 16.098 25.2 29.086 26.87 54.242 1.75 26.485-19.08 44.25-28.66 45.25-25.17 2.621-47.61-15.839-52.81-50.113-1.11-7.344-0.44-14.316 2.39-25.043 3.08-11.683 10.74-23.902 23.13-27.586 12.77-3.793 22.57-0.824 29.08 3.25"
fill="#c7ff00"/></g></g></svg>
<h1>Client certificate generation in your browser</h1>
<div class="row mb-3">
<div class="col-12">
<div class="progress" style="height: 2rem">
<div id="progress-bar" class="progress-bar" role="progressbar" aria-valuenow="1" aria-valuemin="1"
aria-valuemax="5" style="width:20%">Start
</div>
<small id="keySizeHelp" class="form-text text-muted">An RSA key pair will be generated in your
browser. Longer key sizes provide better security but take longer to generate.</small>
</fieldset>
<button type="submit" id="gen-csr-button" class="btn btn-primary">Generate signing request</button>
</form>
</div>
</div>
</div>
</div>
<div id="status-block" class="d-none row">
<div class="col-12 py-3">
<div class="progress" style="height: 2rem">
<div id="progress-bar" class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0"
aria-valuemax="4">Loading ...
</header>
<main>
<div class="container">
<div class="row mb-3">
<div class="col-12">
<h2>1. Generate a key pair</h2>
<form id="csr-form">
<div class="mb-3">
<label for="nameInput">Your name</label>
<input type="text" class="form-control" id="nameInput" aria-describedby="nameHelp" required
minlength="3">
<small id="nameHelp" class="form-text text-muted">Please input your name as it should be added
to
your certificate</small>
</div>
<p>Choose an RSA key size</p>
<fieldset class="mb-3">
<small id="keySizeHelp" class="form-text text-muted">An RSA key pair will be generated in your
browser. Longer key sizes provide better security but take longer to generate.</small>
<div class="form-check">
<input class="form-check-input" type="radio" name="keySize" id="size3072" value="3072"
checked>
<label class="form-check-label" for="size3072">3072 Bit</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="keySize" id="size2048" value="2048">
<label class="form-check-label" for="size2048">2048 Bit (most compatible, least
secure)</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="keySize" id="size4096" value="4096">
<label class="form-check-label" for="size4096">4096 Bit</label>
</div>
</fieldset>
<button type="submit" id="gen-csr-button" class="btn btn-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-key-fill" viewBox="0 0 16 16">
<path d="M3.5 11.5a3.5 3.5 0 1 1 3.163-5H14L15.5 8 14 9.5l-1-1-1 1-1-1-1 1-1-1-1 1H6.663a3.5 3.5 0 0 1-3.163 2zM2.5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
</svg>
Generate key pair and signing request
</button>
</form>
</div>
</div>
<div class="row mb-3 d-none" id="key-wrapper">
<div class="col-12">
<button class="btn btn-danger" type="button" data-bs-toggle="collapse"
data-bs-target="#collapse-key-output"
aria-expanded="false" aria-controls="collapse-key-output">Show private key
</button>
<div class="collapse mt-3" id="collapse-key-output">
<pre id="key-output"></pre>
</div>
</div>
</div>
<div class="col-12 d-none" id="download-wrapper">
<p class="text-info">Your key material is ready for download. The downloadable file contains your private
key and your certificate encrypted with your password. You can now use the file to install your
certificate in your browser or other applications.</p>
<a href="#" class="btn btn-success" id="download-link">
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download" fill="currentColor"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
<path fill-rule="evenodd"
d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
</svg>
Download</a>
<div id="csr-wrapper" class="row mb-3 d-none">
<div class="col-12">
<h2>2. Paste the CSR into the CAcert form</h2>
<p>The CAcert web application allows you to paste a custom Certificate Signing Request (CSR) when you
request a client certificate. Use the button below to copy your CSR to the clipboard.
</p>
<pre id="csr-output"></pre>
<button class="btn btn-primary" id="copy-csr-to-clipboard">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-clipboard-fill" viewBox="0 0 16 16">
<path fill-rule="evenodd"
d="M10 1.5a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-1Zm-5 0A1.5 1.5 0 0 1 6.5 0h3A1.5 1.5 0 0 1 11 1.5v1A1.5 1.5 0 0 1 9.5 4h-3A1.5 1.5 0 0 1 5 2.5v-1Zm-2 0h1v1A2.5 2.5 0 0 0 6.5 5h3A2.5 2.5 0 0 0 12 2.5v-1h1a2 2 0 0 1 2 2V14a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V3.5a2 2 0 0 1 2-2Z"/>
</svg>
Copy CSR to Clipboard
</button>
</div>
</div>
<div id="crt-input-wrapper" class="row mb-3 d-none">
<div class="col-12">
<h2>3. Get your certificate signed</h2>
<p>Go to the <a href="https://secure.cacert.org/account.php?id=3" target="_blank">New client certificate
page</a> of the CAcert web application and click the checkbox "Show advanced options". This will
show
you a text area where you can paste the copied CSR.</p>
<label id="crt-input-label" for="crt-input">Paste the signed certificate from the CAcert web
application</label>
<textarea class="form-control" id="crt-input" name="crt-input" rows="20" autocomplete="off"
placeholder="Paste the certificate here" aria-labelledby="crt-input-label"></textarea>
</div>
</div>
<div id="prepare-download-wrapper" class="row mb-3 d-none">
<div class="col-12">
<h2>4. Build the client certificate file</h2>
<p>You now have all the ingredients for downloading your certificate and corresponding key pair. Enter a
password of your choice and click on the "Prepare Download" button to generate a file that you can
use
in your Browser, email client, or other applications.</p>
<div class="mb-3">
<label for="passwordInput">Password for your client certificate file</label>
<input type="password" class="form-control" id="passwordInput" aria-describedby="nameHelp" required
minlength="8">
</div>
<button class="btn btn-primary" id="generate-p12">
Prepare Download
</button>
</div>
</div>
<div class="row d-none mb-3" id="download-wrapper">
<div class="col-12">
<h2>5. Download ready</h2>
<p>Your key material is ready for download. The downloadable file contains your private
key and your certificate encrypted with your password. You can now use the file to install your
certificate in your browser or other applications.</p>
<a class="btn btn-success" id="download-link" href="#">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-file-earmark-arrow-down-fill" viewBox="0 0 16 16">
<path d="M9.293 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.707A1 1 0 0 0 13.707 4L10 .293A1 1 0 0 0 9.293 0zM9.5 3.5v-2l3 3h-2a1 1 0 0 1-1-1zm-1 4v3.793l1.146-1.147a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 0 1 .708-.708L7.5 11.293V7.5a.5.5 0 0 1 1 0z"/>
</svg>
Download
</a>
</div>
</div>
</div>
<pre id="key"></pre>
<pre id="csr"></pre>
<pre id="crt"></pre>
</div>
</main>
<footer class="sticky-bottom text-bg-dark py-2">
<div class="container">
<p><small>© 2023 CAcert</small></p>
</div>
</footer>
<script src="../public/js/forge.min.js"></script>
<script src="../public/js/bootstrap.bundle.min.js"></script>
<script>
async function postData(url = '', data = {}) {
const response = await fetch(url, {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
document.addEventListener("DOMContentLoaded", function () {
const csrForm = document.getElementById("csr-form");
const crtInput = document.getElementById("crt-input");
const app = {
keyPair: undefined,
certificate: undefined,
progressBar: document.getElementById("progress-bar"),
keyOutput: document.getElementById("key-output"),
csrOutput: document.getElementById("csr-output"),
prepareButton: document.getElementById("generate-p12"),
downloadButton: document.getElementById("download-link"),
updateProgress(percent, valuenow, title, ...tokens) {
this.progressBar.classList.add(...tokens)
this.progressBar.style.width = `${percent}%`;
this.progressBar.setAttribute("aria-valuenow", `${valuenow}`)
this.progressBar.innerHTML = title;
},
redirect: "error",
referrerPolicy: "no-referrer",
body: JSON.stringify(data),
});
return response.json()
}
generateKeyPair(keySize) {
this.updateProgress(40, 2, "Started key generation", 'progress-bar-striped', 'progress-bar-animated');
document.addEventListener("DOMContentLoaded", function () {
const keyElement = document.getElementById('key');
document.getElementById('csr-form').onsubmit = function (event) {
const subject = event.target["nameInput"].value;
const password = event.target["passwordInput"].value;
const keySize = parseInt(event.target["keySize"].value);
if (isNaN(keySize)) {
return false;
}
const statusBlock = document.getElementById('status-block');
const progressBar = document.getElementById('progress-bar');
statusBlock.classList.remove('d-none');
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
progressBar.style.width = "25%";
progressBar.setAttribute("aria-valuenow", "1");
const state = forge.pki.rsa.createKeyPairGenerationState(keySize, 0x10001);
progressBar.innerHTML = 'started key generation';
const startDate = new Date();
const step = function () {
let duration = (new Date()).getTime() - startDate.getTime();
let seconds = Math.floor(duration / 100) / 10;
if (!forge.pki.rsa.stepKeyPairGenerationState(state, 100)) {
setTimeout(step, 1);
progressBar.innerHTML = "key generation running";
} else {
progressBar.classList.remove("progress-bar-animated", 'progress-bar-striped');
progressBar.style.width = "50%";
progressBar.setAttribute("aria-valuenow", "2");
progressBar.innerHTML = "key generated";
const keys = state.keys;
keyElement.innerHTML = forge.pki.privateKeyToPem(keys.privateKey);
const csr = forge.pki.createCertificationRequest();
csr.publicKey = keys.publicKey;
csr.setSubject([{
name: 'commonName',
value: subject,
valueTagClass: forge.asn1.Type.UTF8,
}]);
csr.sign(keys.privateKey, forge.md.sha256.create());
const verified = csr.verify();
if (verified) {
let csrPem = forge.pki.certificationRequestToPem(csr);
document.getElementById("csr").innerHTML = csrPem;
progressBar.style.width = "75%";
progressBar.setAttribute("aria-valuenow", "3");
progressBar.classList.add('progress-bar-striped', 'progress-bar-animated');
progressBar.innerHTML = "key generated, certificate waiting";
const sendButton =
document.getElementById("send-button");
sendButton.addEventListener("click", function () {
postData("/sign/", {"csr": csrPem, "commonName": subject})
.then(data => {
console.log(data);
document.getElementById("crt").innerHTML = data["certificate"];
const certificate = forge.pki.certificateFromPem(data["certificate"]);
// browsers have trouble importing anything but 3des encrypted PKCS#12
const p12asn1 = forge.pkcs12.toPkcs12Asn1(
keys.privateKey, certificate, password,
{algorithm: '3des'}
);
const p12Der = forge.asn1.toDer(p12asn1).getBytes();
const p12B64 = forge.util.encode64(p12Der);
const a = document.createElement('a');
a.download = 'client_certificate.p12';
a.setAttribute('href', 'data:application/x-pkcs12;base64,' + p12B64);
a.appendChild(document.createTextNode("Download"));
document.getElementById('result').appendChild(a);
});
});
sendButton.removeAttribute("disabled");
sendButton.classList.remove("disabled");
function handleCertificateResponse(data) {
document.getElementById("crt").innerHTML = data["certificate"];
let certificates = []
certificates.push(forge.pki.certificateFromPem(data["certificate"]));
for (let certificatePemData of data["ca_chain"]) {
certificates.push(forge.pki.certificateFromPem(certificatePemData));
}
// browsers have trouble importing anything but 3des encrypted PKCS#12
const p12asn1 = forge.pkcs12.toPkcs12Asn1(
keys.privateKey, certificates, password,
{algorithm: '3des'}
);
const p12Der = forge.asn1.toDer(p12asn1).getBytes();
const p12B64 = forge.util.encode64(p12Der);
const downloadLink = document.getElementById('download-link');
downloadLink.download = 'client_certificate.p12';
downloadLink.setAttribute('href', 'data:application/x-pkcs12;base64,' + p12B64);
document.getElementById('download-wrapper').classList.remove("d-none");
progressBar.classList.remove("progress-bar-animated", 'progress-bar-striped');
progressBar.style.width = "100%";
progressBar.innerHTML = i18n.t('keygen.generated', {seconds: seconds}) + ', ' + i18n.t('certificate.received');
progressBar.setAttribute("aria-valuenow", "4");
const state = forge.pki.rsa.createKeyPairGenerationState(keySize, 0x10001);
const startDate = new Date();
return new Promise(done => {
const step = () => {
let duration = (new Date()).getTime() - startDate.getTime();
let seconds = Math.floor(duration / 100) / 10;
if (!forge.pki.rsa.stepKeyPairGenerationState(state, 100)) {
setTimeout(step, 1);
this.progressBar.innerHTML = `Key pair generation running for ${seconds} seconds`;
} else {
this.progressBar.classList.remove("progress-bar-animated", 'progress-bar-striped');
this.progressBar.innerHTML = "Key pair generated";
const keys = state.keys;
document.getElementById("key-wrapper").classList.remove("d-none");
this.keyOutput.innerHTML = forge.pki.privateKeyToPem(keys.privateKey);
this.keyPair = state.keys;
done();
}
}
step();
});
},
generateCSR(subjectName) {
const csr = forge.pki.createCertificationRequest();
csr.publicKey = this.keyPair.publicKey;
csr.setSubject([{
name: 'commonName',
value: subjectName,
valueTagClass: forge.asn1.Type.UTF8,
}]);
csr.sign(this.keyPair.privateKey, forge.md.sha256.create());
const verified = csr.verify();
if (verified) {
let csrPem = forge.pki.certificationRequestToPem(csr);
this.updateProgress(60, 3, "CSR generated");
document.getElementById("csr-output").innerHTML = csrPem;
const csrWrapper = document.getElementById("csr-wrapper");
csrWrapper.classList.remove("d-none");
csrWrapper.scrollIntoView();
document.getElementById("copy-csr-to-clipboard").addEventListener("click", (event) => {
event.preventDefault();
navigator.clipboard.writeText(csrPem)
const crtInputWrapper = document.getElementById("crt-input-wrapper");
crtInputWrapper.classList.remove("d-none");
crtInputWrapper.scrollIntoView();
this.updateProgress(80, 4, "CSR copied to clipboard, waiting for certificate");
});
}
},
handleCertificate(certificate) {
this.certificate = certificate;
const prepareDownloadWrapper = document.getElementById("prepare-download-wrapper");
prepareDownloadWrapper.classList.remove("d-none");
prepareDownloadWrapper.scrollIntoView();
this.progressBar.innerHTML = "Certificate pasted";
this.prepareButton.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
const passwordInput = document.getElementById("passwordInput");
let password = passwordInput.value;
if (password === "" || password.length < 8) {
return false;
}
this.buildKeyStore(password);
});
},
buildKeyStore(password) {
this.updateProgress(100, 5, "Building keystore");
let certificates = [];
certificates.push(this.certificate);
let rootCerts = [];
// add CAcert class 3 certificate from http://www.cacert.org/certs/CAcert_Class3Root_x14E228.crt
rootCerts.push(forge.pki.certificateFromPem("-----BEGIN CERTIFICATE-----\n" +
"MIIGPTCCBCWgAwIBAgIDFOIoMA0GCSqGSIb3DQEBDQUAMHkxEDAOBgNVBAoTB1Jv\n" +
"b3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEiMCAGA1UEAxMZ\n" +
"Q0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYSc3VwcG9y\n" +
"dEBjYWNlcnQub3JnMB4XDTIxMDQxOTEyMTgzMFoXDTMxMDQxNzEyMTgzMFowVDEU\n" +
"MBIGA1UEChMLQ0FjZXJ0IEluYy4xHjAcBgNVBAsTFWh0dHA6Ly93d3cuQ0FjZXJ0\n" +
"Lm9yZzEcMBoGA1UEAxMTQ0FjZXJ0IENsYXNzIDMgUm9vdDCCAiIwDQYJKoZIhvcN\n" +
"AQEBBQADggIPADCCAgoCggIBAKtJNRFIfNImflOUz0Op3SjXQiqL84d4GVh8D57a\n" +
"iX3h++tykA10oZZkq5+gJJlz2uJVdscXe/UErEa4w75/ZI0QbCTzYZzA8pD6Ueb1\n" +
"aQFjww9W4kpCz+JEjCUoqMV5CX1GuYrz6fM0KQhF5Byfy5QEHIGoFLOYZcRD7E6C\n" +
"jQnRvapbjZLQ7N6QxX8KwuPr5jFaXnQ+lzNZ6MMDPWAzv/fRb0fEze5ig1JuLgia\n" +
"pNkVGJGmhZJHsK5I6223IeyFGmhyNav/8BBdwPSUp2rVO5J+TJAFfpPBLIukjmJ0\n" +
"FXFuC3ED6q8VOJrU0gVyb4z5K+taciX5OUbjchs+BMNkJyIQKopPWKcDrb60LhPt\n" +
"XapI19V91Cp7XPpGBFDkzA5CW4zt2/LP/JaT4NsRNlRiNDiPDGCbO5dWOK3z0luL\n" +
"oFvqTpa4fNfVoIZwQNORKbeiPK31jLvPGpKK5DR7wNhsX+kKwsOnIJpa3yxdUly6\n" +
"R9Wb7yQocDggL9V/KcCyQQNokszgnMyXS0XvOhAKq3A6mJVwrTWx6oUrpByAITGp\n" +
"rmB6gCZIALgBwJNjVSKRPFbnr9s6JfOPMVTqJouBWfmh0VMRxXudA/Z0EeBtsSw/\n" +
"LIaRmXGapneLNGDRFLQsrJ2vjBDTn8Rq+G8T/HNZ92ZCdB6K4/jc0m+YnMtHmJVA\n" +
"BfvpAgMBAAGjgfIwge8wDwYDVR0TAQH/BAUwAwEB/zBhBggrBgEFBQcBAQRVMFMw\n" +
"IwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLkNBY2VydC5vcmcvMCwGCCsGAQUFBzAC\n" +
"hiBodHRwOi8vd3d3LkNBY2VydC5vcmcvY2xhc3MzLmNydDBFBgNVHSAEPjA8MDoG\n" +
"CysGAQQBgZBKAgMBMCswKQYIKwYBBQUHAgEWHWh0dHA6Ly93d3cuQ0FjZXJ0Lm9y\n" +
"Zy9jcHMucGhwMDIGA1UdHwQrMCkwJ6AloCOGIWh0dHBzOi8vd3d3LmNhY2VydC5v\n" +
"cmcvY2xhc3MzLmNybDANBgkqhkiG9w0BAQ0FAAOCAgEAxh6td1y0KJvRyI1EEsC9\n" +
"dnYEgyEH+BGCf2vBlULAOBG1JXCNiwzB1Wz9HBoDfIv4BjGlnd5BKdSLm4TXPcE3\n" +
"hnGjH1thKR5dd3278K25FRkTFOY1gP+mGbQ3hZRB6IjDX+CyBqS7+ECpHTms7eo/\n" +
"mARN+Yz5R3lzUvXs3zSX+z534NzRg4i6iHNHWqakFcQNcA0PnksTB37vGD75pQGq\n" +
"eSmx51L6UzrIpn+274mhsaFNL85jhX+lKuk71MGjzwoThbuZ15xmkITnZtRQs6Hh\n" +
"LSIqJWjDILIrxLqYHehK71xYwrRNhFb3TrsWaEJskrhveM0Os/vvoLNkh/L3iEQ5\n" +
"/LnmLMCYJNRALF7I7gsduAJNJrgKGMYvHkt1bo8uIXO8wgNV7qoU4JoaB1ML30QU\n" +
"qGcFr0TI06FFdgK2fwy5hulPxm6wuxW0v+iAtXYx/mRkwQpYbcVQtrIDvx1CT1k5\n" +
"0cQxi+jIKjkcFWHw3kBoDnCos0/ukegPT7aQnk2AbL4c7nCkuAcEKw1BAlSETkfq\n" +
"i5btdlhh58MhewZv1LcL5zQyg8w1puclT3wXQvy8VwPGn0J/mGD4gLLZ9rGcHDUE\n" +
"CokxFoWk+u5MCcVqmGbsyG4q5suS3CNslsHURfM8bQK4oLvHR8LCHEBMRcdFBn87\n" +
"cSvOK6eB1kdGKLA8ymXxZp8=\n" +
"-----END CERTIFICATE-----"));
// add CAcert root certificate from http://www.cacert.org/certs/root_X0F.crt
rootCerts.push(forge.pki.certificateFromPem("-----BEGIN CERTIFICATE-----\n" +
"MIIG7jCCBNagAwIBAgIBDzANBgkqhkiG9w0BAQsFADB5MRAwDgYDVQQKEwdSb290\n" +
"IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB\n" +
"IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA\n" +
"Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO\n" +
"BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi\n" +
"MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ\n" +
"ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC\n" +
"CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ\n" +
"8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6\n" +
"zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y\n" +
"fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7\n" +
"w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc\n" +
"G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k\n" +
"epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q\n" +
"laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ\n" +
"QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU\n" +
"fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826\n" +
"YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAX8w\n" +
"ggF7MB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TAPBgNVHRMBAf8EBTAD\n" +
"AQH/MDQGCWCGSAGG+EIBCAQnFiVodHRwOi8vd3d3LmNhY2VydC5vcmcvaW5kZXgu\n" +
"cGhwP2lkPTEwMFYGCWCGSAGG+EIBDQRJFkdUbyBnZXQgeW91ciBvd24gY2VydGlm\n" +
"aWNhdGUgZm9yIEZSRUUgaGVhZCBvdmVyIHRvIGh0dHA6Ly93d3cuY2FjZXJ0Lm9y\n" +
"ZzAxBgNVHR8EKjAoMCagJKAihiBodHRwOi8vY3JsLmNhY2VydC5vcmcvcmV2b2tl\n" +
"LmNybDAzBglghkgBhvhCAQQEJhYkVVJJOmh0dHA6Ly9jcmwuY2FjZXJ0Lm9yZy9y\n" +
"ZXZva2UuY3JsMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcwAYYWaHR0cDovL29j\n" +
"c3AuY2FjZXJ0Lm9yZzAfBgNVHSMEGDAWgBQWtTIb1Mfz4OaO873SsDrusjkY0TAN\n" +
"BgkqhkiG9w0BAQsFAAOCAgEAR5zXs6IX01JTt7Rq3b+bNRUhbO9vGBMggczo7R0q\n" +
"Ih1kdhS6WzcrDoO6PkpuRg0L3qM7YQB6pw2V+ubzF7xl4C0HWltfzPTbzAHdJtja\n" +
"JQw7QaBlmAYpN2CLB6Jeg8q/1Xpgdw/+IP1GRwdg7xUpReUA482l4MH1kf0W0ad9\n" +
"4SuIfNWQHcdLApmno/SUh1bpZyeWrMnlhkGNDKMxCCQXQ360TwFHc8dfEAaq5ry6\n" +
"cZzm1oetrkSviE2qofxvv1VFiQ+9TX3/zkECCsUB/EjPM0lxFBmu9T5Ih+Eqns9i\n" +
"vmrEIQDv9tNyJHuLsDNqbUBal7OoiPZnXk9LH+qb+pLf1ofv5noy5vX2a5OKebHe\n" +
"+0Ex/A7e+G/HuOjVNqhZ9j5Nispfq9zNyOHGWD8ofj8DHwB50L1Xh5H+EbIoga/h\n" +
"JCQnRtxWkHP699T1JpLFYwapgplivF4TFv4fqp0nHTKC1x9gGrIgvuYJl1txIKmx\n" +
"XdfJzgscMzqpabhtHOMXOiwQBpWzyJkofF/w55e0LttZDBkEsilV/vW0CJsPs3eN\n" +
"aQF+iMWscGOkgLFlWsAS3HwyiYLNJo26aqyWPaIdc8E4ck7Sk08WrFrHIK3EHr4n\n" +
"1FZwmLpFAvucKqgl0hr+2jypyh5puA3KksHF3CsUzjMUvzxMhykh9zrMxQAHLBVr\n" +
"Gwc=\n" +
"-----END CERTIFICATE-----"));
let lastCert = certificates[certificates.length - 1];
while (!lastCert.isIssuer(lastCert)) {
for (let certIndex in rootCerts) {
let rootCert = rootCerts[certIndex];
if (lastCert.isIssuer(rootCert)) {
certificates.push(rootCert);
break;
}
}
lastCert = certificates[certificates.length - 1];
}
// browsers have trouble importing anything but 3des encrypted PKCS#12
const p12asn1 = forge.pkcs12.toPkcs12Asn1(
this.keyPair.privateKey, certificates, password,
{algorithm: '3des'}
);
const p12Der = forge.asn1.toDer(p12asn1).getBytes();
const p12B64 = forge.util.encode64(p12Der);
this.downloadButton.download = 'client_certificate.p12';
this.downloadButton.setAttribute('href', 'data:application/x-pkcs12;base64,' + p12B64);
const downloadWrapper = document.getElementById("download-wrapper");
downloadWrapper.classList.remove("d-none");
downloadWrapper.scrollIntoView();
this.progressBar.innerHTML = "Client certificate ready";
this.progressBar.classList.add("bg-success")
}
setTimeout(step);
return false;
};
csrForm.addEventListener("submit", (event) => {
event.preventDefault();
let valid = csrForm.checkValidity();
if (!valid) {
event.stopPropagation();
}
csrForm.classList.add("was-validated");
if (!valid) {
return;
}
let subjectName = event.target["nameInput"].value;
let keySize = parseInt(event.target["keySize"].value);
if (isNaN(keySize)) {
return;
}
app.generateKeyPair(keySize).then(function () {
app.generateCSR(subjectName);
});
}, false);
crtInput.addEventListener("paste", (event) => {
let crtData = (event.clipboardData || window.clipboardData).getData("text");
try {
const certificate = forge.pki.certificateFromPem(crtData);
app.handleCertificate(certificate);
} catch (e) {
console.warn(e);
return false;
}
});
});
</script>
</body>

Loading…
Cancel
Save