njs icon indicating copy to clipboard operation
njs copied to clipboard

Is it possible generate x509 certificate using njs?

Open sarim opened this issue 2 years ago • 9 comments
trafficstars

What mkcert does. Or @peculiar/x509 npm library has support for. ex: https://codesandbox.io/s/generate-cert-fjwfh?file=/index.html

sarim avatar Aug 07 '23 22:08 sarim

Hi @sarim, thank you for a great question.

A short answer is Yes, it is possible. A longer answer, it is a bit complicated.

The acme-njs repo contains some code that uses pkijs library to create Certificate Signature Request here. it is a bit tricky to get pkijs working in NJS/NGINX runtime, and required extra work (e.g. this). Similarly to CertificationRequest that I was using you can follow the example of creating a self-signed request here

We open-sourced a new repo that enables ACME integration for NGINX. Please check it out: njs-acme. I hope you'll be able to use a similar approach to address your problem of creating a Certificate.

ivanitskiy avatar Aug 07 '23 23:08 ivanitskiy

Wow thank you very much. Thats a lot of good resources to explore. Previously I tried using https://www.npmjs.com/package/@peculiar/x509, but packaging it to one file and fighting with babel to compile it to njs acceptable syntax failed. as njs doesn't have promise, class etc...

I also tried https://github.com/mdn/dom-examples/blob/main/web-crypto/sign-verify/rsassa-pkcs1.js from MDN examples, but it also didn't work.

My goal is to automatically make self signed ssl certs when browsing my local development domains ( https://project-name.test ) . njs-acme looks promising, Hopefully I can get the babel/rollup/tsc configurations from there and make it work this time. Thanks again 🚀

sarim avatar Aug 07 '23 23:08 sarim

Thanks for sharing your use case. Let's encrypt has https://github.com/letsencrypt/pebble for testing integrations. We are using it in integration testing for njs-acme. So you can spin up a pebble instance in your environment and configure NGINX with njs-acme to use that ACME and it would generate certs self-signed certs. you may just need that part of njs-acme, then I guess your use case can be covered partially already. Check out https://github.com/nginxinc/njs-acme/tree/main/integration-tests folder for more info and explore. Feel free to raise an issue if you got any.

ivanitskiy avatar Aug 07 '23 23:08 ivanitskiy

Previously I tried using https://www.npmjs.com/package/@peculiar/x509, but packaging it to one file and fighting with babel to compile it to njs acceptable syntax failed. as njs doesn't have promise, class etc...

Yes, I feel your pain. I ran into exactly those issues when writing njs-acme code. I haven't used https://www.npmjs.com/package/@peculiar/x509 yet. will try it out in some time.

ivanitskiy avatar Aug 07 '23 23:08 ivanitskiy

Yes, I feel your pain. I ran into exactly those issues when writing njs-acme code

yeah :(.

I digged up my old code. I see I had these polyfills,

import 'weakmap-polyfill';
import 'smap/smap-shim';
import "core-js/actual/set";

I started fresh from njs-typescript-starter project, maybe those polyfills aren't needed anymore, or I haven't managed to reach the code-path that needs those polyfills not sure.

So this code:

import * as x509 from "@peculiar/x509";
x509.cryptoProvider.set(crypto);
yarn test

results in

      ----- Error Log -----
      2023/08/11 07:45:45 [error] 22310#22310: *2 js exception: RangeError: Maximum call stack size exceeded
          at main (/home/gittu/njs-ssl/integration-tests/:1)

So maybe I'm getting stuck in these maximum call size exceeded error, and actual code haven't been called yet. So those polyfills maybe will be needed.

Then I tried to use pkijs, seeing it in your repo.

import querystring from 'querystring'
import * as pkijs from 'pkijs'
// import * as asn1js from 'asn1js'

// workaround for PKI.JS to work
globalThis.unescape = querystring.unescape

// make PKI.JS to work with webcrypto
pkijs.setEngine(
  'webcrypto',
  new pkijs.CryptoEngine({ name: 'webcrypto', crypto: crypto })
)
yarn test
 [emerg] 31478#31478: SyntaxError: await in arguments not supported in /home/gittu/njs-ssl/dist/main.js:5487

line 5487 is basically whole pkijs in one line. import * as pkijs from 'pkijs' this line.

Any idea?

sarim avatar Aug 11 '23 02:08 sarim

Update

Seems like I have to use 0.2.1 of babel-preset-njs. The latest 0.7.0 removed couple of plugins as its not needed anymore but I guess its actually needed. pkijs.setEngine call successes with 0.2.1 of babel-preset-njs.

Update 2

Possibility related to https://github.com/jirutka/nginx-binaries/issues/15 nginx-typescript-starter uses nginx binaries from jirutka/nginx-binaries to run tests, and those binaries includes old 0.7 njs. Not latest 0.8.

sarim avatar Aug 11 '23 03:08 sarim

The latest 0.7.0 removed couple of plugins as its not needed anymore but I guess its actually needed. pkijs.setEngine call successes with 0.2.1 of babel-preset-njs.

Can you please investigate where’s the problem? babel-preset-njs 0.7.0 should be compatible with njs 0.7. I would start with tweaking compiler assumptions (you can override them in your babel config). There are optimised for the best performance, not compatibility with every npm library.

and those binaries includes old 0.7 njs. Not latest 0.8.

The periodic CI workflow was stuck due to changes in the latest njs that needed some adjustment of the build scripts. I fixed it now and the new versions will be available shortly.

jirutka avatar Aug 21 '23 22:08 jirutka

Can you please investigate where’s the problem? babel-preset-njs 0.7.0 should be compatible with njs 0.7

From my previous comment: [emerg] 31478#31478: SyntaxError: await in arguments not supported in /home/gittu/njs-ssl/dist/main.js:5487

Googling that error code I find this: https://github.com/nginx/njs/commit/455f6d4f0447a60329c21928d67f61c79ac3b243

    { njs_str("async function f1() {try {f(await f1)} catch(e) {}}"),
      njs_str("SyntaxError: await in arguments not supported in 1") },

So thats the error. await isn't 100% supported, so when you remove async-await polyfill, it errors. So I guess it needs to be transpiled, maybe

fn(await fn2());

To

var r1 = await fn2();
fn(r1);

Another observation njs-acme project also uses "^0.2.1" https://github.com/nginx/njs-acme/blob/main/package.json#L57C6-L57C34

sarim avatar Aug 21 '23 22:08 sarim

{ njs_str("async function f1() {try {f(await f1)} catch(e) {}}"),
 njs_str("SyntaxError: await in arguments not supported in 1") },

Hm, the problem is that it’s not documented, I had no idea that it’s not supported. /cc @xeioex

jirutka avatar Aug 21 '23 23:08 jirutka