Comparing version 1.0.1 to 2.0.0
@@ -0,1 +1,5 @@ | ||
# 2.0.0 - Tue Jan 15 2019 21:50:13 | ||
Revamped and simplified API. | ||
# 1.0.1 - Mon Jan 14 2019 01:21:41 | ||
@@ -2,0 +6,0 @@ |
@@ -5,4 +5,2 @@ "use strict"; | ||
var _pem = _interopRequireDefault(require("pem")); | ||
var _PCA = _interopRequireDefault(require("../PCA")); | ||
@@ -26,9 +24,8 @@ | ||
const acmpca = new _awsSdk.default.ACMPCA(); | ||
acmpca.issueCertificate = jest.fn().mockImplementation(() => { | ||
const acm = new _awsSdk.default.ACM(); | ||
acm.requestCertificate = jest.fn().mockImplementation(() => { | ||
return { | ||
promise: () => { | ||
return Promise.resolve({ | ||
CertificateArn: CERT_TEST_ARN, | ||
Certificate: '-----BEGIN CERTIFICATE-----', | ||
CertificateChain: '-----BEGIN CERTIFICATE-----' | ||
CertificateArn: CERT_TEST_ARN | ||
}); | ||
@@ -58,60 +55,56 @@ } | ||
}); | ||
acmpca.waitFor = jest.fn().mockImplementation((evt, param, cb) => { | ||
cb(null, { | ||
Certificate: '-----BEGIN CERTIFICATE-----', | ||
CertificateChain: '-----BEGIN CERTIFICATE-----' | ||
}); | ||
acm.exportCertificate = jest.fn().mockImplementation(() => { | ||
return { | ||
promise: () => { | ||
return Promise.resolve({ | ||
Certificate: '-----BEGIN CERTIFICATE-----', | ||
CertificateChain: '-----BEGIN CERTIFICATE-----', | ||
PrivateKey: '-----BEGIN RSA PRIVATE KEY-----' | ||
}); | ||
} | ||
}; | ||
}); | ||
describe('PCA class', () => { | ||
it('should create an instance of the PCA', () => { | ||
const pca = new _PCA.default(acmpca, _pem.default, CA_TEST_ARN); | ||
const pca = new _PCA.default(_awsSdk.default, CA_TEST_ARN); | ||
pca.acmpca = acmpca; | ||
pca.acm = acm; | ||
expect(pca).toBeDefined(); | ||
}); | ||
it('should create a CSR and client key', async () => { | ||
const pca = new _PCA.default(acmpca, _pem.default, CA_TEST_ARN); | ||
const data = await pca.createCSR(CSR_TEST_DATA); | ||
expect(data.csr).toBeDefined(); | ||
expect(data.clientKey).toBeDefined(); | ||
expect(data.csr).toContain('-----BEGIN CERTIFICATE REQUEST-----'); | ||
expect(data.clientKey).toContain('-----BEGIN RSA PRIVATE KEY-----'); | ||
}); | ||
it('should issue a certificate', async () => { | ||
const pca = new _PCA.default(acmpca, _pem.default, CA_TEST_ARN); | ||
const csrData = await pca.createCSR(CSR_TEST_DATA); | ||
const issueData = await pca.issueCertificate(csrData.csr, { | ||
SigningAlgorithm: 'SHA256WITHRSA', | ||
Validity: { | ||
Type: 'YEARS', | ||
Value: 1 | ||
} | ||
it('should create a certificate', async () => { | ||
const pca = new _PCA.default(_awsSdk.default, CA_TEST_ARN); | ||
pca.acmpca = acmpca; | ||
pca.acm = acm; | ||
const issueData = await pca.requestCertificate({ | ||
DomainName: 'test.int', | ||
SubjectAlternativeNames: ['blah.test.int'] | ||
}); | ||
expect(acmpca.issueCertificate).toBeCalledWith({ | ||
expect(acm.requestCertificate).toBeCalledWith({ | ||
CertificateAuthorityArn: CA_TEST_ARN, | ||
Csr: Buffer.from(csrData.csr, 'ascii'), | ||
SigningAlgorithm: 'SHA256WITHRSA', | ||
Validity: { | ||
Type: 'YEARS', | ||
Value: 1 | ||
} | ||
DomainName: 'test.int', | ||
SubjectAlternativeNames: ['blah.test.int'] | ||
}); | ||
expect(issueData.CertificateArn).toBe(CERT_TEST_ARN); | ||
expect(issueData.Certificate).toContain('-----BEGIN CERTIFICATE-----'); | ||
expect(issueData.CertificateChain).toContain('-----BEGIN CERTIFICATE-----'); | ||
}); | ||
it('should fetch a certificate', async () => { | ||
const pca = new _PCA.default(acmpca, _pem.default, CA_TEST_ARN); | ||
const csrData = await pca.createCSR(CSR_TEST_DATA); | ||
const issueData = await pca.issueCertificate(csrData.csr, { | ||
SigningAlgorithm: 'SHA256WITHRSA', | ||
Validity: { | ||
Type: 'YEARS', | ||
Value: 1 | ||
} | ||
it('should export a certificate', async () => { | ||
const pca = new _PCA.default(_awsSdk.default, CA_TEST_ARN); | ||
pca.acmpca = acmpca; | ||
pca.acm = acm; | ||
const issueData = await pca.requestCertificate({ | ||
DomainName: 'test.int', | ||
SubjectAlternativeNames: ['blah.test.int'] | ||
}); | ||
const certData = await pca.getCertificate(issueData.CertificateArn); | ||
const certData = await pca.exportCertificate(issueData.CertificateArn, 'test'); | ||
expect(acm.exportCertificate).toBeCalledWith({ | ||
CertificateArn: CERT_TEST_ARN, | ||
Passphrase: 'test' | ||
}); | ||
expect(certData.Certificate).toContain('-----BEGIN CERTIFICATE-----'); | ||
expect(certData.CertificateChain).toContain('-----BEGIN CERTIFICATE-----'); | ||
expect(certData.PrivateKey).toContain('-----BEGIN RSA PRIVATE KEY-----'); | ||
}); | ||
it('should get the Certificate Authority certificate', async () => { | ||
const pca = new _PCA.default(acmpca, _pem.default, CA_TEST_ARN); | ||
const pca = new _PCA.default(_awsSdk.default, CA_TEST_ARN); | ||
pca.acmpca = acmpca; | ||
pca.acm = acm; | ||
const certData = await pca.getCaCertificate(); | ||
@@ -118,0 +111,0 @@ expect(certData.Certificate).toContain('-----BEGIN CERTIFICATE-----'); |
@@ -6,11 +6,11 @@ "use strict"; | ||
}); | ||
exports.default = void 0; | ||
Object.defineProperty(exports, "PCA", { | ||
enumerable: true, | ||
get: function () { | ||
return _PCA.default; | ||
} | ||
}); | ||
var _PCA = _interopRequireDefault(require("./PCA")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var _default = { | ||
PCA: _PCA.default | ||
}; | ||
exports.default = _default; | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } |
138
lib/PCA.js
@@ -14,138 +14,90 @@ "use strict"; | ||
/** | ||
* @param {ACMPCA} AWS An AWS.ACMPCA() instance | ||
* @param {PEM} pem pem module instance | ||
* @param {AWS} AWS An AWS() instance | ||
* @param {String} caArn The Amazon Resource Name (ARN) of the PCA Certificate Authority item. | ||
* Must be in form of arn:aws:acm-pca:region:account:certificate-authority/12345678-1234-1234-1234-123456789012 | ||
* @param {object} data Optional data | ||
* @param {object} data.acmOptions Options to use when initializing AWS.ACM() | ||
* @param {object} data.acmPcaOptions Options to use when initializing AWS.ACMPCA() | ||
*/ | ||
constructor(ACMPCA, pem, caArn) { | ||
this.acmpca = ACMPCA; | ||
this.pem = pem; | ||
constructor(AWS, caArn, { | ||
acmOptions, | ||
acmPcaOptions | ||
} = {}) { | ||
this.acm = new AWS.ACM(acmOptions); | ||
this.acmpca = new AWS.ACMPCA(acmPcaOptions); | ||
this.caArn = caArn; | ||
} | ||
/** | ||
* @typedef {Object} CreateCSRResponse | ||
* @property {String} csr | ||
* @property {String} clientKey | ||
* @typedef {Object} ReqCertRes | ||
* @property {String} CertificateArn ARN of the issued certificate | ||
*/ | ||
/** | ||
* This is a wrapper around PCA#createCSR() | ||
* Wrapper around ACM#requestCertificate() | ||
* | ||
* @link https://www.deineagentur.com/projects/pem/module-pem.html#.createCSR | ||
* @param {Object} options Options object | ||
* @param {String} options.DomainName | ||
* @param {Array<String>} options.SubjectAlternativeNames | ||
* | ||
* Creates a Certificate Signing Request | ||
* If client key is undefined, a new key is created automatically. The used key is included | ||
* in the callback return as clientKey | ||
* @static | ||
* @param {Object} [options] Optional options object | ||
* @param {String} [options.clientKey] Optional client key to use | ||
* @param {Number} [options.keyBitsize] If clientKey is undefined, bit size to use for generating a new key (defaults to 2048) | ||
* @param {String} [options.hash] Hash function to use (either md5 sha1 or sha256, defaults to sha256) | ||
* @param {String} [options.country] CSR country field | ||
* @param {String} [options.state] CSR state field | ||
* @param {String} [options.locality] CSR locality field | ||
* @param {String} [options.organization] CSR organization field | ||
* @param {String} [options.organizationUnit] CSR organizational unit field | ||
* @param {String} [options.commonName='localhost'] CSR common name field | ||
* @param {String} [options.emailAddress] CSR email address field | ||
* @param {String} [options.csrConfigFile] CSR config file | ||
* @param {Array} [options.altNames] is a list of subjectAltNames in the subjectAltName field | ||
* @returns {Promise<CreateCSRResponse>} data | ||
* @link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ACM.html#requestCertificate-property | ||
* | ||
* Creates a certificate. | ||
* @returns {Promise<ReqCertRes>} | ||
*/ | ||
async createCSR(options) { | ||
return new Promise((resolve, reject) => { | ||
this.pem.createCSR(options, (err, data) => { | ||
if (err) { | ||
return reject(err); | ||
} | ||
resolve(data); | ||
}); | ||
}); | ||
async requestCertificate(options) { | ||
return this.acm.requestCertificate(_objectSpread({ | ||
CertificateAuthorityArn: this.caArn | ||
}, options)).promise(); | ||
} | ||
/** | ||
* @typedef {Object} IssueCertificateRes | ||
* @property {String} CertificateArn | ||
* @typedef {Object} GetCertificateRes | ||
* @property {String} Certificate PEM base-64 formatted certificate | ||
* @property {String} CertificateChain PEM base-64 formatted certificate | ||
*/ | ||
/** | ||
* Wrapper around ACMPCA#issueCertificate() and calls ACMPCA#waitFor() to ensure it has been | ||
* created so getCertificate() can be subsequently called | ||
* Wrapper around ACMPCA#getCertificateAuthorityCertificate | ||
* | ||
* Creates the certificate using the AWS PCA, storing it in AWS. | ||
* Gets the Certificate Authority certificate stored in AWS PCA. | ||
* | ||
* @link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ACMPCA.html#issueCertificate-property | ||
* | ||
* @param {string} csr CSR output from createCSR() | ||
* @param {ACMPCA.Types.IssueCertificateRequest} params | ||
* @returns {Promise<IssueCertificateRes>} | ||
* @returns {Promise<GetCertificateRes>} | ||
*/ | ||
async issueCertificate(csr, params) { | ||
return new Promise(async (resolve, reject) => { | ||
try { | ||
const caData = await this.acmpca.issueCertificate(_objectSpread({ | ||
CertificateAuthorityArn: this.caArn | ||
}, params, { | ||
Csr: Buffer.from(csr, 'ascii') | ||
})).promise(); | ||
this.acmpca.waitFor('certificateIssued', { | ||
CertificateAuthorityArn: this.caArn, | ||
CertificateArn: caData.CertificateArn | ||
}, (err, data) => { | ||
if (err) { | ||
return reject(err); | ||
} | ||
resolve(_objectSpread({}, caData, data)); | ||
}); | ||
} catch (e) { | ||
reject(e); | ||
} | ||
}); | ||
async getCaCertificate() { | ||
return this.acmpca.getCertificateAuthorityCertificate({ | ||
CertificateAuthorityArn: this.caArn | ||
}).promise(); | ||
} | ||
/** | ||
* @typedef {Object} GetCertificateRes | ||
* @typedef {Object} ExportCertRes | ||
* @property {String} Certificate PEM base-64 formatted certificate | ||
* @property {String} CertificateChain PEM base-64 formatted certificate | ||
* @property {String} PrivateKey The PEM-encoded private key associated with the public key in the certificate | ||
*/ | ||
/** | ||
* Wrapper around ACMPCA#getCertificate() | ||
* Wrapper around ACM#exportCertificate | ||
* | ||
* Gets the certificate stored in the AWS PCA. | ||
* Returns the certificate and key associated with it. | ||
* | ||
* @link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ACMPCA.html#getCertificate-property | ||
* @link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ACM.html#exportCertificate-property | ||
* | ||
* @param {String} CertificateArn The Certificate ARN returned from issueCertificate() | ||
* @returns {Promise<GetCertificateRes>} | ||
* @param {String|Buffer} Passphrase Passphrase to set to the exported key. Must be length > 3 | ||
* @returns {Promise<ExportCertRes>} | ||
*/ | ||
async getCertificate(CertificateArn) { | ||
return this.acmpca.getCertificate({ | ||
CertificateAuthorityArn: this.caArn, | ||
CertificateArn | ||
async exportCertificate(CertificateArn, Passphrase) { | ||
return this.acm.exportCertificate({ | ||
CertificateArn, | ||
Passphrase | ||
}).promise(); | ||
} | ||
/** | ||
* Wrapper around ACMPCA#getCertificateAuthorityCertificate | ||
* | ||
* Gets the Certificate Authority certificate stored in AWS PCA. | ||
* | ||
* @returns {Promise<GetCertificateRes>} | ||
*/ | ||
async getCaCertificate() { | ||
return this.acmpca.getCertificateAuthorityCertificate({ | ||
CertificateAuthorityArn: this.caArn | ||
}).promise(); | ||
} | ||
} | ||
exports.default = PCA; |
{ | ||
"name": "aws-pca", | ||
"version": "1.0.1", | ||
"version": "2.0.0", | ||
"description": "A library to generate a certificate for HTTPS use using AWS Private Certificate Authority (PCA)", | ||
@@ -28,3 +28,9 @@ "main": "lib/index.js", | ||
"homepage": "https://github.com/theogravity/node-aws-pca#readme", | ||
"keywords": ["aws", "pca", "acm", "https", "ssl"], | ||
"keywords": [ | ||
"aws", | ||
"pca", | ||
"acm", | ||
"https", | ||
"ssl" | ||
], | ||
"devDependencies": { | ||
@@ -31,0 +37,0 @@ "@babel/cli": "^7.2.3", |
@@ -5,3 +5,3 @@ # node-aws-pca | ||
A library to generate and fetch a certificate for HTTPS use using AWS Private Certificate Authority (PCA). | ||
A library to generate and fetch a certificate for HTTPS use using [AWS Private Certificate Authority (PCA)](https://aws.amazon.com/certificate-manager/private-certificate-authority/). | ||
@@ -13,64 +13,50 @@ ## Install | ||
- [aws-sdk](https://www.npmjs.com/package/aws-sdk) for calling AWS | ||
- [pem](https://www.npmjs.com/package/pem) for generating CSRs (Certificate Signing Requests) | ||
`npm i aws-pca pem aws-sdk` | ||
`npm i aws-pca aws-sdk` | ||
## Usage | ||
For more documentation, see source code and tests in `src/` | ||
```js | ||
import AWS from 'aws-sdk' | ||
import pem from 'pem' | ||
import { PCA } from 'aws-pca' | ||
// The following has been personally tested to work | ||
const AWS = require('aws-sdk') | ||
const PCA = require('aws-pca').PCA | ||
export async function sample () { | ||
// See https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ACMPCA.html#constructor-property | ||
// for possible options | ||
const acmpca = new AWS.ACMPCA() | ||
// The value of your CA (Certificate Authority) ARN in AWS PCA | ||
const CA_ARN = 'arn:aws:acm-pca:us-west-2:123456789012:certificate-authority/4819f73f-af7c-4abf-8753-62e40512cac6' | ||
const pca = new PCA(acmpca, pem, CA_ARN) | ||
const pca = new PCA(AWS, CA_ARN) | ||
// Create a CSR + client key which will be used to issue a certificate | ||
// See https://www.deineagentur.com/projects/pem/module-pem.html#.createCSR | ||
// Get the CA certificate if you need to add it to your trust stores | ||
const caData = await pca.getCaCertificate() | ||
const csrData = await pca.createCSR({ | ||
hash: 'sha256', | ||
country: 'US', | ||
state: 'California', | ||
locality: 'San Francisco', | ||
organization: 'Fake Company, Inc', | ||
organizationUnit: 'Engineering', | ||
commonName: 'fake.com', | ||
altNames: ['alt-fake.com'] | ||
}) | ||
console.log('Certificate Authority Data') | ||
console.log(caData) | ||
console.log('--------') | ||
// Create the server certificate | ||
// See https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ACMPCA.html#issueCertificate-property | ||
// - no need to specify CertificateAuthorityArn since you specified it in the constructor | ||
// - also waits for the issue certificate task on AWS to complete (this takes around 5+ secs) | ||
const issueData = await pca.issueCertificate(csrData.csr, { | ||
SigningAlgorithm: 'SHA256WITHRSA', | ||
Validity: { | ||
Type: 'YEARS', | ||
Value: 1 | ||
} | ||
const reqCertRes = await pca.requestCertificate({ | ||
DomainName: 'test.int', | ||
SubjectAlternativeNames: ['blah.test.int'] | ||
}) | ||
// issueData will have { CertificateArn, Certificate, CertificateChain } | ||
// You can technically stop here as you have the Certificate + CertificateChain data | ||
console.log('Request Certificate Response') | ||
console.log(reqCertRes) | ||
console.log('--------') | ||
// Get the server certificate (not needed if you've just called issueCertificate() | ||
const certData = await pca.getCertificate(issueData.CertificateArn) | ||
const cert = await pca.exportCertificate(reqCertRes.CertificateArn, 'password-to-set-for-the-key') | ||
// certData will have { Certificate, CertificateChain } | ||
// Get the CA certificate if you need to add it to your trust stores | ||
const caData = await pca.getCaCertificate() | ||
// caData will have { Certificate, CertificateChain } | ||
console.log('Server Certificate') | ||
console.log(cert) | ||
} | ||
sample().then((c) => { | ||
process.exit(0) | ||
}).catch((e) => { | ||
console.error(e) | ||
process.exit(-1) | ||
}) | ||
``` | ||
@@ -77,0 +63,0 @@ |
/* eslint-env jest */ | ||
import AWS from 'aws-sdk' | ||
import pem from 'pem' | ||
@@ -24,10 +23,9 @@ import PCA from '../PCA' | ||
const acmpca = new AWS.ACMPCA() | ||
const acm = new AWS.ACM() | ||
acmpca.issueCertificate = jest.fn().mockImplementation(() => { | ||
acm.requestCertificate = jest.fn().mockImplementation(() => { | ||
return { | ||
promise: () => { | ||
return Promise.resolve({ | ||
CertificateArn: CERT_TEST_ARN, | ||
Certificate: '-----BEGIN CERTIFICATE-----', | ||
CertificateChain: '-----BEGIN CERTIFICATE-----' | ||
CertificateArn: CERT_TEST_ARN | ||
}) | ||
@@ -60,7 +58,12 @@ } | ||
acmpca.waitFor = jest.fn().mockImplementation((evt, param, cb) => { | ||
cb(null, { | ||
Certificate: '-----BEGIN CERTIFICATE-----', | ||
CertificateChain: '-----BEGIN CERTIFICATE-----' | ||
}) | ||
acm.exportCertificate = jest.fn().mockImplementation(() => { | ||
return { | ||
promise: () => { | ||
return Promise.resolve({ | ||
Certificate: '-----BEGIN CERTIFICATE-----', | ||
CertificateChain: '-----BEGIN CERTIFICATE-----', | ||
PrivateKey: '-----BEGIN RSA PRIVATE KEY-----' | ||
}) | ||
} | ||
} | ||
}) | ||
@@ -70,3 +73,5 @@ | ||
it('should create an instance of the PCA', () => { | ||
const pca = new PCA(acmpca, pem, CA_TEST_ARN) | ||
const pca = new PCA(AWS, CA_TEST_ARN) | ||
pca.acmpca = acmpca | ||
pca.acm = acm | ||
@@ -76,63 +81,49 @@ expect(pca).toBeDefined() | ||
it('should create a CSR and client key', async () => { | ||
const pca = new PCA(acmpca, pem, CA_TEST_ARN) | ||
it('should create a certificate', async () => { | ||
const pca = new PCA(AWS, CA_TEST_ARN) | ||
pca.acmpca = acmpca | ||
pca.acm = acm | ||
const data = await pca.createCSR(CSR_TEST_DATA) | ||
expect(data.csr).toBeDefined() | ||
expect(data.clientKey).toBeDefined() | ||
expect(data.csr).toContain('-----BEGIN CERTIFICATE REQUEST-----') | ||
expect(data.clientKey).toContain('-----BEGIN RSA PRIVATE KEY-----') | ||
}) | ||
it('should issue a certificate', async () => { | ||
const pca = new PCA(acmpca, pem, CA_TEST_ARN) | ||
const csrData = await pca.createCSR(CSR_TEST_DATA) | ||
const issueData = await pca.issueCertificate(csrData.csr, { | ||
SigningAlgorithm: 'SHA256WITHRSA', | ||
Validity: { | ||
Type: 'YEARS', | ||
Value: 1 | ||
} | ||
const issueData = await pca.requestCertificate({ | ||
DomainName: 'test.int', | ||
SubjectAlternativeNames: ['blah.test.int'] | ||
}) | ||
expect(acmpca.issueCertificate).toBeCalledWith({ | ||
expect(acm.requestCertificate).toBeCalledWith({ | ||
CertificateAuthorityArn: CA_TEST_ARN, | ||
Csr: Buffer.from(csrData.csr, 'ascii'), | ||
SigningAlgorithm: 'SHA256WITHRSA', | ||
Validity: { | ||
Type: 'YEARS', | ||
Value: 1 | ||
} | ||
DomainName: 'test.int', | ||
SubjectAlternativeNames: ['blah.test.int'] | ||
}) | ||
expect(issueData.CertificateArn).toBe(CERT_TEST_ARN) | ||
expect(issueData.Certificate).toContain('-----BEGIN CERTIFICATE-----') | ||
expect(issueData.CertificateChain).toContain('-----BEGIN CERTIFICATE-----') | ||
}) | ||
it('should fetch a certificate', async () => { | ||
const pca = new PCA(acmpca, pem, CA_TEST_ARN) | ||
it('should export a certificate', async () => { | ||
const pca = new PCA(AWS, CA_TEST_ARN) | ||
pca.acmpca = acmpca | ||
pca.acm = acm | ||
const csrData = await pca.createCSR(CSR_TEST_DATA) | ||
const issueData = await pca.issueCertificate(csrData.csr, { | ||
SigningAlgorithm: 'SHA256WITHRSA', | ||
Validity: { | ||
Type: 'YEARS', | ||
Value: 1 | ||
} | ||
const issueData = await pca.requestCertificate({ | ||
DomainName: 'test.int', | ||
SubjectAlternativeNames: ['blah.test.int'] | ||
}) | ||
const certData = await pca.getCertificate(issueData.CertificateArn) | ||
const certData = await pca.exportCertificate( | ||
issueData.CertificateArn, | ||
'test' | ||
) | ||
expect(acm.exportCertificate).toBeCalledWith({ | ||
CertificateArn: CERT_TEST_ARN, | ||
Passphrase: 'test' | ||
}) | ||
expect(certData.Certificate).toContain('-----BEGIN CERTIFICATE-----') | ||
expect(certData.CertificateChain).toContain('-----BEGIN CERTIFICATE-----') | ||
expect(certData.PrivateKey).toContain('-----BEGIN RSA PRIVATE KEY-----') | ||
}) | ||
it('should get the Certificate Authority certificate', async () => { | ||
const pca = new PCA(acmpca, pem, CA_TEST_ARN) | ||
const pca = new PCA(AWS, CA_TEST_ARN) | ||
pca.acmpca = acmpca | ||
pca.acm = acm | ||
@@ -139,0 +130,0 @@ const certData = await pca.getCaCertificate() |
@@ -1,5 +0,1 @@ | ||
import PCA from './PCA' | ||
export default { | ||
PCA | ||
} | ||
export { default as PCA } from './PCA' |
151
src/PCA.js
export default class PCA { | ||
/** | ||
* @param {ACMPCA} AWS An AWS.ACMPCA() instance | ||
* @param {PEM} pem pem module instance | ||
* @param {AWS} AWS An AWS() instance | ||
* @param {String} caArn The Amazon Resource Name (ARN) of the PCA Certificate Authority item. | ||
* Must be in form of arn:aws:acm-pca:region:account:certificate-authority/12345678-1234-1234-1234-123456789012 | ||
* @param {object} data Optional data | ||
* @param {object} data.acmOptions Options to use when initializing AWS.ACM() | ||
* @param {object} data.acmPcaOptions Options to use when initializing AWS.ACMPCA() | ||
*/ | ||
constructor (ACMPCA, pem, caArn) { | ||
this.acmpca = ACMPCA | ||
this.pem = pem | ||
constructor (AWS, caArn, { acmOptions, acmPcaOptions } = {}) { | ||
this.acm = new AWS.ACM(acmOptions) | ||
this.acmpca = new AWS.ACMPCA(acmPcaOptions) | ||
this.caArn = caArn | ||
@@ -15,133 +17,74 @@ } | ||
/** | ||
* @typedef {Object} CreateCSRResponse | ||
* @property {String} csr | ||
* @property {String} clientKey | ||
* @typedef {Object} ReqCertRes | ||
* @property {String} CertificateArn ARN of the issued certificate | ||
*/ | ||
/** | ||
* This is a wrapper around PCA#createCSR() | ||
* Wrapper around ACM#requestCertificate() | ||
* | ||
* @link https://www.deineagentur.com/projects/pem/module-pem.html#.createCSR | ||
* @param {Object} options Options object | ||
* @param {String} options.DomainName | ||
* @param {Array<String>} options.SubjectAlternativeNames | ||
* | ||
* Creates a Certificate Signing Request | ||
* If client key is undefined, a new key is created automatically. The used key is included | ||
* in the callback return as clientKey | ||
* @static | ||
* @param {Object} [options] Optional options object | ||
* @param {String} [options.clientKey] Optional client key to use | ||
* @param {Number} [options.keyBitsize] If clientKey is undefined, bit size to use for generating a new key (defaults to 2048) | ||
* @param {String} [options.hash] Hash function to use (either md5 sha1 or sha256, defaults to sha256) | ||
* @param {String} [options.country] CSR country field | ||
* @param {String} [options.state] CSR state field | ||
* @param {String} [options.locality] CSR locality field | ||
* @param {String} [options.organization] CSR organization field | ||
* @param {String} [options.organizationUnit] CSR organizational unit field | ||
* @param {String} [options.commonName='localhost'] CSR common name field | ||
* @param {String} [options.emailAddress] CSR email address field | ||
* @param {String} [options.csrConfigFile] CSR config file | ||
* @param {Array} [options.altNames] is a list of subjectAltNames in the subjectAltName field | ||
* @returns {Promise<CreateCSRResponse>} data | ||
* @link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ACM.html#requestCertificate-property | ||
* | ||
* Creates a certificate. | ||
* @returns {Promise<ReqCertRes>} | ||
*/ | ||
async createCSR (options) { | ||
return new Promise((resolve, reject) => { | ||
this.pem.createCSR(options, (err, data) => { | ||
if (err) { | ||
return reject(err) | ||
} | ||
resolve(data) | ||
async requestCertificate (options) { | ||
return this.acm | ||
.requestCertificate({ | ||
CertificateAuthorityArn: this.caArn, | ||
...options | ||
}) | ||
}) | ||
.promise() | ||
} | ||
/** | ||
* @typedef {Object} IssueCertificateRes | ||
* @property {String} CertificateArn | ||
* @typedef {Object} GetCertificateRes | ||
* @property {String} Certificate PEM base-64 formatted certificate | ||
* @property {String} CertificateChain PEM base-64 formatted certificate | ||
*/ | ||
/** | ||
* Wrapper around ACMPCA#issueCertificate() and calls ACMPCA#waitFor() to ensure it has been | ||
* created so getCertificate() can be subsequently called | ||
* Wrapper around ACMPCA#getCertificateAuthorityCertificate | ||
* | ||
* Creates the certificate using the AWS PCA, storing it in AWS. | ||
* Gets the Certificate Authority certificate stored in AWS PCA. | ||
* | ||
* @link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ACMPCA.html#issueCertificate-property | ||
* | ||
* @param {string} csr CSR output from createCSR() | ||
* @param {ACMPCA.Types.IssueCertificateRequest} params | ||
* @returns {Promise<IssueCertificateRes>} | ||
* @returns {Promise<GetCertificateRes>} | ||
*/ | ||
async issueCertificate (csr, params) { | ||
return new Promise(async (resolve, reject) => { | ||
try { | ||
const caData = await this.acmpca | ||
.issueCertificate({ | ||
CertificateAuthorityArn: this.caArn, | ||
...params, | ||
Csr: Buffer.from(csr, 'ascii') | ||
}) | ||
.promise() | ||
this.acmpca.waitFor( | ||
'certificateIssued', | ||
{ | ||
CertificateAuthorityArn: this.caArn, | ||
CertificateArn: caData.CertificateArn | ||
}, | ||
(err, data) => { | ||
if (err) { | ||
return reject(err) | ||
} | ||
resolve({ | ||
...caData, | ||
...data | ||
}) | ||
} | ||
) | ||
} catch (e) { | ||
reject(e) | ||
} | ||
}) | ||
async getCaCertificate () { | ||
return this.acmpca | ||
.getCertificateAuthorityCertificate({ | ||
CertificateAuthorityArn: this.caArn | ||
}) | ||
.promise() | ||
} | ||
/** | ||
* @typedef {Object} GetCertificateRes | ||
* @typedef {Object} ExportCertRes | ||
* @property {String} Certificate PEM base-64 formatted certificate | ||
* @property {String} CertificateChain PEM base-64 formatted certificate | ||
* @property {String} PrivateKey The PEM-encoded private key associated with the public key in the certificate | ||
*/ | ||
/** | ||
* Wrapper around ACMPCA#getCertificate() | ||
* Wrapper around ACM#exportCertificate | ||
* | ||
* Gets the certificate stored in the AWS PCA. | ||
* Returns the certificate and key associated with it. | ||
* | ||
* @link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ACMPCA.html#getCertificate-property | ||
* @link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ACM.html#exportCertificate-property | ||
* | ||
* @param {String} CertificateArn The Certificate ARN returned from issueCertificate() | ||
* @returns {Promise<GetCertificateRes>} | ||
* @param {String|Buffer} Passphrase Passphrase to set to the exported key. Must be length > 3 | ||
* @returns {Promise<ExportCertRes>} | ||
*/ | ||
async getCertificate (CertificateArn) { | ||
return this.acmpca | ||
.getCertificate({ | ||
CertificateAuthorityArn: this.caArn, | ||
CertificateArn | ||
async exportCertificate (CertificateArn, Passphrase) { | ||
return this.acm | ||
.exportCertificate({ | ||
CertificateArn, | ||
Passphrase | ||
}) | ||
.promise() | ||
} | ||
/** | ||
* Wrapper around ACMPCA#getCertificateAuthorityCertificate | ||
* | ||
* Gets the Certificate Authority certificate stored in AWS PCA. | ||
* | ||
* @returns {Promise<GetCertificateRes>} | ||
*/ | ||
async getCaCertificate () { | ||
return this.acmpca | ||
.getCertificateAuthorityCertificate({ | ||
CertificateAuthorityArn: this.caArn | ||
}) | ||
.promise() | ||
} | ||
} |
20879
430
67