summaryrefslogtreecommitdiff
path: root/testing/wget/wget-1.12-subjectAltName.patch
blob: 20f08d216b2ecde8b5834e00aa489e79db9bc7c9 (plain)
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
206
207
208
209
210
211
212
213
214
215
216
=== modified file 'src/openssl.c'
--- src/openssl.c	2009-09-22 16:16:43 +0000
+++ src/openssl.c	2009-10-24 23:06:44 +0000
@@ -39,7 +39,7 @@
 #include <string.h>
 
 #include <openssl/ssl.h>
-#include <openssl/x509.h>
+#include <openssl/x509v3.h>
 #include <openssl/err.h>
 #include <openssl/rand.h>
 
@@ -486,9 +486,11 @@
 ssl_check_certificate (int fd, const char *host)
 {
   X509 *cert;
+  GENERAL_NAMES *subjectAltNames;
   char common_name[256];
   long vresult;
   bool success = true;
+  bool alt_name_checked = false;
 
   /* If the user has specified --no-check-cert, we still want to warn
      him about problems with the server's certificate.  */
@@ -536,7 +538,8 @@
           break;
         case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
         case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
-          logprintf (LOG_NOTQUIET, _("  Self-signed certificate encountered.\n"));
+          logprintf (LOG_NOTQUIET,
+                     _("  Self-signed certificate encountered.\n"));
           break;
         case X509_V_ERR_CERT_NOT_YET_VALID:
           logprintf (LOG_NOTQUIET, _("  Issued certificate not yet valid.\n"));
@@ -558,10 +561,6 @@
   /* Check that HOST matches the common name in the certificate.
      #### The following remains to be done:
 
-     - It should use dNSName/ipAddress subjectAltName extensions if
-       available; according to rfc2818: "If a subjectAltName extension
-       of type dNSName is present, that MUST be used as the identity."
-
      - When matching against common names, it should loop over all
        common names and choose the most specific one, i.e. the last
        one, not the first one, which the current code picks.
@@ -569,50 +568,123 @@
      - Ensure that ASN1 strings from the certificate are encoded as
        UTF-8 which can be meaningfully compared to HOST.  */
 
-  X509_NAME *xname = X509_get_subject_name(cert);
-  common_name[0] = '\0';
-  X509_NAME_get_text_by_NID (xname, NID_commonName, common_name,
-                             sizeof (common_name));
+  subjectAltNames = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL);
 
-  if (!pattern_match (common_name, host))
+  if (subjectAltNames)
     {
-      logprintf (LOG_NOTQUIET, _("\
-%s: certificate common name %s doesn't match requested host name %s.\n"),
-                 severity, quote_n (0, common_name), quote_n (1, host));
-      success = false;
+      /* Test subject alternative names */
+
+      /* Do we want to check for dNSNAmes or ipAddresses (see RFC 2818)?
+       * Signal it by host_in_octet_string. */
+      ASN1_OCTET_STRING *host_in_octet_string = NULL;
+      host_in_octet_string = a2i_IPADDRESS (host);
+
+      int numaltnames = sk_GENERAL_NAME_num (subjectAltNames);
+      int i;
+      for (i=0; i < numaltnames; i++)
+        {
+          const GENERAL_NAME *name =
+            sk_GENERAL_NAME_value (subjectAltNames, i);
+          if (name)
+            {
+              if (host_in_octet_string)
+                {
+                  if (name->type == GEN_IPADD)
+                    {
+                      /* Check for ipAddress */
+                      /* TODO: Should we convert between IPv4-mapped IPv6
+                       * addresses and IPv4 addresses? */
+                      alt_name_checked = true;
+                      if (!ASN1_STRING_cmp (host_in_octet_string,
+                            name->d.iPAddress))
+                        break;
+                    }
+                }
+              else if (name->type == GEN_DNS)
+                {
+                  /* Check for dNSName */
+                  alt_name_checked = true;
+                  /* dNSName should be IA5String (i.e. ASCII), however who
+                   * does trust CA? Convert it into UTF-8 for sure. */
+                  unsigned char *name_in_utf8 = NULL;
+                  if (0 <= ASN1_STRING_to_UTF8 (&name_in_utf8, name->d.dNSName))
+                    {
+                      /* Compare and check for NULL attack in ASN1_STRING */
+                      if (pattern_match ((char *)name_in_utf8, host) &&
+                            (strlen ((char *)name_in_utf8) ==
+                                ASN1_STRING_length (name->d.dNSName)))
+                        {
+                          OPENSSL_free (name_in_utf8);
+                          break;
+                        }
+                      OPENSSL_free (name_in_utf8);
+                    }
+                }
+            }
+        }
+      sk_GENERAL_NAME_free (subjectAltNames);
+      if (host_in_octet_string)
+        ASN1_OCTET_STRING_free(host_in_octet_string);
+
+      if (alt_name_checked == true && i >= numaltnames)
+        {
+          logprintf (LOG_NOTQUIET,
+              _("%s: no certificate subject alternative name matches\n"
+                "\trequested host name %s.\n"),
+                     severity, quote_n (1, host));
+          success = false;
+        }
     }
