]>
Commit | Line | Data |
---|---|---|
4d16918e CT |
1 | #include "squid.h" |
2 | #include "ssl/ErrorDetail.h" | |
3 | ||
4 | struct SslErrorDetailEntry { | |
461b9576 | 5 | Ssl::ssl_error_t value; |
4d16918e CT |
6 | const char *name; |
7 | const char *detail; | |
8 | }; | |
9 | ||
8211c314 | 10 | static const char *SslErrorDetailDefaultStr = "SSL certificate validation error (%err_name): %ssl_subject"; |
4d16918e CT |
11 | // TODO: optimize by replacing with std::map or similar |
12 | static SslErrorDetailEntry TheSslDetailMap[] = { | |
e34763f4 A |
13 | { SQUID_X509_V_ERR_DOMAIN_MISMATCH, |
14 | "SQUID_X509_V_ERR_DOMAIN_MISMATCH", | |
15 | "%err_name: The hostname you are connecting to (%H), does not match any of the Certificate valid names: %ssl_cn"}, | |
4d16918e CT |
16 | { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, |
17 | "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT", | |
18 | "%err_name: SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name" }, | |
19 | { X509_V_ERR_CERT_NOT_YET_VALID, | |
20 | "X509_V_ERR_CERT_NOT_YET_VALID", | |
21 | "%err_name: SSL Certficate is not valid before: %ssl_notbefore" }, | |
22 | { X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD, | |
23 | "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD", | |
78d04f8f | 24 | "%err_name: SSL Certificate has invalid start date (the 'not before' field): %ssl_subject" }, |
4d16918e CT |
25 | { X509_V_ERR_CERT_HAS_EXPIRED, |
26 | "X509_V_ERR_CERT_HAS_EXPIRED", | |
27 | "%err_name: SSL Certificate expired on %ssl_notafter" }, | |
28 | { X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD, | |
29 | "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD", | |
30 | "%err_name: SSL Certificate has invalid expiration date (the 'not after' field): %ssl_subject" }, | |
31 | {X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, | |
32 | "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT", | |
33 | "%err_name: Self-signed SSL Certificate: %ssl_subject"}, | |
34 | { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, | |
35 | "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY", | |
36 | "%err_name: SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name" }, | |
37 | { SSL_ERROR_NONE, "SSL_ERROR_NONE", "%err_name: No error" }, | |
38 | {SSL_ERROR_NONE, NULL, NULL } | |
39 | }; | |
40 | ||
461b9576 | 41 | Ssl::ssl_error_t |
4d16918e CT |
42 | Ssl::parseErrorString(const char *name) |
43 | { | |
44 | assert(name); | |
45 | ||
46 | for (int i = 0; TheSslDetailMap[i].name; ++i) { | |
47 | if (strcmp(name, TheSslDetailMap[i].name) == 0) | |
48 | return TheSslDetailMap[i].value; | |
49 | } | |
50 | ||
51 | if (xisdigit(*name)) { | |
52 | const long int value = strtol(name, NULL, 0); | |
53 | if (SQUID_SSL_ERROR_MIN <= value && value <= SQUID_SSL_ERROR_MAX) | |
54 | return value; | |
55 | fatalf("Too small or too bug SSL error code '%s'", name); | |
56 | } | |
57 | ||
58 | fatalf("Unknown SSL error name '%s'", name); | |
59 | return SSL_ERROR_SSL; // not reached | |
60 | } | |
61 | ||
62 | const char * | |
461b9576 | 63 | Ssl::getErrorName(Ssl::ssl_error_t value) |
4d16918e CT |
64 | { |
65 | ||
66 | for (int i = 0; TheSslDetailMap[i].name; ++i) { | |
67 | if (TheSslDetailMap[i].value == value) | |
68 | return TheSslDetailMap[i].name; | |
69 | } | |
70 | ||
71 | return NULL; | |
72 | } | |
73 | ||
461b9576 | 74 | static const char *getErrorDetail(Ssl::ssl_error_t value) |
4d16918e CT |
75 | { |
76 | for (int i = 0; TheSslDetailMap[i].name; ++i) { | |
77 | if (TheSslDetailMap[i].value == value) | |
78 | return TheSslDetailMap[i].detail; | |
79 | } | |
80 | ||
8211c314 CT |
81 | // we must always return something because ErrorDetail::buildDetail |
82 | // will hit an assertion | |
83 | return SslErrorDetailDefaultStr; | |
4d16918e CT |
84 | } |
85 | ||
e34763f4 | 86 | Ssl::ErrorDetail::err_frm_code Ssl::ErrorDetail::ErrorFormatingCodes[] = { |
4d16918e CT |
87 | {"ssl_subject", &Ssl::ErrorDetail::subject}, |
88 | {"ssl_ca_name", &Ssl::ErrorDetail::ca_name}, | |
89 | {"ssl_cn", &Ssl::ErrorDetail::cn}, | |
90 | {"ssl_notbefore", &Ssl::ErrorDetail::notbefore}, | |
91 | {"ssl_notafter", &Ssl::ErrorDetail::notafter}, | |
92 | {"err_name", &Ssl::ErrorDetail::err_code}, | |
93 | {NULL,NULL} | |
94 | }; | |
95 | ||
96 | /** | |
97 | * The subject of the current certification in text form | |
98 | */ | |
99 | const char *Ssl::ErrorDetail::subject() const | |
100 | { | |
101 | if (!peer_cert) | |
102 | return "[Not available]"; | |
103 | ||
104 | static char tmpBuffer[256]; // A temporary buffer | |
105 | X509_NAME_oneline(X509_get_subject_name(peer_cert.get()), tmpBuffer, | |
e34763f4 A |
106 | sizeof(tmpBuffer)); |
107 | return tmpBuffer; | |
4d16918e CT |
108 | } |
109 | ||
110 | // helper function to be used with Ssl::matchX509CommonNames | |
111 | static int copy_cn(void *check_data, ASN1_STRING *cn_data) | |
112 | { | |
113 | String *str = (String *)check_data; | |
114 | if (!str) // no data? abort | |
115 | return 0; | |
116 | if (str->defined()) | |
117 | str->append(", "); | |
118 | str->append((const char *)cn_data->data, cn_data->length); | |
119 | return 1; | |
120 | } | |
121 | ||
122 | /** | |
123 | * The list with certificates cn and alternate names | |
124 | */ | |
125 | const char *Ssl::ErrorDetail::cn() const | |
126 | { | |
127 | if (!peer_cert) | |
128 | return "[Not available]"; | |
129 | ||
130 | static String tmpStr; ///< A temporary string buffer | |
131 | tmpStr.clean(); | |
132 | Ssl::matchX509CommonNames(peer_cert.get(), &tmpStr, copy_cn); | |
133 | return tmpStr.termedBuf(); | |
134 | } | |
135 | ||
136 | /** | |
137 | * The issuer name | |
138 | */ | |
139 | const char *Ssl::ErrorDetail::ca_name() const | |
140 | { | |
141 | if (!peer_cert) | |
142 | return "[Not available]"; | |
143 | ||
144 | static char tmpBuffer[256]; // A temporary buffer | |
145 | X509_NAME_oneline(X509_get_issuer_name(peer_cert.get()), tmpBuffer, sizeof(tmpBuffer)); | |
146 | return tmpBuffer; | |
147 | } | |
148 | ||
149 | /** | |
150 | * The certificate "not before" field | |
151 | */ | |
152 | const char *Ssl::ErrorDetail::notbefore() const | |
153 | { | |
154 | if (!peer_cert) | |
155 | return "[Not available]"; | |
156 | ||
157 | static char tmpBuffer[256]; // A temporary buffer | |
158 | ASN1_UTCTIME * tm = X509_get_notBefore(peer_cert.get()); | |
159 | Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer)); | |
160 | return tmpBuffer; | |
161 | } | |
162 | ||
163 | /** | |
164 | * The certificate "not after" field | |
165 | */ | |
166 | const char *Ssl::ErrorDetail::notafter() const | |
167 | { | |
168 | if (!peer_cert) | |
169 | return "[Not available]"; | |
170 | ||
171 | static char tmpBuffer[256]; // A temporary buffer | |
172 | ASN1_UTCTIME * tm = X509_get_notAfter(peer_cert.get()); | |
173 | Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer)); | |
174 | return tmpBuffer; | |
175 | } | |
176 | ||
177 | /** | |
178 | * The string representation of the error_no | |
179 | */ | |
180 | const char *Ssl::ErrorDetail::err_code() const | |
181 | { | |
8211c314 | 182 | static char tmpBuffer[64]; |
4d16918e | 183 | const char *err = getErrorName(error_no); |
8211c314 CT |
184 | if (!err) { |
185 | snprintf(tmpBuffer, 64, "%d", (int)error_no); | |
186 | err = tmpBuffer; | |
187 | } | |
4d16918e CT |
188 | return err; |
189 | } | |
190 | ||
191 | /** | |
192 | * It converts the code to a string value. Currently the following | |
193 | * formating codes are supported: | |
194 | * %err_name: The name of the SSL error | |
195 | * %ssl_cn: The comma-separated list of common and alternate names | |
196 | * %ssl_subject: The certificate subject | |
197 | * %ssl_ca_name: The certificate issuer name | |
198 | * %ssl_notbefore: The certificate "not before" field | |
199 | * %ssl_notafter: The certificate "not after" field | |
200 | \retval the length of the code (the number of characters will be replaced by value) | |
201 | */ | |
202 | int Ssl::ErrorDetail::convert(const char *code, const char **value) const | |
203 | { | |
204 | *value = "-"; | |
205 | for (int i=0; ErrorFormatingCodes[i].code!=NULL; i++) { | |
206 | const int len = strlen(ErrorFormatingCodes[i].code); | |
207 | if (strncmp(code,ErrorFormatingCodes[i].code, len)==0) { | |
208 | ErrorDetail::fmt_action_t action = ErrorFormatingCodes[i].fmt_action; | |
209 | *value = (this->*action)(); | |
210 | return len; | |
211 | } | |
e34763f4 | 212 | } |
4d16918e CT |
213 | return 0; |
214 | } | |
215 | ||
216 | /** | |
e34763f4 A |
217 | * It uses the convert method to build the string errDetailStr using |
218 | * a template message for the current SSL error. The template messages | |
4d16918e CT |
219 | * can also contain normal error pages formating codes. |
220 | * Currently the error template messages are hard-coded | |
221 | */ | |
222 | void Ssl::ErrorDetail::buildDetail() const | |
223 | { | |
224 | char const *s = getErrorDetail(error_no); | |
225 | char const *p; | |
226 | char const *t; | |
227 | int code_len = 0; | |
228 | ||
8211c314 | 229 | assert(s); |
4d16918e CT |
230 | while ((p = strchr(s, '%'))) { |
231 | errDetailStr.append(s, p - s); | |
232 | code_len = convert(++p, &t); | |
233 | if (code_len) | |
234 | errDetailStr.append(t); | |
235 | else | |
236 | errDetailStr.append("%"); | |
237 | s = p + code_len; | |
238 | } | |
239 | errDetailStr.append(s, strlen(s)); | |
240 | } | |
241 | ||
e34763f4 A |
242 | const String &Ssl::ErrorDetail::toString() const |
243 | { | |
4d16918e CT |
244 | if (!errDetailStr.defined()) |
245 | buildDetail(); | |
246 | return errDetailStr; | |
247 | } | |
248 | ||
e34763f4 | 249 | /* We may do not want to use X509_dup but instead |
4d16918e | 250 | internal SSL locking: |
e34763f4 | 251 | CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509); |
4d16918e CT |
252 | peer_cert.reset(cert); |
253 | */ | |
461b9576 | 254 | Ssl::ErrorDetail::ErrorDetail( Ssl::ssl_error_t err_no, X509 *cert): error_no (err_no) |
4d16918e CT |
255 | { |
256 | peer_cert.reset(X509_dup(cert)); | |
257 | } | |
258 | ||
259 | Ssl::ErrorDetail::ErrorDetail(Ssl::ErrorDetail const &anErrDetail) | |
260 | { | |
261 | error_no = anErrDetail.error_no; | |
262 | if (anErrDetail.peer_cert.get()) { | |
263 | peer_cert.reset(X509_dup(anErrDetail.peer_cert.get())); | |
264 | } | |
265 | } |