Add initial implementation

This commit implements a basic static HTML page that uses Bootstrap 4
for layout and node-forge to generate a RSA key pair and a certificate
signing request. The subject of the CSR and the key size can be chosen
by the user.

The implementation uses gulp to collect static assets and to allow
bootstrap customization.
Jan Dittberner 3 years ago
commit 564c1bd76b

.gitignore vendored

@ -0,0 +1,4 @@

@ -0,0 +1,40 @@
# Browser PKCS#10 CSR generation PoC
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](
## Running
1. Clone the repository
git clone
2. Get dependencies and build assets
cd browser_csr_generation
npm install --global gulp-cli
npm install
3. Run a Python web server with the generated resources
python3 -m http.server -d public
Open http://localhost:8000/ in your browser.
4. Run gulp watch
You can run a [gulp watch](
in a second terminal window to automatically publish changes to the files
in the `src` directory:
gulp watch

@ -0,0 +1,65 @@
const {series, parallel, src, dest, watch} = require('gulp');
const csso = require('gulp-csso');
const del = require('delete');
const rename = require('gulp-rename');
const replace = require('gulp-replace');
const sass = require('gulp-sass');
const sourcemaps = require('gulp-sourcemaps');
const sriHash = require('gulp-sri-hash');
const uglify = require('gulp-uglify');
sass.compiler = require('node-sass');
function clean(cb) {
del(['./public/js/*.js', './public/css/*.css'], cb);
function cssTranspile() {
return src('src/scss/**/*.scss')
function cssMinify() {
return src('public/css/styles.css')
.pipe(rename({extname: '.min.css'}))
function jsMinify() {
return src('src/js/*.js')
.pipe(rename({extname: '.min.js'}))
function publishAssets() {
return src([
function publish() {
return src('src/*.html').pipe(sriHash()).pipe(replace('../public/', '')).pipe(dest('public'));
exports.default = series(
parallel(cssMinify, jsMinify),
); = function () {
watch('src/js/*.js', series(jsMinify, publish));
watch('src/scss/*.scss', series(cssTranspile, cssMinify, publish));
watch('src/*.html', publish);

package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,31 @@
"name": "browser-csr-generation",
"version": "0.1.0",
"description": "Browser based CSR and PKCS#12 generation in JavaScript",
"repository": {
"type": "git",
"url": ""
"keywords": [
"author": "Jan Dittberner",
"license": "GPL-2.0+",
"devDependencies": {
"bootstrap": "^4.5.3",
"delete": "^1.1.0",
"gulp": "^4.0.2",
"gulp-csso": "^4.0.1",
"gulp-rename": "^2.0.0",
"gulp-replace": "^1.0.0",
"gulp-sass": "^4.1.0",
"gulp-sourcemaps": "^3.0.0",
"gulp-sri-hash": "^2.2.1",
"gulp-uglify": "^3.0.2",
"jquery": "^3.5.1",
"node-forge": "^0.10.0",
"popper.js": "^1.16.1"

@ -0,0 +1,111 @@
<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="../public/css/styles.min.css">
<meta name="theme-color" content="#ffffff">
<title>CSR generation in browser</title>
<div class="container">
<h1>CSR generation in 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
<small id="nameHelp" class="form-text text-muted">Please input your name as it should be added to
your certificate</small>
<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"
<label class="form-check-label" for="size3072">3072 Bit</label>
<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 (not recommended</label>
<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>
<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>
<button type="submit" id="gen-csr-button" class="btn btn-primary">Generate Signing Request</button>
<div id="status-block" class="d-none row">
<div class="col-12">
<div class="d-flex align-items-center">
<strong id="status-text">Loading ...</strong>
<div class="spinner-border ml-auto" id="status-spinner" role="status" aria-hidden="true"></div>
<pre id="key"></pre>
<pre id="csr"></pre>
<script src="../public/js/jquery.slim.min.js"></script>
<script src="../public/js/forge.all.min.js"></script>
<script src="../public/js/bootstrap.bundle.min.js"></script>
const keyElement = document.getElementById('key');
document.getElementById('csr-form').onsubmit = function (event) {
const subject =["nameInput"].value;
const keySize = parseInt(["keySize"].value);
if (isNaN(keySize)) {
return false;
const spinner = document.getElementById('status-spinner');
const statusText = document.getElementById('status-text');
const statusBlock = document.getElementById('status-block');
const state = forge.pki.rsa.createKeyPairGenerationState(keySize, 0x10001);
statusText.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);
statusText.innerHTML = `key generation running for ${seconds} seconds`;
} else {
statusText.innerHTML = `key generated in ${seconds} seconds`
const keys = state.keys;
keyElement.innerHTML = forge.pki.privateKeyToPem(keys.privateKey);
const csr = forge.pki.createCertificationRequest();
csr.publicKey = keys.publicKey;
name: 'commonName',
value: subject,
const verified = csr.verify();
if (verified) {
document.getElementById("csr").innerHTML = forge.pki.certificationRequestToPem(csr);
return false;

@ -0,0 +1 @@
@import "node_modules/bootstrap/scss/bootstrap";