@@ -78,138 +78,154 @@ exports.convertNPNProtocols = function convertNPNProtocols(NPNProtocols, out) {
78
78
}
79
79
} ;
80
80
81
- exports . checkServerIdentity = function checkServerIdentity ( host , cert ) {
82
- // Create regexp to much hostnames
83
- function regexpify ( host , wildcards ) {
84
- // Add trailing dot (make hostnames uniform)
85
- if ( ! / \. $ / . test ( host ) ) host += '.' ;
86
-
87
- // The same applies to hostname with more than one wildcard,
88
- // if hostname has wildcard when wildcards are not allowed,
89
- // or if there are less than two dots after wildcard (i.e. *.com or *d.com)
90
- //
91
- // also
92
- //
93
- // "The client SHOULD NOT attempt to match a presented identifier in
94
- // which the wildcard character comprises a label other than the
95
- // left-most label (e.g., do not match bar.*.example.net)."
96
- // RFC6125
97
- if ( ! wildcards && / \* / . test ( host ) || / [ \. \* ] .* \* / . test ( host ) ||
98
- / \* / . test ( host ) && ! / \* .* \. .+ \. .+ / . test ( host ) ) {
99
- return / $ ./ ;
100
- }
81
+ function unfqdn ( host ) {
82
+ return host . replace ( / [ . ] $ / , '' ) ;
83
+ }
84
+
85
+ function splitHost ( host ) {
86
+ // String#toLowerCase() is locale-sensitive so we use
87
+ // a conservative version that only lowercases A-Z.
88
+ function replacer ( c ) {
89
+ return String . fromCharCode ( 32 + c . charCodeAt ( 0 ) ) ;
90
+ } ;
91
+ return unfqdn ( host ) . replace ( / [ A - Z ] / g, replacer ) . split ( '.' ) ;
92
+ }
93
+
94
+ function check ( hostParts , pattern , wildcards ) {
95
+ // Empty strings, null, undefined, etc. never match.
96
+ if ( ! pattern )
97
+ return false ;
98
+
99
+ var patternParts = splitHost ( pattern ) ;
101
100
102
- // Replace wildcard chars with regexp's wildcard and
103
- // escape all characters that have special meaning in regexps
104
- // (i.e. '.', '[', '{', '*', and others)
105
- var re = host . replace (
106
- / \* ( [ a - z 0 - 9 \\ -_ \. ] ) | [ \. , \- \\ \^ \$ + ? * \[ \] \( \) : ! \| { } ] / g,
107
- function ( all , sub ) {
108
- if ( sub ) return '[a-z0-9\\-_]*' + ( sub === '-' ? '\\-' : sub ) ;
109
- return '\\' + all ;
110
- } ) ;
111
-
112
- return new RegExp ( '^' + re + '$' , 'i' ) ;
101
+ if ( hostParts . length !== patternParts . length )
102
+ return false ;
103
+
104
+ // Pattern has empty components, e.g. "bad..example.com".
105
+ if ( patternParts . indexOf ( '' ) !== - 1 )
106
+ return false ;
107
+
108
+ // RFC 6125 allows IDNA U-labels (Unicode) in names but we have no
109
+ // good way to detect their encoding or normalize them so we simply
110
+ // reject them. Control characters and blanks are rejected as well
111
+ // because nothing good can come from accepting them.
112
+ function isBad ( s ) {
113
+ return / [ ^ \u0021 - \u007F ] / . test ( s ) ;
113
114
}
114
115
115
- var dnsNames = [ ] ,
116
- uriNames = [ ] ,
117
- ips = [ ] ,
118
- matchCN = true ,
119
- valid = false ,
120
- reason = 'Unknown reason' ;
121
-
122
- // There're several names to perform check against:
123
- // CN and altnames in certificate extension
124
- // (DNS names, IP addresses, and URIs)
125
- //
126
- // Walk through altnames and generate lists of those names
127
- if ( cert . subjectaltname ) {
128
- cert . subjectaltname . split ( / , / g) . forEach ( function ( altname ) {
129
- var option = altname . match ( / ^ ( D N S | I P A d d r e s s | U R I ) : ( .* ) $ / ) ;
130
- if ( ! option )
131
- return ;
132
- if ( option [ 1 ] === 'DNS' ) {
133
- dnsNames . push ( option [ 2 ] ) ;
134
- } else if ( option [ 1 ] === 'IP Address' ) {
135
- ips . push ( option [ 2 ] ) ;
136
- } else if ( option [ 1 ] === 'URI' ) {
137
- var uri = url . parse ( option [ 2 ] ) ;
138
- if ( uri ) uriNames . push ( uri . hostname ) ;
116
+ if ( patternParts . some ( isBad ) )
117
+ return false ;
118
+
119
+ // Check host parts from right to left first.
120
+ for ( var i = hostParts . length - 1 ; i > 0 ; i -= 1 )
121
+ if ( hostParts [ i ] !== patternParts [ i ] )
122
+ return false ;
123
+
124
+ var hostSubdomain = hostParts [ 0 ] ;
125
+ var patternSubdomain = patternParts [ 0 ] ;
126
+ var patternSubdomainParts = patternSubdomain . split ( '*' ) ;
127
+
128
+ // Short-circuit when the subdomain does not contain a wildcard.
129
+ // RFC 6125 does not allow wildcard substitution for components
130
+ // containing IDNA A-labels (Punycode) so match those verbatim.
131
+ if ( patternSubdomainParts . length === 1 ||
132
+ patternSubdomain . indexOf ( 'xn--' ) !== - 1 ) {
133
+ return hostSubdomain === patternSubdomain ;
134
+ }
135
+
136
+ if ( ! wildcards )
137
+ return false ;
138
+
139
+ // More than one wildcard is always wrong.
140
+ if ( patternSubdomainParts . length > 2 )
141
+ return false ;
142
+
143
+ // *.tld wildcards are not allowed.
144
+ if ( patternParts . length <= 2 )
145
+ return false ;
146
+
147
+ var prefix = patternSubdomainParts [ 0 ] ;
148
+ var suffix = patternSubdomainParts [ 1 ] ;
149
+
150
+ if ( prefix . length + suffix . length > hostSubdomain . length )
151
+ return false ;
152
+
153
+ if ( prefix . length > 0 && hostSubdomain . slice ( 0 , prefix . length ) !== prefix )
154
+ return false ;
155
+
156
+ if ( suffix . length > 0 && hostSubdomain . slice ( - suffix . length ) !== suffix )
157
+ return false ;
158
+
159
+ return true ;
160
+ }
161
+
162
+ exports . checkServerIdentity = function checkServerIdentity ( host , cert ) {
163
+ var subject = cert . subject ;
164
+ var altNames = cert . subjectaltname ;
165
+ var dnsNames = [ ] ;
166
+ var uriNames = [ ] ;
167
+ var ips = [ ] ;
168
+
169
+ host = '' + host ;
170
+
171
+ if ( altNames ) {
172
+ altNames . split ( ', ' ) . forEach ( function ( name ) {
173
+ if ( / ^ D N S : / . test ( name ) ) {
174
+ dnsNames . push ( name . slice ( 4 ) ) ;
175
+ } else if ( / ^ U R I : / . test ( name ) ) {
176
+ var uri = url . parse ( name . slice ( 4 ) ) ;
177
+ uriNames . push ( uri . hostname ) ; // TODO(bnoordhuis) Also use scheme.
178
+ } else if ( / ^ I P A d d r e s s : / . test ( name ) ) {
179
+ ips . push ( name . slice ( 11 ) ) ;
139
180
}
140
181
} ) ;
141
182
}
142
183
143
- // If hostname is an IP address, it should be present in the list of IP
144
- // addresses.
145
- if ( net . isIP ( host ) ) {
146
- valid = ips . some ( function ( ip ) {
147
- return ip === host ;
148
- } ) ;
149
- if ( ! valid ) {
150
- reason = util . format ( 'IP: %s is not in the cert\'s list: %s' ,
151
- host ,
152
- ips . join ( ', ' ) ) ;
153
- }
154
- } else {
155
- // Transform hostname to canonical form
156
- if ( ! / \. $ / . test ( host ) ) host += '.' ;
184
+ var valid = false ;
185
+ var reason = 'Unknown reason' ;
157
186
158
- // Otherwise check all DNS/URI records from certificate
159
- // (with allowed wildcards)
160
- dnsNames = dnsNames . map ( function ( name ) {
161
- return regexpify ( name , true ) ;
162
- } ) ;
187
+ if ( net . isIP ( host ) ) {
188
+ valid = ips . indexOf ( host ) !== - 1 ;
189
+ if ( ! valid )
190
+ reason = 'IP: ' + host + ' is not in the cert\'s list: ' + ips . join ( ', ' ) ;
191
+ // TODO(bnoordhuis) Also check URI SANs that are IP addresses.
192
+ } else if ( subject ) {
193
+ host = unfqdn ( host ) ; // Remove trailing dot for error messages.
194
+ var hostParts = splitHost ( host ) ;
163
195
164
- // Wildcards ain't allowed in URI names
165
- uriNames = uriNames . map ( function ( name ) {
166
- return regexpify ( name , false ) ;
167
- } ) ;
196
+ function wildcard ( pattern ) {
197
+ return check ( hostParts , pattern , true ) ;
198
+ }
168
199
169
- dnsNames = dnsNames . concat ( uriNames ) ;
170
-
171
- if ( dnsNames . length > 0 ) matchCN = false ;
172
-
173
- // Match against Common Name (CN) only if no supported identifiers are
174
- // present.
175
- //
176
- // "As noted, a client MUST NOT seek a match for a reference identifier
177
- // of CN-ID if the presented identifiers include a DNS-ID, SRV-ID,
178
- // URI-ID, or any application-specific identifier types supported by the
179
- // client."
180
- // RFC6125
181
- if ( matchCN ) {
182
- var commonNames = cert . subject . CN ;
183
- if ( util . isArray ( commonNames ) ) {
184
- for ( var i = 0 , k = commonNames . length ; i < k ; ++ i ) {
185
- dnsNames . push ( regexpify ( commonNames [ i ] , true ) ) ;
186
- }
187
- } else {
188
- dnsNames . push ( regexpify ( commonNames , true ) ) ;
189
- }
200
+ function noWildcard ( pattern ) {
201
+ return check ( hostParts , pattern , false ) ;
190
202
}
191
203
192
- valid = dnsNames . some ( function ( re ) {
193
- return re . test ( host ) ;
194
- } ) ;
204
+ // Match against Common Name only if no supported identifiers are present.
205
+ if ( dnsNames . length === 0 && ips . length === 0 && uriNames . length === 0 ) {
206
+ var cn = subject . CN ;
195
207
196
- if ( ! valid ) {
197
- if ( cert . subjectaltname ) {
198
- reason = util . format ( 'Host: %s is not in the cert\'s altnames: %s' ,
199
- host ,
200
- cert . subjectaltname ) ;
201
- } else {
202
- reason = util . format ( 'Host: %s is not cert\'s CN: %s' ,
203
- host ,
204
- cert . subject . CN ) ;
208
+ if ( Array . isArray ( cn ) )
209
+ valid = cn . some ( wildcard ) ;
210
+ else if ( cn )
211
+ valid = wildcard ( cn ) ;
212
+
213
+ if ( ! valid )
214
+ reason = 'Host: ' + host + '. is not cert\'s CN: ' + cn ;
215
+ } else {
216
+ valid = dnsNames . some ( wildcard ) || uriNames . some ( noWildcard ) ;
217
+ if ( ! valid ) {
218
+ reason =
219
+ 'Host: ' + host + '. is not in the cert\'s altnames: ' + altNames ;
205
220
}
206
221
}
222
+ } else {
223
+ reason = 'Cert is empty' ;
207
224
}
208
225
209
226
if ( ! valid ) {
210
227
var err = new Error (
211
- util . format ( 'Hostname/IP doesn\'t match certificate\'s altnames: %j' ,
212
- reason ) ) ;
228
+ 'Hostname/IP doesn\'t match certificate\'s altnames: "' + reason + '"' ) ;
213
229
err . reason = reason ;
214
230
err . host = host ;
215
231
err . cert = cert ;
0 commit comments