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