-
Notifications
You must be signed in to change notification settings - Fork 10
/
x509_cert_generator.ts
205 lines (182 loc) · 6.97 KB
/
x509_cert_generator.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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import { AsnConvert } from "@peculiar/asn1-schema";
import * as asn1X509 from "@peculiar/asn1-x509";
import { BufferSource, BufferSourceConverter, Convert } from "pvtsutils";
import { container } from "tsyringe";
import { cryptoProvider } from "./provider";
import { AlgorithmProvider, diAlgorithmProvider } from "./algorithm";
import { Extension } from "./extension";
import { JsonName, Name } from "./name";
import { HashedAlgorithm } from "./types";
import { X509Certificate } from "./x509_cert";
import { diAsnSignatureFormatter, IAsnSignatureFormatter } from "./asn_signature_formatter";
import { PublicKey, PublicKeyType } from "./public_key";
export type X509CertificateCreateParamsName = string | JsonName | Name;
/**
* Base arguments for certificate creation
*/
export interface X509CertificateCreateParamsBase {
/**
* Hexadecimal serial number. If not specified, random value will be generated
*/
serialNumber?: string;
/**
* Date before which certificate can't be used. Default is current date
*/
notBefore?: Date;
/**
* Date after which certificate can't be used. Default is 1 year from now
*/
notAfter?: Date;
/**
* List of extensions
*/
extensions?: Extension[];
/**
* Signing algorithm. Default is SHA-256 with key algorithm
*/
signingAlgorithm?: Algorithm | EcdsaParams;
}
/**
* Common parameters for X509 Certificate generation
*/
export interface X509CertificateCreateCommonParams extends X509CertificateCreateParamsBase {
subject?: X509CertificateCreateParamsName;
issuer?: X509CertificateCreateParamsName;
}
/**
* Parameters for X509 Certificate generation with private key
*/
export interface X509CertificateCreateWithKeyParams extends X509CertificateCreateCommonParams {
publicKey: PublicKeyType;
signingKey: CryptoKey;
}
/**
* Parameters for X509 Certificate generation with existing signature value
*/
export interface X509CertificateCreateWithSignatureParams extends X509CertificateCreateCommonParams {
/**
* Signature for manually initialized certificates
*/
signature: BufferSource;
/**
* Manual signing requires CryptoKey that includes signature algorithm
*/
publicKey: CryptoKey;
}
export type X509CertificateCreateParams = X509CertificateCreateWithKeyParams | X509CertificateCreateWithSignatureParams;
/**
* Parameters for self-signed X509 Certificate generation
*/
export interface X509CertificateCreateSelfSignedParams extends X509CertificateCreateParamsBase {
name?: X509CertificateCreateParamsName;
keys: CryptoKeyPair;
}
/**
* Generator of X509 certificates
*/
export class X509CertificateGenerator {
/**
* Creates a self-signed certificate
* @param params Parameters
* @param crypto Crypto provider. Default is from CryptoProvider
*/
public static async createSelfSigned(params: X509CertificateCreateSelfSignedParams, crypto = cryptoProvider.get()) {
if (!params.keys.privateKey) {
throw new Error("Bad field 'keys' in 'params' argument. 'privateKey' is empty");
}
if (!params.keys.publicKey) {
throw new Error("Bad field 'keys' in 'params' argument. 'publicKey' is empty");
}
return this.create({
serialNumber: params.serialNumber,
subject: params.name,
issuer: params.name,
notBefore: params.notBefore,
notAfter: params.notAfter,
publicKey: params.keys.publicKey,
signingKey: params.keys.privateKey,
signingAlgorithm: params.signingAlgorithm,
extensions: params.extensions,
}, crypto);
}
/**
* Creates a certificate signed by private key
* @param params Parameters
* @param crypto Crypto provider. Default is from CryptoProvider
*/
public static async create(params: X509CertificateCreateParams, crypto = cryptoProvider.get()) {
let spki: BufferSource;
if (params.publicKey instanceof PublicKey) {
spki = params.publicKey.rawData;
} else if ("publicKey" in params.publicKey) {
spki = params.publicKey.publicKey.rawData;
} else if (BufferSourceConverter.isBufferSource(params.publicKey)) {
spki = params.publicKey;
} else {
spki = await crypto.subtle.exportKey("spki", params.publicKey);
}
// SerialNumber must be positive integer
const serialNumber = params.serialNumber
? BufferSourceConverter.toUint8Array(Convert.FromHex(params.serialNumber))
: crypto.getRandomValues(new Uint8Array(16));
if (serialNumber[0] > 0x7F) {
serialNumber[0] &= 0x7F;
}
const notBefore = params.notBefore || new Date();
const notAfter = params.notAfter || new Date(notBefore.getTime() + 31536000000); // 1 year
const asnX509 = new asn1X509.Certificate({
tbsCertificate: new asn1X509.TBSCertificate({
version: asn1X509.Version.v3,
serialNumber: serialNumber,
validity: new asn1X509.Validity({
notBefore,
notAfter,
}),
extensions: new asn1X509.Extensions(params.extensions?.map(o => AsnConvert.parse(o.rawData, asn1X509.Extension)) || []),
subjectPublicKeyInfo: AsnConvert.parse(spki, asn1X509.SubjectPublicKeyInfo),
}),
});
if (params.subject) {
const name = params.subject instanceof Name
? params.subject
: new Name(params.subject);
asnX509.tbsCertificate.subject = AsnConvert.parse(name.toArrayBuffer(), asn1X509.Name);
}
if (params.issuer) {
const name = params.issuer instanceof Name
? params.issuer
: new Name(params.issuer);
asnX509.tbsCertificate.issuer = AsnConvert.parse(name.toArrayBuffer(), asn1X509.Name);
}
// Set signing algorithm
const defaultSigningAlgorithm = {
hash: "SHA-256",
};
const signatureAlgorithm = ("signingKey" in params)
? { ...defaultSigningAlgorithm, ...params.signingAlgorithm, ...params.signingKey.algorithm } as HashedAlgorithm
: params.publicKey.algorithm as HashedAlgorithm;
const algProv = container.resolve<AlgorithmProvider>(diAlgorithmProvider);
asnX509.tbsCertificate.signature = asnX509.signatureAlgorithm = algProv.toAsnAlgorithm(signatureAlgorithm);
// Sign
const tbs = AsnConvert.serialize(asnX509.tbsCertificate);
const signatureValue = ("signingKey" in params)
// Sign self-signed certificate with provided private key.
? await crypto.subtle.sign(signatureAlgorithm, params.signingKey, tbs)
// Otherwise use given pre-signed certificate signature
: params.signature;
// Convert WebCrypto signature to ASN.1 format
const signatureFormatters = container.resolveAll<IAsnSignatureFormatter>(diAsnSignatureFormatter).reverse();
let asnSignature: ArrayBuffer | null = null;
for (const signatureFormatter of signatureFormatters) {
asnSignature = signatureFormatter.toAsnSignature(signatureAlgorithm, signatureValue);
if (asnSignature) {
break;
}
}
if (!asnSignature) {
throw Error("Cannot convert ASN.1 signature value to WebCrypto format");
}
asnX509.signatureValue = asnSignature;
return new X509Certificate(AsnConvert.serialize(asnX509));
}
}