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.
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)
> | 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": {
"@pushrocks/smartdelay": "^2.0.2",
"@pushrocks/smartpromise": "^2.0.5",
"acme-v2": "^1.2.1",
"rsa-compat": "^1.6.0"
"acme-client": "^2.2.1"
},
"devDependencies": {
"@gitzone/tsbuild": "^2.0.22",
"@gitzone/tsrun": "^1.1.12",
"@gitzone/tstest": "^1.0.15",
"@types/node": "^10.11.4",
"cflare": "^1.0.5",
"qenv": "^1.1.7",
"tapbundle": "^2.0.2"
"@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,3 @@
vars:
- CF_EMAIL
- CF_KEY
required:
- CF_EMAIL
- 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';
@ -6,8 +6,16 @@ let smartAcmeInstance: smartacme.SmartAcme;
tap.test('should create a valid instance of SmartAcme', async () => {
smartAcmeInstance = new smartacme.SmartAcme();
await smartAcmeInstance.init();
console.log(smartAcmeInstance.directoryUrls);
await smartAcmeInstance.init({
accountEmail: 'domains@lossless.org',
accountPrivateKey: null,
removeChallenge: async (...args) => {
console.log(args);
},
setChallenge: async (...args) => {
console.log(args);
}
});
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';
const rsa = require('rsa-compat').RSA;
/**
*
*/
export interface ISmartAcmeStorage {}
export class SmartAcme {
domainKeyPair: KeyPair;
accountKeyPair: KeyPair;
accountData: any;
directoryUrls: any;
// the acme client
private client: any;
async init() {
// get directory url
this.directoryUrls = await acme.init('https://acme-staging-v02.api.letsencrypt.org/directory');
// the account private key
private privateKey: string;
// create keyPairs
this.domainKeyPair = await KeyPair.generateFresh();
this.accountKeyPair = await KeyPair.generateFresh();
// challenge fullfillment
private setChallenge: (authz, challenge, keyAuthorization) => Promise<any>;
private removeChallenge: (authz, challenge, keyAuthorization) => Promise<any>;
// get account
const registrationData = await acme.accounts
.create({
email: 'domains@lossless.org', // valid email (server checks MX records)
accountKeypair: this.accountKeyPair.rsaKeyPair,
agreeToTerms: async tosUrl => {
return tosUrl;
public async init(optionsArg: {
accountPrivateKey?: string;
accountEmail: string;
setChallenge: (authz, challenge, keyAuthorization) => Promise<any>;
removeChallenge: (authz, challenge, keyAuthorization) => Promise<any>;
}) {
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);
});
this.accountData = registrationData;
}
});
/* Wait for challenges to complete */
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) {
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);
}
toStorageObject () {};
}

View File

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