zerossl
Version:
ZeroSSL REST API wrapper
202 lines (166 loc) • 7.58 kB
text/typescript
// Copyright 2022 Adam K Dean <adamkdean@googlemail.com>
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import forge from 'node-forge'
import {
Certificate,
CertificateList,
CertificateRecord,
CertificateSigningRequestOptions,
CertificateSigningRequestValidationResult,
CreateCertificateOptions,
KeyPair,
ListCertificateOptions,
VerificationStatus,
VerifyDomainOptions,
ZeroSSLOptions
} from './types'
import { ZeroSSLError, findZeroSSLError } from './errors'
import superagent, { SuperAgentRequest } from 'superagent'
const defaultOptions = {
apiUrl: 'https://api.zerossl.com'
}
export class ZeroSSL {
public options: ZeroSSLOptions
constructor(options: ZeroSSLOptions) {
this.options = { ...defaultOptions, ...options }
}
private queryString(params: { [key: string]: string | number }): string {
return Object.keys(params).map(key => `${key}=${params[key]}`).join('&')
}
private async performRequest(request: SuperAgentRequest): Promise<superagent.Response> {
const response = await request
if (response.status !== 200 || response.body.success === false) {
const error = findZeroSSLError(response.body.error?.code || 0)
if (response.body.error?.details) error.details = response.body.error.details
throw new ZeroSSLError(response.status, error)
}
return response
}
// Create Certificate
public async createCertificate(options: CreateCertificateOptions): Promise<CertificateRecord> {
const qs = this.queryString({ access_key: this.options.accessKey })
const url = `${this.options.apiUrl}/certificates?${qs}`
const postFn = superagent.post(url)
.type('form')
.field('certificate_domains', options.domains.join(','))
.field('certificate_csr', options.csr)
.field('certificate_validity_days', options.validityDays)
.field('strict_domains', options.strictDomains)
const result = await this.performRequest(postFn)
return result.body as CertificateRecord
}
// Verify Domains
public async verifyDomains(id: string, options: VerifyDomainOptions): Promise<CertificateRecord> {
const isEmailValidation = options.validation_method === 'EMAIL'
const missingEmail = isEmailValidation && !options.validation_email
if (missingEmail) throw new Error('Missing verification option: validation_email')
const qs = this.queryString({ access_key: this.options.accessKey })
const url = `${this.options.apiUrl}/certificates/${id}/challenges?${qs}`
let postFn = superagent.post(url)
.type('form')
.field('validation_method', options.validation_method)
if (isEmailValidation) postFn = postFn.field('validation_email', options.validation_email as string)
const result = await this.performRequest(postFn)
return result.body as CertificateRecord
}
// Download Certificate (inline)
public async downloadCertificate(id: string): Promise<Certificate> {
const qs = this.queryString({ access_key: this.options.accessKey })
const url = `${this.options.apiUrl}/certificates/${id}/download/return?${qs}`
const getFn = superagent.get(url)
const result = await this.performRequest(getFn)
return result.body as Certificate
}
// Get Certificate
public async getCertificate(id: string): Promise<CertificateRecord> {
const qs = this.queryString({ access_key: this.options.accessKey })
const url = `${this.options.apiUrl}/certificates/${id}?${qs}`
const getFn = superagent.get(url)
const result = await this.performRequest(getFn)
return result.body as CertificateRecord
}
// List Certificates
public async listCertificates(options?: ListCertificateOptions): Promise<CertificateList> {
const query: Record<string, string | number> = { access_key: this.options.accessKey }
if (options) {
if (options.page) query['page'] = options.page
if (options.limit) query['limit'] = options.limit
if (options.search) query['search'] = options.search
if (options.certificate_status) query['certificate_status'] = options.certificate_status
}
const qs = this.queryString(query)
const url = `${this.options.apiUrl}/certificates?${qs}`
const getFn = superagent.get(url)
const result = await this.performRequest(getFn)
return result.body as CertificateList
}
// Verification Status
public async verificationStatus(id: string): Promise<VerificationStatus> {
const qs = this.queryString({ access_key: this.options.accessKey })
const url = `${this.options.apiUrl}/certificates/${id}/status?${qs}`
const getFn = superagent.get(url)
const result = await this.performRequest(getFn)
return result.body as VerificationStatus
}
// Resend Verification
public async resendVerification(id: string): Promise<boolean> {
const qs = this.queryString({ access_key: this.options.accessKey })
const url = `${this.options.apiUrl}/certificates/${id}/challenges/email?${qs}`
const postFn = superagent.post(url)
const result = await this.performRequest(postFn)
return result.body.success === 1
}
// Cancel Certificate
public async cancelCertificate(id: string): Promise<boolean> {
const qs = this.queryString({ access_key: this.options.accessKey })
const url = `${this.options.apiUrl}/certificates/${id}/cancel?${qs}`
const postFn = superagent.post(url)
const result = await this.performRequest(postFn)
return result.body.success === 1
}
// Revoke Certificate
public async revokeCertificate(id: string): Promise<boolean> {
const qs = this.queryString({ access_key: this.options.accessKey })
const url = `${this.options.apiUrl}/certificates/${id}/revoke?${qs}`
const postFn = superagent.post(url)
const result = await this.performRequest(postFn)
return result.body.success === 1
}
// Validate Certificate Signing Request
public async validateCSR(csr: string): Promise<CertificateSigningRequestValidationResult> {
const qs = this.queryString({ access_key: this.options.accessKey })
const url = `${this.options.apiUrl}/validation/csr?${qs}`
const postFn = superagent.post(url)
.set('Content-Type', 'application/json')
.send({ csr })
const result = await this.performRequest(postFn)
return result.body as CertificateSigningRequestValidationResult
}
// Generate Key Pair
public generateKeyPair(bits = 2048): KeyPair {
const keys = forge.pki.rsa.generateKeyPair(bits)
return {
publicKey: forge.pki.publicKeyToPem(keys.publicKey),
privateKey: forge.pki.privateKeyToPem(keys.privateKey)
}
}
// Generate Certificate Signing Request
public generateCSR(keypair: KeyPair, options: CertificateSigningRequestOptions): string {
const { country, state, locality, organization, organizationUnit, commonName, email } = options
const csr = forge.pki.createCertificationRequest()
csr.publicKey = forge.pki.publicKeyFromPem(keypair.publicKey)
csr.setSubject([
{ name: 'countryName', value: country },
{ name: 'stateOrProvinceName', value: state },
{ name: 'localityName', value: locality},
{ name: 'organizationName', value: organization },
{ name: 'organizationalUnitName', value: organizationUnit },
{ name: 'commonName', value: commonName },
{ name: 'emailAddress', value: email }
])
csr.sign(forge.pki.privateKeyFromPem(keypair.privateKey))
return forge.pki.certificationRequestToPem(csr)
}
}