This commit is contained in:
Philipp Kunz 2016-11-01 20:16:43 +01:00
parent 5313edc306
commit a2c6b6a259
14 changed files with 204 additions and 517 deletions

View File

@ -186,9 +186,9 @@ export declare class AcmeClient {
* @param {number} days_valid * @param {number} days_valid
* @return {{resource: string, csr: string, notBefore: string, notAfter: string}} * @return {{resource: string, csr: string, notBefore: string, notAfter: string}}
*/ */
makeCertRequest(csr: any, DAYS_VALID: number): { makeCertRequest(csr: string, DAYS_VALID: number): {
'resource': string; 'resource': string;
'csr': string; 'csr': any;
'notBefore': string; 'notBefore': string;
'notAfter': string; 'notAfter': string;
}; };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,9 @@
import * as acmeclient from './smartacme.classes.acmeclient'; import * as acmeclient from './smartacme.classes.acmeclient';
export declare class SmartAcme { export declare class SmartAcme {
acmeAccount: AcmeAccount;
acmeClient: acmeclient.AcmeClient; acmeClient: acmeclient.AcmeClient;
constructor(directoryUrlArg?: string); constructor(directoryUrlArg?: string);
createAccount(): void;
}
export declare class AcmeAccount {
} }

View File

@ -4,6 +4,14 @@ class SmartAcme {
constructor(directoryUrlArg = 'https://acme-staging.api.letsencrypt.org/directory') { constructor(directoryUrlArg = 'https://acme-staging.api.letsencrypt.org/directory') {
this.acmeClient = new acmeclient.AcmeClient(directoryUrlArg); this.acmeClient = new acmeclient.AcmeClient(directoryUrlArg);
} }
createAccount() {
this.acmeClient.createAccount('test@bleu.de', (answer) => {
console.log(answer);
});
}
} }
exports.SmartAcme = SmartAcme; exports.SmartAcme = SmartAcme;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLmNsYXNzZXMuc21hcnRhY21lLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRhY21lLmNsYXNzZXMuc21hcnRhY21lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFDQSw2REFBNEQ7QUFFNUQ7SUFFSSxZQUFZLGtCQUEwQixvREFBb0Q7UUFDdEYsSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLFVBQVUsQ0FBQyxVQUFVLENBQUMsZUFBZSxDQUFDLENBQUE7SUFDaEUsQ0FBQztDQUNKO0FBTEQsOEJBS0MifQ== class AcmeAccount {
}
exports.AcmeAccount = AcmeAccount;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLmNsYXNzZXMuc21hcnRhY21lLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRhY21lLmNsYXNzZXMuc21hcnRhY21lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFDQSw2REFBNEQ7QUFFNUQ7SUFHSSxZQUFZLGtCQUEwQixvREFBb0Q7UUFDdEYsSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLFVBQVUsQ0FBQyxVQUFVLENBQUMsZUFBZSxDQUFDLENBQUE7SUFDaEUsQ0FBQztJQUVELGFBQWE7UUFDVCxJQUFJLENBQUMsVUFBVSxDQUFDLGFBQWEsQ0FBQyxjQUFjLEVBQUMsQ0FBQyxNQUFNO1lBQ2hELE9BQU8sQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUE7UUFDdkIsQ0FBQyxDQUFDLENBQUE7SUFDTixDQUFDO0NBQ0o7QUFaRCw4QkFZQztBQUVEO0NBRUM7QUFGRCxrQ0FFQyJ9

View File

@ -1,3 +1,4 @@
import 'typings-global'; import 'typings-global';
import * as path from 'path'; import * as path from 'path';
export { path }; import * as smartstring from 'smartstring';
export { path, smartstring };

View File

@ -2,4 +2,6 @@
require("typings-global"); require("typings-global");
const path = require("path"); const path = require("path");
exports.path = path; exports.path = path;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLnBsdWdpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGFjbWUucGx1Z2lucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsMEJBQXVCO0FBQ3ZCLDZCQUE0QjtBQUd4QixvQkFBSSJ9 const smartstring = require("smartstring");
exports.smartstring = smartstring;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhY21lLnBsdWdpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGFjbWUucGx1Z2lucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsMEJBQXVCO0FBQ3ZCLDZCQUE0QjtBQUl4QixvQkFBSTtBQUhSLDJDQUEwQztBQUl0QyxrQ0FBVyJ9

View File

