Compare commits

..
49 Commits
Author SHA1 Message Date
mcbueller 531be027f5 package-lock update 2025-02-03 15:01:48 +00:00
mcbueller 3deb4ccf9f CA missing required keyUsage and extKeyUsage 2025-01-29 19:08:45 +00:00
Lyndsey Strauss b44c79c3af updating package.json version number 2022-07-05 15:19:27 +00:00
buhler 39dbf747e6 Merge pull request #22 from NationalSecurityAgency/add-new-rdn-types
add new attribute types to certificates
2022-07-05 11:03:29 -04:00
Lyndsey Strauss 663bb10e0b added note on only server cert options in table 2022-07-05 15:01:10 +00:00
Lyndsey Strauss 0c30721bca added table for attribute types 2022-07-05 14:16:56 +00:00
Lyndsey Strauss 5c976801fb add new attribute types to certificates 2022-06-29 21:01:26 +00:00
straussly e7f88431c6 Merge pull request #21 from typoBot3000/main
fix some typos
2022-05-13 14:37:23 -04:00
typoBot3000 f8e3721210 fix some typos
fixed some typos
2022-05-09 18:47:53 -04:00
Lyndsey Strauss e8deada753 updating madcert to 2.3.1 and node-forge version number in package.json 2022-03-30 16:03:00 +00:00
straussly a21293fe94 Merge pull request #20 from NationalSecurityAgency/dependabot/npm_and_yarn/node-forge-1.3.0
Bump node-forge from 1.2.1 to 1.3.0
2022-03-30 10:55:52 -04:00
dependabot[bot] b9067f2342 Bump node-forge from 1.2.1 to 1.3.0
Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.2.1 to 1.3.0.
- [Release notes](https://github.com/digitalbazaar/forge/releases)
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/v1.2.1...v1.3.0)

