start better segregation of concerns

This commit is contained in:
Philipp Kunz 2017-01-14 18:36:33 +01:00
parent 98cc70dbfb
commit fce25c60ed
21 changed files with 723 additions and 487 deletions

View File

@ -1,3 +1,22 @@
import 'typings-global'; /// <reference types="q" />
import * as q from 'q';
import { SmartAcme } from './smartacme.classes.smartacme';
/**
* class AcmeAccount represents an AcmeAccount
*/
export declare class 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): void;
} }

View File

@ -1,6 +1,54 @@
"use strict"; "use strict";
require("typings-global"); const q = require("q");
/**
* class AcmeAccount represents an AcmeAccount
*/
class 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) {
}
} }
exports.AcmeAccount = AcmeAccount; exports.AcmeAccount = AcmeAccount;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLmNsYXNzZXMuYWNtZWFjY291bnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGFjbWUuY2xhc3Nlcy5hY21lYWNjb3VudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsMEJBQXVCO0FBRXZCO0NBRUM7QUFGRCxrQ0FFQyJ9 //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLmNsYXNzZXMuYWNtZWFjY291bnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGFjbWUuY2xhc3Nlcy5hY21lYWNjb3VudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsdUJBQXNCO0FBUXRCOztHQUVHO0FBQ0g7SUFLSSxZQUFZLGtCQUE2QjtRQUNyQyxJQUFJLENBQUMsZUFBZSxHQUFHLGtCQUFrQixDQUFBO0lBQzdDLENBQUM7SUFFRDs7T0FFRztJQUNILFFBQVE7UUFDSixJQUFJLElBQUksR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLENBQUE7UUFDcEIsSUFBSSxDQUFDLGVBQWUsQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUNyQztZQUNJLE9BQU8sRUFBRSxDQUFDLDZCQUE2QixDQUFDO1NBQzNDLEVBQ0QsQ0FBQyxHQUFHLEVBQUUsR0FBRztZQUNMLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7Z0JBQ04sT0FBTyxDQUFDLEtBQUssQ0FBQyxrQ0FBa0MsQ0FBQyxDQUFBO2dCQUNqRCxPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFBO2dCQUNoQixJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFBO2dCQUNoQixNQUFNLENBQUE7WUFDVixDQUFDO1lBQ0QsSUFBSSxDQUFDLEdBQUcsR0FBRyxHQUFHLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQTtZQUN2QixJQUFJLENBQUMsSUFBSSxHQUFHLEdBQUcsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFBO1lBQzVCLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFBO1lBQ3RCLElBQUksQ0FBQyxRQUFRLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUE7WUFDcEMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFBO1FBQ2xCLENBQUMsQ0FBQyxDQUFBO1FBQ04sTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7SUFDdkIsQ0FBQztJQUVEOztPQUVHO0lBQ0gsUUFBUTtRQUNKLElBQUksSUFBSSxHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQTtRQUNwQixJQUFJLE9BQU8sR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtRQUNyQyxJQUFJLGNBQWMsR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBQzFDLElBQUksR0FBRyxHQUFHLGNBQWMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsT0FBTyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsQ0FBQTtRQUNsRSxJQUFJLENBQUMsZUFBZSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUMsR0FBRyxFQUFFLEdBQUc7WUFDakcsRUFBRSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztnQkFDTixPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFBO2dCQUNoQixJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFBO2dCQUNoQixNQUFNLENBQUE7WUFDVixDQUFDO1lBQ0QsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFBO1FBQ2xCLENBQUMsQ0FBQyxDQUFBO1FBQ0YsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7SUFDdkIsQ0FBQztJQUVELGNBQWMsQ0FBQyxhQUFxQjtJQUVwQyxDQUFDO0NBQ0o7QUF4REQsa0NBd0RDIn0=

View File