@ -24,10 +24,9 @@
"homepage": "https://gitlab.com/pushrocks/smartacme#README", "homepage": "https://gitlab.com/pushrocks/smartacme#README",
"dependencies": { "dependencies": {
"@types/base64url": "^2.0.3", "@types/base64url": "^2.0.3",
"base64url": "^2.0.0",
"jwa": "^1.1.3", "jwa": "^1.1.3",
"rsa-pem-to-jwk": "^1.1.3", "rsa-pem-to-jwk": "^1.1.3",
"smartstring": "^2.0.19", "smartstring": "^2.0.20",
"typings-global": "^1.0.14" "typings-global": "^1.0.14"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,7 +1,16 @@
"use strict"; "use strict";
require("typings-test"); require("typings-test");
const should = require("should");
// import the module to test
const smartacme = require("../dist/index");
describe('smartacme', function () { describe('smartacme', function () {
let testAcme; let testAcme;
it('should create a valid instance'); it('should create a valid instance', function () {
testAcme = new smartacme.SmartAcme();
should(testAcme).be.instanceOf(smartacme.SmartAcme);
}); });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLHdCQUFxQjtBQU1yQixRQUFRLENBQUMsV0FBVyxFQUFFO0lBQ2xCLElBQUksUUFBNkIsQ0FBQTtJQUNqQyxFQUFFLENBQUMsZ0NBQWdDLENBQUMsQ0FBQTtBQUN4QyxDQUFDLENBQUMsQ0FBQSJ9 it('should register a new account', function () {
testAcme.createAccount();
});
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLHdCQUFxQjtBQUNyQixpQ0FBZ0M7QUFFaEMsNEJBQTRCO0FBQzVCLDJDQUEwQztBQUUxQyxRQUFRLENBQUMsV0FBVyxFQUFFO0lBQ2xCLElBQUksUUFBNkIsQ0FBQTtJQUNqQyxFQUFFLENBQUMsZ0NBQWdDLEVBQUU7UUFDakMsUUFBUSxHQUFHLElBQUksU0FBUyxDQUFDLFNBQVMsRUFBRSxDQUFBO1FBQ3BDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsQ0FBQTtJQUN2RCxDQUFDLENBQUMsQ0FBQTtJQUNGLEVBQUUsQ0FBQywrQkFBK0IsRUFBRTtRQUNoQyxRQUFRLENBQUMsYUFBYSxFQUFFLENBQUE7SUFDNUIsQ0FBQyxDQUFDLENBQUE7QUFDTixDQUFDLENBQUMsQ0FBQSJ9

View File

@ -5,6 +5,12 @@ import * as should from 'should'
import * as smartacme from '../dist/index' import * as smartacme from '../dist/index'
describe('smartacme', function(){ describe('smartacme', function(){
let testAcme: smartacme.smartacme let testAcme: smartacme.SmartAcme
it('should create a valid instance') it('should create a valid instance', function(){
testAcme = new smartacme.SmartAcme()
should(testAcme).be.instanceOf(smartacme.SmartAcme)
})
it('should register a new account', function() {
testAcme.createAccount()
})
}) })

View File

@ -1,5 +1,4 @@
import * as plugins from './smartacme.plugins' import * as plugins from './smartacme.plugins'
import * as base64url from 'base64url'
import * as child_process from 'child_process' import * as child_process from 'child_process'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as fs from 'fs' import * as fs from 'fs'
@ -14,7 +13,7 @@ import { JWebClient } from './smartacme.classes.jwebclient'
* @return {Buffer} * @return {Buffer}
* @throws Exception if object cannot be stringified or contains cycle * @throws Exception if object cannot be stringified or contains cycle
*/ */
let json_to_utf8buffer = function (obj) { let json_to_utf8buffer = (obj) => {
return new Buffer(JSON.stringify(obj), 'utf8') return new Buffer(JSON.stringify(obj), 'utf8')
} }
@ -27,7 +26,7 @@ let json_to_utf8buffer = function (obj) {
*/ */
export class AcmeClient { export class AcmeClient {
clientProfilePubKey: any clientProfilePubKey: any
days_valid: number daysValid: number
defaultRsaKeySize: number defaultRsaKeySize: number
directory: any directory: any
directoryUrl: string directoryUrl: string
@ -37,7 +36,7 @@ export class AcmeClient {
regLink: string regLink: string
tosLink: string tosLink: string
webroot: string webroot: string
well_known_path: string wellKnownPath: string
withInteraction: boolean withInteraction: boolean
constructor(directoryUrlArg) { constructor(directoryUrlArg) {
/** /**
@ -50,7 +49,7 @@ export class AcmeClient {
* @desc Validity period in days * @desc Validity period in days
* @default 1 * @default 1
*/ */
this.days_valid = 1 this.daysValid = 1
/** /**
* @member {number} module:AcmeClient~AcmeClient#defaultRsaKeySize * @member {number} module:AcmeClient~AcmeClient#defaultRsaKeySize
* @desc Key strength in bits * @desc Key strength in bits
@ -104,7 +103,7 @@ export class AcmeClient {
* @desc Directory structure for challenge data * @desc Directory structure for challenge data
* @default "/.well-known/acme-challenge/" * @default "/.well-known/acme-challenge/"
*/ */
this.well_known_path = '/.well-known/acme-challenge/' // {string} this.wellKnownPath = '/.well-known/acme-challenge/' // {string}
/** /**
* @member {boolean} module:AcmeClient~AcmeClient#withInteraction * @member {boolean} module:AcmeClient~AcmeClient#withInteraction
* @desc Determines if interaction of user is required * @desc Determines if interaction of user is required
@ -154,7 +153,6 @@ export class AcmeClient {
*/ */
getRegistration(uri, payload, callback) { getRegistration(uri, payload, callback) {
/*jshint -W069 */ /*jshint -W069 */
let ctx = this
if (!(payload instanceof Object)) { if (!(payload instanceof Object)) {
payload = {} // ensure payload is object payload = {} // ensure payload is object
} }
@ -162,33 +160,28 @@ export class AcmeClient {
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
callback = this.emptyCallback // ensure callback is function callback = this.emptyCallback // ensure callback is function
} }
this.jWebClient.post(uri, payload, function (ans, res) { this.jWebClient.post(uri, payload,(ans, res) => {
if (ans instanceof Object) { if (ans instanceof Object) {
ctx.clientProfilePubKey = ans.key // cache or reset returned public key this.clientProfilePubKey = ans.key // cache or reset returned public key
if ((res instanceof Object) && (res['headers'] instanceof Object)) { if ((res instanceof Object) && (res['headers'] instanceof Object)) {
let linkStr = res.headers['link'] let linkStr = res.headers['link']
if (typeof linkStr === 'string') { if (typeof linkStr === 'string') {
let tosLink = ctx.getTosLink(linkStr) let tosLink = this.getTosLink(linkStr)
if (typeof tosLink === 'string') { if (typeof tosLink === 'string') {
ctx.tosLink = tosLink // cache TOS link this.tosLink = tosLink // cache TOS link
} else { } else {
ctx.tosLink = null // reset TOS link this.tosLink = null // reset TOS link
} }
} else { } else {
ctx.tosLink = null // reset TOS link this.tosLink = null // reset TOS link
} }
} else { } else {
ctx.tosLink = null // reset TOS link this.tosLink = null // reset TOS link
} }
callback(ans, res) callback(ans, res)
} else { } else {
callback(false) callback(false)
} }
// dereference
ans = null
callback = null
ctx = null
res = null
}) })
// dereference // dereference
payload = null payload = null
@ -202,37 +195,25 @@ export class AcmeClient {
*/ */
authorizeDomain(domain, callback) { authorizeDomain(domain, callback) {
/*jshint -W069 */ /*jshint -W069 */
let ctx = this
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
callback = this.emptyCallback // ensure callback is function callback = this.emptyCallback // ensure callback is function
} }
this.getProfile(function (profile) { this.getProfile((profile) => {
if (!(profile instanceof Object)) { if (!(profile instanceof Object)) {
callback(false) // no profile returned callback(false) // no profile returned
// dereference
callback = null
ctx = null
} else { } else {
ctx.jWebClient.post(ctx.directory['new-authz'], ctx.makeDomainAuthorizationRequest(domain), function (ans, res) { this.jWebClient.post(this.directory['new-authz'], this.makeDomainAuthorizationRequest(domain), (ans, res) => {
if ((res instanceof Object) && (res['statusCode'] === 403)) { // if unauthorized if ((res instanceof Object) && (res['statusCode'] === 403)) { // if unauthorized
ctx.agreeTos(ctx.tosLink, function (ans_, res_) { // agree to TOS this.agreeTos(this.tosLink, (ans_, res_) => { // agree to TOS
if ( // if TOS were agreed successfully if ( // if TOS were agreed successfully
(res_ instanceof Object) (res_ instanceof Object)
&& (res_['statusCode'] >= 200) && (res_['statusCode'] >= 200)
&& (res_['statusCode'] <= 400) && (res_['statusCode'] <= 400)
) { ) {
ctx.authorizeDomain(domain, callback) // try authorization again this.authorizeDomain(domain, callback) // try authorization again
} else { } else {
callback(false) // agreement failed callback(false) // agreement failed
} }
// dereference
ans = null
ans_ = null
callback = null
ctx = null
profile = null
res = null
res_ = null
}) })
} else { } else {
if ( if (
@ -242,48 +223,29 @@ export class AcmeClient {
&& (ans instanceof Object) && (ans instanceof Object)
) { ) {
let poll_uri = res.headers['location'] // status URI for polling let poll_uri = res.headers['location'] // status URI for polling
let challenge = ctx.selectChallenge(ans, 'http-01') // select simple http challenge let challenge = this.selectChallenge(ans, 'http-01') // select simple http challenge
if (challenge instanceof Object) { // desired challenge is in list if (challenge instanceof Object) { // desired challenge is in list
ctx.prepareChallenge(domain, challenge, function () { // prepare all objects and files for challenge this.prepareChallenge(domain, challenge, () => { // prepare all objects and files for challenge
// reset // reset
ans = null ans = null
res = null res = null
// accept challenge // accept challenge
ctx.acceptChallenge(challenge, function (ans, res) { this.acceptChallenge(challenge, (ans, res) => {
if ( if (
(res instanceof Object) (res instanceof Object)
&& (res['statusCode'] < 400) // server confirms challenge acceptance && (res['statusCode'] < 400) // server confirms challenge acceptance
) { ) {
ctx.pollUntilValid(poll_uri, callback) // poll status until server states success this.pollUntilValid(poll_uri, callback) // poll status until server states success
} else { } else {
callback(false) // server did not confirm challenge acceptance callback(false) // server did not confirm challenge acceptance
} }
// dereference
ans = null
callback = null
challenge = null
ctx = null
profile = null
res = null
}) })
}) })
} else { } else {
callback(false) // desired challenge is not in list callback(false) // desired challenge is not in list
// dereference
ans = null
callback = null
ctx = null
profile = null
res = null
} }
} else { } else {
callback(false) // server did not respond with status URI callback(false) // server did not respond with status URI
// dereference
ans = null
callback = null
ctx = null
profile = null
res = null
} }
} }
}) })
@ -317,37 +279,22 @@ export class AcmeClient {
*/ */
pollUntilValid(uri, callback, retry = 1) { pollUntilValid(uri, callback, retry = 1) {
/*jshint -W069 */ /*jshint -W069 */
let ctx = this
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
callback = this.emptyCallback // ensure callback is function callback = this.emptyCallback // ensure callback is function
} }
if (retry > 128) { if (retry > 128) {
callback(false) // stop if retry value exceeds maximum callback(false) // stop if retry value exceeds maximum
} else { } else {
this.jWebClient.get(uri, function (ans, res) { this.jWebClient.get(uri, (ans, res) => {
if (!(ans instanceof Object)) { if (!(ans instanceof Object)) {
callback(false) // invalid answer callback(false) // invalid answer
// dereference
callback = null
ctx = null
res = null
} else { } else {
if (ans['status'] === 'pending') { // still pending if (ans['status'] === 'pending') { // still pending
setTimeout(function () { setTimeout(() => {
ctx.pollUntilValid(uri, callback, retry * 2) // retry this.pollUntilValid(uri, callback, retry * 2) // retry
// dereference
ans = null
callback = null
ctx = null
res = null
}, retry * 500) }, retry * 500)
} else { } else {
callback(ans, res) // challenge complete callback(ans, res) // challenge complete
// dereference
ans = null
callback = null
ctx = null
res = null
} }
} }
}) })
@ -363,38 +310,22 @@ export class AcmeClient {
*/ */
pollUntilIssued(uri, callback, retry = 1) { pollUntilIssued(uri, callback, retry = 1) {
/*jshint -W069 */ /*jshint -W069 */
let ctx = this
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
callback = this.emptyCallback // ensure callback is function callback = this.emptyCallback // ensure callback is function
} }
if (retry > 128) { if (retry > 128) {
callback(false) // stop if retry value exceeds maximum callback(false) // stop if retry value exceeds maximum
} else { } else {
this.jWebClient.get(uri, function (ans, res) { this.jWebClient.get(uri,(ans, res) => {
if ((ans instanceof Buffer) && (ans.length > 0)) { if ((ans instanceof Buffer) && (ans.length > 0)) {
callback(ans) // certificate was returned with answer callback(ans) // certificate was returned with answer
// dereference
ans = null
callback = null
ctx = null
res = null
} else { } else {
if ((res instanceof Object) && (res['statusCode'] < 400)) { // still pending if ((res instanceof Object) && (res['statusCode'] < 400)) { // still pending
setTimeout(function () { setTimeout(() => {
ctx.pollUntilIssued(uri, callback, retry * 2) // retry this.pollUntilIssued(uri, callback, retry * 2) // retry
// dereference
ans = null
callback = null
ctx = null
res = null
}, retry * 500) }, retry * 500)
} else { } else {
callback(false) // CSR complete callback(false) // CSR complete
// dereference
ans = null
callback = null
ctx = null
res = null
} }
} }
}) })
@ -409,32 +340,20 @@ export class AcmeClient {
*/ */
requestSigning(domain, callback) { requestSigning(domain, callback) {
/*jshint -W069 */ /*jshint -W069 */
let ctx = this
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
callback = this.emptyCallback // ensure callback is function callback = this.emptyCallback // ensure callback is function
} }
fs.readFile(domain + '.csr', function (err, csr) { fs.readFile(domain + '.csr', (err, csrBuffer: Buffer) => {
if (err instanceof Object) { // file system error if (err instanceof Object) { // file system error
if (ctx.jWebClient.verbose) { if (this.jWebClient.verbose) {
console.error('Error : File system error', err['code'], 'while reading key from file') console.error('Error : File system error', err['code'], 'while reading key from file')
} }
callback(false) callback(false)
// dereference
callback = null
csr = null
ctx = null
err = null
} else { } else {
ctx.jWebClient.post(ctx.directory['new-cert'], ctx.makeCertRequest(csr, ctx.days_valid), function (ans, res) { let csr = csrBuffer.toString()
this.jWebClient.post(this.directory['new-cert'], this.makeCertRequest(csr, this.daysValid), (ans, res) => {
if ((ans instanceof Buffer) && (ans.length > 0)) { // answer is buffer if ((ans instanceof Buffer) && (ans.length > 0)) { // answer is buffer
callback(ans) // certificate was returned with answer callback(ans) // certificate was returned with answer
// dereference
ans = null
callback = null
csr = null
ctx = null
err = null
res = null
} else { } else {
if (res instanceof Object) { if (res instanceof Object) {
if ((res['statusCode'] < 400) && !ans) { // success response, but no answer was provided if ((res['statusCode'] < 400) && !ans) { // success response, but no answer was provided
@ -442,7 +361,7 @@ export class AcmeClient {
if (!(headers instanceof Object)) { if (!(headers instanceof Object)) {
headers = {} // ensure headers is object headers = {} // ensure headers is object
} }
ctx.pollUntilIssued(headers['location'], callback) // poll provided status URI this.pollUntilIssued(headers['location'], callback) // poll provided status URI
// dereference // dereference
headers = null headers = null
} else { } else {
@ -451,13 +370,6 @@ export class AcmeClient {
} else { } else {
callback(false) // invalid response callback(false) // invalid response
} }
// dereference
ans = null
callback = null
csr = null
ctx = null
err = null
res = null
} }
}) })
} }
@ -471,35 +383,25 @@ export class AcmeClient {
*/ */
getProfile(callback) { getProfile(callback) {
/*jshint -W069 */ /*jshint -W069 */
let ctx = this
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
callback = this.emptyCallback // ensure callback is function callback = this.emptyCallback // ensure callback is function
} }
this.getDirectory(function (dir) { this.getDirectory((dir) => {
if (!(dir instanceof Object)) { if (!(dir instanceof Object)) {
callback(false) // server did not respond with directory callback(false) // server did not respond with directory
// dereference
callback = null
ctx = null
} else { } else {
ctx.directory = dir // cache directory this.directory = dir // cache directory
ctx.newRegistration(null, function (ans, res) { // try new registration to get registration link this.newRegistration(null, (ans, res) => { // try new registration to get registration link
if ( if (
(res instanceof Object) (res instanceof Object)
&& (res['headers'] instanceof Object) && (res['headers'] instanceof Object)
&& (typeof res.headers['location'] === 'string') && (typeof res.headers['location'] === 'string')
) { ) {
ctx.regLink = res.headers['location'] this.regLink = res.headers['location']
ctx.getRegistration(ctx.regLink, null, callback) // get registration info from link this.getRegistration(this.regLink, null, callback) // get registration info from link
} else { } else {
callback(false) // registration failed callback(false) // registration failed
} }
// dereference
ans = null
callback = null
ctx = null
dir = null
res = null
}) })
} }
}) })
@ -513,40 +415,31 @@ export class AcmeClient {
*/ */
createAccount(email, callback) { createAccount(email, callback) {
/*jshint -W069 */ /*jshint -W069 */
let ctx = this
if (typeof email === 'string') { if (typeof email === 'string') {
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
callback = this.emptyCallback // ensure callback is function callback = this.emptyCallback // ensure callback is function
} }
ctx.newRegistration( this.newRegistration(
{ {
contact: [ contact: [
'mailto:' + email 'mailto:' + email
] ]
}, },
function (ans, res) { (ans, res) => {
if ( if (
(res instanceof Object) (res instanceof Object)
&& (res['statusCode'] === 201) && (res['statusCode'] === 201)
&& (res['headers'] instanceof Object) && (res['headers'] instanceof Object)
&& (typeof res.headers['location'] === 'string') && (typeof res.headers['location'] === 'string')
) { ) {
ctx.regLink = res.headers['location'] this.regLink = res.headers['location']
callback(ctx.regLink) // registration URI callback(this.regLink) // registration URI
} else { } else {
callback(false) // registration failed callback(false) // registration failed
} }
// dereference
ans = null
callback = null
ctx = null
res = null
}) })
} else { } else {
callback(false) // no email address provided callback(false) // no email address provided
// dereference
callback = null
ctx = null
} }
} }
@ -573,66 +466,47 @@ export class AcmeClient {
*/ */
requestCertificate(domain, organization, country, callback) { requestCertificate(domain, organization, country, callback) {
/*jshint -W069 */ /*jshint -W069 */
let ctx = this
if (typeof domain !== 'string') { if (typeof domain !== 'string') {
domain = '' // ensure domain is string domain = '' // ensure domain is string
} }
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
callback = this.emptyCallback // ensure callback is function callback = this.emptyCallback // ensure callback is function
} }
this.getProfile(function (profile) { this.getProfile((profile) => {
let email = ctx.extractEmail(profile) // try to determine email address from profile let email = this.extractEmail(profile) // try to determine email address from profile
if (typeof ctx.emailOverride === 'string') { if (typeof this.emailOverride === 'string') {
email = ctx.emailOverride // override email address if set email = this.emailOverride // override email address if set
} else if (typeof email !== 'string') { } else if (typeof email !== 'string') {
email = ctx.emailDefaultPrefix + '@' + domain // or set default email = this.emailDefaultPrefix + '@' + domain // or set default
} }
let bit = ctx.defaultRsaKeySize let bit = this.defaultRsaKeySize
// sanitize // sanitize
bit = Number(bit) bit = Number(bit)
country = ctx.makeSafeFileName(country) country = this.makeSafeFileName(country)
domain = ctx.makeSafeFileName(domain) domain = this.makeSafeFileName(domain)
email = ctx.makeSafeFileName(email) email = this.makeSafeFileName(email)
organization = ctx.makeSafeFileName(organization) organization = this.makeSafeFileName(organization)
// create key pair // create key pair
ctx.createKeyPair(bit, country, organization, domain, email, function (e) { // create key pair this.createKeyPair(bit, country, organization, domain, email, (e) => { // create key pair
if (!e) { if (!e) {
ctx.requestSigning(domain, function (cert) { // send CSR this.requestSigning(domain, (cert) => { // send CSR
if ((cert instanceof Buffer) || (typeof cert === 'string')) { // valid certificate data if ((cert instanceof Buffer) || (typeof cert === 'string')) { // valid certificate data
fs.writeFile(domain + '.der', cert, function (err) { // sanitize domain name for file path fs.writeFile(domain + '.der', cert, (err) => { // sanitize domain name for file path
if (err instanceof Object) { // file system error if (err instanceof Object) { // file system error
if (ctx.jWebClient.verbose) { if (this.jWebClient.verbose) {
console.error('Error : File system error', err['code'], 'while writing certificate to file') console.error('Error : File system error', err['code'], 'while writing certificate to file')
} }
callback(false) callback(false)
} else { } else {
callback(true) // CSR complete and certificate written to file system callback(true) // CSR complete and certificate written to file system
} }
// dereference
callback = null
cert = null
ctx = null
e = null
err = null
profile = null
}) })
} else { } else {
callback(false) // invalid certificate data callback(false) // invalid certificate data
// dereference
callback = null
cert = null
ctx = null
e = null
profile = null
} }
}) })
} else { } else {
callback(false) // could not create key pair callback(false) // could not create key pair
// dereference
callback = null
ctx = null
e = null
profile = null
} }
}) })
}) })
@ -656,7 +530,7 @@ export class AcmeClient {
if (this.jWebClient.verbose) { if (this.jWebClient.verbose) {
console.error('Running:', openssl) console.error('Running:', openssl)
} }
child_process.exec(openssl, function (e) { child_process.exec(openssl, (e) => {
if (!e) { if (!e) {
console.error('Result : done') console.error('Result : done')
} else { } else {
@ -690,7 +564,7 @@ export class AcmeClient {
// respects file name restrictions for ntfs and ext2 // respects file name restrictions for ntfs and ext2
let regex_file = '[<>:\"/\\\\\\|\\?\\*\\u0000-\\u001f\\u007f\\u0080-\\u009f]' let regex_file = '[<>:\"/\\\\\\|\\?\\*\\u0000-\\u001f\\u007f\\u0080-\\u009f]'
let regex_path = '[<>:\"\\\\\\|\\?\\*\\u0000-\\u001f\\u007f\\u0080-\\u009f]' let regex_path = '[<>:\"\\\\\\|\\?\\*\\u0000-\\u001f\\u007f\\u0080-\\u009f]'
return name.replace(new RegExp(withPath ? regex_path : regex_file, 'g'), function (charToReplace) { return name.replace(new RegExp(withPath ? regex_path : regex_file, 'g'), (charToReplace) => {
if (typeof charToReplace === 'string') { if (typeof charToReplace === 'string') {
return '%' + charToReplace.charCodeAt(0).toString(16).toLocaleUpperCase() return '%' + charToReplace.charCodeAt(0).toString(16).toLocaleUpperCase()
} }
@ -706,66 +580,42 @@ export class AcmeClient {
*/ */
prepareChallenge(domain, challenge, callback) { prepareChallenge(domain, challenge, callback) {
/*jshint -W069, unused:false*/ /*jshint -W069, unused:false*/
let ctx = this
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
callback = this.emptyCallback // ensure callback is function callback = this.emptyCallback // ensure callback is function
} }
if (challenge instanceof Object) { if (challenge instanceof Object) {
if (challenge['type'] === 'http-01') { // simple http challenge if (challenge['type'] === 'http-01') { // simple http challenge
let path = this.webroot + this.well_known_path + challenge['token'] // webroot and well_known_path are expected to be already sanitized let path = this.webroot + this.wellKnownPath + challenge['token'] // webroot and well_known_path are expected to be already sanitized
fs.writeFile(path, this.makeKeyAuthorization(challenge), function (err) { // create challenge file fs.writeFile(path, this.makeKeyAuthorization(challenge), (err) => { // create challenge file
if (err instanceof Object) { // file system error if (err instanceof Object) { // file system error
if (ctx.jWebClient.verbose) { if (this.jWebClient.verbose) {
console.error( console.error(
'Error : File system error', 'Error : File system error',
err['code'], 'while writing challenge data to file' err['code'], 'while writing challenge data to file'
) )
} }
callback() callback()
// dereference
callback = null
challenge = null
ctx = null
err = null
} else { } else {
// let uri = "http://" + domain + this.well_known_path + challenge["token"] // let uri = "http://" + domain + this.well_known_path + challenge["token"]
let rl = readline.createInterface(process.stdin, process.stdout) let rl = readline.createInterface(process.stdin, process.stdout)
if (ctx.withInteraction) { if (this.withInteraction) {
rl.question('Press enter to proceed', function (answer) { // wait for user to proceed rl.question('Press enter to proceed', (answer) => { // wait for user to proceed
rl.close() rl.close()
callback() callback()
// dereference
callback = null
challenge = null
ctx = null
rl = null
}) })
} else { } else {
rl.close() rl.close()
callback() // skip interaction prompt if desired callback() // skip interaction prompt if desired
// dereference
callback = null
challenge = null
ctx = null
rl = null
} }
} }
}) })
} else { // no supported challenge } else { // no supported challenge
console.error('Error : Challenge not supported') console.error('Error : Challenge not supported')
callback() callback()
// dereference
callback = null
challenge = null
ctx = null
} }
} else { // invalid challenge response } else { // invalid challenge response
console.error('Error : Invalid challenge response') console.error('Error : Invalid challenge response')
callback() callback()
// dereference
callback = null
challenge = null
ctx = null
} }
} }
@ -782,9 +632,6 @@ export class AcmeClient {
match = null match = null
return result return result
} }
// dereference
match = null
return void 0
} }
/** /**
@ -796,7 +643,7 @@ export class AcmeClient {
selectChallenge(ans, challengeType: string) { selectChallenge(ans, challengeType: string) {
/*jshint -W069 */ /*jshint -W069 */
if ((ans instanceof Object) && (ans['challenges'] instanceof Array)) { if ((ans instanceof Object) && (ans['challenges'] instanceof Array)) {
return ans.challenges.filter(function (entry) { return ans.challenges.filter((entry) => {
let type = entry['type'] let type = entry['type']
// dereference // dereference
entry = null entry = null
@ -824,7 +671,7 @@ export class AcmeClient {
return void 0 // invalid profile return void 0 // invalid profile
} }
let prefix = 'mailto:' let prefix = 'mailto:'
let email = profile.contact.filter(function (entry) { let email = profile.contact.filter((entry) => {
if (typeof entry !== 'string') { if (typeof entry !== 'string') {
return false return false
} else { } else {
@ -871,11 +718,9 @@ export class AcmeClient {
} }
) )
let hash = crypto.createHash('sha256').update(jwk.toString('utf8'), 'utf8').digest() let hash = crypto.createHash('sha256').update(jwk.toString('utf8'), 'utf8').digest()
let ACCOUNT_KEY = base64url.default.encode(hash) // create base64 encoded hash of account key // create base64 encoded hash of account key
let ACCOUNT_KEY = plugins.smartstring.base64.encodeUri(hash.toString())
let token = challenge['token'] let token = challenge['token']
// dereference
challenge = null
jwk = null
return token + '.' + ACCOUNT_KEY return token + '.' + ACCOUNT_KEY
} }
} else { } else {
@ -901,14 +746,14 @@ export class AcmeClient {
* @param {number} days_valid * @param {number} days_valid
* @return {{resource: string, csr: string, notBefore: string, notAfter: string}} * @return {{resource: string, csr: string, notBefore: string, notAfter: string}}
*/ */
makeCertRequest(csr, DAYS_VALID: number) { makeCertRequest(csr: string, DAYS_VALID: number) {
if (typeof csr !== 'string' && !(csr instanceof Buffer)) { if (typeof csr !== 'string' && !(csr instanceof Buffer)) {
csr = '' // default string for CSR csr = '' // default string for CSR
} }
if ((typeof DAYS_VALID !== 'number') || (isNaN(DAYS_VALID)) || (DAYS_VALID === 0)) { if ((typeof DAYS_VALID !== 'number') || (isNaN(DAYS_VALID)) || (DAYS_VALID === 0)) {
DAYS_VALID = 1 // default validity duration (1 day) DAYS_VALID = 1 // default validity duration (1 day)
} }
let DOMAIN_CSR_DER = base64url.default.encode(csr) // create base64 encoded CSR let DOMAIN_CSR_DER = plugins.smartstring.base64.encodeUri(csr) // create base64 encoded CSR
let CURRENT_DATE = (new Date()).toISOString() // set start date to current date let CURRENT_DATE = (new Date()).toISOString() // set start date to current date
// set end date to current date + days_valid // set end date to current date + days_valid

View File

@ -1,5 +1,4 @@
import * as plugins from './smartacme.plugins' import * as plugins from './smartacme.plugins'
import * as base64url from 'base64url'
import * as https from 'https' import * as https from 'https'
let jwa = require('jwa') let jwa = require('jwa')
import * as url from 'url' import * as url from 'url'
@ -13,7 +12,7 @@ import * as url from 'url'
* @throws Exception if object cannot be stringified or contains cycle * @throws Exception if object cannot be stringified or contains cycle
*/ */
let json_to_utf8base64url = function (obj) { let json_to_utf8base64url = function (obj) {
return base64url.default.encode(new Buffer(JSON.stringify(obj), 'utf8')) return plugins.smartstring.base64.encodeUri(JSON.stringify(obj))
} }
/** /**
@ -30,17 +29,17 @@ export class JWebClient {
* @member {Object} module:JWebClient~JWebClient#key_pair * @member {Object} module:JWebClient~JWebClient#key_pair
* @desc User account key pair * @desc User account key pair
*/ */
this.key_pair = null // {Object} this.key_pair = {}
/** /**
* @member {string} module:JWebClient~JWebClient#last_nonce * @member {string} module:JWebClient~JWebClient#last_nonce
* @desc Cached nonce returned with last request * @desc Cached nonce returned with last request
*/ */
this.last_nonce = null // {string} this.last_nonce = null
/** /**
* @member {boolean} module:JWebClient~JWebClient#verbose * @member {boolean} module:JWebClient~JWebClient#verbose
* @desc Determines verbose mode * @desc Determines verbose mode
*/ */
this.verbose = false // {boolean} this.verbose = false
} }
/** /**
@ -57,7 +56,7 @@ export class JWebClient {
/*jshint -W069 */ /*jshint -W069 */
// prepare key // prepare key
if (key instanceof Object) { if (key instanceof Object) {
key = base64url.default.toBuffer(key['k']) key = new Buffer(plugins.smartstring.base64.decode(key['k']))
} }
// prepare header // prepare header
let header = { let header = {
@ -83,13 +82,6 @@ export class JWebClient {
input, input,
sig sig
].join('.') ].join('.')
// dereference
header = null
hmac = null
input = null
jwk = null
key = null
payload = null
// output // output
return output return output
} }
@ -218,11 +210,12 @@ export class JWebClient {
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
callback = this.emptyCallback // ensure callback is function callback = this.emptyCallback // ensure callback is function
} }
let key_pair = this.key_pair let jwt = this.createJWT(
if (!(key_pair instanceof Object)) { this.last_nonce,
key_pair = {} // ensure key pair is object payload,
} 'RS256',
let jwt = this.createJWT(this.last_nonce, payload, 'RS256', key_pair['private_pem'], key_pair['public_jwk']) this.key_pair['private_pem'],
this.key_pair['public_jwk'])
this.request(uri, jwt, (ans, res) => { this.request(uri, jwt, (ans, res) => {
ctx.evaluateStatus(uri, payload, ans, res) ctx.evaluateStatus(uri, payload, ans, res)
// save replay nonce for later requests // save replay nonce for later requests
@ -230,16 +223,7 @@ export class JWebClient {
ctx.last_nonce = res.headers['replay-nonce'] ctx.last_nonce = res.headers['replay-nonce']
} }
callback(ans, res) callback(ans, res)
// dereference
ans = null
callback = null
ctx = null
key_pair = null
payload = null
res = null
}, errorCallback ) }, errorCallback )
// dereference
errorCallback = null
} }
/** /**
@ -278,11 +262,6 @@ export class JWebClient {
console.error('Receive:', res['headers']) // received headers console.error('Receive:', res['headers']) // received headers
console.error('Receive:', ans) // received data console.error('Receive:', ans) // received data
} }
// dereference
ans = null
payload = null
res = null
uri_parsed = null
} }
/** /**

View File

@ -2,8 +2,19 @@ import * as plugins from './smartacme.plugins'
import * as acmeclient from './smartacme.classes.acmeclient' import * as acmeclient from './smartacme.classes.acmeclient'
export class SmartAcme { export class SmartAcme {
acmeAccount: AcmeAccount
acmeClient: acmeclient.AcmeClient acmeClient: acmeclient.AcmeClient
constructor(directoryUrlArg: string = 'https://acme-staging.api.letsencrypt.org/directory') { constructor(directoryUrlArg: string = 'https://acme-staging.api.letsencrypt.org/directory') {
this.acmeClient = new acmeclient.AcmeClient(directoryUrlArg) this.acmeClient = new acmeclient.AcmeClient(directoryUrlArg)
} }
createAccount() {
this.acmeClient.createAccount('test@bleu.de',(answer) => {
console.log(answer)
})
}
}
export class AcmeAccount {
} }

View File

@ -1,6 +1,8 @@
import 'typings-global' import 'typings-global'
import * as path from 'path' import * as path from 'path'
import * as smartstring from 'smartstring'
export { export {
path path,
smartstring
} }