-  else
+  
+  if (alt_name_checked == false)
     {
-      /* We now determine the length of the ASN1 string. If it differs from
-       * common_name's length, then there is a \0 before the string terminates.
-       * This can be an instance of a null-prefix attack.
-       *
-       * https://www.blackhat.com/html/bh-usa-09/bh-usa-09-archives.html#Marlinspike
-       * */
-
-      int i = -1, j;
-      X509_NAME_ENTRY *xentry;
-      ASN1_STRING *sdata;
-
-      if (xname) {
-        for (;;)
-          {
-            j = X509_NAME_get_index_by_NID (xname, NID_commonName, i);
-            if (j == -1) break;
-            i = j;
+      /* Test commomName */
+      X509_NAME *xname = X509_get_subject_name(cert);
+      common_name[0] = '\0';
+      X509_NAME_get_text_by_NID (xname, NID_commonName, common_name,
+                                 sizeof (common_name));
+
+      if (!pattern_match (common_name, host))
+        {
+          logprintf (LOG_NOTQUIET, _("\
+    %s: certificate common name %s doesn't match requested host name %s.\n"),
+                     severity, quote_n (0, common_name), quote_n (1, host));
+          success = false;
+        }
+      else
+        {
+          /* We now determine the length of the ASN1 string. If it
+           * differs from common_name's length, then there is a \0
+           * before the string terminates.  This can be an instance of a
+           * null-prefix attack.
+           *
+           * https://www.blackhat.com/html/bh-usa-09/bh-usa-09-archives.html#Marlinspike
+           * */
+
+          int i = -1, j;
+          X509_NAME_ENTRY *xentry;
+          ASN1_STRING *sdata;
+
+          if (xname) {
+            for (;;)
+              {
+                j = X509_NAME_get_index_by_NID (xname, NID_commonName, i);
+                if (j == -1) break;
+                i = j;
+              }
           }
-      }
 
-      xentry = X509_NAME_get_entry(xname,i);
-      sdata = X509_NAME_ENTRY_get_data(xentry);
-      if (strlen (common_name) != ASN1_STRING_length (sdata))
-        {
-          logprintf (LOG_NOTQUIET, _("\
-%s: certificate common name is invalid (contains a NUL character).\n\
-This may be an indication that the host is not who it claims to be\n\
-(that is, it is not the real %s).\n"),
-                     severity, quote (host));
-          success = false;
+          xentry = X509_NAME_get_entry(xname,i);
+          sdata = X509_NAME_ENTRY_get_data(xentry);
+          if (strlen (common_name) != ASN1_STRING_length (sdata))
+            {
+              logprintf (LOG_NOTQUIET, _("\
+    %s: certificate common name is invalid (contains a NUL character).\n\
+    This may be an indication that the host is not who it claims to be\n\
+    (that is, it is not the real %s).\n"),
+                         severity, quote (host));
+              success = false;
+            }
         }
     }
 
@@ -631,3 +703,7 @@
   /* Allow --no-check-cert to disable certificate checking. */
   return opt.check_cert ? success : true;
 }
+
+/*
+ * vim: tabstop=2 shiftwidth=2 softtabstop=2
+ */