---
updated-dependencies:
- dependency-name: node-forge
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-03-25 23:54:01 +00:00
straussly aa4746a13e updating version number 2022-02-10 18:03:31 -05:00
straussly d978f6acb9 Merge pull request #19 from NationalSecurityAgency/remove-key-usages
remove nonRepudiation and dataEncipherment key usages
2022-02-10 11:07:54 -05:00
rtbuhle 3bc0fefe5f remove nonRepudiation and datEncipherment key usages 2022-02-10 11:14:03 +00:00
buhler 0828d1b4cf Merge pull request #18 from NationalSecurityAgency/upgrade-mocha-fsextra-yargs
upgrade mocha, fsextra, yargs, and madcert version number
2022-01-28 07:44:23 -05:00
Lyndsey Strauss f14953fc14 upgrade date fns, chai,and node forge 2022-01-27 22:49:10 +00:00
Lyndsey Strauss 52dae35d1d upgrade mocha, fsextra, yargs, and madcert version number 2022-01-27 15:05:20 +00:00
buhler ae650a88a6 Merge pull request #17 from NationalSecurityAgency/server-cert-cn-fix
added fix to automatically fill in cn from server name
2022-01-20 07:20:56 -05:00
straussly 594173b92e Merge pull request #16 from NationalSecurityAgency/dependabot/npm_and_yarn/node-forge-1.0.0
Bump node-forge from 0.10.0 to 1.0.0
2022-01-19 08:43:37 -05:00
Lyndsey Strauss d0d6b2ff0b added fix to automatically fill in cn from server name 2022-01-19 13:42:49 +00:00
dependabot[bot] 8e8b3807da Bump node-forge from 0.10.0 to 1.0.0
Bumps [node-forge](https://github.com/digitalbazaar/forge) from 0.10.0 to 1.0.0.
- [Release notes](https://github.com/digitalbazaar/forge/releases)
- [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/0.10.0...v1.0.0)

---
updated-dependencies:
- dependency-name: node-forge
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2022-01-15 07:53:29 +00:00
Seth Marple swmarpl 3e303ae429 Update version to 2.2.0 2021-07-06 16:56:13 +00:00
buhler b9b59e30d4 Merge pull request #15 from Marpyre/cert-to-json
Add capability to print MADCert certificates via commandline
2021-07-06 12:08:16 -04:00
Seth Marple swmarpl c966de9bec Add documentation for print command and certToJSON function to README.md 2021-07-06 15:42:45 +00:00
Seth Marple swmarpl 7f40edba5b Modifying madcert print commandline api to match existing api 2021-06-24 13:12:51 +00:00
Seth Marple swmarpl 0a339b75ad Add madcert commandline commands and options for cert printing. 2021-06-22 19:18:19 +00:00
Seth Marple swmarpl daaefa8d35 Use global variable across tests 2021-06-21 19:38:21 +00:00
Seth Marple swmarpl b0bd906e5f Remove .only from tests 2021-06-21 19:01:05 +00:00
Seth Marple swmarpl 2b514276e5 Add cert to JSON capability 2021-06-21 18:55:42 +00:00
buhler bd11bed32a Merge pull request #14 from Marpyre/v2.1.3
Release version 2.1.3
2021-06-10 14:57:56 -04:00
Seth Marple swmarpl 371d563b03 Release version 2.1.3 2021-06-10 18:54:43 +00:00
Kyle Miracle cd8ee3ed07 Fix docs typo for CLI (intermediate CA) (#13)
The documentation example describing how to create an intermediate CA has a typo.
2021-06-10 14:27:05 -04:00
buhler 09f5993dda update version to 2.1.2 (#12) 2021-06-10 10:03:48 -04:00
buhlerandMarpyre 34582c77e9 add node.js examples to the readme as well as additional cli examples (#11)
* add node.js examples to the readme as well as additional cli examples

* provide node.js basePath example

Co-authored-by: Marpyre <[email protected]>
2021-06-10 09:25:13 -04:00
Seth Marple swmarpl 40e19eb40c Release version 2.1.1 2021-05-27 13:35:54 +00:00
Seth Marple swmarpl b3aa9a4872 Add keywords to package.json 2021-05-27 13:34:15 +00:00
Seth Marple swmarpl 88dee9e9d2 Release version 2.1.0 2021-05-27 12:30:34 +00:00
buhler 101d1cc751 Merge pull request #10 from Marpyre/certificate-improvements
Add and remove and mark critical extensions based on certificate lint…
2021-05-27 07:28:51 -04:00
Seth Marple swmarpl 9cc105eb20 Add and remove and mark critical extensions based on certificate linting to improve validity of generated certificates. 2021-05-26 19:05:13 +00:00
buhler 31dfef67d3 Merge pull request #9 from Marpyre/v2.0.0
Release version 2.0.0
2021-04-06 11:36:39 -04:00
Seth Marple swmarpl 1aa6f942f2 Release version 2.0.0 2021-04-06 15:28:15 +00:00
buhler 9b68121938 Merge pull request #8 from Marpyre/updates
MADCert Updates
2021-04-05 15:56:44 -04:00
Seth Marple swmarpl 9dc66d7ce6 Update packages. 2021-04-01 19:16:14 +00:00
Seth Marple swmarpl 0120acb529 Replace moment.js with date-fns 2021-04-01 18:55:25 +00:00
Seth Marple swmarpl 7ea29a8849 Remove CRLF endings in favor of LF endings. 2021-04-01 18:39:23 +00:00
Seth Marple swmarpl 1e77afd0d6 Update dependencies. 2021-04-01 18:35:04 +00:00
Seth Marple swmarpl e24fb69f15 Fixing list commands to ignore files and only list directories as CAs. 2021-04-01 18:21:20 +00:00
Seth Marple swmarpl e6eaf78e27 Remove script in favor of using npx to run MADCert binary. Updated README to use npm/npx instead of requiring yarn. 2021-04-01 17:54:39 +00:00
19 changed files with 3916 additions and 2536 deletions

No files matched your search

+2 -3
View File
@@ -1,3 +1,2 @@
# IntelliJ IDEA files
.idea/
*.iml
node_modules/
.idea/
+1 -1
View File
@@ -10,8 +10,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
MADCert uses a number of libraries that are licensed under various licenses:
- [date-fns](https://www.npmjs.com/package/date-fns) is licensed under the MIT license.
- [fs-extra](https://www.npmjs.com/package/fs-extra) is licensed under the MIT license.
- [lodash](https://www.npmjs.com/package/lodash) is licensed under the MIT license.
- [moment](https://www.npmjs.com/package/moment) is licensed under the MIT license.
- [node-forge](https://www.npmjs.com/package/node-forge) is licensed under either the BSD 3 Clause.
- [yargs](https://www.npmjs.com/package/yargs) is licensed under the MIT license.
+212 -80
View File
@@ -1,80 +1,212 @@
# Overview
Use MADCert to create root and intermediate Certificate Authorities, issue user and server certificates, etc for testing purposes.
MADCert is a cross-platform tool that does not require openssl or any other programs to be installed in the Operating System.
## Prerequisites
1. You will need Node installed on your operating system. For Linux and Mac OS X we recommend using nvm
at http://nvm.sh . For Windows we recommend using nvm-windows at https://github.com/coreybutler/nvm-windows .
2. Install the Yarn Package Manager. Follow instructions for your Operating System at https://yarnpkg.com/en/docs/install .
## Installation
From within the MADCert directory:
yarn
## Running MADCert
yarn run madcert <command> <arguments> [options]
## Environment
- `MADCERT_LH_SUBJECT_ALT_DNS_NAME` - Only used when creating a localhost server certificate (--localhost true). When generating a localhost server certificate, MADCert will add a subject alternative DNS name to the certificate. This environment variable can be used to specify the subject alternative DNS name that will be added. If this environment variable is not set the host name will be used.
## Commands, Arguments, and Options
When you run MADCert help (using `yarn run madcert --help`) it will display all of the commands, arguments, and options.
### Arguments
- `name` Name is used for the name of the directory under which PKI files are created. If the `--common-name` option is not used then `name` will also be used for the Common Name in the certificate subject.
- `ca_name` CA name is used to identify the Certificate Authority to use when creating or removing users and servers.
- `root_ca_name` When creating an intermediate Certificate Authority, `root_ca_name` identifies the root CA that issues
the intermediate CA's certificate.
### Options
The MADCert options are grouped in the help output by the commands that they can be used with.
- Creation Options - these options can be used with any of the commands that create PKI files for CAs, servers, and users
- User Creation Options - these options can be used with the `user-create` command
- Server Creation Options - these options can be used with the `server-create` command
- Options - these options are not specific to any particular command
## Certificate Storage Location
By default, the certs created by MADCert will be placed under a "pki" folder in the current working directory. MADCert commands also have the `--path` or `-p` option to specify
a top-level path where the certs will be stored. It is important that all of the certs are placed under the same top-level folder, so either run MADCert from within the same
directory each time or always provide the same path option when calling the MADCert commands.
## Examples
- Create a Certificate Authority (CA)
yarn run madcert ca-create test-root-ca --common-name "Test Root CA" --org "Some Organization" --country "US"
- Create an Intermediate CA (Root CA must exist)
yarn run madcert ca-create test-ca test-root-ca --common-name "Test CA" --org-unit "orgA" --org-unit "A123" --org "Some Organization" --country "US"
- Create a Server Certificate (CA must exist)
yarn run madcert server-create test-server test-ca --common-name "test.server" --org-unit "S333" --org "Some Organization" --country "US" --subject-alt-ip "123.45.23.111"
- Create a localhost Server Certificate (CA must exist)
yarn run madcert server-create localhost test-ca --localhost true --common-name "localhost" --org-unit "D009" --org-unit "DoD" --org "U.S. Government" --country "US"
Note: A subject alternative name is automatically added to the generated certificate. See ENVIRONMENT above.
- Create a User Certificate with CN=test-user (CA must exist)
yarn run madcert user-create test-user test-ca --org-unit "SECURITY" --org-unit "A456" --org "Some Organization" --country "US"
- Create a User Certificate CN=Alice Example (CA must exist)
yarn run madcert user-create test-user test-ca --common-name "Alice Example" --org-unit "SECURITY" --org-unit "A456" --org "Some Organization" --country "US"
# MADCert
Use MADCert to create root and intermediate Certificate Authorities, issue user and server certificates, etc for testing purposes.
MADCert is a cross-platform tool that consists of a certificate generator, a file system certificate manager, and a command line interface.
MADCert does not require openssl or any other programs to be installed in the Operating System.
Both Node.js and CLI examples are provided below.
## Usage
### Node.js Examples
To add MADCert to your project:
npm install madcert
var madcert = require("madcert")
- Create a Certificate Authority (CA)
madcert.createCACert("Test Root CA", {validFrom: "2019-08-08", validTo: "2024-08-09"})
- Create an Intermediate CA (Root CA must exist)
madcert.createIntermediateCACert("Test CA","rootCA", {validFrom: "2019-08-08", validTo: "2024-08-09"});
- Create a Server Certificate (CA must exist)
madcert.createServerCert("Test Server", "test-ca", false, {validFrom: "2019-08-08", validTo: "2024-08-09"});
- Create a localhost Server Certificate (CA must exist)
madcert.createServerCert("localhost", "test-ca", true, {validFrom: "2019-08-08", validTo: "2024-08-09"});
Note: A subject alternative name is automatically added to the generated certificate. See ENVIRONMENT above.
- Create a User Certificate with CN=test-user (CA must exist)
madcert.createUserCert("test-user", "test-ca", {commonName: "test-user", organizationalUnits: ["SECURITY", "A456"], organizations: ["Some Organization"], country: "US", validFrom: "2019-08-08", validTo: "2024-08-09"});
- Create a User Certificate CN=Alice Example (CA must exist)
madcert.createUserCert("Alice Example", "test-ca", {commonName: "Alice Example", organizationalUnits: ["SECURITY", "A456"], organizations: ["Some Organization"], country: "US", validFrom: "2019-08-08", validTo: "2024-08-09"});
- List CA Certificates
madcert.listCACerts();
- List Server Certificates
madcert.listServerCerts();
- List User Certificates
madcert.listUserCerts();
- Remove a CA Certificate
madcert.removeCACert("test-root-ca");
- Remove a Server Certificate
madcert.removeServerCert("test-server", "test-ca");
- Remove a User Certificate
madcert.removeUserCert("test-user", "test-ca");
- Retrieve a CA Certificate as JSON
madcert.caCertToJSON("test-root-ca");
- Retrieve a Server Certificate as JSON
madcert.serverCertToJSON("test-server", "test-ca");
- Retrieve a User Certificate as JSON
madcert.userCertToJSON("test-user", "test-ca");
- Convert name to a File Path Friendly Name
madcert.normalizeName("Test 123");
> 'test-123'
### Additional Node.js Options
#### Certificate Storage Location
By default, the certs created by MADCert will be placed under a "pki" folder in the current working directory. MADCert commands also have the `basePath` option to specify a top-level path where the certs will be stored. It is important that all of the certs are placed under the same top-level folder, so either run MADCert from within the same location each time or always provide the same basepath option when calling the MADCert commands.
- Example Specifying the Certificate Storage Location (available on all certificate related commands)
madcert.createCACert("Test Root CA", {validFrom: "2019-08-08", validTo: "2024-08-09", basePath: "/some/path"})
### CLI Examples
- General Syntax
npx madcert <command> <arguments> [options]
- Help
npx madcert --help
The MADCert options are grouped in the help output by the commands that they can be used with.
- Creation Options - these options can be used with any of the commands that create PKI files for CAs, servers, and users
- User Creation Options - these options can be used with the `user-create` command
- Server Creation Options - these options can be used with the `server-create` command
- Print Options - these options can be used with the `ca-print`, `server-print`, and `user-print` commands
- Options - these options are not specific to any particular command
- Create a Certificate Authority (CA)
npx madcert ca-create test-root-ca --common-name "Test Root CA" --org "Some Organization" --country "US"
- Create an Intermediate CA (Root CA must exist)
npx madcert ca-intermediate-create test-ca test-root-ca --common-name "Test CA" --org-unit "orgA" --org-unit "A123" --org "Some Organization" --country "US"
- Create a Server Certificate (CA must exist)
npx madcert server-create test-server test-ca --common-name "test.server" --org-unit "S333" --org "Some Organization" --country "US" --subject-alt-ip "123.45.23.111"
- Create a localhost Server Certificate (CA must exist)
npx madcert server-create localhost test-ca --localhost true --common-name "localhost" --org-unit "D009" --org-unit "DoD" --org "U.S. Government" --country "US"
Note: A subject alternative name is automatically added to the generated certificate. See ENVIRONMENT above.
- Create a User Certificate with CN=test-user (CA must exist)
npx madcert user-create test-user test-ca --org-unit "SECURITY" --org-unit "A456" --org "Some Organization" --country "US"
- Create a User Certificate CN=Alice Example (CA must exist)
npx madcert user-create test-user test-ca --common-name "Alice Example" --org-unit "SECURITY" --org-unit "A456" --org "Some Organization" --country "US"
- List CA Certificates
npx madcert ca-list
- List Server Certificates
npx madcert server-list
- List User Certificates
npx madcert user-list
- Remove a CA Certificate
npx madcert ca-remove test-ca
- Remove a Server Certificate
npx madcert server-remove test-server test-ca
- Remove a User Certificate
npx madcert user-remove test-user test-ca
- Print a CA Certificate
npx madcert ca-print test-ca
- Print a Server Certificate
npx madcert server-print test-server test-ca
- Print a User Certificate
npx madcert user-print test-user test-ca
### Additional CLI Options
#### Certificate Storage Location
By default, the certs created by MADCert will be placed under a "pki" folder in the current working directory. MADCert commands also have the `--path` or `-p` option to specify a top-level path where the certs will be stored. It is important that all of the certs are placed under the same top-level folder, so either run MADCert from within the same directory each time or always provide the same path option when calling the MADCert commands.
#### Environment
- `MADCERT_LH_SUBJECT_ALT_DNS_NAME` - Only used when creating a localhost server certificate (`--localhost true`). When generating a localhost server certificate, MADCert will add a subject alternative DNS name to the certificate. This environment variable can be used to specify the subject alternative DNS name that will be added. If this environment variable is not set, then the host name will be used.
#### Arguments
| | Alias | Type | Default Value | Description |
| :--- | :----:| :----: | :----: | ---: |
| `name` | | String | | Name is used for the name of the directory under which PKI files are created. If the `--common-name` option is not used then `name` will also be used for the Common Name in the certificate subject. |
| `ca-name` | | String | | CA name is used to identify the Certificate Authority to use when creating or removing users and servers. |
| `root_ca-name` | `-r` | String | | When creating an intermediate Certificate Authority, `root_ca_name` identifies the root CA that issues the intermediate CA's certificate.
| `path` | `-p` | String | '/pki' | Base path for pki |
| `common-name` | `-n` | String | | Common Name in the Distinguished Name |
| `country` | `-c` | String | | Country |
| `locality` | | String | | Locality|
| `state` | `-st` | String | | State|
| `expired` | `-e` | Boolean | | Create an expired certificate |
| `org` | `-o` | Array | | Organization name. This option can be specified multiple times|
| `org-unit` | `-u` | Array | | Organizational unit name. This option can be specified multiple times.|
| `password` | `-w` | String | 'changeme' | Certificate password |
| `subject-alt-dns` | `-d` | Array | | Create certificate with DNS subject alternative name. This option can be specified multiple times and only applies to server certs.|
| `subject-alt-ip` | `-i` | Array | | Create certificate with IP subject alternative name. This option can be specified multiple times and only applies to server certs. |
| `subject-alt-email`| `-m` | Array | | Create certificate with rfc822/email subject alternative name. This option can be specified multiple times and only applies to server certs. |
| `valid-from` | `-f` | Date | | Valid from date in ISO 8601 format |
| `valid-to` | `-t` | Date | | Valid to date in ISO 8601 format|
+1887
View File
File diff suppressed because it is too large. Load diff
+35 -11
View File
@@ -1,13 +1,12 @@
{
"name": "madcert",
"version": "1.0.2",
"version": "2.5.0",
"description": "Make a Development Cert",
"engines": {
"node": ">=10.16.0"
},
"main": "src/certs.js",
"scripts": {
"madcert": "node src/madcert.js",
"test": "mocha --timeout 15000 --recursive"
},
"author": "National Security Agency (https://code.nsa.gov/)",
@@ -20,22 +19,47 @@
"url": "git+https://github.com/NationalSecurityAgency/MADCert.git"
},
"keywords": [
"madcert",
"ca",
"cert",
"certificates",
"testing"
"client certificates",
"crt",
"crypto",
"cryptography",
"development",
"encryption",
"https",
"keypair",
"keys",
"localhost",
"madcert",
"node-forge",
"openssl",
"pem",
"pki",
"p12",
"self",
"server",
"signed",
"ssl",
"testing",
"tls",
"trust",
"user",
"x509"
],
"bugs": {
"url": "https://github.com/NationalSecurityAgency/MADCert/issues"
},
"dependencies": {
"fs-extra": "^9.0.1",
"lodash": "^4.17.11",
"moment": "^2.27.0",
"node-forge": "^0.10.0",
"yargs": "^15.4.0"
"date-fns": "^2.19.0",
"fs-extra": "10.0.0",
"lodash": "^4.17.21",
"node-forge": "^1.3.0",
"yargs": "17.3.1"
},
"devDependencies": {
"chai": "^4.2.0",
"mocha": "^8.2.1"
"chai": "^4.3.4",
"mocha": "9.2.0"
}
}
+200 -196
View File
@@ -1,196 +1,200 @@
const _ = require('lodash');
const forge = require('node-forge');
const fs = require('fs-extra');
forge.options.usePureJavaScript = true;
const pki = forge.pki;
const subjectAttrs = require('./subjectAttributes');
const utils = require('./utils');
const normalizeName = utils.normalizeName;
function buildCACert(keys, options, caSubject = null) {
const cert = pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = utils.getSerial();
const attrs = subjectAttrs(options);
// Set the default expiration, then override with valid-from and/or valid-to
const expired = _.get(options, 'expired', false);
utils.setExpirationDate(cert, expired);
if (options.validFrom) {
// Parse the validFrom from ISO 8601 format
cert.validity.notBefore = new Date(options.validFrom);
}
if (options.validTo) {
// Parse the validTo from ISO 8601 format
cert.validity.notAfter = new Date(options.validTo);
}
cert.setSubject(attrs);
cert.setIssuer(caSubject ? caSubject : attrs);
cert.setExtensions([
{
name: 'basicConstraints',
cA: true,
},
{
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true,
cRLSign: true,
},
{
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true,
},
{
name: 'nsCertType',
client: true,
server: false,
email: true,
objsign: true,
sslCA: true,
emailCA: true,
objCA: true,
},
{
name: 'subjectKeyIdentifier',
},
]);
return cert;
}
function createCACert(caName, options) {
let basePath = _.get(options, 'basePath', 'pki/');
if (basePath.substr(-1) !== '/') basePath += '/';
if (options.validFrom && options.validTo) {
const validFrom = new Date(options.validFrom);
const validTo = new Date(options.validTo);
if (validTo.getTime() < validFrom.getTime()) {
console.log(
`Expiration date ${options.validTo} before start date ${options.validFrom}, aborting creation of new CA certificate`
);
return;
}
}
const caPath = basePath + normalizeName(caName) + '/ca/';
if (!fs.existsSync(caPath)) {
const keys = pki.rsa.generateKeyPair(2048);
const cert = buildCACert(keys, options);
fs.ensureDirSync(caPath);
// self-sign certificate
cert.sign(keys.privateKey, utils.createMessageDigest());
// convert a Forge certificate to PEM
const certPem = pki.certificateToPem(cert);
const privateKeyPem = pki.privateKeyToPem(keys.privateKey);
//write out crt, key PEM, and serial.txt files
fs.outputFileSync(caPath + '/' + 'crt.pem', certPem);
fs.outputFileSync(caPath + '/' + 'key.pem', privateKeyPem);
fs.outputFileSync(caPath + '/' + 'serial.txt', '01');
console.log('Certificate authority ' + caName + ' was created.');
} else {
console.log('Certificate authority already exists');
}
}
function createIntermediateCACert(caName, rootCaName, options) {
let basePath = _.get(options, 'basePath', 'pki/');
if (basePath.substr(-1) !== '/') basePath += '/';
if (options.validFrom && options.validTo) {
const validFrom = new Date(options.validFrom);
const validTo = new Date(options.validTo);
if (validTo.getTime() < validFrom.getTime()) {
console.log(
`Expiration date ${options.validTo} before start date ${options.validFrom}, aborting creation of new intermediate CA certificate`
);
return;
}
}
const newCaPath = basePath + normalizeName(caName) + '/ca/';
const rootCaPath = basePath + normalizeName(rootCaName) + '/ca/';
if (!fs.existsSync(rootCaPath)) {
console.log(
`Certificate Authority ${rootCaName} does not exist, aborting creation of new intermediate CA certificate`
);
return;
}
if (!fs.existsSync(newCaPath)) {
// sign certificate with CA private key
const caCertPem = fs.readFileSync(rootCaPath + '/crt.pem', 'utf8');
const caCert = forge.pki.certificateFromPem(caCertPem);
const rootCaKeyPem = fs.readFileSync(rootCaPath + '/key.pem', 'utf8');
const caKey = forge.pki.privateKeyFromPem(rootCaKeyPem);
const keys = pki.rsa.generateKeyPair(2048);
const cert = buildCACert(keys, options, caCert.subject.attributes);
fs.ensureDirSync(newCaPath);
// self-sign certificate
cert.sign(caKey, utils.createMessageDigest());
// convert a Forge certificate to PEM
const certPem = pki.certificateToPem(cert);
const privateKeyPem = pki.privateKeyToPem(keys.privateKey);
//write out crt, key PEM, and serial.txt files
fs.outputFileSync(newCaPath + '/crt.pem', certPem);
fs.outputFileSync(newCaPath + '/key.pem', privateKeyPem);
fs.outputFileSync(newCaPath + '/serial.txt', '01');
fs.outputFileSync(
basePath + normalizeName(caName) + '/parent.txt',
normalizeName(rootCaName)
);
const bundleFile = `${rootCaPath}/${normalizeName(rootCaName)}.ca-bundle`;
if (!fs.existsSync(bundleFile)) {
//TODO: Create bundle file
} else {
//TODO: Update bundle file
}
console.log('Certificate authority ' + caName + ' was created.');
} else {
console.log('Certificate authority already exists');
}
}
function listCACerts(path) {
utils.listCerts('ca', path);
}
function removeCACert(name, basePath = 'pki/') {
if (basePath.substr(-1) !== '/') basePath += '/';
if (fs.existsSync(basePath + '/' + normalizeName(name))) {
fs.removeSync(basePath + '/' + normalizeName(name));
console.log(
'Certificate Authority ' + name + ' and all associated certificates were removed.'
);
} else {
console.log('Certificate Authority ' + name + ' does not exist.');
}
}
module.exports = {
createCACert,
createIntermediateCACert,
listCACerts,
removeCACert,
};
const _ = require('lodash');
const forge = require('node-forge');
const fs = require('fs-extra');
forge.options.usePureJavaScript = true;
const pki = forge.pki;
const subjectAttrs = require('./subjectAttributes');
const utils = require('./utils');
const normalizeName = utils.normalizeName;
function buildCACert(keys, options, caCert = null) {
const cert = pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = utils.getSerial();
const attrs = subjectAttrs(options);
// Set the default expiration, then override with valid-from and/or valid-to
const expired = _.get(options, 'expired', false);
utils.setExpirationDate(cert, expired);
if (options.validFrom) {
// Parse the validFrom from ISO 8601 format
cert.validity.notBefore = new Date(options.validFrom);
}
if (options.validTo) {
// Parse the validTo from ISO 8601 format
cert.validity.notAfter = new Date(options.validTo);
}
const caSubject = _.get(caCert, "subject.attributes", null);
cert.setSubject(attrs);
cert.setIssuer(caSubject ? caSubject : attrs);
const extensions = [
{
name: 'basicConstraints',
cA: true,
critical: true,
},
{
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
critical: true,
cRLSign: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
},
{
name: 'subjectKeyIdentifier',
},
];
if(caCert){
extensions.push({
name: 'authorityKeyIdentifier',
keyIdentifier: caCert.generateSubjectKeyIdentifier().getBytes(),
});
extensions.push(
{
name: 'extKeyUsage',
serverAuth: true,
critical: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true
});
}
cert.setExtensions(extensions);
return cert;
}
function createCACert(caName, options) {
let basePath = _.get(options, 'basePath', 'pki/');
if (basePath.substr(-1) !== '/') basePath += '/';
if (options.validFrom && options.validTo) {
const validFrom = new Date(options.validFrom);
const validTo = new Date(options.validTo);
if (validTo.getTime() < validFrom.getTime()) {
console.log(
`Expiration date ${options.validTo} before start date ${options.validFrom}, aborting creation of new CA certificate`
);
return;
}
}
const caPath = basePath + normalizeName(caName) + '/ca/';
if (!fs.existsSync(caPath)) {
const keys = pki.rsa.generateKeyPair(2048);
const cert = buildCACert(keys, options);
fs.ensureDirSync(caPath);
// self-sign certificate
cert.sign(keys.privateKey, utils.createMessageDigest());
// convert a Forge certificate to PEM
const certPem = pki.certificateToPem(cert);
const privateKeyPem = pki.privateKeyToPem(keys.privateKey);
//write out crt, key PEM, and serial.txt files
fs.outputFileSync(caPath + '/' + 'crt.pem', certPem);
fs.outputFileSync(caPath + '/' + 'key.pem', privateKeyPem);
fs.outputFileSync(caPath + '/' + 'serial.txt', '01');
console.log('Certificate authority ' + caName + ' was created.');
} else {
console.log('Certificate authority already exists');
}
}
function createIntermediateCACert(caName, rootCaName, options) {
let basePath = _.get(options, 'basePath', 'pki/');
if (basePath.substr(-1) !== '/') basePath += '/';
if (options.validFrom && options.validTo) {
const validFrom = new Date(options.validFrom);
const validTo = new Date(options.validTo);
if (validTo.getTime() < validFrom.getTime()) {
console.log(
`Expiration date ${options.validTo} before start date ${options.validFrom}, aborting creation of new intermediate CA certificate`
);
return;
}
}
const newCaPath = basePath + normalizeName(caName) + '/ca/';
const rootCaPath = basePath + normalizeName(rootCaName) + '/ca/';
if (!fs.existsSync(rootCaPath)) {
console.log(
`Certificate Authority ${rootCaName} does not exist, aborting creation of new intermediate CA certificate`
);
return;
}
if (!fs.existsSync(newCaPath)) {
// sign certificate with CA private key
const caCertPem = fs.readFileSync(rootCaPath + '/crt.pem', 'utf8');
const caCert = forge.pki.certificateFromPem(caCertPem);
const rootCaKeyPem = fs.readFileSync(rootCaPath + '/key.pem', 'utf8');
const caKey = forge.pki.privateKeyFromPem(rootCaKeyPem);
const keys = pki.rsa.generateKeyPair(2048);
const cert = buildCACert(keys, options, caCert);
fs.ensureDirSync(newCaPath);
// self-sign certificate
cert.sign(caKey, utils.createMessageDigest());
// convert a Forge certificate to PEM
const certPem = pki.certificateToPem(cert);
const privateKeyPem = pki.privateKeyToPem(keys.privateKey);
//write out crt, key PEM, and serial.txt files
fs.outputFileSync(newCaPath + '/crt.pem', certPem);
fs.outputFileSync(newCaPath + '/key.pem', privateKeyPem);
fs.outputFileSync(newCaPath + '/serial.txt', '01');
fs.outputFileSync(
basePath + normalizeName(caName) + '/parent.txt',
normalizeName(rootCaName)
);
const bundleFile = `${rootCaPath}/${normalizeName(rootCaName)}.ca-bundle`;
if (!fs.existsSync(bundleFile)) {
//TODO: Create bundle file
} else {
//TODO: Update bundle file
}
console.log('Certificate authority ' + caName + ' was created.');
} else {
console.log('Certificate authority already exists');
}
}
function listCACerts(path) {
utils.listCerts('ca', path);
}
function removeCACert(name, basePath = 'pki/') {
if (basePath.substr(-1) !== '/') basePath += '/';
if (fs.existsSync(basePath + '/' + normalizeName(name))) {
fs.removeSync(basePath + '/' + normalizeName(name));
console.log(
'Certificate Authority ' + name + ' and all associated certificates were removed.'
);
} else {
console.log('Certificate Authority ' + name + ' does not exist.');
}
}
module.exports = {
createCACert,
createIntermediateCACert,
listCACerts,
removeCACert,
};
+56 -56
View File
@@ -1,56 +1,56 @@
const _ = require('lodash');
const forge = require('node-forge');
const fs = require('fs');
const moment = require('moment');
forge.options.usePureJavaScript = true;
const pki = forge.pki;
function getSubject(cert) {
const parts = cert.subject.attributes.map(function(attr) {
return `${attr.shortName}=${attr.value}`;
});
return `/${parts.reverse().join('/')}`;
}
function expired(cert) {
return moment(cert.validity.notAfter).isBefore(moment());
}
function getExpires(cert) {
return moment(cert.validity.notAfter).format('YYMMDDHHmmss[Z]');
}
function createDatabaseEntries(pemCerts, revokedCNs = []) {
return pemCerts.map(pemCert => {
const pem = fs.readFileSync(pemCert, 'utf8');
const cert = pki.certificateFromPem(pem);
const subject = getSubject(cert);
// Determine if the list of revoked CNs contains this cert so we can mark it revoked in the database.
const revokedCn = _.find(revokedCNs, cn => {
return subject.toUpperCase().indexOf(cn.toUpperCase()) > -1;
});
if (revokedCn) {
return `R\t${getExpires(cert)}\t${moment().format('YYMMDDHHmmss[Z]')}\t${cert.serialNumber}\tunknown\t${subject}`;
} else {
return `${expired(cert) ? 'E' : 'V'}\t${getExpires(cert)}\t\t${cert.serialNumber}\tunknown\t${subject}`;
}
});
}
function writeDatabaseFile(entries, dbFile) {
fs.writeFileSync(dbFile, entries.join('\n'));
fs.writeFileSync(`${dbFile}.attr`, 'unique_subject = no'); // Seems to be required by openssl to generate a CRL.
}
function createDatabase(pemCerts, dbFile = 'index.txt', revokedCNs = []) {
writeDatabaseFile(createDatabaseEntries(pemCerts, revokedCNs), dbFile);
}
module.exports = {
create: createDatabase
};
const _ = require('lodash');
const forge = require('node-forge');
const fs = require('fs');
const { isBefore, format } = require('date-fns');
forge.options.usePureJavaScript = true;
const pki = forge.pki;
function getSubject(cert) {
const parts = cert.subject.attributes.map(function(attr) {
return `${attr.shortName}=${attr.value}`;
});
return `/${parts.reverse().join('/')}`;
}
function expired(cert) {
return isBefore(cert.validity.notAfter, new Date());
}
function getExpires(cert) {
return format(cert.validity.notAfter, 'yyMMddHHmmssX');
}
function createDatabaseEntries(pemCerts, revokedCNs = []) {
return pemCerts.map(pemCert => {
const pem = fs.readFileSync(pemCert, 'utf8');
const cert = pki.certificateFromPem(pem);
const subject = getSubject(cert);
// Determine if the list of revoked CNs contains this cert so we can mark it revoked in the database.
const revokedCn = _.find(revokedCNs, cn => {
return subject.toUpperCase().indexOf(cn.toUpperCase()) > -1;
});
if (revokedCn) {
return `R\t${getExpires(cert)}\t${format(new Date(), 'yyMMddHHmmssX')}\t${cert.serialNumber}\tunknown\t${subject}`;
} else {
return `${expired(cert) ? 'E' : 'V'}\t${getExpires(cert)}\t\t${cert.serialNumber}\tunknown\t${subject}`;
}
});
}
function writeDatabaseFile(entries, dbFile) {
fs.writeFileSync(dbFile, entries.join('\n'));
fs.writeFileSync(`${dbFile}.attr`, 'unique_subject = no'); // Seems to be required by openssl to generate a CRL.
}
function createDatabase(pemCerts, dbFile = 'index.txt', revokedCNs = []) {
writeDatabaseFile(createDatabaseEntries(pemCerts, revokedCNs), dbFile);
}
module.exports = {
create: createDatabase
};
+76
View File
@@ -0,0 +1,76 @@
const _ = require('lodash');
const forge = require('node-forge');
const fs = require('fs-extra');
forge.options.usePureJavaScript = true;
const extensionsFilter = [
'name',
'cA',
'critical',
'digitalSignature',
'nonRepudiation',
'keyEncipherment',
'dataEncipherment',
'keyAgreement',
'keyCertSign',
'cRLSign',
'encipherOnly',
'decipherOnly',
'subjectKeyIdentifier',
'altNames',
'serverAuth',
'clientAuth',
'codeSigning',
'emailProtection',
'timeStamping',
'client',
'server',
'email',
'objsign',
'reserved:',
'sslCA',
'emailCA',
'objCA',
];
/**
*
* @param {Array} attributes
* @returns {String} Distinguished name
*/
function attributesToDN(attributes){
let dn = '';
for (let i=0; i<attributes.length; i++){
const attribute = attributes[i];
dn += `${attribute.shortName}=${attribute.value}${i<attributes.length-1 ? ',' : ''}`
}
return dn;
}
/**
*
* @param {String} path Path to pem certificate
* @param {Array} propertiesFilter Optional Array of Strings to filter response object properties by
* @returns {Object} Simplified JSON representation of pem certificate
*/
const certToJSON = function certToJSON(path, propertiesFilter) {
const crt = fs.readFileSync(path);
const forgeCrtObj = forge.pki.certificateFromPem(crt.toString());
const crtJSON = _.pick(forgeCrtObj, ['version', 'serialNumber', 'validity']);
crtJSON.subject = attributesToDN(forgeCrtObj.subject.attributes);
crtJSON.issuer = attributesToDN(forgeCrtObj.issuer.attributes);
crtJSON['signature algorithm'] = forge.oids[forgeCrtObj.signatureOid];
crtJSON.extensions = _.transform(forgeCrtObj.extensions, (result, value) => {
result.push(_.pick(value, extensionsFilter));
}, []);
if(propertiesFilter && propertiesFilter.length > 0)
return _.pick(crtJSON, propertiesFilter);
return crtJSON;
}
module.exports = certToJSON;
+120 -100
View File
@@ -1,100 +1,120 @@
const fs = require('fs-extra');
const _ = require('lodash');
const caDb = require('./caDatabase');
const ca = require('./ca');
const server = require('./server');
const user = require('./user');
const utils = require('./utils');
const normalizeName = utils.normalizeName;
const createSubjectAttributes = require('./subjectAttributes');
function jsonListCerts(basePath = 'pki/') {
const certs = {};
if (basePath.substr(-1) !== '/') basePath += '/';
if (fs.existsSync(basePath)) {
certs.ca = [];
const files = fs.readdirSync(basePath);
_.forEach(files, filename => {
if (fs.lstatSync(basePath + filename).isDirectory()) {
const caCerts = {
name: filename,
servers: [],
users: [],
};
const tempServersBase = `${basePath}${filename}/servers/`;
if (fs.existsSync(tempServersBase)) {
const serverFiles = fs.readdirSync(tempServersBase);
_.forEach(serverFiles, serverName => {
caCerts.servers.push(serverName);
});
}
const tempUsersBase = `${basePath}${filename}/users/`;
if (fs.existsSync(tempUsersBase)) {
const userFiles = fs.readdirSync(tempUsersBase);
_.forEach(userFiles, userName => {
caCerts.users.push(userName);
});
}
certs.ca.push(caCerts);
}
});
}
return certs;
}
function getCertFilenames(type, caName, basePath) {
let certs = [];
if (!basePath.endsWith('/')) basePath += '/';
const certDir = `${basePath}${normalizeName(caName)}/${type}`;
if (fs.existsSync(certDir)) {
const certNames = fs.readdirSync(certDir);
certNames.forEach(certName => {
if (fs.existsSync(`${certDir}/${certName}/crt.pem`)) {
certs.push(`${certDir}/${certName}/crt.pem`);
}
if (fs.existsSync(`${certDir}/${certName}/expired/crt.pem`)) {
certs.push(`${certDir}/${certName}/expired/crt.pem`);
}
});
}
return certs;
}
function createCertDatabase(caName, { basePath = 'pki/', revokedCNs = [] } = {}) {
const serverCerts = getCertFilenames('users', caName, basePath);
const userCerts = getCertFilenames('servers', caName, basePath);
caDb.create(
userCerts.concat(serverCerts),
`${basePath}/${normalizeName(caName)}/ca/index.txt`,
revokedCNs
);
}
const certs = {
createCACert: ca.createCACert,
createIntermediateCACert: ca.createIntermediateCACert,
listCACerts: ca.listCACerts,
removeCACert: ca.removeCACert,
createServerCert: server.createServerCert,
listServerCerts: server.listServerCerts,
removeServerCert: server.removeServerCert,
createUserCert: user.createUserCert,
listUserCerts: user.listUserCerts,
removeUserCert: user.removeUserCert,
listCerts: jsonListCerts,
createCertDatabase,
normalizeName,
createSubjectAttributes,
};
module.exports = certs;
const fs = require('fs-extra');
const _ = require('lodash');
const path = require('path');
const caDb = require('./caDatabase');
const ca = require('./ca');
const server = require('./server');
const user = require('./user');
const utils = require('./utils');
const normalizeName = utils.normalizeName;
const createSubjectAttributes = require('./subjectAttributes');
const certToJSON = require('./certToJSON');
function jsonListCerts(basePath = 'pki/') {
const certs = {};
if (basePath.substr(-1) !== '/') basePath += '/';
if (fs.existsSync(basePath)) {
certs.ca = [];
const files = fs.readdirSync(basePath);
_.forEach(files, filename => {
if (fs.lstatSync(basePath + filename).isDirectory()) {
const caCerts = {
name: filename,
servers: [],
users: [],
};
const tempServersBase = `${basePath}${filename}/servers/`;
if (fs.existsSync(tempServersBase)) {
const serverFiles = fs.readdirSync(tempServersBase);
_.forEach(serverFiles, serverName => {
caCerts.servers.push(serverName);
});
}
const tempUsersBase = `${basePath}${filename}/users/`;
if (fs.existsSync(tempUsersBase)) {
const userFiles = fs.readdirSync(tempUsersBase);
_.forEach(userFiles, userName => {
caCerts.users.push(userName);
});
}
certs.ca.push(caCerts);
}
});
}
return certs;
}
function getCertFilenames(type, caName, basePath) {
let certs = [];
if (!basePath.endsWith('/')) basePath += '/';
const certDir = `${basePath}${normalizeName(caName)}/${type}`;
if (fs.existsSync(certDir)) {
const certNames = fs.readdirSync(certDir);
certNames.forEach(certName => {
if (fs.existsSync(`${certDir}/${certName}/crt.pem`)) {
certs.push(`${certDir}/${certName}/crt.pem`);
}
if (fs.existsSync(`${certDir}/${certName}/expired/crt.pem`)) {
certs.push(`${certDir}/${certName}/expired/crt.pem`);
}
});
}
return certs;
}
function createCertDatabase(caName, { basePath = 'pki/', revokedCNs = [] } = {}) {
const serverCerts = getCertFilenames('users', caName, basePath);
const userCerts = getCertFilenames('servers', caName, basePath);
caDb.create(
userCerts.concat(serverCerts),
`${basePath}/${normalizeName(caName)}/ca/index.txt`,
revokedCNs
);
}
function caCertToJSONWrapper(basePath, caName, propertiesFilter){
const certPath = basePath ? path.join(basePath, normalizeName(caName), 'ca/crt.pem') : path.join(__dirname, 'pki', normalizeName(caName), 'ca/crt.pem');
return certToJSON(certPath, propertiesFilter);
}
function serverCertToJSONWrapper(basePath, caName, serverName, propertiesFilter){
const certPath = basePath ? path.join(basePath, normalizeName(caName), 'servers', serverName, 'crt.pem') : path.join(__dirname, 'pki', normalizeName(caName), 'servers', serverName, 'crt.pem');
return certToJSON(certPath, propertiesFilter);
}
function userCertToJSONWrapper(basePath, caName, userName, propertiesFilter){
const certPath = basePath ? path.join(basePath, normalizeName(caName), 'users', userName, 'crt.pem') : path.join(__dirname, 'pki', normalizeName(caName), 'users', userName, 'crt.pem');
return certToJSON(certPath, propertiesFilter);
}
const certs = {
createCACert: ca.createCACert,
createIntermediateCACert: ca.createIntermediateCACert,
listCACerts: ca.listCACerts,
removeCACert: ca.removeCACert,
createServerCert: server.createServerCert,
listServerCerts: server.listServerCerts,
removeServerCert: server.removeServerCert,
createUserCert: user.createUserCert,
listUserCerts: user.listUserCerts,
removeUserCert: user.removeUserCert,
listCerts: jsonListCerts,
createCertDatabase,
normalizeName,
createSubjectAttributes,
caCertToJSON: caCertToJSONWrapper,
serverCertToJSON: serverCertToJSONWrapper,
userCertToJSON: userCertToJSONWrapper,
};
module.exports = certs;
Regular → Executable
+273 -225
View File
@@ -1,225 +1,273 @@
#!/usr/bin/env node
const version = require('../package.json').version;
const yargs = require('yargs');
const certs = require('./certs');
let executed = false;
const argv = yargs
.scriptName('madcert')
.usage('madcert <cmd>')
.command('ca-create <name>', 'create a certificate authority', {}, function(argv) {
executed = true;
certs.createCACert(argv.name, {
basePath: argv.path,
commonName: argv['common-name'],
country: argv.country,
expired: argv.expired,
organizations: argv.org,
organizationalUnits: argv['org-unit'],
validFrom: argv['valid-from'],
validTo: argv['valid-to'],
});
})
.command(
'ca-intermediate-create <name> <root_ca_name>',
'create an intermediate certificate authority',
{},
function(argv) {
executed = true;
certs.createIntermediateCACert(argv.name, argv.root_ca_name, {
basePath: argv.path,
commonName: argv['common-name'],
country: argv.country,
expired: argv.expired,
organizations: argv.org,
organizationalUnits: argv['org-unit'],
validFrom: argv['valid-from'],
validTo: argv['valid-to'],
});
}
)
.command('ca-list', 'list certificate authorities', {}, function(argv) {
executed = true;
certs.listCACerts(argv.path);
})
.command(
'ca-remove <name>',
'remove a certificate authority and all associated users and servers',
{},
function(argv) {
executed = true;
certs.removeCACert(argv.name, argv.path);
}
)
.command('server-create <name> <ca_name>', 'create a server certificate', {}, function(argv) {
executed = true;
certs.createServerCert(argv.name, argv.ca_name, argv.localhost, {
basePath: argv.path,
commonName: argv['common-name'],
country: argv.country,
expired: argv.expired,
organizations: argv.org,
organizationalUnits: argv['org-unit'],
password: argv.password,
rootCaName: argv['root-ca-name'],
subjectAltDnsNames: argv['subject-alt-dns'],
subjectAltIpNames: argv['subject-alt-ip'],
validFrom: argv['valid-from'],
validTo: argv['valid-to'],
});
})
.command('server-list', 'list server certificates', {}, function(argv) {
executed = true;
certs.listServerCerts(argv.path);
})
.command('server-remove <name> <ca_name>', 'remove a server certificate', {}, function(argv) {
executed = true;
certs.removeServerCert(argv.name, argv.ca_name, argv.path);
})
.command('user-create <name> <ca_name>', 'create a user certificate', {}, function(argv) {
executed = true;
certs.createUserCert(argv.name, argv.ca_name, {
basePath: argv.path,
commonName: argv['common-name'],
country: argv.country,
expired: argv.expired,
rootCaName: argv['root-ca-name'],
organizations: argv.org,
organizationalUnits: argv['org-unit'],
password: argv.password,
subjectAltEmailNames: argv['subject-alt-email'],
validFrom: argv['valid-from'],
validTo: argv['valid-to'],
});
})
.command('user-list', 'list user certificates', {}, function(argv) {
executed = true;
certs.listUserCerts(argv.path);
})
.command('user-remove <name> <ca_name>', 'remove a user certificates', {}, function(argv) {
executed = true;
certs.removeUserCert(argv.name, argv.ca_name, argv.path);
})
.command(
'create-db <ca_name>',
'create an openssl database file from existing certs',
{},
function(argv) {
executed = true;
certs.createCertDatabase(argv.ca_name, {
basePath: argv.path ? argv.path : undefined,
});
}
)
.option('path', {
alias: 'p',
describe: 'Base path for pki.',
default: 'pki/',
requiresArg: true,
})
.option('common-name', {
alias: 'n',
describe: 'Common Name in the Distinguished Name.',
requiresArg: true,
})
.option('country', {
alias: 'c',
describe: 'Country.',
default: 'US',
requiresArg: true,
})
.option('expired', {
alias: 'e',
describe: 'Create an expired certificate.',
type: 'boolean',
})
.option('org', {
alias: 'o',
describe: 'Organization name. This option can be specified multiple times.',
type: 'array',
requiresArg: true,
})
.option('org-unit', {
alias: 'u',
describe: 'Organizational unit name. This option can be specified multiple times.',
type: 'array',
requiresArg: true,
})
.option('root-ca-name', {
alias: 'r',
describe: 'Root CA name.',
requiresArg: true,
})
.option('localhost', {
alias: 'l',
describe: 'Create a localhost server certificate with subject alternative names.',
type: 'boolean',
default: false,
})
.option('password', {
alias: 'w',
describe: 'Create the key with the the password (defaults to "changeme").',
type: 'string',
default: 'changeme',
requiresArg: true,
})
.option('subject-alt-dns', {
alias: 'd',
describe:
'Create certificate with DNS subject alternative name. This option can be specified multiple times.',
type: 'array',
default: [],
requiresArg: true,
})
.option('subject-alt-ip', {
alias: 'i',
describe:
'Create certificate with IP subject alternative name. This option can be specified multiple times.',
type: 'array',
default: [],
requiresArg: true,
})
.option('subject-alt-email', {
alias: ['m', 'subject-alt-rfc822'],
describe:
'Create certificate with rfc822/email subject alternative name. This option can be specified multiple times.',
type: 'array',
default: [],
requiresArg: true,
})
.option('valid-from', {
alias: 'f',
describe: 'Valid from date in ISO 8601 format.',
requiresArg: true,
})
.option('valid-to', {
alias: 't',
describe: 'Valid to date in ISO 8601 format.',
requiresArg: true,
})
.alias('version', 'v')
.alias('h', 'help')
.conflicts('expired', 'valid-to')
.version(version)
.help('help')
.group('common-name', 'Creation Options:')
.group('country', 'Creation Options:')
.group('expired', 'Creation Options:')
.group('org-unit', 'Creation Options:')
.group('org', 'Creation Options:')
.group('root-ca-name', 'Creation Options:')
.group('valid-from', 'Creation Options:')
.group('valid-to', 'Creation Options:')
.group('password', 'User Creation Options:')
.group('subject-alt-email', 'User Creation Options:')
.group('localhost', 'Server Creation Options:')
.group('password', 'Server Creation Options:')
.group('subject-alt-dns', 'Server Creation Options:')
.group('subject-alt-ip', 'Server Creation Options:')
.wrap(yargs.terminalWidth()).argv;
if (!executed) {
yargs.showHelp();
}
#!/usr/bin/env node
const version = require('../package.json').version;
const util = require('util')
const yargs = require('yargs');
const certs = require('./certs');
let executed = false;
const argv = yargs
.scriptName('madcert')
.usage('madcert <cmd>')
.command('ca-create <name>', 'create a certificate authority', {}, function(argv) {
executed = true;
certs.createCACert(argv.name, {
basePath: argv.path,
commonName: argv['common-name'],
country: argv.country,
expired: argv.expired,
organizations: argv.org,
organizationalUnits: argv['org-unit'],
validFrom: argv['valid-from'],
validTo: argv['valid-to'],
});
})
.command(
'ca-intermediate-create <name> <root_ca_name>',
'create an intermediate certificate authority',
{},
function(argv) {
executed = true;
certs.createIntermediateCACert(argv.name, argv.root_ca_name, {
basePath: argv.path,
commonName: argv['common-name'],
country: argv.country,
expired: argv.expired,
organizations: argv.org,
organizationalUnits: argv['org-unit'],
validFrom: argv['valid-from'],
validTo: argv['valid-to'],
});
}
)
.command('ca-list', 'list certificate authorities', {}, function(argv) {
executed = true;
certs.listCACerts(argv.path);
})
.command(
'ca-print <name>',
'print certificate authority details',
{},
function(argv) {
executed = true;
console.log(util.inspect(certs.caCertToJSON(argv.path, argv.name, argv.properties), {showHidden: false, depth: null}));
}
)
.command(
'ca-remove <name>',
'remove a certificate authority and all associated users and servers',
{},
function(argv) {
executed = true;
certs.removeCACert(argv.name, argv.path);
}
)
.command('server-create <name> <ca_name>', 'create a server certificate', {}, function(argv) {
executed = true;
certs.createServerCert(argv.name, argv.ca_name, argv.localhost, {
basePath: argv.path,
commonName: argv['common-name'],
country: argv.country,
locality: argv.locality,
state: argv.state,
expired: argv.expired,
organizations: argv.org,
organizationalUnits: argv['org-unit'],
password: argv.password,
rootCaName: argv['root-ca-name'],
subjectAltDnsNames: argv['subject-alt-dns'],
subjectAltIpNames: argv['subject-alt-ip'],
validFrom: argv['valid-from'],
validTo: argv['valid-to'],
});
})
.command('server-list', 'list server certificates', {}, function(argv) {
executed = true;
certs.listServerCerts(argv.path);
})
.command(
'server-print <name> <ca_name>',
'print server certificate details',
{},
function(argv) {
executed = true;
console.log(util.inspect(certs.serverCertToJSON(argv.path, argv.ca_name, argv.name, argv.properties), {showHidden: false, depth: null}));
}
)
.command('server-remove <name> <ca_name>', 'remove a server certificate', {}, function(argv) {
executed = true;
certs.removeServerCert(argv.name, argv.ca_name, argv.path);
})
.command('user-create <name> <ca_name>', 'create a user certificate', {}, function(argv) {
executed = true;
certs.createUserCert(argv.name, argv.ca_name, {
basePath: argv.path,
commonName: argv['common-name'],
country: argv.country,
locality: argv.locality,
state: argv.state,
expired: argv.expired,
rootCaName: argv['root-ca-name'],
organizations: argv.org,
organizationalUnits: argv['org-unit'],
password: argv.password,
subjectAltEmailNames: argv['subject-alt-email'],
validFrom: argv['valid-from'],
validTo: argv['valid-to'],
});
})
.command('user-list', 'list user certificates', {}, function(argv) {
executed = true;
certs.listUserCerts(argv.path);
})
.command(
'user-print <name> <ca_name>',
'print user certificate details',
{},
function(argv) {
executed = true;
console.log(util.inspect(certs.userCertToJSON(argv.path, argv.ca_name, argv.name, argv.properties), {showHidden: false, depth: null}));
}
)
.command('user-remove <name> <ca_name>', 'remove a user certificates', {}, function(argv) {
executed = true;
certs.removeUserCert(argv.name, argv.ca_name, argv.path);
})
.command(
'create-db <ca_name>',
'create an openssl database file from existing certs',
{},
function(argv) {
executed = true;
certs.createCertDatabase(argv.ca_name, {
basePath: argv.path ? argv.path : undefined,
});
}
)
.option('path', {
alias: 'p',
describe: 'Base path for pki.',
default: 'pki/',
requiresArg: true,
})
.option('common-name', {
alias: 'n',
describe: 'Common Name in the Distinguished Name.',
requiresArg: true,
})
.option('country', {
alias: 'c',
describe: 'Country.',
default: 'US',
requiresArg: true,
})
.option('locality', {
describe: 'Locality',
})
.option('state', {
alias: 'st',
describe: 'State',
})
.option('expired', {
alias: 'e',
describe: 'Create an expired certificate.',
type: 'boolean',
})
.option('org', {
alias: 'o',
describe: 'Organization name. This option can be specified multiple times.',
type: 'array',
requiresArg: true,
})
.option('org-unit', {
alias: 'u',
describe: 'Organizational unit name. This option can be specified multiple times.',
type: 'array',
requiresArg: true,
})
.option('root-ca-name', {
alias: 'r',
describe: 'Root CA name.',
requiresArg: true,
})
.option('localhost', {
alias: 'l',
describe: 'Create a localhost server certificate with subject alternative names.',
type: 'boolean',
default: false,
})
.option('password', {
alias: 'w',
describe: 'Create the key with the the password (defaults to "changeme").',
type: 'string',
default: 'changeme',
requiresArg: true,
})
.option('subject-alt-dns', {
alias: 'd',
describe:
'Create certificate with DNS subject alternative name. This option can be specified multiple times.',
type: 'array',
default: [],
requiresArg: true,
})
.option('subject-alt-ip', {
alias: 'i',
describe:
'Create certificate with IP subject alternative name. This option can be specified multiple times.',
type: 'array',
default: [],
requiresArg: true,
})
.option('subject-alt-email', {
alias: ['m', 'subject-alt-rfc822'],
describe:
'Create certificate with rfc822/email subject alternative name. This option can be specified multiple times.',
type: 'array',
default: [],
requiresArg: true,
})
.option('valid-from', {
alias: 'f',
describe: 'Valid from date in ISO 8601 format.',
requiresArg: true,
})
.option('valid-to', {
alias: 't',
describe: 'Valid to date in ISO 8601 format.',
requiresArg: true,
})
.option('properties', {
describe: 'Properties to filter print result. This option can be specified multiple times.',
type: 'array',
default: [],
requiresArg: true,
})
.alias('version', 'v')
.alias('h', 'help')
.conflicts('expired', 'valid-to')
.version(version)
.help('help')
.group('common-name', 'Creation Options:')
.group('country', 'Creation Options:')
.group('locality', 'Creation Options:')
.group('state', 'Creation Options:')
.group('expired', 'Creation Options:')
.group('org-unit', 'Creation Options:')
.group('org', 'Creation Options:')
.group('root-ca-name', 'Creation Options:')
.group('valid-from', 'Creation Options:')
.group('valid-to', 'Creation Options:')
.group('password', 'User Creation Options:')
.group('subject-alt-email', 'User Creation Options:')
.group('localhost', 'Server Creation Options:')
.group('password', 'Server Creation Options:')
.group('subject-alt-dns', 'Server Creation Options:')
.group('subject-alt-ip', 'Server Creation Options:')
.group('properties', 'Print Options:')
.wrap(yargs.terminalWidth()).argv;
if (!executed) {
yargs.showHelp();
}
+305 -310
View File
@@ -1,310 +1,305 @@
const _ = require('lodash');
const forge = require('node-forge');
const fs = require('fs-extra');
const os = require('os');
const path = require('path');
forge.options.usePureJavaScript = true;
const pki = forge.pki;
const subjectAttrs = require('./subjectAttributes');
const utils = require('./utils');
const normalizeName = utils.normalizeName;
const DNS_TYPE = 2;
const IP_TYPE = 7;
const madcertLhSubjectAltDNSName = process.env.MADCERT_LH_SUBJECT_ALT_DNS_NAME || '';
function getIpAddresses() {
const localIPs = [];
// Pull the IP address
const nic = os.networkInterfaces();
for (let i in nic) {
const names = nic[i];
for (let j = 0; j < names.length; j++) {
const k = names[j];
if (k.family === 'IPv4' && k.address !== '127.0.0.1' && !k.internal) {
localIPs.push(k.address);
}
}
}
return localIPs;
}
function buildServerCert(keys, caName, caSubject, localhost, options) {
const cert = pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = utils.getSerial();
// Set the default expiration, then override with valid-from and/or valid-to
const expired = _.get(options, 'expired', false);
utils.setExpirationDate(cert, expired);
if (options.validFrom) {
// Parse the validFrom from ISO 8601 format
cert.validity.notBefore = new Date(options.validFrom);
}
if (options.validTo) {
// Parse the validTo from ISO 8601 format
cert.validity.notAfter = new Date(options.validTo);
}
const attrs = subjectAttrs(options);
cert.setSubject(attrs);
cert.setIssuer(caSubject);
const extensions = [
{
name: 'basicConstraints',
cA: false,
},
{
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true,
},
{
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true,
},
{
name: 'nsCertType',
client: true,
server: true,
email: true,
objsign: true,
sslCA: true,
emailCA: true,
objCA: true,
},
{
name: 'subjectKeyIdentifier',
},
];
const altNames = [];
// Create the subject alternative names if this is creating the localhost certificate
if (localhost) {
// If the environment variable MADCERT_LH_SUBJECT_ALT_DNS_NAME is not set use the hostname
const localHostName = madcertLhSubjectAltDNSName || os.hostname();
// Add to the extensions object
altNames.push.apply(altNames, [
{
type: DNS_TYPE,
value: localHostName,
},
{
type: DNS_TYPE,
value: 'localhost',
},
{
type: IP_TYPE,
ip: '127.0.0.1',
},
]);
const localIPs = getIpAddresses().map(ip => {
return {
type: IP_TYPE,
ip,
};
});
altNames.push.apply(altNames, localIPs);
}
// Add any subject alternative names provided on the command line
_.each(_.get(options, 'subjectAltDnsNames', []), name => {
altNames.push({
type: DNS_TYPE,
value: name,
});
});
_.each(_.get(options, 'subjectAltIpNames', []), name => {
altNames.push({
type: IP_TYPE,
ip: name,
});
});
if (!_.isEmpty(altNames)) {
extensions.push.apply(extensions, [{ name: 'subjectAltName', altNames: altNames }]);
}
// Add all extensions
cert.setExtensions(extensions);
return cert;
}
function createServerCert(
serverName,
caCertName,
localhost,
options,
callback = (err, data) => {}
) {
let basePath = _.get(options, 'basePath', 'pki/');
if (basePath.substr(-1) !== '/') basePath += '/';
if (options.validFrom && options.validTo) {
const validFrom = new Date(options.validFrom);
const validTo = new Date(options.validTo);
if (validTo.getTime() < validFrom.getTime()) {
const err = {
message: `Expiration date ${options.validTo} before start date ${options.validFrom}, aborting creation of new server certificate`,
};
console.error(err.message);
callback(err);
return;
}
}
let expired = _.get(options, 'expired', false);
// If valid-to was passed to madcert then check if it is in the past
if (options.validTo) {
const date = new Date(options.validTo);
if (date.getTime() < new Date().getTime()) {
expired = true;
}
}
const password = _.get(options, 'password', 'changeme');
const caPath = path.join(basePath, normalizeName(caCertName), '/ca/');
const serverPath = path.join(basePath, normalizeName(caCertName), '/servers/');
const serverDir = path.join(
serverPath,
normalizeName(serverName),
options.expired ? '/expired' : ''
);
if (!fs.existsSync(caPath)) {
const err = {
message: `Certificate Authority ${caCertName} does not exist, aborting creation of new server certificate`,
};
console.error(err.message);
callback(err);
return;
}
const serverCertPath = path.join(serverDir, '/crt.pem');
const serverKeyPath = path.join(serverDir, '/key.pem');
const serverP12Path = path.join(serverDir, '/bundle.p12');
if (
!fs.existsSync(serverDir) ||
!fs.existsSync(serverCertPath) ||
!fs.existsSync(serverKeyPath) ||
!fs.existsSync(serverP12Path)
) {
const keys = pki.rsa.generateKeyPair(2048);
let rootCaCert;
const rootCaName = _.get(options, 'rootCaName', null);
if (rootCaName !== null) {
const rootCaPath = path.join(basePath, normalizeName(rootCaName), '/ca/');
if (!fs.existsSync(caPath)) {
const err = {
message: `Root Certificate Authority ${rootCaName} does not exist, aborting creation of new server certificate`,
};
console.error(err.message);
callback(err);
return;
}
const rootCaCertPem = fs.readFileSync(path.join(rootCaPath, '/crt.pem'), 'utf8');
rootCaCert = forge.pki.certificateFromPem(rootCaCertPem);
}
// sign certificate with CA private key
const caCertPem = fs.readFileSync(path.join(caPath, '/crt.pem'), 'utf8');
const caCert = forge.pki.certificateFromPem(caCertPem);
const caKeyPem = fs.readFileSync(path.join(caPath, '/key.pem'), 'utf8');
const caKey = forge.pki.privateKeyFromPem(caKeyPem);
const cert = buildServerCert(
keys,
caCertName,
caCert.subject.attributes,
localhost,
options
);
fs.ensureDirSync(serverPath);
cert.sign(caKey, utils.createMessageDigest());
// convert a Forge certificate to PEM
const pem = pki.certificateToPem(cert);
fs.outputFileSync(serverCertPath, pem);
const privateKeyPem = pki.privateKeyToPem(keys.privateKey);
fs.outputFileSync(serverKeyPath, privateKeyPem);
let p12Asn1;
//create .p12 file
if (rootCaCert) {
p12Asn1 = forge.pkcs12.toPkcs12Asn1(
keys.privateKey,
[cert, caCertPem, rootCaCert],
password,
{
algorithm: '3des',
}
);
} else {
p12Asn1 = forge.pkcs12.toPkcs12Asn1(keys.privateKey, [cert, caCertPem], password, {
algorithm: '3des',
});
}
const p12Der = forge.asn1.toDer(p12Asn1).getBytes();
fs.writeFileSync(serverP12Path, p12Der, {
encoding: 'binary',
});
const success = {
message: `${serverName} ${
expired ? 'expired ' : ''
}server certificate was created and signed by ${caCertName}.`,
};
console.error(success.message);
callback(null, success);
} else {
const err = {
message: `${serverName} ${
expired ? 'expired ' : ''
}server certificate already exists in ${caCertName}.`,
};
console.error(err);
callback(err);
}
}
function listServerCerts(path) {
utils.listCerts('servers', path);
}
function removeServerCert(name, caName, basePath = 'pki/', callback) {
utils.removeCerts('servers', name, caName, basePath, callback);
}
module.exports = {
createServerCert,
listServerCerts,
removeServerCert,
};
const _ = require('lodash');
const forge = require('node-forge');
const fs = require('fs-extra');
const os = require('os');
const path = require('path');
forge.options.usePureJavaScript = true;
const pki = forge.pki;
const subjectAttrs = require('./subjectAttributes');
const utils = require('./utils');
const normalizeName = utils.normalizeName;
const DNS_TYPE = 2;
const IP_TYPE = 7;
const madcertLhSubjectAltDNSName = process.env.MADCERT_LH_SUBJECT_ALT_DNS_NAME || '';
function getIpAddresses() {
const localIPs = [];
// Pull the IP address
const nic = os.networkInterfaces();
for (let i in nic) {
const names = nic[i];
for (let j = 0; j < names.length; j++) {
const k = names[j];
if (k.family === 'IPv4' && k.address !== '127.0.0.1' && !k.internal) {
localIPs.push(k.address);
}
}
}
return localIPs;
}
function buildServerCert(keys, caName, caCert, localhost, options) {
const cert = pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = utils.getSerial();
// Set the default expiration, then override with valid-from and/or valid-to
const expired = _.get(options, 'expired', false);
utils.setExpirationDate(cert, expired);
if (options.validFrom) {
// Parse the validFrom from ISO 8601 format
cert.validity.notBefore = new Date(options.validFrom);
}
if (options.validTo) {
// Parse the validTo from ISO 8601 format
cert.validity.notAfter = new Date(options.validTo);
}
const attrs = subjectAttrs(options);
cert.setSubject(attrs);
cert.setIssuer(caCert.subject.attributes);
const extensions = [
{
name: 'basicConstraints',
cA: false,
critical: true,
},
{
name: 'keyUsage',
digitalSignature: true,
keyEncipherment: true,
critical: true,
},
{
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
emailProtection: true,
},
{
name: 'subjectKeyIdentifier',
},
{
name: 'authorityKeyIdentifier',
keyIdentifier: caCert.generateSubjectKeyIdentifier().getBytes(),
},
];
const altNames = [];
// Create the subject alternative names if this is creating the localhost certificate
if (localhost) {
// If the environment variable MADCERT_LH_SUBJECT_ALT_DNS_NAME is not set use the hostname
const localHostName = madcertLhSubjectAltDNSName || os.hostname();
// Add to the extensions object
altNames.push.apply(altNames, [
{
type: DNS_TYPE,
value: localHostName,
},
{
type: DNS_TYPE,
value: 'localhost',
},
{
type: IP_TYPE,
ip: '127.0.0.1',
},
]);
const localIPs = getIpAddresses().map(ip => {
return {
type: IP_TYPE,
ip,
};
});
altNames.push.apply(altNames, localIPs);
}
// Add any subject alternative names provided on the command line
_.each(_.get(options, 'subjectAltDnsNames', []), name => {
altNames.push({
type: DNS_TYPE,
value: name,
});
});
_.each(_.get(options, 'subjectAltIpNames', []), name => {
altNames.push({
type: IP_TYPE,
ip: name,
});
});
if (!_.isEmpty(altNames)) {
extensions.push.apply(extensions, [{ name: 'subjectAltName', altNames: altNames }]);
}
// Add all extensions
cert.setExtensions(extensions);
return cert;
}
function createServerCert(
serverName,
caCertName,
localhost,
options,
callback = (err, data) => {}
) {
let basePath = _.get(options, 'basePath', 'pki/');
if (basePath.substr(-1) !== '/') basePath += '/';
if (options.validFrom && options.validTo) {
const validFrom = new Date(options.validFrom);
const validTo = new Date(options.validTo);
if (validTo.getTime() < validFrom.getTime()) {
const err = {
message: `Expiration date ${options.validTo} before start date ${options.validFrom}, aborting creation of new server certificate`,
};
console.error(err.message);
callback(err);
return;
}
}
let expired = _.get(options, 'expired', false);
// If valid-to was passed to madcert then check if it is in the past
if (options.validTo) {
const date = new Date(options.validTo);
if (date.getTime() < new Date().getTime()) {
expired = true;
}
}
const password = _.get(options, 'password', 'changeme');
const caPath = path.join(basePath, normalizeName(caCertName), '/ca/');
const serverPath = path.join(basePath, normalizeName(caCertName), '/servers/');
const serverDir = path.join(
serverPath,
normalizeName(serverName),
options.expired ? '/expired' : ''
);
if (!fs.existsSync(caPath)) {
const err = {
message: `Certificate Authority ${caCertName} does not exist, aborting creation of new server certificate`,
};
console.error(err.message);
callback(err);
return;
}
// Adding a check for the common name to be populated
// with the name field if it is blank.
options.commonName = _.get(options, 'commonName', serverName);
const serverCertPath = path.join(serverDir, '/crt.pem');
const serverKeyPath = path.join(serverDir, '/key.pem');
const serverP12Path = path.join(serverDir, '/bundle.p12');
if (
!fs.existsSync(serverDir) ||
!fs.existsSync(serverCertPath) ||
!fs.existsSync(serverKeyPath) ||
!fs.existsSync(serverP12Path)
) {
const keys = pki.rsa.generateKeyPair(2048);
let rootCaCert;
const rootCaName = _.get(options, 'rootCaName', null);
if (rootCaName !== null) {
const rootCaPath = path.join(basePath, normalizeName(rootCaName), '/ca/');
if (!fs.existsSync(caPath)) {
const err = {
message: `Root Certificate Authority ${rootCaName} does not exist, aborting creation of new server certificate`,
};
console.error(err.message);
callback(err);
return;
}
const rootCaCertPem = fs.readFileSync(path.join(rootCaPath, '/crt.pem'), 'utf8');
rootCaCert = forge.pki.certificateFromPem(rootCaCertPem);
}
// sign certificate with CA private key
const caCertPem = fs.readFileSync(path.join(caPath, '/crt.pem'), 'utf8');
const caCert = forge.pki.certificateFromPem(caCertPem);
const caKeyPem = fs.readFileSync(path.join(caPath, '/key.pem'), 'utf8');
const caKey = forge.pki.privateKeyFromPem(caKeyPem);
const cert = buildServerCert(
keys,
caCertName,
caCert,
localhost,
options
);
fs.ensureDirSync(serverPath);
cert.sign(caKey, utils.createMessageDigest());
// convert a Forge certificate to PEM
const pem = pki.certificateToPem(cert);
fs.outputFileSync(serverCertPath, pem);
const privateKeyPem = pki.privateKeyToPem(keys.privateKey);
fs.outputFileSync(serverKeyPath, privateKeyPem);
let p12Asn1;
//create .p12 file
if (rootCaCert) {
p12Asn1 = forge.pkcs12.toPkcs12Asn1(
keys.privateKey,
[cert, caCertPem, rootCaCert],
password,
{
algorithm: '3des',
}
);
} else {
p12Asn1 = forge.pkcs12.toPkcs12Asn1(keys.privateKey, [cert, caCertPem], password, {
algorithm: '3des',
});
}
const p12Der = forge.asn1.toDer(p12Asn1).getBytes();
fs.writeFileSync(serverP12Path, p12Der, {
encoding: 'binary',
});
const success = {
message: `${serverName} ${
expired ? 'expired ' : ''
}server certificate was created and signed by ${caCertName}.`,
};
console.log(success.message);
callback(null, success);
} else {
const err = {
message: `${serverName} ${
expired ? 'expired ' : ''
}server certificate already exists in ${caCertName}.`,
};
console.error(err);
callback(err);
}
}
function listServerCerts(path) {
utils.listCerts('servers', path);
}
function removeServerCert(name, caName, basePath = 'pki/', callback) {
utils.removeCerts('servers', name, caName, basePath, callback);
}
module.exports = {
createServerCert,
listServerCerts,
removeServerCert,
};
+54 -39
View File
@@ -1,39 +1,54 @@
const _ = require('lodash');
function createSubjectAttributes(options) {
const attrs = [];
const country = _.get(options, 'country', 'US');
attrs.push({
shortName: 'C',
value: country,
});
const orgs = _.get(options, 'organizations', []);
_.each(orgs, org => {
attrs.push({
shortName: 'O',
value: org,
});
});
const ous = _.get(options, 'organizationalUnits', []);
_.each(ous, ou => {
attrs.push({
shortName: 'OU',
value: ou,
});
});
const cn = _.get(options, 'commonName');
if (cn) {
attrs.push({
shortName: 'CN',
value: cn,
});
}
return attrs;
}
module.exports = createSubjectAttributes;
const _ = require('lodash');
function createSubjectAttributes(options) {
const attrs = [];
const country = _.get(options, 'country', 'US');
attrs.push({
shortName: 'C',
value: country,
});
const orgs = _.get(options, 'organizations', []);
_.each(orgs, org => {
attrs.push({
shortName: 'O',
value: org,
});
});
const ous = _.get(options, 'organizationalUnits', []);
_.each(ous, ou => {
attrs.push({
shortName: 'OU',
value: ou,
});
});
const cn = _.get(options, 'commonName');
if (cn) {
attrs.push({
shortName: 'CN',
value: cn,
});
}
const l = _.get(options, 'locality');
if (l) {
attrs.push({
shortName: 'L',
value: l,
});
}
const st = _.get(options, 'state');
if(st) {
attrs.push({
shortName: 'ST',
value: st,
});
}
return attrs;
}
module.exports = createSubjectAttributes;
+229 -238
View File
@@ -1,239 +1,230 @@
const _ = require('lodash');
const forge = require('node-forge');
const fs = require('fs-extra');
forge.options.usePureJavaScript = true;
const pki = forge.pki;
const subjectAttrs = require('./subjectAttributes');
const utils = require('./utils');
const normalizeName = utils.normalizeName;
function buildUserCert(keys, options, caSubject) {
const cert = pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = utils.getSerial();
// Set the default expiration, then override with valid-from and/or valid-to
const expired = _.get(options, 'expired', false);
utils.setExpirationDate(cert, expired);
if (options.validFrom) {
// Parse the validFrom from ISO 8601 format
cert.validity.notBefore = new Date(options.validFrom);
}
if (options.validTo) {
// Parse the validTo from ISO 8601 format
cert.validity.notAfter = new Date(options.validTo);
}
const attrs = subjectAttrs(options);
cert.setSubject(attrs);
cert.setIssuer(caSubject);
const extensions = [
{
name: 'basicConstraints',
cA: false,
},
{
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true,
},
{
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true,
},
{
name: 'nsCertType',
client: true,
server: false,
email: true,
objsign: true,
sslCA: true,
emailCA: true,
objCA: true,
},
{
name: 'subjectKeyIdentifier',
},
];
const altNames = [];
// Add any subject alternative names provided on the command line
_.each(_.get(options, 'subjectAltEmailNames', []), name => {
altNames.push({
type: 1, // rfc822Name
value: name,
});
});
if (!_.isEmpty(altNames)) {
extensions.push.apply(extensions, [{ name: 'subjectAltName', altNames: altNames }]);
}
// Add all extensions
cert.setExtensions(extensions);
return cert;
}
function createUserCert(userName, caCertName, options, callback = (err, data) => {}) {
let basePath = _.get(options, 'basePath', 'pki/');
if (basePath.substr(-1) !== '/') basePath += '/';
if (options.validFrom && options.validTo) {
const validFrom = new Date(options.validFrom);
const validTo = new Date(options.validTo);
if (validTo.getTime() < validFrom.getTime()) {
const err = {
message: `Expiration date ${options.validTo} before start date ${options.validFrom}, aborting creation of new server certificate`,
};
console.error(err.message);
callback(err);
return;
}
}
const caPath = basePath + normalizeName(caCertName) + '/ca/';
const userPath = basePath + normalizeName(caCertName) + '/users/';
let expired = _.get(options, 'expired', false);
// If valid-to was passed to madcert then check if it is in the past
if (options.validTo) {
const date = new Date(options.validTo);
if (date.getTime() < new Date().getTime()) {
expired = true;
}
}
const password = _.get(options, 'password', 'changeme');
const userDir = userPath + normalizeName(userName) + (expired ? '/expired' : '');
if (!fs.existsSync(caPath)) {
const err = {
message: `Certificate Authority ${caCertName} does not exist, aborting creation of new user certificate`,
};
console.error(err.message);
callback(err);
return;
}
// Adding a check for the common name to be populated
// with the name field if it is blank.
options.commonName = _.get(options, 'commonName', userName);
const userCertPath = userDir + '/crt.pem';
const userKeyPath = userDir + '/key.pem';
const userP12Path = userDir + '/bundle.p12';
if (
!fs.existsSync(userDir) ||
!fs.existsSync(userCertPath) ||
!fs.existsSync(userKeyPath) ||
!fs.existsSync(userP12Path)
) {
const keys = pki.rsa.generateKeyPair(2048);
let rootCaCert;
const rootCaName = _.get(options, 'rootCaName', null);
if (rootCaName !== null) {
const rootCaPath = basePath + normalizeName(rootCaName) + '/ca/';
if (!fs.existsSync(caPath)) {
const err = {
message: `Root Certificate Authority ${rootCaName} does not exist, aborting creation of new user certificate`,
};
console.error(err.message);
callback(err);
return;
}
const rootCaCertPem = fs.readFileSync(rootCaPath + '/crt.pem', 'utf8');
rootCaCert = forge.pki.certificateFromPem(rootCaCertPem);
}
fs.ensureDirSync(userPath);
// sign certificate with CA private key
const caCertPem = fs.readFileSync(caPath + '/crt.pem', 'utf8');
const caCert = forge.pki.certificateFromPem(caCertPem);
const caKeyPem = fs.readFileSync(caPath + '/key.pem', 'utf8');
const caKey = forge.pki.privateKeyFromPem(caKeyPem);
const cert = buildUserCert(keys, options, caCert.subject.attributes);
cert.sign(caKey, utils.createMessageDigest());
// convert a Forge certificate to PEM
const pem = pki.certificateToPem(cert);
fs.outputFileSync(userCertPath, pem);
const privateKeyPem = pki.privateKeyToPem(keys.privateKey);
fs.outputFileSync(userKeyPath, privateKeyPem);
let p12Asn1;
//create .p12 file
if (rootCaCert) {
p12Asn1 = forge.pkcs12.toPkcs12Asn1(
keys.privateKey,
[cert, caCert, rootCaCert],
password,
{
algorithm: '3des',
}
);
} else {
p12Asn1 = forge.pkcs12.toPkcs12Asn1(keys.privateKey, [cert, caCert], password, {
algorithm: '3des',
});
}
const p12Der = forge.asn1.toDer(p12Asn1).getBytes();
fs.writeFileSync(userP12Path, p12Der, {
encoding: 'binary',
});
const success = {
message: `${userName} ${
expired ? 'expired ' : ''
}user certificate was created and signed by ${caCertName}.`,
};
console.log(success.message);
callback(null, success);
} else {
const err = {
message: `${userName} ${
expired ? 'expired ' : ''
}user certificate already exists in ${caCertName}.`,
};
console.error(err.message);
callback(err);
}
}
function listUserCerts(path) {
return utils.listCerts('users', path);
}
function removeUserCert(name, caName, basePath = 'pki/', callback) {
utils.removeCerts('users', name, caName, basePath, callback);
}
module.exports = {
createUserCert,
listUserCerts,
removeUserCert,
const _ = require('lodash');
const forge = require('node-forge');
const fs = require('fs-extra');
forge.options.usePureJavaScript = true;
const pki = forge.pki;
const subjectAttrs = require('./subjectAttributes');
const utils = require('./utils');
const normalizeName = utils.normalizeName;
function buildUserCert(keys, options, caCert) {
const cert = pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = utils.getSerial();
// Set the default expiration, then override with valid-from and/or valid-to
const expired = _.get(options, 'expired', false);
utils.setExpirationDate(cert, expired);
if (options.validFrom) {
// Parse the validFrom from ISO 8601 format
cert.validity.notBefore = new Date(options.validFrom);
}
if (options.validTo) {
// Parse the validTo from ISO 8601 format
cert.validity.notAfter = new Date(options.validTo);
}
const attrs = subjectAttrs(options);
cert.setSubject(attrs);
cert.setIssuer(caCert.subject.attributes);
const extensions = [
{
name: 'basicConstraints',
cA: false,
critical: true,
},
{
name: 'keyUsage',
digitalSignature: true,
keyEncipherment: true,
critical: true,
},
{
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
emailProtection: true,
},
{
name: 'subjectKeyIdentifier',
},
{
name: 'authorityKeyIdentifier',
keyIdentifier: caCert.generateSubjectKeyIdentifier().getBytes(),
},
];
const altNames = [];
// Add any subject alternative names provided on the command line
_.each(_.get(options, 'subjectAltEmailNames', []), name => {
altNames.push({
type: 1, // rfc822Name
value: name,
});
});
if (!_.isEmpty(altNames)) {
extensions.push.apply(extensions, [{ name: 'subjectAltName', altNames: altNames }]);
}
// Add all extensions
cert.setExtensions(extensions);
return cert;
}
function createUserCert(userName, caCertName, options, callback = (err, data) => {}) {
let basePath = _.get(options, 'basePath', 'pki/');
if (basePath.substr(-1) !== '/') basePath += '/';
if (options.validFrom && options.validTo) {
const validFrom = new Date(options.validFrom);
const validTo = new Date(options.validTo);
if (validTo.getTime() < validFrom.getTime()) {
const err = {
message: `Expiration date ${options.validTo} before start date ${options.validFrom}, aborting creation of new server certificate`,
};
console.error(err.message);
callback(err);
return;
}
}
const caPath = basePath + normalizeName(caCertName) + '/ca/';
const userPath = basePath + normalizeName(caCertName) + '/users/';
let expired = _.get(options, 'expired', false);
// If valid-to was passed to madcert then check if it is in the past
if (options.validTo) {
const date = new Date(options.validTo);
if (date.getTime() < new Date().getTime()) {
expired = true;
}
}
const password = _.get(options, 'password', 'changeme');
const userDir = userPath + normalizeName(userName) + (expired ? '/expired' : '');
if (!fs.existsSync(caPath)) {
const err = {
message: `Certificate Authority ${caCertName} does not exist, aborting creation of new user certificate`,
};
console.error(err.message);
callback(err);
return;
}
// Adding a check for the common name to be populated
// with the name field if it is blank.
options.commonName = _.get(options, 'commonName', userName);
const userCertPath = userDir + '/crt.pem';
const userKeyPath = userDir + '/key.pem';
const userP12Path = userDir + '/bundle.p12';
if (
!fs.existsSync(userDir) ||
!fs.existsSync(userCertPath) ||
!fs.existsSync(userKeyPath) ||
!fs.existsSync(userP12Path)
) {
const keys = pki.rsa.generateKeyPair(2048);
let rootCaCert;
const rootCaName = _.get(options, 'rootCaName', null);
if (rootCaName !== null) {
const rootCaPath = basePath + normalizeName(rootCaName) + '/ca/';
if (!fs.existsSync(caPath)) {
const err = {
message: `Root Certificate Authority ${rootCaName} does not exist, aborting creation of new user certificate`,
};
console.error(err.message);
callback(err);
return;
}
const rootCaCertPem = fs.readFileSync(rootCaPath + '/crt.pem', 'utf8');
rootCaCert = forge.pki.certificateFromPem(rootCaCertPem);
}
fs.ensureDirSync(userPath);
// sign certificate with CA private key
const caCertPem = fs.readFileSync(caPath + '/crt.pem', 'utf8');
const caCert = forge.pki.certificateFromPem(caCertPem);
const caKeyPem = fs.readFileSync(caPath + '/key.pem', 'utf8');
const caKey = forge.pki.privateKeyFromPem(caKeyPem);
const cert = buildUserCert(keys, options, caCert);
cert.sign(caKey, utils.createMessageDigest());
// convert a Forge certificate to PEM
const pem = pki.certificateToPem(cert);
fs.outputFileSync(userCertPath, pem);
const privateKeyPem = pki.privateKeyToPem(keys.privateKey);
fs.outputFileSync(userKeyPath, privateKeyPem);
let p12Asn1;
//create .p12 file
if (rootCaCert) {
p12Asn1 = forge.pkcs12.toPkcs12Asn1(
keys.privateKey,
[cert, caCert, rootCaCert],
password,
{
algorithm: '3des',
}
);
} else {
p12Asn1 = forge.pkcs12.toPkcs12Asn1(keys.privateKey, [cert, caCert], password, {
algorithm: '3des',
});
}
const p12Der = forge.asn1.toDer(p12Asn1).getBytes();
fs.writeFileSync(userP12Path, p12Der, {
encoding: 'binary',
});
const success = {
message: `${userName} ${
expired ? 'expired ' : ''
}user certificate was created and signed by ${caCertName}.`,
};
console.log(success.message);
callback(null, success);
} else {
const err = {
message: `${userName} ${
expired ? 'expired ' : ''
}user certificate already exists in ${caCertName}.`,
};
console.error(err.message);
callback(err);
}
}
function listUserCerts(path) {
return utils.listCerts('users', path);
}
function removeUserCert(name, caName, basePath = 'pki/', callback) {
utils.removeCerts('users', name, caName, basePath, callback);
}
module.exports = {
createUserCert,
listUserCerts,
removeUserCert,
};
+108 -107
View File
@@ -1,107 +1,108 @@
const forge = require('node-forge');
const fs = require('fs-extra');
const _ = require('lodash');
const moment = require('moment');
const path = require('path');
forge.options.usePureJavaScript = true;
function getSerial() {
return moment().format('x'); // Current time formatted in milliseconds since the epoch.
}
function listCerts(type, basePath = 'pki/') {
const certs = [];
if (basePath.substr(-1) !== '/') basePath += '/';
if (fs.existsSync(basePath)) {
const files = fs.readdirSync(basePath);
_.forEach(files, (c, i) => {
if (type !== 'ca') {
if (type === 'users') {
console.log('User certificates for ' + c + ':');
} else {
console.log('Server certificates for ' + c + ':');
}
const tempBase = basePath + c + '/' + type + '/';
if (fs.existsSync(tempBase)) {
const files = fs.readdirSync(tempBase);
_.forEach(files, c => {
certs.push(c);
console.log('\t' + c);
});
}
} else {
if (i === 0) {
console.log('Available Certificate Authorities:');
}
certs.push(c);
console.log('\t' + c);
}
});
}
return certs;
}
// Converts name to file path friendly name
function normalizeName(name) {
return name.replace(/\s+/g, '-').toLowerCase();
}
function removeCerts(type, name, caName, basePath = 'pki/', callback = (err, data) => {}) {
if (basePath.substr(-1) !== '/') basePath += '/';
const certDir = path.join(basePath, normalizeName(caName), type, normalizeName(name));
if (fs.existsSync(certDir)) {
fs.removeSync(certDir);
const success = {
message: `${name} was removed from ${caName}.`,
};
console.log(success.message);
callback(null, success);
} else {
const error = {
message: `${name} does not exist for ${caName}.`,
};
console.error(error.message);
callback(error);
}
}
function setExpirationDate(cert, expired) {
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
// if expired, set cert to be valid starting 5 years before today
// if not expired, set cert to be valid starting yesterday
expired
? cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 5)
: cert.validity.notBefore.setDate(cert.validity.notBefore.getDate() - 1);
// if expired, set cert to be valid until yesterday
// if not expired, set cert to be valid until 5 years from today
expired
? cert.validity.notAfter.setDate(cert.validity.notAfter.getDate() - 1)
: cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 5);
}
function setValidFrom(cert, date) {}
function setValidTo(cert, date) {}
function createMessageDigest() {
return forge.md.sha384.create();
}
module.exports = {
createMessageDigest,
getSerial,
listCerts,
normalizeName,
removeCerts,
setExpirationDate,
};
const forge = require('node-forge');
const fs = require('fs-extra');
const _ = require('lodash');
const path = require('path');
forge.options.usePureJavaScript = true;
function getSerial() {
return new Date().getTime().toString(); // Current time formatted in milliseconds since the epoch.
}
function listCerts(type, basePath = 'pki/') {
const certs = [];
if (basePath.substr(-1) !== '/') basePath += '/';
if (fs.existsSync(basePath)) {
const files = fs.readdirSync(basePath);
_.forEach(files, (c, i) => {
if(fs.lstatSync(basePath + c).isDirectory()){
if (type !== 'ca') {
if (type === 'users') {
console.log('User certificates for ' + c + ':');
} else {
console.log('Server certificates for ' + c + ':');
}
const tempBase = basePath + c + '/' + type + '/';
if (fs.existsSync(tempBase)) {
const files = fs.readdirSync(tempBase);
_.forEach(files, c => {
certs.push(c);
console.log('\t' + c);
});
}
} else {
if (i === 0) {
console.log('Available Certificate Authorities:');
}
certs.push(c);
console.log('\t' + c);
}
}
});
}
return certs;
}
// Converts name to file path friendly name
function normalizeName(name) {
return name.replace(/\s+/g, '-').toLowerCase();
}
function removeCerts(type, name, caName, basePath = 'pki/', callback = (err, data) => {}) {
if (basePath.substr(-1) !== '/') basePath += '/';
const certDir = path.join(basePath, normalizeName(caName), type, normalizeName(name));
if (fs.existsSync(certDir)) {
fs.removeSync(certDir);
const success = {
message: `${name} was removed from ${caName}.`,
};
console.log(success.message);
callback(null, success);
} else {
const error = {
message: `${name} does not exist for ${caName}.`,
};
console.error(error.message);
callback(error);
}
}
function setExpirationDate(cert, expired) {
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
// if expired, set cert to be valid starting 1 year before today
// if not expired, set cert to be valid starting yesterday
expired
? cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1)
: cert.validity.notBefore.setDate(cert.validity.notBefore.getDate() - 1);
// if expired, set cert to be valid until yesterday
// if not expired, set cert to be valid until 1 year from today
expired
? cert.validity.notAfter.setDate(cert.validity.notAfter.getDate() - 1)
: cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 1);
}
function setValidFrom(cert, date) {}
function setValidTo(cert, date) {}
function createMessageDigest() {
return forge.md.sha384.create();
}
module.exports = {
createMessageDigest,
getSerial,
listCerts,
normalizeName,
removeCerts,
setExpirationDate,
};
+5
View File
@@ -0,0 +1,5 @@
const fs = require('fs-extra');
after('Remove PKI directory', function() {
fs.removeSync(global.pkiPath);
});
+9
View File
@@ -0,0 +1,9 @@
const certs = require('..');
const fs = require('fs-extra');
before('Create PKI directory', function(done) {
global.pkiPath = `${__dirname}/pki`;
fs.ensureDirSync(global.pkiPath);
certs.createCACert('example', { basePath: global.pkiPath });
done();
});
+19
View File
@@ -0,0 +1,19 @@
const certToJSON = require('../src/certToJSON');
const expect = require('chai').expect;
const path = require('path');
const util = require('util');
describe('Testing certToJSON.js', function() {
it('Converts a PEM certificate to JSON', function() {
const certJSON = certToJSON(path.join(global.pkiPath, 'example/ca/crt.pem'));
expect(certJSON).to.be.an('object');
expect(certJSON).to.include.all.keys('version', 'serialNumber', 'validity', 'subject', 'issuer', 'signature algorithm', 'extensions');
});
it('Filters properties in returned JSON object', function(){
const propertiesArray = ['subject', 'issuer'];
const certJSON = certToJSON(path.join(global.pkiPath, 'example/ca/crt.pem'), propertiesArray);
expect(certJSON).to.be.an('object');
expect(Object.keys(certJSON)).to.deep.equal(propertiesArray)
});
});
+325 -297
View File
@@ -1,297 +1,325 @@
const _ = require('lodash');
const certs = require('..');
const fs = require('fs-extra');
const path = require('path');
const expect = require('chai').expect;
const pkiPath = `${__dirname}/pki`;
describe('Testing MADCert', function() {
before('Create PKI directory', function() {
fs.ensureDirSync(pkiPath);
});
after('Remove PKI directory', function() {
fs.removeSync(pkiPath);
});
describe('CA Tests', function() {
it('Create CA Cert', function(done) {
try {
certs.createCACert('CA Cert', { basePath: pkiPath });
const created = fs.pathExistsSync(path.join(pkiPath, 'ca-cert/ca/crt.pem'));
expect(created).to.be.true;
done();
} catch (e) {
done(e);
}
});
it('Create intermediate CA Cert', function(done) {
try {
certs.createIntermediateCACert('CA Cert 2', 'CA Cert', { basePath: pkiPath });
const created = fs.pathExistsSync(path.join(pkiPath, 'ca-cert-2/ca/crt.pem'));
expect(created).to.be.true;
done();
} catch (e) {
done(e);
}
});
it('List CA Certs', function() {
certs.listCACerts(pkiPath);
});
it('Remove CA Cert', function() {
certs.createCACert('CA Cert Test Removal', { basePath: pkiPath });
certs.removeCACert('CA Cert Test Removal', pkiPath);
});
});
describe('Server Tests', function() {
it('Create Server Cert including Root CA in bundle', function() {
certs.createServerCert('Server', 'ca-cert-2', false, {
basePath: pkiPath,
expired: false,
rootCaName: 'CA Cert',
});
});
it('Create Server Cert excluding Root CA in bundle', function() {
certs.createServerCert('Server 2', 'ca-cert-2', false, {
basePath: pkiPath,
expired: false,
rootCaName: null,
});
});
it('Create Server Cert with start and expiration dates', function(done) {
try {
certs.createServerCert('Server Valid From and To', 'ca-cert-2', false, {
validFrom: '2050-01-01T00:00:00',
validTo: '2051-01-01T00:00:00',
basePath: pkiPath,
expired: false,
rootCaName: 'CA Cert',
});
const created = fs.pathExistsSync(
path.join(pkiPath, 'ca-cert-2/servers/server-valid-from-and-to/crt.pem')
);
expect(created).to.be.true;
done();
} catch (e) {
done(e);
}
});
it('Create Server Cert with expiration before start should not be created', function(done) {
try {
certs.createServerCert('Server Valid To before From', 'ca-cert-2', false, {
validFrom: '2051-01-01T00:00:00',
validTo: '2050-01-01T00:00:00',
basePath: pkiPath,
expired: false,
rootCaName: 'CA Cert',
});
const created = fs.pathExistsSync(
path.join(pkiPath, 'ca-cert-2/servers/server-valid-to-before-from/crt.pem')
);
expect(created).to.be.false;
done();
} catch (e) {
done(e);
}
});
it('Create Expired Server Cert', function() {
certs.createServerCert('Server 3', 'ca-cert-2', false, {
basePath: pkiPath,
expired: true,
rootCaName: 'CA Cert',
});
});
it('Create Expired Server Cert for existing server', function() {
certs.createServerCert('Server 2', 'ca-cert-2', false, {
basePath: pkiPath,
expired: true,
rootCaName: null,
});
});
it('List Server Certs', function() {
certs.listServerCerts(pkiPath);
});
it('Remove Server Cert', function() {
certs.createServerCert('Server Test Removal', 'ca-cert-2', false, {
basePath: pkiPath,
expired: false,
rootCaName: 'CA Cert',
});
certs.removeServerCert('Server Test Removal', 'ca-cert-2', pkiPath);
});
});
describe('User Tests', function() {
const caName = 'User Tests CA';
const caDir = 'user-tests-ca';
before(function(done) {
certs.createCACert(caName, { basePath: pkiPath });
done();
});
after(function(done) {
certs.removeCACert(caName, pkiPath);
done();
});
it('Create User Cert', function(done) {
certs.createUserCert('User 1', caName, { basePath: pkiPath, expired: false });
const userCerts = certs.listUserCerts(pkiPath);
expect(userCerts).to.contain('user-1');
done();
});
it('Create User Cert with start and expiration dates', function(done) {
try {
certs.createUserCert('User Valid From and To', caName, {
validFrom: '2050-01-01T00:00:00',
validTo: '2051-01-01T00:00:00',
basePath: pkiPath,
expired: false,
});
const created = fs.pathExistsSync(
path.join(pkiPath, caDir, 'users/user-valid-from-and-to/crt.pem')
);
expect(created).to.be.true;
done();
} catch (e) {
done(e);
}
});
it('Create User Cert with expiration before start should not be created', function(done) {
try {
certs.createUserCert('User Valid To before From', caName, {
validFrom: '2051-01-01T00:00:00',
validTo: '2050-01-01T00:00:00',
basePath: pkiPath,
expired: false,
});
const created = fs.pathExistsSync(
path.join(pkiPath, caDir, 'users/user-valid-to-before-from/crt.pem')
);
expect(created).to.be.false;
done();
} catch (e) {
done(e);
}
});
it('Create Expired User Cert', function(done) {
certs.createUserCert('User 2', caName, { basePath: pkiPath, expired: true });
const userCerts = certs.listUserCerts(pkiPath);
expect(userCerts).to.contain('user-2');
done();
});
it('Create Expired User Cert for existing User', function() {
certs.createUserCert('User 1', caName, { basePath: pkiPath, expired: true });
});
it('List User Certs', function(done) {
const userCerts = certs.listUserCerts(pkiPath);
expect(userCerts).to.not.be.empty;
done();
});
it('Create User Cert with rfc822 Subject Alternative', function(done) {
certs.createUserCert('User 3', caName, {
basePath: pkiPath,
expired: false,
subjectAltEmailNames: ['[email protected]'],
});
const userCerts = certs.listUserCerts(pkiPath);
expect(userCerts).to.contain('user-3');
done();
});
it('Remove User Cert', function(done) {
certs.createUserCert('User Test Removal', caName, {
basePath: pkiPath,
expired: false,
});
let userCerts = certs.listUserCerts(pkiPath);
expect(userCerts).to.contain('user-test-removal');
certs.removeUserCert('User Test Removal', caName, pkiPath);
userCerts = certs.listUserCerts(pkiPath);
expect(userCerts).not.to.contain('user-test-removal');
done();
});
});
describe('List Certs', function() {
const originalTimeout = this.timeout;
const newTimeout = originalTimeout * 4;
after(function() {
this.timeout(originalTimeout);
});
it('when CA, Servers, and Users Exist', function() {
this.timeout(newTimeout); //this test creates 4 certs, so we allow 4x the time
certs.createCACert('CA Cert List', { basePath: pkiPath });
certs.createIntermediateCACert('CA Cert List 2', 'CA Cert List', { basePath: pkiPath });
certs.createServerCert('Server List', 'ca-cert-list-2', false, {
basePath: pkiPath,
expired: false,
rootCaName: 'CA Cert List',
});
certs.createUserCert('User List 1', 'ca-cert-list-2', {
basePath: pkiPath,
expired: false,
});
const list = certs.listCerts(pkiPath);
expect(list.ca).to.not.be.null;
const caCert = _.find(list.ca, { name: 'ca-cert-list' });
expect(caCert).to.not.be.null;
expect(caCert.servers).to.be.empty;
expect(caCert.users).to.be.empty;
const caCert2 = _.find(list.ca, { name: 'ca-cert-list-2' });
expect(caCert2).to.not.be.null;
expect(caCert2.servers).to.not.be.empty;
expect(caCert2.users).to.not.be.empty;
});
it('when none exist', function() {
fs.removeSync(pkiPath);
const list = certs.listCerts(pkiPath);
expect(list.ca).to.be.undefined;
});
});
describe('Cert Database Tests', function() {
before('', function() {
certs.createCACert('ca cert db', { basePath: pkiPath });
});
it('Create Cert DB', function() {
certs.createCertDatabase('ca cert db', { basePath: pkiPath });
});
});
it('Normalize Name', function() {
const name = certs.normalizeName('Test Name 3');
expect(name).to.not.include(' ');
expect(name).to.not.include('T');
expect(name).to.not.include('N');
expect(name).to.include('-');
expect(name).to.equal('test-name-3');
});
});
const _ = require('lodash');
const certs = require('..');
const fs = require('fs-extra');
const path = require('path');
const expect = require('chai').expect;
describe('Testing MADCert', function() {
describe('CA Tests', function() {
it('Create CA Cert', function(done) {
try {
certs.createCACert('CA Cert', { basePath: global.pkiPath });
const created = fs.pathExistsSync(path.join(global.pkiPath, 'ca-cert/ca/crt.pem'));
expect(created).to.be.true;
done();
} catch (e) {
done(e);
}
});
it('Create intermediate CA Cert', function(done) {
try {
certs.createIntermediateCACert('CA Cert 2', 'CA Cert', { basePath: global.pkiPath });
const created = fs.pathExistsSync(path.join(global.pkiPath, 'ca-cert-2/ca/crt.pem'));
expect(created).to.be.true;
done();
} catch (e) {
done(e);
}
});
it('List CA Certs', function() {
certs.listCACerts(global.pkiPath);
});
it('Remove CA Cert', function() {
certs.createCACert('CA Cert Test Removal', { basePath: global.pkiPath });
certs.removeCACert('CA Cert Test Removal', global.pkiPath);
});
});
describe('Server Tests', function() {
it('Create Server Cert including Root CA in bundle', function() {
certs.createServerCert('Server', 'ca-cert-2', false, {
basePath: global.pkiPath,
expired: false,
rootCaName: 'CA Cert',
});
});
it('Create Server Cert excluding Root CA in bundle', function() {
certs.createServerCert('Server 2', 'ca-cert-2', false, {
basePath: global.pkiPath,
expired: false,
rootCaName: null,
});
});
it('Create Server Cert with locality and state', function(done) {
try {
certs.createServerCert('Server Locality and State', 'ca-cert-2', false, {
locality: 'Baltimore',
state: 'Maryland',
basePath: global.pkiPath,
expired: false,
rootCaName: 'CA Cert',
});
const created = fs.pathExistsSync(
path.join(global.pkiPath, 'ca-cert-2/servers/server-locality-and-state/crt.pem')
);
expect(created).to.be.true;
done();
} catch (e) {
done(e);
}
});
it('Create Server Cert with start and expiration dates', function(done) {
try {
certs.createServerCert('Server Valid From and To', 'ca-cert-2', false, {
validFrom: '2050-01-01T00:00:00',
validTo: '2051-01-01T00:00:00',
basePath: global.pkiPath,
expired: false,
rootCaName: 'CA Cert',
});
const created = fs.pathExistsSync(
path.join(global.pkiPath, 'ca-cert-2/servers/server-valid-from-and-to/crt.pem')
);
expect(created).to.be.true;
done();
} catch (e) {
done(e);
}
});
it('Create Server Cert with expiration before start should not be created', function(done) {
try {
certs.createServerCert('Server Valid To before From', 'ca-cert-2', false, {
validFrom: '2051-01-01T00:00:00',
validTo: '2050-01-01T00:00:00',
basePath: global.pkiPath,
expired: false,
rootCaName: 'CA Cert',
});
const created = fs.pathExistsSync(
path.join(global.pkiPath, 'ca-cert-2/servers/server-valid-to-before-from/crt.pem')
);
expect(created).to.be.false;
done();
} catch (e) {
done(e);
}
});
it('Create Expired Server Cert', function() {
certs.createServerCert('Server 3', 'ca-cert-2', false, {
basePath: global.pkiPath,
expired: true,
rootCaName: 'CA Cert',
});
});
it('Create Expired Server Cert for existing server', function() {
certs.createServerCert('Server 2', 'ca-cert-2', false, {
basePath: global.pkiPath,
expired: true,
rootCaName: null,
});
});
it('List Server Certs', function() {
certs.listServerCerts(global.pkiPath);
});
it('Remove Server Cert', function() {
certs.createServerCert('Server Test Removal', 'ca-cert-2', false, {
basePath: global.pkiPath,
expired: false,
rootCaName: 'CA Cert',
});
certs.removeServerCert('Server Test Removal', 'ca-cert-2', global.pkiPath);
});
});
describe('User Tests', function() {
const caName = 'User Tests CA';
const caDir = 'user-tests-ca';
before(function(done) {
certs.createCACert(caName, { basePath: global.pkiPath });
done();
});
after(function(done) {
certs.removeCACert(caName, global.pkiPath);
done();
});
it('Create User Cert', function(done) {
certs.createUserCert('User 1', caName, { basePath: global.pkiPath, expired: false });
const userCerts = certs.listUserCerts(global.pkiPath);
expect(userCerts).to.contain('user-1');
done();
});
it('Create User Cert with start and expiration dates', function(done) {
try {
certs.createUserCert('User Valid From and To', caName, {
validFrom: '2050-01-01T00:00:00',
validTo: '2051-01-01T00:00:00',
basePath: global.pkiPath,
expired: false,
});
const created = fs.pathExistsSync(
path.join(global.pkiPath, caDir, 'users/user-valid-from-and-to/crt.pem')
);
expect(created).to.be.true;
done();
} catch (e) {
done(e);
}
});
it('Create User Cert with locality and state', function(done) {
try {
certs.createUserCert('User locality and state', caName, {
locality: 'Baltimore',
state: 'Maryland',
basePath: global.pkiPath,
expired: false,
});
const created = fs.pathExistsSync(
path.join(global.pkiPath, caDir, 'users/user-locality-and-state/crt.pem')
);
expect(created).to.be.true;
done();
} catch (e) {
done(e);
}
});
it('Create User Cert with expiration before start should not be created', function(done) {
try {
certs.createUserCert('User Valid To before From', caName, {
validFrom: '2051-01-01T00:00:00',
validTo: '2050-01-01T00:00:00',
basePath: global.pkiPath,
expired: false,
});
const created = fs.pathExistsSync(
path.join(global.pkiPath, caDir, 'users/user-valid-to-before-from/crt.pem')
);
expect(created).to.be.false;
done();
} catch (e) {
done(e);
}
});
it('Create Expired User Cert', function(done) {
certs.createUserCert('User 2', caName, { basePath: global.pkiPath, expired: true });
const userCerts = certs.listUserCerts(global.pkiPath);
expect(userCerts).to.contain('user-2');
done();
});
it('Create Expired User Cert for existing User', function() {
certs.createUserCert('User 1', caName, { basePath: global.pkiPath, expired: true });
});
it('List User Certs', function(done) {
const userCerts = certs.listUserCerts(global.pkiPath);
expect(userCerts).to.not.be.empty;
done();
});
it('Create User Cert with rfc822 Subject Alternative', function(done) {
certs.createUserCert('User 3', caName, {
basePath: global.pkiPath,
expired: false,
subjectAltEmailNames: ['[email protected]'],
});
const userCerts = certs.listUserCerts(global.pkiPath);
expect(userCerts).to.contain('user-3');
done();
});
it('Remove User Cert', function(done) {
certs.createUserCert('User Test Removal', caName, {
basePath: global.pkiPath,
expired: false,
});
let userCerts = certs.listUserCerts(global.pkiPath);
expect(userCerts).to.contain('user-test-removal');
certs.removeUserCert('User Test Removal', caName, global.pkiPath);
userCerts = certs.listUserCerts(global.pkiPath);
expect(userCerts).not.to.contain('user-test-removal');
done();
});
});
describe('List Certs', function() {
const originalTimeout = this.timeout;
const newTimeout = originalTimeout * 4;
after(function() {
this.timeout(originalTimeout);
});
it('when CA, Servers, and Users Exist', function() {
this.timeout(newTimeout); //this test creates 4 certs, so we allow 4x the time
certs.createCACert('CA Cert List', { basePath: global.pkiPath });
certs.createIntermediateCACert('CA Cert List 2', 'CA Cert List', { basePath: global.pkiPath });
certs.createServerCert('Server List', 'ca-cert-list-2', false, {
basePath: global.pkiPath,
expired: false,
rootCaName: 'CA Cert List',
});
certs.createUserCert('User List 1', 'ca-cert-list-2', {
basePath: global.pkiPath,
expired: false,
});
const list = certs.listCerts(global.pkiPath);
expect(list.ca).to.not.be.null;
const caCert = _.find(list.ca, { name: 'ca-cert-list' });
expect(caCert).to.not.be.null;
expect(caCert.servers).to.be.empty;
expect(caCert.users).to.be.empty;
const caCert2 = _.find(list.ca, { name: 'ca-cert-list-2' });
expect(caCert2).to.not.be.null;
expect(caCert2.servers).to.not.be.empty;
expect(caCert2.users).to.not.be.empty;
});
it('when none exist', function() {
fs.removeSync(global.pkiPath);
const list = certs.listCerts(global.pkiPath);
expect(list.ca).to.be.undefined;
});
});
describe('Cert Database Tests', function() {
before('', function() {
certs.createCACert('ca cert db', { basePath: global.pkiPath });
});
it('Create Cert DB', function() {
certs.createCertDatabase('ca cert db', { basePath: global.pkiPath });
});
});
it('Normalize Name', function() {
const name = certs.normalizeName('Test Name 3');
expect(name).to.not.include(' ');
expect(name).to.not.include('T');
expect(name).to.not.include('N');
expect(name).to.include('-');
expect(name).to.equal('test-name-3');
});
});
-873
View File
@@ -1,873 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@babel/runtime-corejs3@^7.8.3":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz#f29fc1990307c4c57b10dbd6ce667b27159d9e0d"
integrity sha512-BFlgP2SoLO9HJX9WBwN67gHWMBhDX/eDz64Jajd6mR/UAUzqrNMm99d4qHnVaKscAElZoFiPv+JpR/Siud5lXw==
dependencies:
core-js-pure "^3.0.0"
regenerator-runtime "^0.13.4"
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@ungap/[email protected]":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
[email protected]:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
ansi-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
ansi-regex@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
ansi-regex@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
ansi-styles@^3.2.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.0.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
dependencies:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
anymatch@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
dependencies:
sprintf-js "~1.0.2"
assertion-error@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
at-least-node@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
binary-extensions@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9"
integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
[email protected]:
version "1.3.1"
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
camelcase@^5.0.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
camelcase@^6.0.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
chai@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5"
integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==
dependencies:
assertion-error "^1.1.0"
check-error "^1.0.2"
deep-eql "^3.0.1"
get-func-name "^2.0.0"
pathval "^1.1.0"
type-detect "^4.0.5"
chalk@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
check-error@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
[email protected]:
version "3.4.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
glob-parent "~5.1.0"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.5.0"
optionalDependencies:
fsevents "~2.1.2"
cliui@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
dependencies:
string-width "^3.1.0"
strip-ansi "^5.2.0"
wrap-ansi "^5.1.0"
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
[email protected]:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
[email protected]:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
core-js-pure@^3.0.0:
version "3.6.5"
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
[email protected]:
version "4.2.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1"
integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
dependencies:
ms "2.1.2"
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
decamelize@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-3.2.0.tgz#84b8e8f4f8c579f938e35e2cc7024907e0090851"
integrity sha512-4TgkVUsmmu7oCSyGBm5FvfMoACuoh9EOidm7V5/J2X2djAwwt57qb3F2KMP2ITqODTCSwb+YRV+0Zqrv18k/hw==
dependencies:
xregexp "^4.2.4"
decamelize@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
deep-eql@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==
dependencies:
type-detect "^4.0.0"
[email protected]:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
[email protected]:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
esprima@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
[email protected]:
version "5.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
dependencies:
locate-path "^6.0.0"
path-exists "^4.0.0"
find-up@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
dependencies:
locate-path "^3.0.0"
find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
dependencies:
locate-path "^5.0.0"
path-exists "^4.0.0"
flat@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
fs-extra@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc"
integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==
dependencies:
at-least-node "^1.0.0"
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^1.0.0"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@~2.1.2:
version "2.1.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-func-name@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=
glob-parent@~5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
dependencies:
is-glob "^4.0.1"
[email protected]:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
[email protected]:
version "1.10.5"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
[email protected]:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-plain-obj@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
[email protected]:
version "3.14.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
jsonfile@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179"
integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==
dependencies:
universalify "^1.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
dependencies:
p-locate "^3.0.0"
path-exists "^3.0.0"
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
dependencies:
p-locate "^4.1.0"
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
dependencies:
p-locate "^5.0.0"
lodash@^4.17.11:
version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
[email protected]:
version "4.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
dependencies:
chalk "^4.0.0"
[email protected], minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
mocha@^8.2.1:
version "8.2.1"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.2.1.tgz#f2fa68817ed0e53343d989df65ccd358bc3a4b39"
integrity sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==
dependencies:
"@ungap/promise-all-settled" "1.1.2"
ansi-colors "4.1.1"
browser-stdout "1.3.1"
chokidar "3.4.3"
debug "4.2.0"
diff "4.0.2"
escape-string-regexp "4.0.0"
find-up "5.0.0"
glob "7.1.6"
growl "1.10.5"
he "1.2.0"
js-yaml "3.14.0"
log-symbols "4.0.0"
minimatch "3.0.4"
ms "2.1.2"
nanoid "3.1.12"
serialize-javascript "5.0.1"
strip-json-comments "3.1.1"
supports-color "7.2.0"
which "2.0.2"
wide-align "1.1.3"
workerpool "6.0.2"
yargs "13.3.2"
yargs-parser "13.1.2"
yargs-unparser "2.0.0"
moment@^2.27.0:
version "2.27.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d"
integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==
[email protected]:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
[email protected]:
version "3.1.12"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654"
integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==
node-forge@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
p-limit@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
dependencies:
yocto-queue "^0.1.0"
p-locate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
dependencies:
p-limit "^2.0.0"
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
dependencies:
p-limit "^2.2.0"
p-locate@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
dependencies:
p-limit "^3.0.2"
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
path-exists@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
pathval@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA=
picomatch@^2.0.4, picomatch@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
dependencies:
safe-buffer "^5.1.0"
readdirp@~3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
dependencies:
picomatch "^2.2.1"
regenerator-runtime@^0.13.4:
version "0.13.5"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
safe-buffer@^5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
[email protected]:
version "5.0.1"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
dependencies:
randombytes "^2.1.0"
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
"string-width@^1.0.2 || 2":
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
dependencies:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
string-width@^3.0.0, string-width@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
dependencies:
emoji-regex "^7.0.1"
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
string-width@^4.1.0, string-width@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
strip-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
dependencies:
ansi-regex "^3.0.0"
strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
dependencies:
ansi-regex "^4.1.0"
strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
dependencies:
ansi-regex "^5.0.0"
[email protected]:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
[email protected], supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
type-detect@^4.0.0, type-detect@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
universalify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d"
integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
[email protected]:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies:
isexe "^2.0.0"
[email protected]:
version "1.1.3"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
dependencies:
string-width "^1.0.2 || 2"
[email protected]:
version "6.0.2"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.0.2.tgz#e241b43d8d033f1beb52c7851069456039d1d438"
integrity sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==
wrap-ansi@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
dependencies:
ansi-styles "^3.2.0"
string-width "^3.0.0"
strip-ansi "^5.0.0"
wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
xregexp@^4.2.4:
version "4.3.0"
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50"
integrity sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==
dependencies:
"@babel/runtime-corejs3" "^7.8.3"
y18n@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
[email protected], yargs-parser@^13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
[email protected]:
version "2.0.0"
resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
dependencies:
camelcase "^6.0.0"
decamelize "^4.0.0"
flat "^5.0.2"
is-plain-obj "^2.1.0"
[email protected]:
version "13.3.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
dependencies:
cliui "^5.0.0"
find-up "^3.0.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^3.0.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^13.1.2"
yargs@^15.4.0:
version "15.4.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.0.tgz#53949fb768309bac1843de9b17b80051e9805ec2"
integrity sha512-D3fRFnZwLWp8jVAAhPZBsmeIHY8tTsb8ItV9KaAaopmC6wde2u6Yw29JBIZHXw14kgkRnYmDgmQU4FVMDlIsWw==
dependencies:
cliui "^6.0.0"
decamelize "^3.2.0"
find-up "^4.1.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==