@ -1,3 +1,71 @@
import 'typings-global'; /// <reference types="q" />
export declare class AcmeCert { 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 ISmartAcmeChallengeAccepted extends ISmartAcmeChallenge {
keyHash: 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 {
attributes: any;
fullchain: string;
parentAcmeAccount: AcmeAccount;
csr: any;
validFrom: Date;
validTo: Date;
keypair: IRsaKeypair;
keyPairFinal: IRsaKeypair;
constructor(optionsArg: IAcmeCsrConstructorOptions, parentSmartAcmeArg: any);
/**
* requests a challenge for a domain
* @param domainNameArg - the domain name to request a challenge for
* @param challengeType - the challenge type to request
*/
requestChallenge(domainNameArg: string, challengeTypeArg?: TChallengeType): q.Promise<ISmartAcmeChallengeAccepted>;
/**
* validates a challenge, only call after you have set the challenge at the expected location
*/
validate(challenge: ISmartAcmeChallengeAccepted): q.Promise<{}>;
/**
* 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
*/
private acceptChallenge(challenge);
} }

File diff suppressed because one or more lines are too long

View File

@ -1,21 +0,0 @@
/// <reference types="q" />
import 'typings-global';
import * as q from 'q';
import { SmartAcme } from './smartacme.classes.smartacme';
export interface IRsaKeypair {
publicKey: string;
privateKey: string;
}
export declare class SmartacmeHelper {
parentSmartAcme: SmartAcme;
constructor(smartAcmeArg: SmartAcme);
/**
* creates a keypair to use with requests and to generate JWK from
*/
createKeypair(bit?: number): IRsaKeypair;
/**
* gets an existing registration
* @executes ASYNC
*/
getReg(): q.Promise<{}>;
}

View File

@ -1,40 +0,0 @@
"use strict";
require("typings-global");
const q = require("q");
let rsaKeygen = require('rsa-keygen');
class SmartacmeHelper {
constructor(smartAcmeArg) {
this.parentSmartAcme = smartAcmeArg;
}
/**
* creates a keypair to use with requests and to generate JWK from
*/
createKeypair(bit = 2048) {
let result = rsaKeygen.generate(bit);
return {
publicKey: result.public_key,
privateKey: result.private_key
};
}
/**
* gets an existing registration
* @executes ASYNC
*/
getReg() {
let done = q.defer();
let body = { resource: 'reg' };
this.parentSmartAcme.rawacmeClient.post(this.parentSmartAcme.location, body, this.parentSmartAcme.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;
}
}
exports.SmartacmeHelper = SmartacmeHelper;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLmNsYXNzZXMuaGVscGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRhY21lLmNsYXNzZXMuaGVscGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSwwQkFBdUI7QUFDdkIsdUJBQXNCO0FBQ3RCLElBQUksU0FBUyxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUMsQ0FBQTtBQVNyQztJQUdJLFlBQVksWUFBdUI7UUFDL0IsSUFBSSxDQUFDLGVBQWUsR0FBRyxZQUFZLENBQUE7SUFDdkMsQ0FBQztJQUVEOztPQUVHO0lBQ0gsYUFBYSxDQUFDLEdBQUcsR0FBRyxJQUFJO1FBQ3BCLElBQUksTUFBTSxHQUFHLFNBQVMsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUE7UUFDcEMsTUFBTSxDQUFDO1lBQ0gsU0FBUyxFQUFFLE1BQU0sQ0FBQyxVQUFVO1lBQzVCLFVBQVUsRUFBRSxNQUFNLENBQUMsV0FBVztTQUNqQyxDQUFBO0lBQ0wsQ0FBQztJQUVEOzs7T0FHRztJQUNILE1BQU07UUFDRixJQUFJLElBQUksR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLENBQUE7UUFDcEIsSUFBSSxJQUFJLEdBQUcsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLENBQUE7UUFDOUIsSUFBSSxDQUFDLGVBQWUsQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUNuQyxJQUFJLENBQUMsZUFBZSxDQUFDLFFBQVEsRUFDN0IsSUFBSSxFQUFFLElBQUksQ0FBQyxlQUFlLENBQUMsT0FBTyxFQUNsQyxDQUFDLEdBQUcsRUFBRSxHQUFHO1lBQ0wsRUFBRSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztnQkFDTixPQUFPLENBQUMsS0FBSyxDQUFDLGtDQUFrQyxDQUFDLENBQUE7Z0JBQ2pELE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUE7Z0JBQ2hCLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUE7Z0JBQ2hCLE1BQU0sQ0FBQTtZQUNWLENBQUM7WUFDRCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUE7WUFDckMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFBO1FBQ2xCLENBQUMsQ0FDSixDQUFBO1FBQ0QsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7SUFDdkIsQ0FBQztDQUNKO0FBekNELDBDQXlDQyJ9

View File

@ -1,57 +1,34 @@
/// <reference types="q" /> /// <reference types="q" />
import 'typings-global';
import * as q from 'q'; import * as q from 'q';
import { SmartacmeHelper, IRsaKeypair } from './smartacme.classes.helper'; import { AcmeAccount } from './smartacme.classes.acmeaccount';
export declare type TChallengeType = 'dns-01' | 'http-01'; /**
export declare type TChallengeStatus = 'pending'; * a rsa keypair needed for account creation and subsequent requests
export interface ISmartAcmeChallenge { */
uri: string; export interface IRsaKeypair {
status: TChallengeStatus; publicKey: string;
type: TChallengeType; privateKey: string;
token: string;
keyAuthorization: string;
}
export interface ISmartAcmeChallengeAccepted extends ISmartAcmeChallenge {
keyHash: string;
} }
export { AcmeAccount } from './smartacme.classes.acmeaccount';
export { AcmeCert, ISmartAcmeChallenge, ISmartAcmeChallengeAccepted } from './smartacme.classes.acmecert';
/** /**
* class SmartAcme exports methods for maintaining SSL Certificates * class SmartAcme exports methods for maintaining SSL Certificates
*/ */
export declare class SmartAcme { export declare class SmartAcme {
helper: SmartacmeHelper;
acmeUrl: string; acmeUrl: string;
productionBool: boolean; productionBool: boolean;
keyPair: IRsaKeypair; keyPair: IRsaKeypair;
location: string;
link: string;
rawacmeClient: any; rawacmeClient: any;
JWK: any;
/** /**
* the constructor for class SmartAcme * the constructor for class SmartAcme
*/ */
constructor(productionArg?: boolean); constructor(productionArg?: boolean);
/**
* init the smartacme instance
*/
init(): q.Promise<{}>;
/** /**
* creates an account if not currently present in module * creates an account if not currently present in module
* @executes ASYNC * @executes ASYNC
*/ */
createAccount(): q.Promise<{}>; createAccount(): q.Promise<AcmeAccount>;
agreeTos(): q.Promise<{}>;
/**
* requests a challenge for a domain
* @param domainNameArg - the domain name to request a challenge for
* @param challengeType - the challenge type to request
*/
requestChallenge(domainNameArg: string, challengeTypeArg?: TChallengeType): q.Promise<ISmartAcmeChallengeAccepted>;
/**
* getCertificate - takes care of cooldown, validation polling and certificate retrieval
*/
getCertificate(): void;
/**
* validates a challenge
*/
validate(challenge: ISmartAcmeChallengeAccepted): q.Promise<{}>;
/**
* accept a challenge - for private use only
*/
private acceptChallenge(challenge);
} }

File diff suppressed because one or more lines are too long

6
dist/smartacme.helpers.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
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;

34
dist/smartacme.helpers.js vendored Normal file
View File

@ -0,0 +1,34 @@
"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
};
};
/**
* 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLmhlbHBlcnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGFjbWUuaGVscGVycy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsMEJBQXVCO0FBQ3ZCLHVCQUFzQjtBQUV0QiwrQ0FBOEM7QUFLOUM7O0dBRUc7QUFDUSxRQUFBLGFBQWEsR0FBRyxDQUFDLEdBQUcsR0FBRyxJQUFJO0lBQ2xDLElBQUksTUFBTSxHQUFHLE9BQU8sQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFBO0lBQzVDLE1BQU0sQ0FBQztRQUNILFNBQVMsRUFBRSxNQUFNLENBQUMsVUFBVTtRQUM1QixVQUFVLEVBQUUsTUFBTSxDQUFDLFdBQVc7S0FDakMsQ0FBQTtBQUNMLENBQUMsQ0FBQTtBQUVEOzs7R0FHRztBQUNILElBQUksTUFBTSxHQUFHLENBQUMsWUFBdUIsRUFBRSxRQUFnQjtJQUNuRCxJQUFJLElBQUksR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLENBQUE7SUFDcEIsSUFBSSxJQUFJLEdBQUcsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLENBQUE7SUFDOUIsWUFBWSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQzNCLFFBQVEsRUFDUixJQUFJLEVBQ0osWUFBWSxDQUFDLE9BQU8sRUFDcEIsQ0FBQyxHQUFHLEVBQUUsR0FBRztRQUNMLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7WUFDTixPQUFPLENBQUMsS0FBSyxDQUFDLGtDQUFrQyxDQUFDLENBQUE7WUFDakQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQTtZQUNoQixJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFBO1lBQ2hCLE1BQU0sQ0FBQTtRQUNWLENBQUM7UUFDRCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUE7UUFDckMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFBO0lBQ2xCLENBQUMsQ0FDSixDQUFBO0lBQ0QsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7QUFDdkIsQ0FBQyxDQUFBIn0=

8
dist/smartacme.plugins.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import 'typings-global';
declare let rsaKeygen: any;
declare let rawacme: any;
declare let nodeForge: any;
import * as smartfile from 'smartfile';
import * as smartstring from 'smartstring';
import * as paths from './smartacme.paths';
export { rsaKeygen, rawacme, nodeForge, smartfile, smartstring, paths };

16
dist/smartacme.plugins.js vendored Normal file
View File

@ -0,0 +1,16 @@
"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 smartfile = require("smartfile");
exports.smartfile = smartfile;
const smartstring = require("smartstring");
exports.smartstring = smartstring;
const paths = require("./smartacme.paths");
exports.paths = paths;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLnBsdWdpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGFjbWUucGx1Z2lucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsMEJBQXVCLENBQUMsbUJBQW1CO0FBRzNDLElBQUksU0FBUyxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUMsQ0FBQSxDQUFDLGFBQWE7QUFVL0MsOEJBQVM7QUFUYixJQUFJLE9BQU8sR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUEsQ0FBQyx3QkFBd0I7QUFVckQsMEJBQU87QUFUWCxJQUFJLFNBQVMsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUE7QUFVakMsOEJBQVM7QUFSYiwwQkFBMEI7QUFDMUIsdUNBQXNDO0FBUWxDLDhCQUFTO0FBUGIsMkNBQTBDO0FBUXRDLGtDQUFXO0FBUGYsMkNBQTBDO0FBUXRDLHNCQUFLIn0=

View File

@ -35,6 +35,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/should": "^8.1.30", "@types/should": "^8.1.30",
"cflare": "0.0.10",
"should": "^11.1.1", "should": "^11.1.1",
"typings-test": "^1.0.3" "typings-test": "^1.0.3"
} }

View File

@ -4,44 +4,42 @@ const should = require("should");
// import the module to test // import the module to test
const smartacme = require("../dist/index"); const smartacme = require("../dist/index");
describe('smartacme', function () { describe('smartacme', function () {
let testAcme; let testSmartAcme;
let testAcmeAccount;
let testChallenge; let testChallenge;
it('should create a valid instance', function () { it('should create a valid instance', function (done) {
this.timeout(10000); this.timeout(10000);
testAcme = new smartacme.SmartAcme(); testSmartAcme = new smartacme.SmartAcme();
should(testAcme).be.instanceOf(smartacme.SmartAcme); testSmartAcme.init().then(() => {
should(testSmartAcme).be.instanceOf(smartacme.SmartAcme);
done();
}).catch(err => { done(err); });
}); });
it('should have created keyPair', function () { it('should have created keyPair', function () {
should(testAcme.acmeUrl).be.of.type('string'); should(testSmartAcme.acmeUrl).be.of.type('string');
}); });
it('should register a new account', function (done) { it('should register a new account', function (done) {
this.timeout(10000); this.timeout(10000);
testAcme.createAccount().then(x => { testSmartAcme.createAccount().then(x => {
done(); done();
}).catch(err => { }).catch(err => {
console.log(err); console.log(err);
done(err); done(err);
}); });
}); });
it('should agree to ToS', function (done) { it.skip('should request a cert for a domain', function (done) {
this.timeout(10000); this.timeout(10000);
testAcme.agreeTos().then(() => { testAcmeAccount.requestChallenge('bleu.de').then((challengeAccepted) => {
done();
});
});
it('should request a challenge for a domain', function (done) {
this.timeout(10000);
testAcme.requestChallenge('bleu.de').then((challengeAccepted) => {
console.log(challengeAccepted); console.log(challengeAccepted);
testChallenge = challengeAccepted; testChallenge = challengeAccepted;
done(); done();
}); });
}); });
it('should poll for validation of a challenge', function (done) { it.skip('should poll for validation of a challenge', function (done) {
this.timeout(10000); this.timeout(10000);
testAcme.validate(testChallenge).then(x => { testSmartAcme.validate(testChallenge).then(x => {
done(); done();
}); });
}); });
}); });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLHdCQUFxQjtBQUNyQixpQ0FBZ0M7QUFFaEMsNEJBQTRCO0FBQzVCLDJDQUEwQztBQUUxQyxRQUFRLENBQUMsV0FBVyxFQUFFO0lBQ2xCLElBQUksUUFBNkIsQ0FBQTtJQUNqQyxJQUFJLGFBQW9ELENBQUE7SUFDeEQsRUFBRSxDQUFDLGdDQUFnQyxFQUFFO1FBQ2pDLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUE7UUFDbkIsUUFBUSxHQUFHLElBQUksU0FBUyxDQUFDLFNBQVMsRUFBRSxDQUFBO1FBQ3BDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsQ0FBQTtJQUN2RCxDQUFDLENBQUMsQ0FBQTtJQUVGLEVBQUUsQ0FBQyw2QkFBNkIsRUFBRTtRQUM5QixNQUFNLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFBO0lBQ2pELENBQUMsQ0FBQyxDQUFBO0lBRUYsRUFBRSxDQUFDLCtCQUErQixFQUFFLFVBQVUsSUFBSTtRQUM5QyxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFBO1FBQ25CLFFBQVEsQ0FBQyxhQUFhLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUMzQixJQUFJLEVBQUUsQ0FBQTtRQUNWLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxHQUFHO1lBQ1IsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQTtZQUNoQixJQUFJLENBQUMsR0FBRyxDQUFDLENBQUE7UUFDYixDQUFDLENBQUMsQ0FBQTtJQUNOLENBQUMsQ0FBQyxDQUFBO0lBRUYsRUFBRSxDQUFDLHFCQUFxQixFQUFFLFVBQVMsSUFBSTtRQUNuQyxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFBO1FBQ25CLFFBQVEsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxJQUFJLENBQUM7WUFDckIsSUFBSSxFQUFFLENBQUE7UUFDVixDQUFDLENBQUMsQ0FBQTtJQUNOLENBQUMsQ0FBQyxDQUFBO0lBRUYsRUFBRSxDQUFDLHlDQUF5QyxFQUFFLFVBQVMsSUFBSTtRQUN2RCxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFBO1FBQ25CLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxpQkFBaUI7WUFDeEQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFBO1lBQzlCLGFBQWEsR0FBRyxpQkFBaUIsQ0FBQTtZQUNqQyxJQUFJLEVBQUUsQ0FBQTtRQUNWLENBQUMsQ0FBQyxDQUFBO0lBQ04sQ0FBQyxDQUFDLENBQUE7SUFFRixFQUFFLENBQUMsMkNBQTJDLEVBQUMsVUFBUyxJQUFJO1FBQ3hELElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUE7UUFDbkIsUUFBUSxDQUFDLFFBQVEsQ0FBQyxhQUFhLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUNuQyxJQUFJLEVBQUUsQ0FBQTtRQUNWLENBQUMsQ0FBQyxDQUFBO0lBQ04sQ0FBQyxDQUFDLENBQUE7QUFDTixDQUFDLENBQUMsQ0FBQSJ9 //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLHdCQUFxQjtBQUNyQixpQ0FBZ0M7QUFHaEMsNEJBQTRCO0FBQzVCLDJDQUEwQztBQUUxQyxRQUFRLENBQUMsV0FBVyxFQUFFO0lBQ2xCLElBQUksYUFBa0MsQ0FBQTtJQUN0QyxJQUFJLGVBQXNDLENBQUE7SUFDMUMsSUFBSSxhQUFvRCxDQUFBO0lBQ3hELEVBQUUsQ0FBQyxnQ0FBZ0MsRUFBRSxVQUFVLElBQUk7UUFDL0MsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQTtRQUNuQixhQUFhLEdBQUcsSUFBSSxTQUFTLENBQUMsU0FBUyxFQUFFLENBQUE7UUFDekMsYUFBYSxDQUFDLElBQUksRUFBRSxDQUFDLElBQUksQ0FBQztZQUN0QixNQUFNLENBQUMsYUFBYSxDQUFDLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLENBQUE7WUFDeEQsSUFBSSxFQUFFLENBQUE7UUFDVixDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxNQUFNLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQSxDQUFDLENBQUMsQ0FBQyxDQUFBO0lBQ2xDLENBQUMsQ0FBQyxDQUFBO0lBRUYsRUFBRSxDQUFDLDZCQUE2QixFQUFFO1FBQzlCLE1BQU0sQ0FBQyxhQUFhLENBQUMsT0FBTyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUE7SUFDdEQsQ0FBQyxDQUFDLENBQUE7SUFFRixFQUFFLENBQUMsK0JBQStCLEVBQUUsVUFBVSxJQUFJO1FBQzlDLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUE7UUFDbkIsYUFBYSxDQUFDLGFBQWEsRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ2hDLElBQUksRUFBRSxDQUFBO1FBQ1YsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUc7WUFDUixPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFBO1lBQ2hCLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQTtRQUNiLENBQUMsQ0FBQyxDQUFBO0lBQ04sQ0FBQyxDQUFDLENBQUE7SUFFRixFQUFFLENBQUMsSUFBSSxDQUFDLG9DQUFvQyxFQUFFLFVBQVUsSUFBSTtRQUN4RCxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFBO1FBQ25CLGVBQWUsQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxpQkFBaUI7WUFDL0QsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFBO1lBQzlCLGFBQWEsR0FBRyxpQkFBaUIsQ0FBQTtZQUNqQyxJQUFJLEVBQUUsQ0FBQTtRQUNWLENBQUMsQ0FBQyxDQUFBO0lBQ04sQ0FBQyxDQUFDLENBQUE7SUFFRixFQUFFLENBQUMsSUFBSSxDQUFDLDJDQUEyQyxFQUFFLFVBQVUsSUFBSTtRQUMvRCxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFBO1FBQ25CLGFBQWEsQ0FBQyxRQUFRLENBQUMsYUFBYSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDeEMsSUFBSSxFQUFFLENBQUE7UUFDVixDQUFDLENBQUMsQ0FBQTtJQUNOLENBQUMsQ0FBQyxDQUFBO0FBQ04sQ0FBQyxDQUFDLENBQUEifQ==

View File

@ -1,25 +1,30 @@
import 'typings-test' import 'typings-test'
import * as should from 'should' import * as should from 'should'
import * as cflare from 'cflare'
// import the module to test // import the module to test
import * as smartacme from '../dist/index' import * as smartacme from '../dist/index'
describe('smartacme', function () { describe('smartacme', function () {
let testAcme: smartacme.SmartAcme let testSmartAcme: smartacme.SmartAcme
let testAcmeAccount: smartacme.AcmeAccount
let testChallenge: smartacme.ISmartAcmeChallengeAccepted let testChallenge: smartacme.ISmartAcmeChallengeAccepted
it('should create a valid instance', function () { it('should create a valid instance', function (done) {
this.timeout(10000) this.timeout(10000)
testAcme = new smartacme.SmartAcme() testSmartAcme = new smartacme.SmartAcme()
should(testAcme).be.instanceOf(smartacme.SmartAcme) testSmartAcme.init().then(() => {
should(testSmartAcme).be.instanceOf(smartacme.SmartAcme)
done()
}).catch(err => { done(err) })
}) })
it('should have created keyPair', function () { it('should have created keyPair', function () {
should(testAcme.acmeUrl).be.of.type('string') should(testSmartAcme.acmeUrl).be.of.type('string')
}) })
it('should register a new account', function (done) { it('should register a new account', function (done) {
this.timeout(10000) this.timeout(10000)
testAcme.createAccount().then(x => { testSmartAcme.createAccount().then(x => {
done() done()
}).catch(err => { }).catch(err => {
console.log(err) console.log(err)
@ -27,25 +32,18 @@ describe('smartacme', function () {
}) })
}) })
it('should agree to ToS', function(done) { it.skip('should request a cert for a domain', function (done) {
this.timeout(10000) this.timeout(10000)
testAcme.agreeTos().then(() => { testAcmeAccount.requestChallenge('bleu.de').then((challengeAccepted) => {
done()
})
})
it('should request a challenge for a domain', function(done) {
this.timeout(10000)
testAcme.requestChallenge('bleu.de').then((challengeAccepted) => {
console.log(challengeAccepted) console.log(challengeAccepted)
testChallenge = challengeAccepted testChallenge = challengeAccepted
done() done()
}) })
}) })
it('should poll for validation of a challenge',function(done) { it.skip('should poll for validation of a challenge', function (done) {
this.timeout(10000) this.timeout(10000)
testAcme.validate(testChallenge).then(x => { testSmartAcme.validate(testChallenge).then(x => {
done() done()
}) })
}) })

View File

@ -1,8 +1,68 @@
import 'typings-global' 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 * class AcmeAccount represents an AcmeAccount
*/ */
export class 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) {
}
} }

