-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(certificates): implement certificate manager
After the implementation from cloudmos refs #76
- Loading branch information
1 parent
0211266
commit bbdf668
Showing
7 changed files
with
217 additions
and
10 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
src/certificates/certificate-manager/CertificateManager.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { faker } from "@faker-js/faker"; | ||
|
||
import { CertificateManager } from "./CertificateManager"; | ||
|
||
describe("CertificateManager", () => { | ||
let certificateManager: CertificateManager; | ||
let address: string; | ||
|
||
beforeEach(() => { | ||
certificateManager = new CertificateManager(); | ||
address = `akash1${faker.string.alpha({ length: 38 })}`; | ||
}); | ||
|
||
describe("prototype.generateCertificate", () => { | ||
it("should generate certificate PEMs", () => { | ||
expect(certificateManager.generatePEM(address)).toMatchObject({ | ||
certKey: expect.stringMatching(/^-----BEGIN CERTIFICATE-----[\s\S]*-----END CERTIFICATE-----\r\n$/), | ||
publicKey: expect.stringMatching(/^-----BEGIN EC PUBLIC KEY-----[\s\S]*-----END EC PUBLIC KEY-----\r\n$/), | ||
privateKey: expect.stringMatching(/^-----BEGIN PRIVATE KEY-----[\s\S]*-----END PRIVATE KEY-----\r\n$/) | ||
}); | ||
}); | ||
}); | ||
|
||
describe("prototype.parsePem", () => { | ||
it("should extract certificate data", () => { | ||
const cert = certificateManager.parsePem(certificateManager.generatePEM(address).certKey); | ||
|
||
expect(cert).toMatchObject({ | ||
hSerial: expect.any(String), | ||
sIssuer: expect.any(String), | ||
sSubject: expect.any(String), | ||
sNotBefore: expect.any(String), | ||
sNotAfter: expect.any(String), | ||
issuedOn: expect.any(Date), | ||
expiresOn: expect.any(Date) | ||
}); | ||
}); | ||
}); | ||
|
||
describe("prototype.strToDate", () => { | ||
it("should convert string to date", () => { | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-expect-error | ||
const date = certificateManager.strToDate("240507122350Z"); | ||
|
||
expect(date).toBeInstanceOf(Date); | ||
expect(date.toISOString()).toBe("2024-05-07T12:23:50.000Z"); | ||
}); | ||
}); | ||
|
||
describe("prototype.dateToStr", () => { | ||
it("should convert date to string", () => { | ||
const date = new Date("2024-05-07T12:23:50.000Z"); | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-expect-error | ||
const str = certificateManager.dateToStr(date); | ||
|
||
expect(str).toBe("240507122350Z"); | ||
}); | ||
}); | ||
}); |
106 changes: 106 additions & 0 deletions
106
src/certificates/certificate-manager/CertificateManager.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-expect-error | ||
import rs from "jsrsasign"; | ||
|
||
export interface CertificatePem { | ||
certKey: string; | ||
publicKey: string; | ||
privateKey: string; | ||
} | ||
|
||
export interface CertificateInfo { | ||
hSerial: string; | ||
sIssuer: string; | ||
sSubject: string; | ||
sNotBefore: string; | ||
sNotAfter: string; | ||
issuedOn: Date; | ||
expiresOn: Date; | ||
} | ||
|
||
export class CertificateManager { | ||
parsePem(certPEM: string): CertificateInfo { | ||
const certificate = new rs.X509(); | ||
certificate.readCertPEM(certPEM); | ||
const hSerial: string = certificate.getSerialNumberHex(); | ||
const sIssuer: string = certificate.getIssuerString(); | ||
const sSubject: string = certificate.getSubjectString(); | ||
const sNotBefore: string = certificate.getNotBefore(); | ||
const sNotAfter: string = certificate.getNotAfter(); | ||
|
||
return { | ||
hSerial, | ||
sIssuer, | ||
sSubject, | ||
sNotBefore, | ||
sNotAfter, | ||
issuedOn: this.strToDate(sNotBefore), | ||
expiresOn: this.strToDate(sNotAfter) | ||
}; | ||
} | ||
|
||
generatePEM(address: string): CertificatePem { | ||
const { notBeforeStr, notAfterStr } = this.createValidityRange(); | ||
const { prvKeyObj, pubKeyObj } = rs.KEYUTIL.generateKeypair("EC", "secp256r1"); | ||
const cert = new rs.KJUR.asn1.x509.Certificate({ | ||
version: 3, | ||
serial: { int: Math.floor(new Date().getTime() * 1000) }, | ||
issuer: { str: "/CN=" + address }, | ||
notbefore: notBeforeStr, | ||
notafter: notAfterStr, | ||
subject: { str: "/CN=" + address }, | ||
sbjpubkey: pubKeyObj, | ||
ext: [ | ||
{ extname: "keyUsage", critical: true, names: ["keyEncipherment", "dataEncipherment"] }, | ||
{ | ||
extname: "extKeyUsage", | ||
array: [{ name: "clientAuth" }] | ||
}, | ||
{ extname: "basicConstraints", cA: true, critical: true } | ||
], | ||
sigalg: "SHA256withECDSA", | ||
cakey: prvKeyObj | ||
}); | ||
const publicKey: string = rs.KEYUTIL.getPEM(pubKeyObj, "PKCS8PUB").replaceAll("PUBLIC KEY", "EC PUBLIC KEY"); | ||
const certKey: string = cert.getPEM(); | ||
|
||
return { | ||
certKey, | ||
publicKey, | ||
privateKey: rs.KEYUTIL.getPEM(prvKeyObj, "PKCS8PRV") | ||
}; | ||
} | ||
|
||
private createValidityRange() { | ||
const notBefore = new Date(); | ||
const notAfter = new Date(); | ||
notAfter.setFullYear(notBefore.getFullYear() + 1); | ||
|
||
const notBeforeStr = this.dateToStr(notBefore); | ||
const notAfterStr = this.dateToStr(notAfter); | ||
|
||
return { notBeforeStr, notAfterStr }; | ||
} | ||
|
||
private dateToStr(date: Date): string { | ||
const year = date.getUTCFullYear().toString().substring(2).padStart(2, "0"); | ||
const month = (date.getUTCMonth() + 1).toString().padStart(2, "0"); | ||
const day = date.getUTCDate().toString().padStart(2, "0"); | ||
const hours = date.getUTCHours().toString().padStart(2, "0"); | ||
const minutes = date.getUTCMinutes().toString().padStart(2, "0"); | ||
const secs = date.getUTCSeconds().toString().padStart(2, "0"); | ||
|
||
return `${year}${month}${day}${hours}${minutes}${secs}Z`; | ||
} | ||
|
||
private strToDate(str: string): Date { | ||
const year = parseInt(`20${str.substring(0, 2)}`); | ||
const month = parseInt(str.substring(2, 4)) - 1; | ||
const day = parseInt(str.substring(4, 6)); | ||
const hours = parseInt(str.substring(6, 8)); | ||
const minutes = parseInt(str.substring(8, 10)); | ||
const secs = parseInt(str.substring(10, 12)); | ||
|
||
return new Date(Date.UTC(year, month, day, hours, minutes, secs)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { CertificateManager } from "./CertificateManager"; | ||
|
||
const certificateManager = new CertificateManager(); | ||
|
||
export { CertificateManager, certificateManager }; |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters