fix(core): update

This commit is contained in:
Philipp Kunz 2019-01-06 20:41:21 +01:00
parent 280335f6f6
commit 6c33111074
11 changed files with 1124 additions and 731 deletions

View File

@ -25,9 +25,39 @@ acme implementation in TypeScript
Use TypeScript for best in class instellisense. Use TypeScript for best in class instellisense.
For further information read the linked docs at the top of this README. ```javascript
import { SmartAcme } from 'smartacme';
let smac = new SmartAcme()(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();
// 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');
/* ----------
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 |
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh) > MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
> | By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html) > | 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) [![repo-footer](https://umbrellazone.gitlab.io/assets/repo-footer.svg)](https://umbrella.zone

View File

@ -1,63 +0,0 @@
# smartacme
acme implementation in TypeScript
## Availabililty
[![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/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/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';
let smac = new SmartAcme()(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();
// 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');
/* ----------
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 |
> 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

1513
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -27,16 +27,17 @@
"dependencies": { "dependencies": {
"@pushrocks/smartdelay": "^2.0.2", "@pushrocks/smartdelay": "^2.0.2",
"@pushrocks/smartpromise": "^2.0.5", "@pushrocks/smartpromise": "^2.0.5",
"acme-v2": "^1.2.1", "acme-client": "^2.2.1"
"rsa-compat": "^1.6.0"
}, },
"devDependencies": { "devDependencies": {
"@gitzone/tsbuild": "^2.0.22", "@gitzone/tsbuild": "^2.1.4",
"@gitzone/tsrun": "^1.1.12", "@gitzone/tsrun": "^1.1.17",
"@gitzone/tstest": "^1.0.15", "@gitzone/tstest": "^1.0.18",
"@types/node": "^10.11.4", "@mojoio/cloudflare": "^2.0.0",
"cflare": "^1.0.5", "@pushrocks/qenv": "^3.0.2",
"qenv": "^1.1.7", "@pushrocks/tapbundle": "^3.0.7",
"tapbundle": "^2.0.2" "@types/node": "^10.12.18",
"tslint": "^5.12.0",
"tslint-config-prettier": "^1.17.0"
} }
} }

View File

@ -1,3 +1,3 @@
vars: required:
- CF_EMAIL - CF_EMAIL
- CF_KEY - CF_KEY

View File

@ -1,4 +1,4 @@
import { tap, expect } from 'tapbundle'; import { tap, expect } from '@pushrocks/tapbundle';
import * as smartacme from '../ts/index'; import * as smartacme from '../ts/index';
@ -6,8 +6,16 @@ let smartAcmeInstance: smartacme.SmartAcme;
tap.test('should create a valid instance of SmartAcme', async () => { tap.test('should create a valid instance of SmartAcme', async () => {
smartAcmeInstance = new smartacme.SmartAcme(); smartAcmeInstance = new smartacme.SmartAcme();
await smartAcmeInstance.init(); await smartAcmeInstance.init({
console.log(smartAcmeInstance.directoryUrls); accountEmail: 'domains@lossless.org',
accountPrivateKey: null,
removeChallenge: async (...args) => {
console.log(args);
},
setChallenge: async (...args) => {
console.log(args);
}
});
await smartAcmeInstance.getCertificateForDomain('bleu.de'); await smartAcmeInstance.getCertificateForDomain('bleu.de');
}); });

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;
}

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

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

View File

@ -1,27 +0,0 @@
import * as plugins from './smartacme.plugins';
const rsa = require('rsa-compat').RSA;
export class KeyPair {
rsaKeyPair: any;
/**
* generates a fresh rsa keyPair
*/
static async generateFresh(): Promise<KeyPair> {
const done = plugins.smartpromise.defer();
var options = { bitlen: 2048, exp: 65537, public: true, pem: true, internal: true };
rsa.generateKeypair(options, function(err, keypair) {
if (err) {
console.log(err);
}
done.resolve(keypair);
});
const result: any = await done.promise;
const keyPair = new KeyPair(result);
return keyPair;
}
constructor(rsaKeyPairArg) {
this.rsaKeyPair = rsaKeyPairArg;
}
}

View File

@ -1,86 +1,96 @@
const acme = require('acme-v2').ACME.create({
RSA: require('rsa-compat').RSA,
// used for constructing user-agent
os: require('os'),
process: require('process'),
// used for overriding the default user-agent
userAgent: 'My custom UA String',
getUserAgentString: function(deps) {
return 'My custom UA String';
},
// don't try to validate challenges locally
skipChallengeTest: true
});
import { KeyPair } from './smartacme.classes.keypair';
import * as plugins from './smartacme.plugins'; import * as plugins from './smartacme.plugins';
const rsa = require('rsa-compat').RSA;
/**
*
*/
export interface ISmartAcmeStorage {}
export class SmartAcme { export class SmartAcme {
domainKeyPair: KeyPair; // the acme client
accountKeyPair: KeyPair; private client: any;
accountData: any;
directoryUrls: any;
async init() { // the account private key
// get directory url private privateKey: string;
this.directoryUrls = await acme.init('https://acme-staging-v02.api.letsencrypt.org/directory');
// create keyPairs // challenge fullfillment
this.domainKeyPair = await KeyPair.generateFresh(); private setChallenge: (authz, challenge, keyAuthorization) => Promise<any>;
this.accountKeyPair = await KeyPair.generateFresh(); private removeChallenge: (authz, challenge, keyAuthorization) => Promise<any>;
// get account public async init(optionsArg: {
const registrationData = await acme.accounts accountPrivateKey?: string;
.create({ accountEmail: string;
email: 'domains@lossless.org', // valid email (server checks MX records) setChallenge: (authz, challenge, keyAuthorization) => Promise<any>;
accountKeypair: this.accountKeyPair.rsaKeyPair, removeChallenge: (authz, challenge, keyAuthorization) => Promise<any>;
agreeToTerms: async tosUrl => { }) {
return tosUrl; this.privateKey = optionsArg.accountPrivateKey || (await plugins.acme.forge.createPrivateKey());
this.setChallenge = optionsArg.setChallenge;
this.removeChallenge = optionsArg.removeChallenge;
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:${optionsArg.accountEmail}`]
});
}
public async getCertificateForDomain(domainArg: string) {
const domain = domainArg;
/* 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);
const promises = authorizations.map(async authz => {
const challenge = authz.challenges.pop();
const keyAuthorization = await this.client.getChallengeKeyAuthorization(challenge);
try {
/* Satisfy challenge */
await this.setChallenge(authz, challenge, keyAuthorization);
/* Verify that challenge is satisfied */
await this.client.verifyChallenge(authz, challenge);
/* Notify ACME provider that challenge is satisfied */
await this.client.completeChallenge(challenge);
/* Wait for ACME provider to respond with valid status */
await this.client.waitForValidStatus(challenge);
} finally {
/* Clean up challenge response */
try {
await this.removeChallenge(authz, challenge, keyAuthorization);
} catch (e) {
console.log(e);
} }
}) }
.catch(e => { });
console.log(e);
}); /* Wait for challenges to complete */
this.accountData = registrationData; await Promise.all(promises);
/* 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()}`);
} }
async getCertificateForDomain(domain) { toStorageObject () {};
const result = await acme.certificates
.create({
domainKeypair: this.domainKeyPair.rsaKeyPair,
accountKeypair: this.accountKeyPair.rsaKeyPair,
domains: ['bleu.de'],
challengeType: 'dns-01',
setChallenge: async (hostname, key, val, cb) => {
console.log('set challenge');
console.log(hostname);
//console.log(key);
//console.log(val);
const dnsKey = rsa.utils.toWebsafeBase64(
require('crypto')
.createHash('sha256')
.update(val)
.digest('base64')
);
console.log(dnsKey);
await plugins.smartdelay.delayFor(20000);
console.log('ready!');
cb();
}, // return Promise
removeChallenge: async (hostname, key) => {
console.log('removing challenge');
return;
} // return Promise
})
.catch(e => {
console.log(e);
}); // returns Promise<pems={ privkey (key), cert, chain (ca) }>
console.log(result);
}
} }

View File

@ -1,4 +1,10 @@
// @pushrocks scope
import * as smartpromise from '@pushrocks/smartpromise'; import * as smartpromise from '@pushrocks/smartpromise';
import * as smartdelay from '@pushrocks/smartdelay'; import * as smartdelay from '@pushrocks/smartdelay';
export { smartpromise, smartdelay }; export { smartpromise, smartdelay };
// thirs party scope
import * as acme from 'acme-client';
export { acme };