@@ -3,7 +3,7 @@ import * as fs from 'fs/promises';
3
3
import { v4 as uuid } from "uuid" ;
4
4
import * as forge from 'node-forge' ;
5
5
6
- const { pki, md, util : { encode64 } } = forge ;
6
+ const { asn1 , pki, md, util } = forge ;
7
7
8
8
export type CAOptions = ( CertDataOptions | CertPathOptions ) ;
9
9
@@ -63,7 +63,8 @@ export async function generateCACertificate(options: {
63
63
commonName ?: string ,
64
64
organizationName ?: string ,
65
65
countryName ?: string ,
66
- bits ?: number
66
+ bits ?: number ,
67
+ contrainToDomains ?: string [ ]
67
68
} = { } ) {
68
69
options = _ . defaults ( { } , options , {
69
70
commonName : 'Mockttp Testing CA - DO NOT TRUST - TESTING ONLY' ,
@@ -98,11 +99,21 @@ export async function generateCACertificate(options: {
98
99
{ name : 'organizationName' , value : options . organizationName }
99
100
] ) ;
100
101
101
- cert . setExtensions ( [
102
+ const extensions : any [ ] = [
102
103
{ name : 'basicConstraints' , cA : true , critical : true } ,
103
104
{ name : 'keyUsage' , keyCertSign : true , digitalSignature : true , nonRepudiation : true , cRLSign : true , critical : true } ,
104
- { name : 'subjectKeyIdentifier' }
105
- ] ) ;
105
+ { name : 'subjectKeyIdentifier' } ,
106
+ ] ;
107
+ if ( options . contrainToDomains && options . contrainToDomains . length > 0 ) {
108
+ extensions . push ( {
109
+ critical : true ,
110
+ name : 'nameConstraints' ,
111
+ value : generateNameConstraints ( {
112
+ permitted : options . contrainToDomains ,
113
+ } ) ,
114
+ } )
115
+ }
116
+ cert . setExtensions ( extensions ) ;
106
117
107
118
// Self-issued too
108
119
cert . setIssuer ( cert . subject . attributes ) ;
@@ -116,9 +127,73 @@ export async function generateCACertificate(options: {
116
127
} ;
117
128
}
118
129
130
+
131
+ type GenerateNameConstraintsInput = {
132
+ /**
133
+ * Array of excluded domains
134
+ */
135
+ excluded ?: string [ ] ;
136
+
137
+ /**
138
+ * Array of permitted domains
139
+ */
140
+ permitted ?: string [ ] ;
141
+ } ;
142
+
143
+ /**
144
+ * Generate name constraints in conformance with
145
+ * [RFC 5280 § 4.2.1.10](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10)
146
+ */
147
+ function generateNameConstraints (
148
+ input : GenerateNameConstraintsInput
149
+ ) : forge . asn1 . Asn1 {
150
+ const ipsToSequence = ( ips : string [ ] ) =>
151
+ ips . map ( ( domain ) => {
152
+ return asn1 . create ( asn1 . Class . UNIVERSAL , asn1 . Type . SEQUENCE , true , [
153
+ asn1 . create (
154
+ asn1 . Class . CONTEXT_SPECIFIC ,
155
+ 2 ,
156
+ false ,
157
+ util . encodeUtf8 ( domain )
158
+ ) ,
159
+ ] ) ;
160
+ } ) ;
161
+
162
+ const permittedAndExcluded : forge . asn1 . Asn1 [ ] = [ ] ;
163
+
164
+ if ( input . permitted !== undefined ) {
165
+ permittedAndExcluded . push (
166
+ asn1 . create (
167
+ asn1 . Class . CONTEXT_SPECIFIC ,
168
+ 0 ,
169
+ true ,
170
+ ipsToSequence ( input . permitted )
171
+ )
172
+ ) ;
173
+ }
174
+
175
+ if ( input . excluded !== undefined ) {
176
+ permittedAndExcluded . push (
177
+ asn1 . create (
178
+ asn1 . Class . CONTEXT_SPECIFIC ,
179
+ 1 ,
180
+ true ,
181
+ ipsToSequence ( input . excluded )
182
+ )
183
+ ) ;
184
+ }
185
+
186
+ return asn1 . create (
187
+ asn1 . Class . UNIVERSAL ,
188
+ asn1 . Type . SEQUENCE ,
189
+ true ,
190
+ permittedAndExcluded
191
+ ) ;
192
+ }
193
+
119
194
export function generateSPKIFingerprint ( certPem : PEM ) {
120
195
let cert = pki . certificateFromPem ( certPem . toString ( 'utf8' ) ) ;
121
- return encode64 (
196
+ return util . encode64 (
122
197
pki . getPublicKeyFingerprint ( cert . publicKey , {
123
198
type : 'SubjectPublicKeyInfo' ,
124
199
md : md . sha256 . create ( ) ,
0 commit comments