-
Notifications
You must be signed in to change notification settings - Fork 0
/
certificate.ts
189 lines (140 loc) · 5.98 KB
/
certificate.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import {Command, Flags} from "@oclif/core";
import * as x509 from "@peculiar/x509";
import { Crypto } from "@peculiar/webcrypto";
import * as fs from "fs-extra";
import {KeyImporter} from "../sign/keyImporter";
import inquirer from "inquirer";
import {PemConverter} from "@peculiar/x509";
import {FlagOutput} from "@oclif/core/lib/interfaces";
import {join} from "path";
const crypto = new Crypto();
x509.cryptoProvider.set(crypto);
export default class Certificate extends Command {
static CERT_REQUEST_FILENAME = "ts-certificate-request.pem";
static CERT_FILENAME = "ts-certificate.pem";
static description = 'Create a certificate request or sign an existing request.';
static flags = {
privateKeyFile: Flags.string({char: 'k', description: 'Hex encoded private key filename (for creating CSR)', default: "ts-signing.key"}),
masterPrivateKeyFile: Flags.string({char: 'm', description: 'Hex encoded master private key filename (for signing CSR)', default: "ts-master.key"}),
certRequestFile: Flags.string({char: 'r', description: 'Certificate signing request PEM input or output filename', default: Certificate.CERT_REQUEST_FILENAME}),
certFile: Flags.string({char: 'r', description: 'Certificate PEM input or output filename', default: Certificate.CERT_FILENAME}),
cn: Flags.string({char: 'c', description: 'The CN for the certificate, or issuer CN if signing', default: ""}),
}
static args = [
{
name: 'command',
description: "Whether to create a signing 'request' or 'sign' an existing request",
required: true,
options: ["request", "sign"]
}
];
public async run(): Promise<void> {
const {args, flags} = await this.parse(Certificate);
if (args['command'] === "request"){
await this.createCertificateRequest(flags);
} else if (args['command'] === "sign"){
await this.createCertificateFromRequest(flags);
}
}
private async createCertificateRequest(flags: FlagOutput){
const privateKeyLocation = flags.privateKeyFile.toString();
if (!fs.existsSync(privateKeyLocation)){
this.error("Private key cannot be read at " + privateKeyLocation);
return;
}
const privKeyStr = fs.readFileSync(privateKeyLocation, 'utf-8').trim();
const keyImporter = KeyImporter.fromPrivate(privKeyStr);
const cn = await this.getCn(flags.cn.toString());
this.log("Creating certificate signing request...");
const csr = await x509.Pkcs10CertificateRequestGenerator.create({
name: "CN=" + cn,
keys: {
privateKey: await keyImporter.getPrivateKey(),
publicKey: await keyImporter.getPublicKey()
},
signingAlgorithm: {name: "ECDSA", hash: "SHA-256"},
extensions: [
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment),
],
attributes: [
new x509.ChallengePasswordAttribute("password"),
]
});
await csr.verify();
//console.log(csr.toTextObject());
const pem = PemConverter.encode(csr.rawData, "NEW CERTIFICATE REQUEST");
//console.log(pem);
const reqLocation = flags.certRequestFile.toString();
this.checkAndWriteFile(reqLocation, pem);
this.log("Successfully created certificate request at: " + reqLocation);
}
private async createCertificateFromRequest(flags: FlagOutput){
const certRequestLocation = flags.certRequestFile.toString();
if (!fs.existsSync(certRequestLocation)){
this.error("Certificate request cannot be read at " + certRequestLocation);
return;
}
const masterPrivKeyFile = flags.masterPrivateKeyFile.toString();
if (!fs.existsSync(masterPrivKeyFile)){
this.error("Master private key cannot be read at " + masterPrivKeyFile);
return;
}
const pem = fs.readFileSync(certRequestLocation, 'utf-8').trim();
const csr = new x509.Pkcs10CertificateRequest(pem);
await csr.verify();
let signerPrivKeyStr = fs.readFileSync(masterPrivKeyFile, 'utf-8').trim();
let signerKeyImporter = KeyImporter.fromPrivate(signerPrivKeyStr);
const issuerCn = await this.getCn(flags.cn.toString());
this.log("Creating certificate from signing request...");
const masterKeyExt = new x509.Extension("2.5.29.18", true, await signerKeyImporter.getRawPublicKey());
// TODO: use parameter
const expiry = new Date();
expiry.setFullYear(expiry.getFullYear() + 2); // default two year validity
const cert = await x509.X509CertificateGenerator.create({
serialNumber: "01",
subject: csr.subjectName,
issuer: "CN=" + issuerCn,
notBefore: new Date(),
notAfter: expiry,
signingAlgorithm: {name: "ECDSA", hash: "SHA-256"},
publicKey: csr.publicKey,
signingKey: await signerKeyImporter.getPrivateKey(),
extensions: [
new x509.BasicConstraintsExtension(false, 0, true), // This key should not be used to sign intermediate certs
// new x509.ExtendedKeyUsageExtension(["1.2.3.4.5.6.7", "2.3.4.5.6.7.8"], true),
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true), // This key can be used for digital signatures
await x509.SubjectKeyIdentifierExtension.create(csr.publicKey),
await x509.AuthorityKeyIdentifierExtension.create(await signerKeyImporter.getPublicKey()),
masterKeyExt
]
});
//console.log(cert.toTextObject());
const certPem = PemConverter.encode(cert.rawData, "CERTIFICATE");
//console.log(certPem);
const certLocation = flags.certFile.toString();
this.checkAndWriteFile(certLocation, certPem);
this.log("Successfully created certificate at: " + certLocation);
}
private async getCn(cn: string){
if (!cn){
let responses: any = await inquirer.prompt([{
name: "CN",
message: "Please enter the CN for the certificate request (This should be a human readable domain or other unique identifier): ",
type: "input",
validate: (val) => {
return val != "";
}
}]);
cn = responses["CN"];
}
return cn;
}
private checkAndWriteFile(fileLocation: string, data: string){
// TODO: Prompt if overwriting existing file
if (fs.existsSync(fileLocation)){
this.error("File already exists at: " + fileLocation);
return;
}
fs.writeFileSync(fileLocation, data, 'utf-8');
}
}