Compare commits

...

51 Commits

Author SHA1 Message Date
c0271648fc 2.0.18 2019-01-12 21:06:29 +01:00
5546fa5f49 fix(core): update 2019-01-12 21:06:29 +01:00
54fe89860e 2.0.17 2019-01-12 19:12:53 +01:00
d1edf75f6f fix(core): update 2019-01-12 19:12:52 +01:00
6f9c644221 2.0.16 2019-01-12 19:11:39 +01:00
0b26054687 fix(core): update 2019-01-12 19:11:39 +01:00
e3323ed4ef 2.0.15 2019-01-12 13:52:21 +01:00
24f692636c fix(core): update 2019-01-12 13:52:21 +01:00
a9f709ee7b 2.0.14 2019-01-12 13:44:18 +01:00
1b11b637a5 fix(core): update 2019-01-12 13:44:18 +01:00
ad54bf41ea 2.0.13 2019-01-09 00:01:02 +01:00
060ebf1b29 fix(core): update 2019-01-09 00:01:01 +01:00
a87c6acb8a 2.0.12 2019-01-08 20:45:36 +01:00
62d27619f4 fix(core): update 2019-01-08 20:45:35 +01:00
0faebf2a79 2.0.11 2019-01-07 12:29:11 +01:00
29ea50796c fix(core): update 2019-01-07 12:29:10 +01:00
26d1b7cbf0 2.0.10 2019-01-07 01:08:50 +01:00
c0c97835ea fix(core): update 2019-01-07 01:08:50 +01:00
d4d50b7dcf 2.0.9 2019-01-07 01:00:58 +01:00
2492fd4de2 fix(core): update 2019-01-07 01:00:58 +01:00
bef54799b6 2.0.8 2019-01-07 00:36:51 +01:00
dbe09f320a fix(core): update 2019-01-07 00:36:51 +01:00
18045dadaf 2.0.7 2019-01-06 23:54:47 +01:00
ee300c3e12 fix(core): update 2019-01-06 23:54:46 +01:00
ed4ba0cb61 2.0.6 2019-01-06 23:30:39 +01:00
a8ab27045d fix(core): update 2019-01-06 23:30:38 +01:00
975c3ed190 2.0.5 2019-01-06 20:41:43 +01:00
a99dea549b fix(core): update 2019-01-06 20:41:42 +01:00
f8b78c433a 2.0.4 2019-01-06 20:41:22 +01:00
6c33111074 fix(core): update 2019-01-06 20:41:21 +01:00
280335f6f6 2.0.3 2019-01-04 23:30:37 +01:00
b90092c043 fix(core): update 2019-01-04 23:30:37 +01:00
9e1c73febf 2.0.2 2018-10-07 21:06:28 +02:00
dcf1915816 2.0.1 2018-10-07 21:05:46 +02:00
748c911168 fix(core): update 2018-10-07 21:05:45 +02:00
3a48cb4ea8 2.0.0 2018-10-07 21:02:18 +02:00
a035c5c0b0 BREAKING CHANGE(scope): change to @pushrocks 2018-10-07 21:02:17 +02:00
f9c521b7b3 1.1.4 2018-08-12 21:45:21 +02:00
19cfe8bdc5 fix(core): update 2018-08-12 21:45:21 +02:00
601d6b30d3 1.1.3 2018-08-12 20:59:56 +02:00
57ffc82c43 1.1.2 2018-08-12 20:59:00 +02:00
312d3c01cd fix(npm publishing): update 2018-08-12 20:58:59 +02:00
8814c1fc62 1.1.1 2018-08-12 01:35:14 +02:00
223a47c997 fix(core): now creating certs all right 2018-08-12 01:35:14 +02:00
651ef6d281 1.1.0 2018-08-12 00:29:02 +02:00
9eda0da9a7 feat(swaitch to acme-v2): switch to letsencrypt v2 2018-08-12 00:29:02 +02:00
3e350dfed5 1.0.11 2017-04-28 19:01:04 +02:00
6fc280e168 add updated ci config 2017-04-28 19:00:56 +02:00
a9efae65d6 1.0.10 2017-04-28 18:59:45 +02:00
0f09bdaf9f update to latest standards 2017-04-28 18:59:43 +02:00
84177cd575 update 2017-04-28 18:56:55 +02:00
41 changed files with 3233 additions and 1303 deletions

View File

@ -1,59 +1,150 @@
image: hosttoday/ht-docker-node:npmts
# gitzone standard
image: hosttoday/ht-docker-node:npmci
cache:
paths:
- .npmci_cache/
key: "$CI_BUILD_STAGE"
stages:
- security
- test
- release
- trigger
- pages
- metadata
testLEGACY:
stage: test
# ====================
# security stage
# ====================
mirror:
stage: security
script:
- npmci test legacy
- npmci git mirror
tags:
- docker
- docker
- notpriv
snyk:
stage: security
script:
- npmci npm prepare
- npmci command npm install -g snyk
- npmci command npm install --ignore-scripts
- npmci command snyk test
tags:
- docker
- notpriv
sast:
stage: security
image: registry.gitlab.com/hosttoday/ht-docker-dbase:npmci
variables:
DOCKER_DRIVER: overlay2
allow_failure: true
services:
- docker:stable-dind
script:
- npmci npm prepare
- npmci npm install
- npmci command npm run build
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run
--env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}"
--volume "$PWD:/code"
--volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code
artifacts:
reports:
sast: gl-sast-report.json
tags:
- docker
- priv
# ====================
# test stage
# ====================
testLTS:
stage: test
script:
- npmci test lts
- npmci npm prepare
- npmci node install lts
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
- docker
- notpriv
testSTABLE:
stage: test
script:
- npmci test stable
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
- docker
- notpriv
release:
stage: release
script:
- npmci publish
- npmci node install stable
- npmci npm publish
only:
- tags
- tags
tags:
- docker
- docker
- notpriv
# ====================
# metadata stage
# ====================
codequality:
stage: metadata
image: docker:stable
allow_failure: true
services:
- docker:stable-dind
script:
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run
--env SOURCE_CODE="$PWD"
--volume "$PWD":/code
--volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
artifacts:
paths: [codeclimate.json]
tags:
- docker
- priv
trigger:
stage: trigger
stage: metadata
script:
- npmci trigger
- npmci trigger
only:
- tags
- tags
tags:
- docker
- docker
- notpriv
pages:
image: hosttoday/ht-docker-node:npmpage
stage: pages
image: hosttoday/ht-docker-node:npmci
stage: metadata
script:
- npmci command npmpage --host gitlab
- npmci command npm install -g typedoc typescript
- npmci npm prepare
- npmci npm install
- npmci command typedoc --module "commonjs" --target "ES2016" --out public/ ts/
tags:
- docker
- notpriv
only:
- tags
artifacts:
expire_in: 1 week
paths:
- public
- public
allow_failure: true

12
.snyk Normal file
View File

@ -0,0 +1,12 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.12.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
'npm:node-forge:20180226':
- rsa-compat > node-forge:
reason: None given
expires: '2018-09-11T19:17:24.148Z'
- acme-v2 > rsa-compat > node-forge:
reason: None given
expires: '2018-09-11T19:17:24.148Z'
patch: {}

View File

@ -1,57 +1,63 @@
# smartacme
acme implementation in TypeScript
## Availabililty
[![npm](https://push.rocks/assets/repo-button-npm.svg)](https://www.npmjs.com/package/smartacme)
[![git](https://push.rocks/assets/repo-button-git.svg)](https://GitLab.com/pushrocks/smartacme)
[![git](https://push.rocks/assets/repo-button-mirror.svg)](https://github.com/pushrocks/smartacme)
[![docs](https://push.rocks/assets/repo-button-docs.svg)](https://pushrocks.gitlab.io/smartacme/)
[![npm](https://umbrellazone.gitlab.io/assets/repo-button-npm.svg)](https://www.npmjs.com/package/smartacme)
[![git](https://umbrellazone.gitlab.io/assets/repo-button-git.svg)](https://GitLab.com/umbrellazone/smartacme)
[![git](https://umbrellazone.gitlab.io/assets/repo-button-mirror.svg)](https://github.com/umbrellazone/smartacme)
[![docs](https://umbrellazone.gitlab.io/assets/repo-button-docs.svg)](https://umbrellazone.gitlab.io/smartacme/)
## Status for master
[![build status](https://GitLab.com/pushrocks/smartacme/badges/master/build.svg)](https://GitLab.com/pushrocks/smartacme/commits/master)
[![coverage report](https://GitLab.com/pushrocks/smartacme/badges/master/coverage.svg)](https://GitLab.com/pushrocks/smartacme/commits/master)
[![build status](https://GitLab.com/umbrellazone/smartacme/badges/master/build.svg)](https://GitLab.com/umbrellazone/smartacme/commits/master)
[![coverage report](https://GitLab.com/umbrellazone/smartacme/badges/master/coverage.svg)](https://GitLab.com/umbrellazone/smartacme/commits/master)
[![npm downloads per month](https://img.shields.io/npm/dm/smartacme.svg)](https://www.npmjs.com/package/smartacme)
[![Dependency Status](https://david-dm.org/pushrocks/smartacme.svg)](https://david-dm.org/pushrocks/smartacme)
[![bitHound Dependencies](https://www.bithound.io/github/pushrocks/smartacme/badges/dependencies.svg)](https://www.bithound.io/github/pushrocks/smartacme/master/dependencies/npm)
[![bitHound Code](https://www.bithound.io/github/pushrocks/smartacme/badges/code.svg)](https://www.bithound.io/github/pushrocks/smartacme)
[![Dependency Status](https://david-dm.org/umbrellazone/smartacme.svg)](https://david-dm.org/umbrellazone/smartacme)
[![bitHound Dependencies](https://www.bithound.io/github/umbrellazone/smartacme/badges/dependencies.svg)](https://www.bithound.io/github/umbrellazone/smartacme/master/dependencies/npm)
[![bitHound Code](https://www.bithound.io/github/umbrellazone/smartacme/badges/code.svg)](https://www.bithound.io/github/umbrellazone/smartacme)
[![TypeScript](https://img.shields.io/badge/TypeScript-2.x-blue.svg)](https://nodejs.org/dist/latest-v6.x/docs/api/)
[![node](https://img.shields.io/badge/node->=%206.x.x-blue.svg)](https://nodejs.org/dist/latest-v6.x/docs/api/)
[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
## Usage
Use TypeScript for best in class instellisense.
```javascript
import { SmartAcme } from 'smartacme'
import { SmartAcme } from 'smartacme';
let smac = new SmartAcme()
let smac = new SmartAcme()(async () => {
// learn async/await, it'll make your life easier
(async () => { // learn async/await, it'll make your life easier
// optionally accepts a filePath Arg with a stored acmeaccount.json
// will create an account and
let myAccount = await smac.createAcmeAccount();
// optionally accepts a filePath Arg with a stored acmeaccount.json
// will create an account and
let myAccount = await smac.createAcmeAccount()
// will return a dnsHash to set in your DNS record
let myCert = await myAccount.createAcmeCert('example.com')
// will return a dnsHash to set in your DNS record
let myCert = await myAccount.createAcmeCert('example.com');
// gets and accepts the specified challenge
// first argument optional, defaults to dns-01 (which is the cleanest method for production use)
let myChallenge = await myCert.getChallenge('dns-01')
// gets and accepts the specified challenge
// first argument optional, defaults to dns-01 (which is the cleanest method for production use)
let myChallenge = await myCert.getChallenge('dns-01');
/* ----------
/* ----------
Now you need to set the challenge in your DNS
myChallenge.domainNamePrefixed is the address for the record
myChallenge.dnsKeyHash is the ready to use txt record value expected by letsencrypt
-------------*/
})()
})();
```
## Other relevant npm modules
module name | description
--- | ---
cert | a higlevel production module that uses smartacme to manage certs
smartnginx | a highlevel production tool for docker environments to manage nginx
| module name | description |
| ----------- | ------------------------------------------------------------------- |
| cert | a higlevel production module that uses smartacme to manage certs |
| smartnginx | a highlevel production tool for docker environments to manage nginx |
[![npm](https://push.rocks/assets/repo-header.svg)](https://push.rocks)
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
> | By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html)
[![repo-footer](https://umbrellazone.gitlab.io/assets/repo-footer.svg)](https://umbrella.zone

1
dist/index.d.ts vendored
View File

@ -1 +0,0 @@
export * from './smartacme.classes.smartacme';

6
dist/index.js vendored
View File

@ -1,6 +0,0 @@
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
__export(require("./smartacme.classes.smartacme"));
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBQUEsbURBQTZDIn0=

View File

@ -1,23 +0,0 @@
/// <reference types="q" />
import * as q from 'q';
import { SmartAcme } from './smartacme.classes.smartacme';
import { AcmeCert } from './smartacme.classes.acmecert';
/**
* class AcmeAccount represents an AcmeAccount
*/
export declare class AcmeAccount {
parentSmartAcme: SmartAcme;
location: string;
link: string;
JWK: any;
constructor(smartAcmeParentArg: SmartAcme);
/**
* register the account with letsencrypt
*/
register(): q.Promise<{}>;
/**
* agree to letsencrypr terms of service
*/
agreeTos(): q.Promise<{}>;
createAcmeCert(domainNameArg: string, countryArg?: string, countryShortArg?: string, city?: string, companyArg?: string, companyShortArg?: string): q.Promise<AcmeCert>;
}

View File

@ -1,71 +0,0 @@
"use strict";
const q = require("q");
const smartacme_classes_acmecert_1 = require("./smartacme.classes.acmecert");
/**
* class AcmeAccount represents an AcmeAccount
*/
class AcmeAccount {
constructor(smartAcmeParentArg) {
this.parentSmartAcme = smartAcmeParentArg;
}
/**
* register the account with letsencrypt
*/
register() {
let done = q.defer();
this.parentSmartAcme.rawacmeClient.newReg({
contact: ['mailto:domains@lossless.org']
}, (err, res) => {
if (err) {
console.error('smartacme: something went wrong:');
console.log(err);
done.reject(err);
return;
}
this.JWK = res.body.key;
this.link = res.headers.link;
console.log(this.link);
this.location = res.headers.location;
done.resolve();
});
return done.promise;
}
/**
* agree to letsencrypr terms of service
*/
agreeTos() {
let done = q.defer();
let tosPart = this.link.split(',')[1];
let tosLinkPortion = tosPart.split(';')[0];
let url = tosLinkPortion.split(';')[0].trim().replace(/[<>]/g, '');
this.parentSmartAcme.rawacmeClient.post(this.location, { Agreement: url, resource: 'reg' }, (err, res) => {
if (err) {
console.log(err);
done.reject(err);
return;
}
done.resolve();
});
return done.promise;
}
createAcmeCert(domainNameArg, countryArg = 'Germany', countryShortArg = 'DE', city = 'Bremen', companyArg = 'Some Company', companyShortArg = 'SC') {
let done = q.defer();
let acmeCert = new smartacme_classes_acmecert_1.AcmeCert({
bit: 2064,
key: null,
domain: domainNameArg,
country: countryArg,
country_short: countryShortArg,
locality: city,
organization: companyArg,
organization_short: companyShortArg,
password: null,
unstructured: null,
subject_alt_names: null
}, this);
done.resolve(acmeCert);
return done.promise;
}
}
exports.AcmeAccount = AcmeAccount;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLmNsYXNzZXMuYWNtZWFjY291bnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGFjbWUuY2xhc3Nlcy5hY21lYWNjb3VudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsdUJBQXNCO0FBTXRCLDZFQUF1RDtBQUV2RDs7R0FFRztBQUNIO0lBS0ksWUFBWSxrQkFBNkI7UUFDckMsSUFBSSxDQUFDLGVBQWUsR0FBRyxrQkFBa0IsQ0FBQTtJQUM3QyxDQUFDO0lBRUQ7O09BRUc7SUFDSCxRQUFRO1FBQ0osSUFBSSxJQUFJLEdBQUcsQ0FBQyxDQUFDLEtBQUssRUFBRSxDQUFBO1FBQ3BCLElBQUksQ0FBQyxlQUFlLENBQUMsYUFBYSxDQUFDLE1BQU0sQ0FDckM7WUFDSSxPQUFPLEVBQUUsQ0FBQyw2QkFBNkIsQ0FBQztTQUMzQyxFQUNELENBQUMsR0FBRyxFQUFFLEdBQUc7WUFDTCxFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO2dCQUNOLE9BQU8sQ0FBQyxLQUFLLENBQUMsa0NBQWtDLENBQUMsQ0FBQTtnQkFDakQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQTtnQkFDaEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQTtnQkFDaEIsTUFBTSxDQUFBO1lBQ1YsQ0FBQztZQUNELElBQUksQ0FBQyxHQUFHLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUE7WUFDdkIsSUFBSSxDQUFDLElBQUksR0FBRyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQTtZQUM1QixPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQTtZQUN0QixJQUFJLENBQUMsUUFBUSxHQUFHLEdBQUcsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFBO1lBQ3BDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQTtRQUNsQixDQUFDLENBQUMsQ0FBQTtRQUNOLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFBO0lBQ3ZCLENBQUM7SUFFRDs7T0FFRztJQUNILFFBQVE7UUFDSixJQUFJLElBQUksR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLENBQUE7UUFDcEIsSUFBSSxPQUFPLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFDckMsSUFBSSxjQUFjLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtRQUMxQyxJQUFJLEdBQUcsR0FBRyxjQUFjLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDLENBQUE7UUFDbEUsSUFBSSxDQUFDLGVBQWUsQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsRUFBRSxTQUFTLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUFDLEdBQUcsRUFBRSxHQUFHO1lBQ2pHLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7Z0JBQ04sT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQTtnQkFDaEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQTtnQkFDaEIsTUFBTSxDQUFBO1lBQ1YsQ0FBQztZQUNELElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQTtRQUNsQixDQUFDLENBQUMsQ0FBQTtRQUNGLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFBO0lBQ3ZCLENBQUM7SUFFRCxjQUFjLENBQ1YsYUFBcUIsRUFDckIsVUFBVSxHQUFHLFNBQVMsRUFDdEIsZUFBZSxHQUFHLElBQUksRUFDdEIsSUFBSSxHQUFHLFFBQVEsRUFDZixVQUFVLEdBQUcsY0FBYyxFQUMzQixlQUFlLEdBQUcsSUFBSTtRQUd0QixJQUFJLElBQUksR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFZLENBQUE7UUFDOUIsSUFBSSxRQUFRLEdBQUcsSUFBSSxxQ0FBUSxDQUN2QjtZQUNJLEdBQUcsRUFBRSxJQUFJO1lBQ1QsR0FBRyxFQUFFLElBQUk7WUFDVCxNQUFNLEVBQUUsYUFBYTtZQUNyQixPQUFPLEVBQUUsVUFBVTtZQUNuQixhQUFhLEVBQUUsZUFBZTtZQUM5QixRQUFRLEVBQUUsSUFBSTtZQUNkLFlBQVksRUFBRSxVQUFVO1lBQ3hCLGtCQUFrQixFQUFFLGVBQWU7WUFDbkMsUUFBUSxFQUFFLElBQUk7WUFDZCxZQUFZLEVBQUUsSUFBSTtZQUNsQixpQkFBaUIsRUFBRSxJQUFJO1NBQzFCLEVBQ0QsSUFBSSxDQUNQLENBQUE7UUFDRCxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFBO1FBQ3RCLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFBO0lBQ3ZCLENBQUM7Q0FDSjtBQWxGRCxrQ0FrRkMifQ==

View File

@ -1,80 +0,0 @@
/// <reference types="q" />
import * as q from 'q';
import { IRsaKeypair } from './smartacme.classes.smartacme';
import { AcmeAccount } from './smartacme.classes.acmeaccount';
/**
* types of challenges supported by letsencrypt and this module
*/
export declare type TChallengeType = 'dns-01' | 'http-01';
/**
* values that a challenge's status can have
*/
export declare type TChallengeStatus = 'pending';
export interface ISmartAcmeChallenge {
uri: string;
status: TChallengeStatus;
type: TChallengeType;
token: string;
keyAuthorization: string;
}
export interface ISmartAcmeChallengeChosen extends ISmartAcmeChallenge {
dnsKeyHash: string;
domainName: string;
domainNamePrefixed: string;
}
export interface IAcmeCsrConstructorOptions {
bit: number;
key: string;
domain: string;
country: string;
country_short: string;
locality: string;
organization: string;
organization_short: string;
password: string;
unstructured: string;
subject_alt_names: string[];
}
/**
* class AcmeCert represents a cert for domain
*/
export declare class AcmeCert {
domainName: string;
attributes: any;
fullchain: string;
parentAcmeAccount: AcmeAccount;
csr: any;
validFrom: Date;
validTo: Date;
keypair: IRsaKeypair;
keyPairFinal: IRsaKeypair;
chosenChallenge: ISmartAcmeChallengeChosen;
dnsKeyHash: string;
constructor(optionsArg: IAcmeCsrConstructorOptions, parentAcmeAccount: AcmeAccount);
/**
* requests a challenge for a domain
* @param domainNameArg - the domain name to request a challenge for
* @param challengeType - the challenge type to request
*/
requestChallenge(challengeTypeArg?: TChallengeType): q.Promise<ISmartAcmeChallengeChosen>;
/**
* checks if DNS records are set, will go through a max of 30 cycles
*/
checkDns(cycleArg?: number): Promise<void>;
/**
* validates a challenge, only call after you have set the challenge at the expected location
*/
requestValidation(): Promise<void>;
/**
* requests a certificate
*/
requestCert(): q.Promise<{}>;
/**
* getCertificate - takes care of cooldown, validation polling and certificate retrieval
*/
getCertificate(): void;
/**
* accept a challenge - for private use only
*/
acceptChallenge(): q.Promise<{}>;
}

File diff suppressed because one or more lines are too long

View File

@ -1,34 +0,0 @@
/// <reference types="q" />
import * as q from 'q';
import { AcmeAccount } from './smartacme.classes.acmeaccount';
/**
* a rsa keypair needed for account creation and subsequent requests
*/
export interface IRsaKeypair {
publicKey: string;
privateKey: string;
}
export { AcmeAccount } from './smartacme.classes.acmeaccount';
export { AcmeCert, ISmartAcmeChallenge, ISmartAcmeChallengeChosen } from './smartacme.classes.acmecert';
/**
* class SmartAcme exports methods for maintaining SSL Certificates
*/
export declare class SmartAcme {
acmeUrl: string;
productionBool: boolean;
keyPair: IRsaKeypair;
rawacmeClient: any;
/**
* the constructor for class SmartAcme
*/
constructor(productionArg?: boolean);
/**
* init the smartacme instance
*/
init(): q.Promise<{}>;
/**
* creates an account if not currently present in module
* @executes ASYNC
*/
createAcmeAccount(): q.Promise<AcmeAccount>;
}

View File

@ -1,66 +0,0 @@
"use strict";
// third party modules
const q = require("q"); // promises
const plugins = require("./smartacme.plugins");
const helpers = require("./smartacme.helpers");
const smartacme_classes_acmeaccount_1 = require("./smartacme.classes.acmeaccount");
var smartacme_classes_acmeaccount_2 = require("./smartacme.classes.acmeaccount");
exports.AcmeAccount = smartacme_classes_acmeaccount_2.AcmeAccount;
var smartacme_classes_acmecert_1 = require("./smartacme.classes.acmecert");
exports.AcmeCert = smartacme_classes_acmecert_1.AcmeCert;
/**
* class SmartAcme exports methods for maintaining SSL Certificates
*/
class SmartAcme {
/**
* the constructor for class SmartAcme
*/
constructor(productionArg = false) {
this.productionBool = productionArg;
this.keyPair = helpers.createKeypair();
if (this.productionBool) {
this.acmeUrl = plugins.rawacme.LETSENCRYPT_URL;
}
else {
this.acmeUrl = plugins.rawacme.LETSENCRYPT_STAGING_URL;
}
}
/**
* init the smartacme instance
*/
init() {
let done = q.defer();
plugins.rawacme.createClient({
url: this.acmeUrl,
publicKey: this.keyPair.publicKey,
privateKey: this.keyPair.privateKey
}, (err, client) => {
if (err) {
console.error('smartacme: something went wrong:');
console.log(err);
done.reject(err);
return;
}
// make client available in class
this.rawacmeClient = client;
done.resolve();
});
return done.promise;
}
/**
* creates an account if not currently present in module
* @executes ASYNC
*/
createAcmeAccount() {
let done = q.defer();
let acmeAccount = new smartacme_classes_acmeaccount_1.AcmeAccount(this);
acmeAccount.register().then(() => {
return acmeAccount.agreeTos();
}).then(() => {
done.resolve(acmeAccount);
});
return done.promise;
}
}
exports.SmartAcme = SmartAcme;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLmNsYXNzZXMuc21hcnRhY21lLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRhY21lLmNsYXNzZXMuc21hcnRhY21lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSxzQkFBc0I7QUFDdEIsdUJBQXNCLENBQUMsV0FBVztBQUNsQywrQ0FBOEM7QUFDOUMsK0NBQThDO0FBRTlDLG1GQUE2RDtBQVU3RCxpRkFBNkQ7QUFBcEQsc0RBQUEsV0FBVyxDQUFBO0FBQ3BCLDJFQUF1RztBQUE5RixnREFBQSxRQUFRLENBQUE7QUFFakI7O0dBRUc7QUFDSDtJQU1JOztPQUVHO0lBQ0gsWUFBWSxnQkFBeUIsS0FBSztRQUN0QyxJQUFJLENBQUMsY0FBYyxHQUFHLGFBQWEsQ0FBQTtRQUNuQyxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQyxhQUFhLEVBQUUsQ0FBQTtRQUN0QyxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQztZQUN0QixJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsZUFBZSxDQUFBO1FBQ2xELENBQUM7UUFBQyxJQUFJLENBQUMsQ0FBQztZQUNKLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyx1QkFBdUIsQ0FBQTtRQUMxRCxDQUFDO0lBQ0wsQ0FBQztJQUVEOztPQUVHO0lBQ0gsSUFBSTtRQUNBLElBQUksSUFBSSxHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQTtRQUNwQixPQUFPLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FDeEI7WUFDSSxHQUFHLEVBQUUsSUFBSSxDQUFDLE9BQU87WUFDakIsU0FBUyxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUztZQUNqQyxVQUFVLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVO1NBQ3RDLEVBQ0QsQ0FBQyxHQUFHLEVBQUUsTUFBTTtZQUNSLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7Z0JBQ04sT0FBTyxDQUFDLEtBQUssQ0FBQyxrQ0FBa0MsQ0FBQyxDQUFBO2dCQUNqRCxPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFBO2dCQUNoQixJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFBO2dCQUNoQixNQUFNLENBQUE7WUFDVixDQUFDO1lBRUQsa0NBQWtDO1lBQ2xDLElBQUksQ0FBQyxhQUFhLEdBQUcsTUFBTSxDQUFBO1lBQzNCLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQTtRQUNsQixDQUFDLENBQ0osQ0FBQTtRQUNELE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFBO0lBQ3ZCLENBQUM7SUFFRDs7O09BR0c7SUFDSCxpQkFBaUI7UUFDYixJQUFJLElBQUksR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFlLENBQUE7UUFDakMsSUFBSSxXQUFXLEdBQUcsSUFBSSwyQ0FBVyxDQUFDLElBQUksQ0FBQyxDQUFBO1FBQ3ZDLFdBQVcsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxJQUFJLENBQUM7WUFDeEIsTUFBTSxDQUFDLFdBQVcsQ0FBQyxRQUFRLEVBQUUsQ0FBQTtRQUNqQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7WUFDSixJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxDQUFBO1FBQzdCLENBQUMsQ0FBQyxDQUFBO1FBQ0YsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7SUFDdkIsQ0FBQztDQUNKO0FBNURELDhCQTREQyJ9

View File

@ -1,10 +0,0 @@
import 'typings-global';
import { IRsaKeypair } from './smartacme.classes.smartacme';
/**
* creates a keypair to use with requests and to generate JWK from
*/
export declare let createKeypair: (bit?: number) => IRsaKeypair;
/**
* prefix a domain name to make sure it complies with letsencrypt
*/
export declare let prefixName: (domainNameArg: string) => string;

View File

@ -1,40 +0,0 @@
"use strict";
require("typings-global");
const q = require("q");
const plugins = require("./smartacme.plugins");
/**
* creates a keypair to use with requests and to generate JWK from
*/
exports.createKeypair = (bit = 2048) => {
let result = plugins.rsaKeygen.generate(bit);
return {
publicKey: result.public_key,
privateKey: result.private_key
};
};
/**
* prefix a domain name to make sure it complies with letsencrypt
*/
exports.prefixName = (domainNameArg) => {
return '_acme-challenge.' + domainNameArg;
};
/**
* gets an existing registration
* @executes ASYNC
*/
let getReg = (SmartAcmeArg, location) => {
let done = q.defer();
let body = { resource: 'reg' };
SmartAcmeArg.rawacmeClient.post(location, body, SmartAcmeArg.keyPair, (err, res) => {
if (err) {
console.error('smartacme: something went wrong:');
console.log(err);
done.reject(err);
return;
}
console.log(JSON.stringify(res.body));
done.resolve();
});
return done.promise;
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLmhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGFjbWUuaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsMEJBQXVCO0FBQ3ZCLHVCQUFzQjtBQUV0QiwrQ0FBOEM7QUFLOUM7O0dBRUc7QUFDUSxRQUFBLGFBQWEsR0FBRyxDQUFDLEdBQUcsR0FBRyxJQUFJO0lBQ2xDLElBQUksTUFBTSxHQUFHLE9BQU8sQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFBO0lBQzVDLE1BQU0sQ0FBQztRQUNILFNBQVMsRUFBRSxNQUFNLENBQUMsVUFBVTtRQUM1QixVQUFVLEVBQUUsTUFBTSxDQUFDLFdBQVc7S0FDakMsQ0FBQTtBQUNMLENBQUMsQ0FBQTtBQUVEOztHQUVHO0FBQ1EsUUFBQSxVQUFVLEdBQUcsQ0FBQyxhQUFxQjtJQUMxQyxNQUFNLENBQUMsa0JBQWtCLEdBQUcsYUFBYSxDQUFBO0FBQzdDLENBQUMsQ0FBQTtBQUVEOzs7R0FHRztBQUNILElBQUksTUFBTSxHQUFHLENBQUMsWUFBdUIsRUFBRSxRQUFnQjtJQUNuRCxJQUFJLElBQUksR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLENBQUE7SUFDcEIsSUFBSSxJQUFJLEdBQUcsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLENBQUE7SUFDOUIsWUFBWSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQzNCLFFBQVEsRUFDUixJQUFJLEVBQ0osWUFBWSxDQUFDLE9BQU8sRUFDcEIsQ0FBQyxHQUFHLEVBQUUsR0FBRztRQUNMLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7WUFDTixPQUFPLENBQUMsS0FBSyxDQUFDLGtDQUFrQyxDQUFDLENBQUE7WUFDakQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQTtZQUNoQixJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFBO1lBQ2hCLE1BQU0sQ0FBQTtRQUNWLENBQUM7UUFDRCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUE7UUFDckMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFBO0lBQ2xCLENBQUMsQ0FDSixDQUFBO0lBQ0QsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7QUFDdkIsQ0FBQyxDQUFBIn0=

View File

@ -1,2 +0,0 @@
export declare let packageDir: string;
export declare let assetDir: string;

View File

@ -1,7 +0,0 @@
"use strict";
const path = require("path");
const smartfile = require("smartfile");
exports.packageDir = path.join(__dirname, '../');
exports.assetDir = path.join(exports.packageDir, 'assets/');
smartfile.fs.ensureDirSync(exports.assetDir);
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLnBhdGhzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRhY21lLnBhdGhzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSw2QkFBNEI7QUFDNUIsdUNBQXNDO0FBRTNCLFFBQUEsVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFDLEtBQUssQ0FBQyxDQUFBO0FBQ3ZDLFFBQUEsUUFBUSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsa0JBQVUsRUFBQyxTQUFTLENBQUMsQ0FBQTtBQUNyRCxTQUFTLENBQUMsRUFBRSxDQUFDLGFBQWEsQ0FBQyxnQkFBUSxDQUFDLENBQUEifQ==

View File

@ -1,9 +0,0 @@
import 'typings-global';
declare let rsaKeygen: any;
declare let rawacme: any;
declare let nodeForge: any;
import * as dnsly from 'dnsly';
import * as smartdelay from 'smartdelay';
import * as smartfile from 'smartfile';
import * as smartstring from 'smartstring';
export { dnsly, rsaKeygen, rawacme, nodeForge, smartdelay, smartfile, smartstring };

View File

@ -1,18 +0,0 @@
"use strict";
require("typings-global"); // typings for node
let rsaKeygen = require('rsa-keygen'); // rsa keygen
exports.rsaKeygen = rsaKeygen;
let rawacme = require('rawacme'); // acme helper functions
exports.rawacme = rawacme;
let nodeForge = require('node-forge');
exports.nodeForge = nodeForge;
// push.rocks modules here
const dnsly = require("dnsly");
exports.dnsly = dnsly;
const smartdelay = require("smartdelay");
exports.smartdelay = smartdelay;
const smartfile = require("smartfile");
exports.smartfile = smartfile;
const smartstring = require("smartstring");
exports.smartstring = smartstring;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLnBsdWdpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGFjbWUucGx1Z2lucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsMEJBQXVCLENBQUMsbUJBQW1CO0FBRzNDLElBQUksU0FBUyxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUMsQ0FBQSxDQUFDLGFBQWE7QUFZL0MsOEJBQVM7QUFYYixJQUFJLE9BQU8sR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUEsQ0FBQyx3QkFBd0I7QUFZckQsMEJBQU87QUFYWCxJQUFJLFNBQVMsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUE7QUFZakMsOEJBQVM7QUFWYiwwQkFBMEI7QUFDMUIsK0JBQThCO0FBTTFCLHNCQUFLO0FBTFQseUNBQXdDO0FBU3BDLGdDQUFVO0FBUmQsdUNBQXNDO0FBU2xDLDhCQUFTO0FBUmIsMkNBQTBDO0FBU3RDLGtDQUFXIn0=

View File

@ -1,7 +1,6 @@
{
"npmci": {
"globalNpmTools": [
"npmts"
]
}
}
"npmci": {
"npmGlobalTools": [],
"npmAccessLevel": "public"
}
}

2578
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,17 @@
{
"name": "smartacme",
"version": "1.0.9",
"name": "@pushrocks/smartacme",
"version": "2.0.18",
"private": false,
"description": "acme implementation in TypeScript",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"scripts": {
"test": "(npmts --nodocs)"
"test": "(tstest test/)",
"build": "(tsbuild)"
},
"repository": {
"type": "git",
"url": "git+ssh://git@gitlab.com/pushrocks/smartacme.git"
"url": "git+ssh://git@gitlab.com/umbrellazone/smartacme.git"
},
"keywords": [
"TypeScript",
@ -19,26 +21,30 @@
"author": "Lossless GmbH",
"license": "MIT",
"bugs": {
"url": "https://gitlab.com/pushrocks/smartacme/issues"
"url": "https://gitlab.com/umbrellazone/smartacme/issues"
},
"homepage": "https://gitlab.com/pushrocks/smartacme#README",
"homepage": "https://gitlab.com/umbrellazone/smartacme#README",
"dependencies": {
"@types/node-forge": "^0.6.5",
"@types/q": "0.x.x",
"dnsly": "^2.0.3",
"node-forge": "^0.6.48",
"q": "^1.4.1",
"rawacme": "^0.2.1",
"rsa-keygen": "^1.0.6",
"smartdelay": "^1.0.1",
"smartfile": "^4.1.4",
"smartstring": "^2.0.22",
"typings-global": "^1.0.14"
"@pushrocks/lik": "^3.0.4",
"@pushrocks/smartdata": "^3.1.13",
"@pushrocks/smartdelay": "^2.0.2",
"@pushrocks/smartdns": "^3.0.8",
"@pushrocks/smartexpress": "^3.0.0",
"@pushrocks/smartpromise": "^2.0.5",
"@pushrocks/smartrequest": "^1.1.14",
"@pushrocks/smarttime": "^3.0.5",
"@pushrocks/smartunique": "^3.0.1",
"acme-client": "^2.2.2"
},
"devDependencies": {
"cflare": "0.0.11",
"qenv": "^1.1.3",
"smartchai": "^1.0.1",
"typings-test": "^1.0.3"
"@gitzone/tsbuild": "^2.1.4",
"@gitzone/tsrun": "^1.1.17",
"@gitzone/tstest": "^1.0.18",
"@mojoio/cloudflare": "^2.0.0",
"@pushrocks/qenv": "^3.0.2",
"@pushrocks/tapbundle": "^3.0.7",
"@types/node": "^10.12.18",
"tslint": "^5.12.0",
"tslint-config-prettier": "^1.17.0"
}
}

View File

@ -1,3 +1,6 @@
vars:
- CF_EMAIL
- CF_KEY
required:
- CF_EMAIL
- CF_KEY
- MONGODB_URL
- MONGODB_PASSWORD
- MONGODB_DATABASE

1
test/test.d.ts vendored
View File

@ -1 +0,0 @@
import 'typings-test';

View File

@ -1,85 +0,0 @@
"use strict";
require("typings-test");
const smartchai_1 = require("smartchai");
const cflare = require("cflare");
const qenv = require("qenv");
let testQenv = new qenv.Qenv(process.cwd(), process.cwd() + '/.nogit');
// import the module to test
const smartacme = require("../dist/index");
let myCflareAccount = new cflare.CflareAccount();
myCflareAccount.auth({
email: process.env.CF_EMAIL,
key: process.env.CF_KEY
});
describe('smartacme', function () {
let testSmartAcme;
let testAcmeAccount;
let testAcmeCert;
let testChallenge;
it('should create a valid instance', function (done) {
this.timeout(10000);
testSmartAcme = new smartacme.SmartAcme(false);
testSmartAcme.init().then(() => {
smartchai_1.expect(testSmartAcme).to.be.instanceOf(smartacme.SmartAcme);
done();
}).catch(err => { done(err); });
});
it('should have created keyPair', function () {
smartchai_1.expect(testSmartAcme.acmeUrl).to.be.a('string');
});
it('should register a new account', function (done) {
this.timeout(10000);
testSmartAcme.createAcmeAccount().then(x => {
testAcmeAccount = x;
done();
}).catch(err => {
console.log(err);
done(err);
});
});
it('should create a AcmeCert', function () {
testAcmeAccount.createAcmeCert('test2.bleu.de').then(x => {
testAcmeCert = x;
smartchai_1.expect(testAcmeAccount).to.be.instanceOf(smartacme.AcmeCert);
});
});
it('should get a challenge for a AcmeCert', function (done) {
this.timeout(10000);
testAcmeCert.requestChallenge().then((challengeChosen) => {
console.log(challengeChosen);
testChallenge = challengeChosen;
done();
});
});
it('should set the challenge', function (done) {
this.timeout(20000);
myCflareAccount.createRecord(testChallenge.domainNamePrefixed, 'TXT', testChallenge.dnsKeyHash).then(() => {
done();
});
});
it('should check for a DNS record', function (done) {
this.timeout(20000);
testAcmeCert.checkDns().then(x => {
console.log(x);
done();
});
});
it('should accept the challenge', function (done) {
this.timeout(10000);
testAcmeCert.acceptChallenge().then(() => { done(); });
});
it('should poll for validation of a challenge', function (done) {
this.timeout(10000);
testAcmeCert.requestValidation().then(x => {
console.log(x);
done();
});
});
it('should remove the challenge', function (done) {
this.timeout(20000);
myCflareAccount.removeRecord(testChallenge.domainNamePrefixed, 'TXT').then(() => {
done();
});
});
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLHdCQUFxQjtBQUNyQix5Q0FBa0M7QUFDbEMsaUNBQWdDO0FBQ2hDLDZCQUE0QjtBQUU1QixJQUFJLFFBQVEsR0FBRyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxFQUFFLE9BQU8sQ0FBQyxHQUFHLEVBQUUsR0FBRyxTQUFTLENBQUMsQ0FBQTtBQUV0RSw0QkFBNEI7QUFDNUIsMkNBQTBDO0FBRTFDLElBQUksZUFBZSxHQUFHLElBQUksTUFBTSxDQUFDLGFBQWEsRUFBRSxDQUFBO0FBQ2hELGVBQWUsQ0FBQyxJQUFJLENBQUM7SUFDakIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsUUFBUTtJQUMzQixHQUFHLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxNQUFNO0NBQzFCLENBQUMsQ0FBQTtBQUVGLFFBQVEsQ0FBQyxXQUFXLEVBQUU7SUFDbEIsSUFBSSxhQUFrQyxDQUFBO0lBQ3RDLElBQUksZUFBc0MsQ0FBQTtJQUMxQyxJQUFJLFlBQWdDLENBQUE7SUFDcEMsSUFBSSxhQUFrRCxDQUFBO0lBRXRELEVBQUUsQ0FBQyxnQ0FBZ0MsRUFBRSxVQUFVLElBQUk7UUFDL0MsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQTtRQUNuQixhQUFhLEdBQUcsSUFBSSxTQUFTLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFBO1FBQzlDLGFBQWEsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxJQUFJLENBQUM7WUFDdEIsa0JBQU0sQ0FBQyxhQUFhLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLENBQUE7WUFDM0QsSUFBSSxFQUFFLENBQUE7UUFDVixDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxNQUFNLElBQUksQ0FB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

View File

@ -1,103 +1,37 @@
import 'typings-test'
import { expect } from 'smartchai'
import * as cflare from 'cflare'
import * as qenv from 'qenv'
import { tap, expect } from '@pushrocks/tapbundle';
import { Qenv } from '@pushrocks/qenv';
let testQenv = new qenv.Qenv(process.cwd(), process.cwd() + '/.nogit')
const testQenv = new Qenv('./', './.nogit/');
// import the module to test
import * as smartacme from '../dist/index'
import * as smartacme from '../ts/index';
let myCflareAccount = new cflare.CflareAccount()
myCflareAccount.auth({
email: process.env.CF_EMAIL,
key: process.env.CF_KEY
})
let smartAcmeInstance: smartacme.SmartAcme;
describe('smartacme', function () {
let testSmartAcme: smartacme.SmartAcme
let testAcmeAccount: smartacme.AcmeAccount
let testAcmeCert: smartacme.AcmeCert
let testChallenge: smartacme.ISmartAcmeChallengeChosen
it('should create a valid instance', function (done) {
this.timeout(10000)
testSmartAcme = new smartacme.SmartAcme(false)
testSmartAcme.init().then(() => {
expect(testSmartAcme).to.be.instanceOf(smartacme.SmartAcme)
done()
}).catch(err => { done(err) })
})
tap.test('should create a valid instance of SmartAcme', async () => {
smartAcmeInstance = new smartacme.SmartAcme({
accountEmail: 'domains@lossless.org',
accountPrivateKey: null,
mongoDescriptor: {
mongoDbName: testQenv.getEnvVarOnDemand('MONGODB_DATABASE'),
mongoDbPass: testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'),
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGODB_URL')
},
removeChallenge: async (...args) => {
console.log(args);
},
setChallenge: async (...args) => {
console.log(args);
},
validateRemoteRequest: async () => {
return true;
}
});
await smartAcmeInstance.init();
// await smartAcmeInstance.getCertificateForDomain('bleu.de');
});
it('should have created keyPair', function () {
expect(testSmartAcme.acmeUrl).to.be.a('string')
})
tap.test('should stop correctly', async () => {
await smartAcmeInstance.stop();
});
it('should register a new account', function (done) {
this.timeout(10000)
testSmartAcme.createAcmeAccount().then(x => {
testAcmeAccount = x
done()
}).catch(err => {
console.log(err)
done(err)
})
})
it('should create a AcmeCert', function() {
testAcmeAccount.createAcmeCert('test2.bleu.de').then(x => {
testAcmeCert = x
expect(testAcmeAccount).to.be.instanceOf(smartacme.AcmeCert)
})
})
it('should get a challenge for a AcmeCert', function (done) {
this.timeout(10000)
testAcmeCert.requestChallenge().then((challengeChosen) => {
console.log(challengeChosen)
testChallenge = challengeChosen
done()
})
})
it('should set the challenge', function(done) {
this.timeout(20000)
myCflareAccount.createRecord(
testChallenge.domainNamePrefixed,
'TXT', testChallenge.dnsKeyHash
).then(() => {
done()
})
})
it('should check for a DNS record', function(done) {
this.timeout(20000)
testAcmeCert.checkDns().then(x => {
console.log(x)
done()
})
})
it('should accept the challenge', function(done){
this.timeout(10000)
testAcmeCert.acceptChallenge().then(() => { done() })
})
it('should poll for validation of a challenge', function (done) {
this.timeout(10000)
testAcmeCert.requestValidation().then(x => {
console.log(x)
done()
})
})
it('should remove the challenge', function(done) {
this.timeout(20000)
myCflareAccount.removeRecord(
testChallenge.domainNamePrefixed,
'TXT'
).then(() => {
done()
})
})
})
tap.start();

View File

@ -1 +1,3 @@
export * from './smartacme.classes.smartacme'
export * from './smartacme.classes.smartacme';
export * from './smartacme.classes.certremoteclient';

View File

@ -0,0 +1,8 @@
export interface IAccountData {
id: number;
key: { kty: 'RSA'; n: string; e: string; kid: string };
contact: string[];
initialIp: string;
createdAt: string;
status: string;
}

9
ts/interfaces/cert.ts Normal file
View File

@ -0,0 +1,9 @@
export type TCertStatus = 'existing' | 'nonexisting' | 'pending' | 'failed';
export interface ICert {
domainName: string;
created: number;
privateKey: string;
publicKey: string;
csr: string;
}

View File

@ -0,0 +1,11 @@
import { ICert, TCertStatus } from './cert';
export interface ICertRemoteRequest {
secret: string;
domainName: string;
}
export interface ICertRemoteResponse {
status: TCertStatus;
certificate?: ICert;
}

3
ts/interfaces/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from './accountdata';
export * from './cert';
export * from './certremote';

View File

@ -1,94 +0,0 @@
import * as q from 'q'
import * as plugins from './smartacme.plugins'
import * as helpers from './smartacme.helpers'
import { SmartAcme, IRsaKeypair } from './smartacme.classes.smartacme'
import { AcmeCert } from './smartacme.classes.acmecert'
/**
* class AcmeAccount represents an AcmeAccount
*/
export class AcmeAccount {
parentSmartAcme: SmartAcme
location: string
link: string
JWK
constructor(smartAcmeParentArg: SmartAcme) {
this.parentSmartAcme = smartAcmeParentArg
}
/**
* register the account with letsencrypt
*/
register() {
let done = q.defer()
this.parentSmartAcme.rawacmeClient.newReg(
{
contact: ['mailto:domains@lossless.org']
},
(err, res) => {
if (err) {
console.error('smartacme: something went wrong:')
console.log(err)
done.reject(err)
return
}
this.JWK = res.body.key
this.link = res.headers.link
console.log(this.link)
this.location = res.headers.location
done.resolve()
})
return done.promise
}
/**
* agree to letsencrypr terms of service
*/
agreeTos() {
let done = q.defer()
let tosPart = this.link.split(',')[1]
let tosLinkPortion = tosPart.split(';')[0]
let url = tosLinkPortion.split(';')[0].trim().replace(/[<>]/g, '')
this.parentSmartAcme.rawacmeClient.post(this.location, { Agreement: url, resource: 'reg' }, (err, res) => {
if (err) {
console.log(err)
done.reject(err)
return
}
done.resolve()
})
return done.promise
}
createAcmeCert(
domainNameArg: string,
countryArg = 'Germany',
countryShortArg = 'DE',
city = 'Bremen',
companyArg = 'Some Company',
companyShortArg = 'SC'
) {
let done = q.defer<AcmeCert>()
let acmeCert = new AcmeCert(
{
bit: 2064,
key: null, // not needed right now
domain: domainNameArg,
country: countryArg,
country_short: countryShortArg,
locality: city,
organization: companyArg,
organization_short: companyShortArg,
password: null,
unstructured: null,
subject_alt_names: null
},
this
)
done.resolve(acmeCert)
return done.promise
}
}

View File

@ -1,255 +0,0 @@
import * as q from 'q'
import * as plugins from './smartacme.plugins'
import * as helpers from './smartacme.helpers'
import { SmartAcme, IRsaKeypair } from './smartacme.classes.smartacme'
import { AcmeAccount } from './smartacme.classes.acmeaccount'
/**
* types of challenges supported by letsencrypt and this module
*/
export type TChallengeType = 'dns-01' | 'http-01'
/**
* values that a challenge's status can have
*/
export type TChallengeStatus = 'pending'
export interface ISmartAcmeChallenge {
uri: string
status: TChallengeStatus
type: TChallengeType
token: string
keyAuthorization: string
}
export interface ISmartAcmeChallengeChosen extends ISmartAcmeChallenge {
dnsKeyHash: string
domainName: string
domainNamePrefixed: string
}
export interface IAcmeCsrConstructorOptions {
bit: number,
key: string,
domain: string,
country: string,
country_short: string,
locality: string,
organization: string,
organization_short: string,
password: string,
unstructured: string,
subject_alt_names: string[]
}
// Dnsly instance (we really just need one)
let myDnsly = new plugins.dnsly.Dnsly('google')
/**
* class AcmeCert represents a cert for domain
*/
export class AcmeCert {
domainName: string
attributes
fullchain: string
parentAcmeAccount: AcmeAccount
csr
validFrom: Date
validTo: Date
keypair: IRsaKeypair
keyPairFinal: IRsaKeypair
chosenChallenge: ISmartAcmeChallengeChosen
dnsKeyHash: string
constructor(optionsArg: IAcmeCsrConstructorOptions, parentAcmeAccount: AcmeAccount) {
this.domainName = optionsArg.domain
this.parentAcmeAccount = parentAcmeAccount
this.keypair = helpers.createKeypair(optionsArg.bit)
let privateKeyForged = plugins.nodeForge.pki.privateKeyFromPem(this.keypair.privateKey)
let publicKeyForged = plugins.nodeForge.pki.publicKeyToPem(
plugins.nodeForge.pki.setRsaPublicKey(privateKeyForged.n, privateKeyForged.e)
)
this.keyPairFinal = {
privateKey: privateKeyForged,
publicKey: publicKeyForged
}
// set dates
this.validFrom = new Date()
this.validTo = new Date()
this.validTo.setDate(this.validFrom.getDate() + 90)
// set attributes
this.attributes = [
{ name: 'commonName', value: optionsArg.domain },
{ name: 'countryName', value: optionsArg.country },
{ shortName: 'ST', value: optionsArg.country_short },
{ name: 'localityName', value: optionsArg.locality },
{ name: 'organizationName', value: optionsArg.organization },
{ shortName: 'OU', value: optionsArg.organization_short },
{ name: 'challengePassword', value: optionsArg.password },
{ name: 'unstructuredName', value: optionsArg.unstructured }
]
// set up csr
this.csr = plugins.nodeForge.pki.createCertificationRequest()
this.csr.setSubject(this.attributes)
this.csr.setAttributes(this.attributes)
}
/**
* requests a challenge for a domain
* @param domainNameArg - the domain name to request a challenge for
* @param challengeType - the challenge type to request
*/
requestChallenge(challengeTypeArg: TChallengeType = 'dns-01') {
let done = q.defer<ISmartAcmeChallengeChosen>()
this.parentAcmeAccount.parentSmartAcme.rawacmeClient.newAuthz(
{
identifier: {
type: 'dns',
value: this.domainName
}
},
this.parentAcmeAccount.parentSmartAcme.keyPair,
(err, res) => {
if (err) {
console.error('smartacme: something went wrong:')
console.log(err)
done.reject(err)
}
let preChosenChallenge = res.body.challenges.filter(x => {
return x.type === challengeTypeArg
})[0]
/**
* the key is needed to accept the challenge
*/
let authKey: string = plugins.rawacme.keyAuthz(
preChosenChallenge.token,
this.parentAcmeAccount.parentSmartAcme.keyPair.publicKey
)
/**
* needed in case selected challenge is of type dns-01
*/
this.dnsKeyHash = plugins.rawacme.dnsKeyAuthzHash(authKey) // needed if dns challenge is chosen
/**
* the return challenge
*/
this.chosenChallenge = {
uri: preChosenChallenge.uri,
type: preChosenChallenge.type,
token: preChosenChallenge.token,
keyAuthorization: authKey,
status: preChosenChallenge.status,
dnsKeyHash: this.dnsKeyHash,
domainName: this.domainName,
domainNamePrefixed: helpers.prefixName(this.domainName)
}
done.resolve(this.chosenChallenge)
}
)
return done.promise
}
/**
* checks if DNS records are set, will go through a max of 30 cycles
*/
async checkDns(cycleArg = 1) {
let result = await myDnsly.checkUntilAvailable(helpers.prefixName(this.domainName), 'TXT', this.dnsKeyHash)
if (result) {
console.log('DNS is set!')
return
} else {
throw new Error('DNS not set!')
}
}
/**
* validates a challenge, only call after you have set the challenge at the expected location
*/
async requestValidation() {
let makeRequest = () => {
let done = q.defer()
this.parentAcmeAccount.parentSmartAcme.rawacmeClient.poll(this.chosenChallenge.uri, async (err, res) => {
if (err) {
console.log(err)
return
}
console.log(`Validation response:`)
console.log(JSON.stringify(res.body))
if (res.body.status === 'pending' || res.body.status === 'invalid') {
await plugins.smartdelay.delayFor(3000)
makeRequest().then((x: any) => { done.resolve(x) })
} else {
console.log('perfect!')
done.resolve(res.body)
}
})
return done.promise
}
await makeRequest()
}
/**
* requests a certificate
*/
requestCert() {
let done = q.defer()
let payload = {
csr: plugins.rawacme.base64.encode(
plugins.rawacme.toDer(
plugins.nodeForge.pki.certificationRequestToPem(
this.csr
)
)
),
notBefore: this.validFrom.toISOString(),
notAfter: this.validTo.toISOString()
}
this.parentAcmeAccount.parentSmartAcme.rawacmeClient.newCert(
payload,
helpers.createKeypair(),
(err, res) => {
if (err) {
console.log(err)
done.reject(err)
}
console.log(res.body)
done.resolve(res.body)
})
return done.promise
}
/**
* getCertificate - takes care of cooldown, validation polling and certificate retrieval
*/
getCertificate() {
}
/**
* accept a challenge - for private use only
*/
acceptChallenge() {
let done = q.defer()
this.parentAcmeAccount.parentSmartAcme.rawacmeClient.post(
this.chosenChallenge.uri,
{
resource: 'challenge',
keyAuthorization: this.chosenChallenge.keyAuthorization
},
this.parentAcmeAccount.parentSmartAcme.keyPair,
(err, res) => {
if (err) {
console.log(err)
done.reject(err)
}
done.resolve(res.body)
}
)
return done.promise
}
}

View File

@ -0,0 +1,40 @@
import * as plugins from './smartacme.plugins';
import * as interfaces from './interfaces';
import { CertManager } from './smartacme.classes.certmanager';
import { Collection, svDb, unI } from '@pushrocks/smartdata';
import { ICert } from './interfaces';
@plugins.smartdata.Collection(() => {
return CertManager.activeDB;
})
export class Cert extends plugins.smartdata.SmartDataDbDoc<Cert> implements interfaces.ICert {
@unI()
public index: string;
@svDb()
public domainName: string;
@svDb()
public created: number;
@svDb()
public privateKey: string;
@svDb()
public publicKey: string;
@svDb()
public csr: string;
constructor(optionsArg: ICert) {
super();
this.created = optionsArg.created;
this.domainName = optionsArg.domainName;
this.privateKey = optionsArg.privateKey;
this.publicKey = optionsArg.publicKey;
this.csr = optionsArg.csr;
}
}

View File

@ -0,0 +1,93 @@
import * as plugins from './smartacme.plugins';
import { Cert } from './smartacme.classes.cert';
import { SmartAcme } from './smartacme.classes.smartacme';
import * as interfaces from './interfaces';
import { ICert } from './interfaces';
export class CertManager {
// =========
// STATIC
// =========
public static activeDB: plugins.smartdata.SmartdataDb;
// =========
// INSTANCE
// =========
private mongoDescriptor: plugins.smartdata.IMongoDescriptor;
public smartdataDb: plugins.smartdata.SmartdataDb;
public pendingMap: plugins.lik.Stringmap;
constructor(smartAcmeArg: SmartAcme,optionsArg: {
mongoDescriptor: plugins.smartdata.IMongoDescriptor;
}) {
this.mongoDescriptor = optionsArg.mongoDescriptor;
}
public async init () {
// Smartdata DB
this.smartdataDb = new plugins.smartdata.SmartdataDb(this.mongoDescriptor);
await this.smartdataDb.init();
CertManager.activeDB = this.smartdataDb;
// Pending Map
this.pendingMap = new plugins.lik.Stringmap();
};
/**
* retrieves a certificate
* @returns the Cert class or null
* @param domainName the domain Name to retrieve the vcertificate for
*/
public async retrieveCertificate(domainName: string): Promise<Cert> {
await this.checkCerts();
const existingCertificate: Cert = await Cert.getInstance({
name: domainName
});
if(existingCertificate) {
return existingCertificate;
} else {
return null;
}
}
/**
* stores the certificate with the
* @param publicKeyArg
* @param privateKeyArg
* @param csrArg
*/
public async storeCertificate(optionsArg: ICert) {
const cert = new Cert(optionsArg);
cert.save();
};
public async deleteCertificate(domainNameArg: string) {
}
public async getCertificateStatus(domainNameArg: string): Promise<interfaces.TCertStatus> {
const isPending = this.pendingMap.checkString('domainNameArg');
if (isPending) {
return 'pending';
}
// otherwise lets continue
const existingCertificate = this.retrieveCertificate(domainNameArg);
if (existingCertificate) {
return 'existing';
}
return 'nonexisting';
}
/**
* checks all certs for expiration
*/
private async checkCerts() {};
}

View File

@ -0,0 +1,47 @@
import * as plugins from './smartacme.plugins';
import * as interfaces from './interfaces';
import { ICertRemoteResponse } from './interfaces';
// tslint:disable-next-line: max-classes-per-file
export class CertRemoteClient {
private remoteUrl: string;
private secret: string;
constructor(optionsArg: {
remoteUrl: string;
secret: string;
}) {
this.remoteUrl = optionsArg.remoteUrl;
this.secret = optionsArg.secret;
}
/**
*
* @param domainNameArg
*/
async getCertificateForDomain(domainNameArg: string): Promise<interfaces.ICert> {
let certificate: interfaces.ICert;
const doRequestCycle = async (): Promise<interfaces.ICert> => {
const response: ICertRemoteResponse = (await plugins.smartrequest.postJson(this.remoteUrl, {
requestBody: <interfaces.ICertRemoteRequest>{
domainName: domainNameArg,
secret: this.secret
}
})).body;
switch(response.status) {
case 'pending':
await plugins.smartdelay.delayFor(5000);
const finalResponse = await doRequestCycle();
return finalResponse;
case 'existing':
return response.certificate;
case 'failed':
default:
console.log(`could not retrieve certificate for ${domainNameArg}`);
return null;
}
};
certificate = await doRequestCycle();
return certificate;
}
}

View File

@ -1,82 +1,194 @@
// third party modules
import * as q from 'q' // promises
import * as plugins from './smartacme.plugins'
import * as helpers from './smartacme.helpers'
import * as plugins from './smartacme.plugins';
import { Cert } from './smartacme.classes.cert';
import { CertManager } from './smartacme.classes.certmanager';
import { AcmeAccount } from './smartacme.classes.acmeaccount'
import * as interfaces from './interfaces';
import { request } from 'http';
/**
* a rsa keypair needed for account creation and subsequent requests
* the options for the class @see SmartAcme
*/
export interface IRsaKeypair {
publicKey: string
privateKey: string
export interface ISmartAcmeOptions {
accountPrivateKey?: string;
accountEmail: string;
mongoDescriptor: plugins.smartdata.IMongoDescriptor;
setChallenge: (domainName: string, keyAuthorization: string) => Promise<any>;
removeChallenge: (domainName: string) => Promise<any>;
validateRemoteRequest: () => Promise<boolean>;
}
export { AcmeAccount } from './smartacme.classes.acmeaccount'
export { AcmeCert, ISmartAcmeChallenge, ISmartAcmeChallengeChosen } from './smartacme.classes.acmecert'
/**
* class SmartAcme exports methods for maintaining SSL Certificates
* class SmartAcme
* can be used for setting up communication with an ACME authority
*
* ```ts
* const mySmartAcmeInstance = new SmartAcme({
* // see ISmartAcmeOptions for options
* })
* ```
*/
export class SmartAcme {
acmeUrl: string // the acme url to use for this instance
productionBool: boolean // a boolean to quickly know wether we are in production or not
keyPair: IRsaKeypair // the keyPair needed for account creation
rawacmeClient
private options: ISmartAcmeOptions;
/**
* the constructor for class SmartAcme
*/
constructor(productionArg: boolean = false) {
this.productionBool = productionArg
this.keyPair = helpers.createKeypair()
if (this.productionBool) {
this.acmeUrl = plugins.rawacme.LETSENCRYPT_URL
} else {
this.acmeUrl = plugins.rawacme.LETSENCRYPT_STAGING_URL
}
}
// the acme client
private client: any;
private smartdns = new plugins.smartdns.Smartdns();
/**
* init the smartacme instance
*/
init() {
let done = q.defer()
plugins.rawacme.createClient(
{
url: this.acmeUrl,
publicKey: this.keyPair.publicKey,
privateKey: this.keyPair.privateKey
},
(err, client) => {
if (err) {
console.error('smartacme: something went wrong:')
console.log(err)
done.reject(err)
return
}
// the account private key
private privateKey: string;
// make client available in class
this.rawacmeClient = client
done.resolve()
// challenge fullfillment
private setChallenge: (domainName: string, keyAuthorization: string) => Promise<any>;
private removeChallenge: (domainName: string) => Promise<any>;
private validateRemoteRequest: () => Promise<boolean>;
// certmanager
private certmanager: CertManager;
private certremoteHandler: plugins.smartexpress.Handler;
constructor(optionsArg: ISmartAcmeOptions) {
this.options = optionsArg;
}
/**
* inits the instance
* ```ts
* await myCloudlyInstance.init() // does not support options
* ```
*/
public async init() {
this.privateKey =
this.options.accountPrivateKey || (await plugins.acme.forge.createPrivateKey());
this.setChallenge = this.options.setChallenge;
this.removeChallenge = this.options.removeChallenge;
// CertMangaer
this.certmanager = new CertManager(this, {
mongoDescriptor: this.options.mongoDescriptor
});
await this.certmanager.init();
// CertRemoteHandler
this.certremoteHandler = new plugins.smartexpress.Handler('POST', async (req, res) => {
const requestBody: interfaces.ICertRemoteRequest = req.body;
const status: interfaces.TCertStatus = await this.certmanager.getCertificateStatus(requestBody.domainName);
const existingCertificate = await this.certmanager.retrieveCertificate(
requestBody.domainName
);
let response: interfaces.ICertRemoteResponse;
switch (status) {
case 'existing':
response = {
status,
certificate: {
created: existingCertificate.created,
csr: existingCertificate.csr,
domainName: existingCertificate.domainName,
privateKey: existingCertificate.privateKey,
publicKey: existingCertificate.publicKey
}
)
return done.promise
};
break;
default:
response = {
status
};
break;
}
res.status(200);
res.send(response);
res.end();
});
// ACME Client
this.client = new plugins.acme.Client({
directoryUrl: plugins.acme.directory.letsencrypt.staging,
accountKey: this.privateKey
});
/* Register account */
await this.client.createAccount({
termsOfServiceAgreed: true,
contact: [`mailto:${this.options.accountEmail}`]
});
}
public async stop() {
await this.certmanager.smartdataDb.close();
}
public async getCertificateForDomain(domainArg: string): Promise<Cert> {
const domain = domainArg;
const retrievedCertificate = await this.certmanager.retrieveCertificate(domain);
if (retrievedCertificate) {
return retrievedCertificate;
}
/**
* creates an account if not currently present in module
* @executes ASYNC
*/
createAcmeAccount() {
let done = q.defer<AcmeAccount>()
let acmeAccount = new AcmeAccount(this)
acmeAccount.register().then(() => {
return acmeAccount.agreeTos()
}).then(() => {
done.resolve(acmeAccount)
})
return done.promise
/* Place new order */
const order = await this.client.createOrder({
identifiers: [{ type: 'dns', value: domain }, { type: 'dns', value: `*.${domain}` }]
});
/* Get authorizations and select challenges */
const authorizations = await this.client.getAuthorizations(order);
for (const authz of authorizations) {
console.log(authz);
const domainDnsName: string = `_acme-challenge.${authz.identifier.value}`;
const dnsChallenge: string = authz.challenges.find(challengeArg => {
return challengeArg.type === 'dns-01';
});
// process.exit(1);
const keyAuthorization: string = await this.client.getChallengeKeyAuthorization(dnsChallenge);
try {
/* Satisfy challenge */
await this.setChallenge(domainDnsName, keyAuthorization);
await this.smartdns.checkUntilAvailable(domainDnsName, 'TXT', keyAuthorization, 100, 5000);
/* Verify that challenge is satisfied */
await this.client.verifyChallenge(authz, dnsChallenge);
/* Notify ACME provider that challenge is satisfied */
await this.client.completeChallenge(dnsChallenge);
/* Wait for ACME provider to respond with valid status */
await this.client.waitForValidStatus(dnsChallenge);
} finally {
/* Clean up challenge response */
try {
await this.removeChallenge(domainDnsName);
} catch (e) {
console.log(e);
}
}
}
/* Finalize order */
const [key, csr] = await plugins.acme.forge.createCsr({
commonName: `*.${domain}`,
altNames: [domain]
});
await this.client.finalizeOrder(order, csr);
const cert = await this.client.getCertificate(order);
/* Done */
console.log(`CSR:\n${csr.toString()}`);
console.log(`Private key:\n${key.toString()}`);
console.log(`Certificate:\n${cert.toString()}`);
await this.certmanager.storeCertificate({
domainName: domainArg,
privateKey: key.toString(),
publicKey: cert.toString(),
csr: csr.toString(),
created: Date.now()
});
const newCertificate = await this.certmanager.retrieveCertificate(domainArg);
return newCertificate;
}
}

View File

@ -1,50 +0,0 @@
import 'typings-global'
import * as q from 'q'
import * as plugins from './smartacme.plugins'
import { SmartAcme, IRsaKeypair } from './smartacme.classes.smartacme'
import { AcmeAccount } from './smartacme.classes.acmeaccount'
/**
* creates a keypair to use with requests and to generate JWK from
*/
export let createKeypair = (bit = 2048): IRsaKeypair => {
let result = plugins.rsaKeygen.generate(bit)
return {
publicKey: result.public_key,
privateKey: result.private_key
}
}
/**
* prefix a domain name to make sure it complies with letsencrypt
*/
export let prefixName = (domainNameArg: string): string => {
return '_acme-challenge.' + domainNameArg
}
/**
* gets an existing registration
* @executes ASYNC
*/
let getReg = (SmartAcmeArg: SmartAcme, location: string) => {
let done = q.defer()
let body = { resource: 'reg' }
SmartAcmeArg.rawacmeClient.post(
location,
body,
SmartAcmeArg.keyPair,
(err, res) => {
if (err) {
console.error('smartacme: something went wrong:')
console.log(err)
done.reject(err)
return
}
console.log(JSON.stringify(res.body))
done.resolve()
}
)
return done.promise
}

View File

@ -1,6 +0,0 @@
import * as path from 'path'
import * as smartfile from 'smartfile'
export let packageDir = path.join(__dirname,'../')
export let assetDir = path.join(packageDir,'assets/')
smartfile.fs.ensureDirSync(assetDir)

View File

@ -1,22 +1,17 @@
import 'typings-global' // typings for node
// @pushrocks scope
import * as lik from '@pushrocks/lik';
import * as smartdata from '@pushrocks/smartdata';
import * as smartdelay from '@pushrocks/smartdelay';
import * as smartdns from '@pushrocks/smartdns';
import * as smartexpress from '@pushrocks/smartexpress';
import * as smartpromise from '@pushrocks/smartpromise';
import * as smartrequest from '@pushrocks/smartrequest';
import * as smartunique from '@pushrocks/smartunique';
import * as smarttime from '@pushrocks/smarttime';
import * as path from 'path' // native node path module
let rsaKeygen = require('rsa-keygen') // rsa keygen
let rawacme = require('rawacme') // acme helper functions
let nodeForge = require('node-forge')
export { lik, smartdata, smartdelay, smartdns, smartexpress, smartpromise, smartrequest, smartunique, smarttime };
// push.rocks modules here
import * as dnsly from 'dnsly'
import * as smartdelay from 'smartdelay'
import * as smartfile from 'smartfile'
import * as smartstring from 'smartstring'
// thirs party scope
import * as acme from 'acme-client';
export {
dnsly,
rsaKeygen,
rawacme,
nodeForge,
smartdelay,
smartfile,
smartstring
}
export { acme };

View File

7
tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "es2017",
"module": "commonjs"
}
}

View File

@ -1,3 +1,17 @@
{
"extends": "tslint-config-standard"
"extends": ["tslint:latest", "tslint-config-prettier"],
"rules": {
"semicolon": [true, "always"],
"no-console": false,
"ordered-imports": false,
"object-literal-sort-keys": false,
"member-ordering": {
"options":{
"order": [
"static-method"
]
}
}
},
"defaultSeverity": "warning"
}