UNPKG

zerossl

Version:
202 lines (166 loc) 7.58 kB
// 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) } }