View File

@ -1,8 +1,226 @@
import 'typings-global' 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 ISmartAcmeChallengeAccepted extends ISmartAcmeChallenge {
keyHash: 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 * class AcmeCert represents a cert for domain
*/ */
export class AcmeCert { export class AcmeCert {
attributes
fullchain: string fullchain: string
parentAcmeAccount: AcmeAccount
csr
validFrom: Date
validTo: Date
keypair: IRsaKeypair
keyPairFinal: IRsaKeypair
constructor(optionsArg: IAcmeCsrConstructorOptions, parentSmartAcmeArg) {
this.parentAcmeAccount = parentSmartAcmeArg
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(domainNameArg: string, challengeTypeArg: TChallengeType = 'dns-01') {
let done = q.defer<ISmartAcmeChallengeAccepted>()
this.parentAcmeAccount.parentSmartAcme.rawacmeClient.newAuthz(
{
identifier: {
type: 'dns',
value: domainNameArg
}
},
this.parentAcmeAccount.parentSmartAcme.keyPair,
(err, res) => {
if (err) {
console.error('smartacme: something went wrong:')
console.log(err)
done.reject(err)
}
console.log(JSON.stringify(res.body))
let dnsChallenge = res.body.challenges.filter(x => {
return x.type === challengeTypeArg
})[0]
this.acceptChallenge(dnsChallenge)
.then((x: ISmartAcmeChallengeAccepted) => {
done.resolve(x)
})
}
)
return done.promise
}
/**
* validates a challenge, only call after you have set the challenge at the expected location
*/
validate(challenge: ISmartAcmeChallengeAccepted) {
let done = q.defer()
this.parentAcmeAccount.parentSmartAcme.rawacmeClient.poll(challenge.uri, function (err, res) {
if (err) {
console.log(err)
done.reject(err)
}
console.log(res.status)
console.log(JSON.stringify(res.body))
done.resolve()
})
return done.promise
}
/**
* 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)
}
})
return done.promise
}
/**
* getCertificate - takes care of cooldown, validation polling and certificate retrieval
*/
getCertificate() {
}
/**
* accept a challenge - for private use only
*/
private acceptChallenge(challenge: ISmartAcmeChallenge) {
let done = q.defer()
/**
* the key is needed to accept the challenge
*/
let authKey: string = plugins.rawacme.keyAuthz(
challenge.token,
this.parentAcmeAccount.parentSmartAcme.keyPair.publicKey
)
/**
* needed in case selected challenge is of type dns-01
*/
let keyHash: string = plugins.rawacme.dnsKeyAuthzHash(authKey) // needed if dns challenge is chosen
/**
* the return challenge
*/
let returnDNSChallenge: ISmartAcmeChallengeAccepted = {
uri: challenge.uri,
type: challenge.type,
token: challenge.token,
keyAuthorization: challenge.keyAuthorization,
keyHash: keyHash,
status: challenge.status
}
this.parentAcmeAccount.parentSmartAcme.rawacmeClient.post(
challenge.uri,
{
resource: 'challenge',
keyAuthorization: authKey
},
this.parentAcmeAccount.parentSmartAcme.keyPair,
(err, res) => {
if (err) {
console.log(err)
done.reject(err)
}
done.resolve(returnDNSChallenge)
}
)
return done.promise
}
} }

View File

@ -1,52 +0,0 @@
import * as plugins from './smartacme.plugins'
import * as helpers from './smartacme.helpers'
import { IRsaKeypair } from './smartacme.classes.smartacme'
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[]
}
export class AcmeCsr {
validFrom: Date
validTo: Date
keypair: IRsaKeypair
keyPairForged: IRsaKeypair
constructor(optionsArg: IAcmeCsrConstructorOptions) {
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.keyPairForged = {
privateKey: privateKeyForged,
publicKey: publicKeyForged
}
// set dates
this.validFrom = new Date()
this.validTo = new Date()
this.validTo.setDate(this.validFrom.getDate() + 90)
// create the csr
let attributes = [
{ name: "commonName", value: domain },
{ name: "countryName", value: country },
{ shortName: "ST", value: country_short },
{ name: "localityName", value: locality },
{ name: "organizationName", value: organization },
{ shortName: "OU", value: organization_short }
]
}
}

View File

@ -3,37 +3,27 @@ import * as q from 'q' // promises
import * as plugins from './smartacme.plugins' import * as plugins from './smartacme.plugins'
import * as helpers from './smartacme.helpers' import * as helpers from './smartacme.helpers'
import { AcmeAccount } from './smartacme.classes.acmeaccount'
/**
* a rsa keypair needed for account creation and subsequent requests
*/
export interface IRsaKeypair { export interface IRsaKeypair {
publicKey: string publicKey: string
privateKey: string privateKey: string
} }
export type TChallengeType = 'dns-01' | 'http-01' export { AcmeAccount } from './smartacme.classes.acmeaccount'
export type TChallengeStatus = 'pending' export { AcmeCert, ISmartAcmeChallenge, ISmartAcmeChallengeAccepted } from './smartacme.classes.acmecert'
export interface ISmartAcmeChallenge {
uri: string
status: TChallengeStatus
type: TChallengeType
token: string
keyAuthorization: string
}
export interface ISmartAcmeChallengeAccepted extends ISmartAcmeChallenge {
keyHash: string
}
/** /**
* class SmartAcme exports methods for maintaining SSL Certificates * class SmartAcme exports methods for maintaining SSL Certificates
*/ */
export class SmartAcme { export class SmartAcme {
acmeUrl: string // the acme url to use acmeUrl: string // the acme url to use for this instance
productionBool: boolean // a boolean to quickly know wether we are in production or not productionBool: boolean // a boolean to quickly know wether we are in production or not
keyPair: IRsaKeypair // the keyPair needed for account creation keyPair: IRsaKeypair // the keyPair needed for account creation
location: string
link: string
rawacmeClient rawacmeClient
JWK
/** /**
* the constructor for class SmartAcme * the constructor for class SmartAcme
@ -49,10 +39,9 @@ export class SmartAcme {
} }
/** /**
* creates an account if not currently present in module * init the smartacme instance
* @executes ASYNC
*/ */
createAccount() { init() {
let done = q.defer() let done = q.defer()
plugins.rawacme.createClient( plugins.rawacme.createClient(
{ {
@ -70,151 +59,24 @@ export class SmartAcme {
// make client available in class // make client available in class
this.rawacmeClient = client this.rawacmeClient = client
// create the registration
client.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() done.resolve()
})
}
)
return done.promise
}
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.rawacmeClient.post(this.location, { Agreement: url, resource: 'reg' }, (err, res) => {
if (err) {
console.log(err)
done.reject(err)
return
}
done.resolve()
})
return done.promise
}
/**
* requests a challenge for a domain
* @param domainNameArg - the domain name to request a challenge for
* @param challengeType - the challenge type to request
*/
requestChallenge(domainNameArg: string, challengeTypeArg: TChallengeType = 'dns-01') {
let done = q.defer<ISmartAcmeChallengeAccepted>()
this.rawacmeClient.newAuthz(
{
identifier: {
type: 'dns',
value: domainNameArg
}
},
this.keyPair,
(err, res) => {
if (err) {
console.error('smartacme: something went wrong:')
console.log(err)
done.reject(err)
}
console.log(JSON.stringify(res.body))
let dnsChallenge = res.body.challenges.filter(x => {
return x.type === challengeTypeArg
})[0]
this.acceptChallenge(dnsChallenge)
.then((x: ISmartAcmeChallengeAccepted) => {
done.resolve(x)
})
} }
) )
return done.promise return done.promise
} }
/** /**
* getCertificate - takes care of cooldown, validation polling and certificate retrieval * creates an account if not currently present in module
* @executes ASYNC
*/ */
getCertificate() { createAccount() {
let done = q.defer<AcmeAccount>()
} let acmeAccount = new AcmeAccount(this)
acmeAccount.register().then(() => {
/** return acmeAccount.agreeTos()
* validates a challenge }).then(() => {
*/ done.resolve(acmeAccount)
validate(challenge: ISmartAcmeChallengeAccepted) {
let done = q.defer()
this.rawacmeClient.poll(challenge.uri, function(err, res) {
if (err) {
console.log(err)
done.reject(err)
}
console.log(res.status)
console.log(JSON.stringify(res.body))
done.resolve()
}) })
return done.promise return done.promise
} }
/**
* accept a challenge - for private use only
*/
private acceptChallenge(challenge: ISmartAcmeChallenge) {
let done = q.defer()
/**
* the key is needed to accept the challenge
*/
let authKey: string = plugins.rawacme.keyAuthz(challenge.token, this.keyPair.publicKey)
/**
* needed in case selected challenge is of type dns-01
*/
let keyHash: string = plugins.rawacme.dnsKeyAuthzHash(authKey) // needed if dns challenge is chosen
/**
* the return challenge
*/
let returnDNSChallenge: ISmartAcmeChallengeAccepted = {
uri: challenge.uri,
type: challenge.type,
token: challenge.token,
keyAuthorization: challenge.keyAuthorization,
keyHash: keyHash,
status: challenge.status
}
this.rawacmeClient.post(
challenge.uri,
{
resource: 'challenge',
keyAuthorization: authKey
},
this.keyPair,
(err, res) => {
if (err) {
console.log(err)
done.reject(err)
}
done.resolve(returnDNSChallenge)
}
)
return done.promise
}
} }

View File

@ -4,8 +4,7 @@ import * as q from 'q'
import * as plugins from './smartacme.plugins' import * as plugins from './smartacme.plugins'
import { SmartAcme, IRsaKeypair } from './smartacme.classes.smartacme' 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 * creates a keypair to use with requests and to generate JWK from
@ -22,12 +21,13 @@ export let createKeypair = (bit = 2048): IRsaKeypair => {
* gets an existing registration * gets an existing registration
* @executes ASYNC * @executes ASYNC
*/ */
let getReg = (smartAcmeArg: SmartAcme) => { let getReg = (SmartAcmeArg: SmartAcme, location: string) => {
let done = q.defer() let done = q.defer()
let body = { resource: 'reg' } let body = { resource: 'reg' }
smartAcmeArg.rawacmeClient.post( SmartAcmeArg.rawacmeClient.post(
smartAcmeArg.location, location,
body, smartAcmeArg.keyPair, body,
SmartAcmeArg.keyPair,
(err, res) => { (err, res) => {
if (err) { if (err) {
console.error('smartacme: something went wrong:') console.error('smartacme: something went wrong:')