Line | Count | Source (jump to first uncovered line) |
1 | | // |
2 | | // TLS routines for CUPS. |
3 | | // |
4 | | // Copyright © 2021-2024 by OpenPrinting. |
5 | | // Copyright @ 2007-2014 by Apple Inc. |
6 | | // Copyright @ 1997-2007 by Easy Software Products, all rights reserved. |
7 | | // |
8 | | // Licensed under Apache License v2.0. See the file "LICENSE" for more |
9 | | // information. |
10 | | // |
11 | | |
12 | | #include "cups-private.h" |
13 | | #include "dir.h" |
14 | | #include <fcntl.h> |
15 | | #include <math.h> |
16 | | #include <sys/stat.h> |
17 | | #ifdef __APPLE__ |
18 | | # include <Security/Security.h> |
19 | | #endif // __APPLE__ |
20 | | #ifdef _WIN32 |
21 | | # pragma comment(lib, "crypt32.lib") // Link in crypt32 library... |
22 | | # include <tchar.h> |
23 | | # include <wincrypt.h> |
24 | | #else |
25 | | # include <poll.h> |
26 | | # include <signal.h> |
27 | | # include <sys/time.h> |
28 | | # include <sys/resource.h> |
29 | | #endif // _WIN32 |
30 | | |
31 | | |
32 | | // |
33 | | // Local globals... |
34 | | // |
35 | | |
36 | | static bool tls_auto_create = false; |
37 | | // Auto-create self-signed certs? |
38 | | static char *tls_common_name = NULL; |
39 | | // Default common name |
40 | | static char *tls_keypath = NULL; |
41 | | // Certificate store path |
42 | | static cups_mutex_t tls_mutex = CUPS_MUTEX_INITIALIZER; |
43 | | // Mutex for certificates |
44 | | static int tls_options = -1,// Options for TLS connections |
45 | | tls_min_version = _HTTP_TLS_1_2, |
46 | | tls_max_version = _HTTP_TLS_MAX; |
47 | | #ifndef __APPLE__ |
48 | | static cups_array_t *tls_root_certs = NULL; |
49 | | // List of known root CAs |
50 | | #endif // __APPLE__ |
51 | | |
52 | | |
53 | | // |
54 | | // Local functions... |
55 | | // |
56 | | |
57 | | static bool http_check_roots(const char *creds); |
58 | | static char *http_copy_file(const char *path, const char *common_name, const char *ext); |
59 | | static const char *http_default_path(char *buffer, size_t bufsize); |
60 | | static bool http_default_san_cb(const char *common_name, const char *subject_alt_name, void *data); |
61 | | #if defined(_WIN32) || defined(HAVE_GNUTLS) |
62 | | static char *http_der_to_pem(const unsigned char *der, size_t dersize); |
63 | | #endif // _WIN32 || HAVE_GNUTLS |
64 | | static const char *http_make_path(char *buffer, size_t bufsize, const char *dirname, const char *filename, const char *ext); |
65 | | static bool http_save_file(const char *path, const char *common_name, const char *ext, const char *value); |
66 | | |
67 | | |
68 | | // |
69 | | // Include platform-specific TLS code... |
70 | | // |
71 | | |
72 | | #ifdef HAVE_OPENSSL |
73 | | # include "tls-openssl.c" |
74 | | #else // HAVE_GNUTLS |
75 | | # include "tls-gnutls.c" |
76 | | #endif // HAVE_OPENSSL |
77 | | |
78 | | |
79 | | // |
80 | | // 'cupsCopyCredentials()' - Copy the X.509 certificate chain to a string. |
81 | | // |
82 | | |
83 | | char * |
84 | | cupsCopyCredentials( |
85 | | const char *path, // I - Directory path for certificate/key store or `NULL` for default |
86 | | const char *common_name) // I - Common name |
87 | 0 | { |
88 | 0 | return (http_copy_file(path, common_name, "crt")); |
89 | 0 | } |
90 | | |
91 | | |
92 | | // |
93 | | // 'cupsCopyCredentialsKey()' - Copy the private key to a string. |
94 | | // |
95 | | |
96 | | char * |
97 | | cupsCopyCredentialsKey( |
98 | | const char *path, // I - Directory path for certificate/key store or `NULL` for default |
99 | | const char *common_name) // I - Common name |
100 | 0 | { |
101 | 0 | return (http_copy_file(path, common_name, "key")); |
102 | 0 | } |
103 | | |
104 | | |
105 | | // |
106 | | // 'cupsCopyCredentialsPublicKey()' - Copy the public key for a X.509 certificate request. |
107 | | // |
108 | | |
109 | | char * // O - PEM-encoded public key |
110 | | cupsCopyCredentialsPublicKey( |
111 | | const char *path, // I - Directory path for certificate/key store or `NULL` for default |
112 | | const char *common_name) // I - Common name |
113 | 0 | { |
114 | 0 | return (http_copy_file(path, common_name, "pub")); |
115 | 0 | } |
116 | | |
117 | | |
118 | | // |
119 | | // 'cupsCopyCredentialsRequest()' - Copy the X.509 certificate signing request to a string. |
120 | | // |
121 | | |
122 | | char * // O - PEM-encoded X.509 certificate signing request |
123 | | cupsCopyCredentialsRequest( |
124 | | const char *path, // I - Directory path for certificate/key store or `NULL` for default |
125 | | const char *common_name) // I - Common name |
126 | 0 | { |
127 | 0 | return (http_copy_file(path, common_name, "csr")); |
128 | 0 | } |
129 | | |
130 | | |
131 | | // |
132 | | // 'cupsSaveCredentials()' - Save the credentials associated with a printer/server. |
133 | | // |
134 | | // This function saves the the PEM-encoded X.509 certificate chain string and |
135 | | // private key (if not `NULL`) to the directory "path" or, if "path" is `NULL`, |
136 | | // in a per-user or system-wide (when running as root) certificate/key store. |
137 | | // |
138 | | |
139 | | bool // O - `true` on success, `false` on failure |
140 | | cupsSaveCredentials( |
141 | | const char *path, // I - Directory path for certificate/key store or `NULL` for default |
142 | | const char *common_name, // I - Common name for certificate |
143 | | const char *credentials, // I - PEM-encoded certificate chain or `NULL` to remove |
144 | | const char *key) // I - PEM-encoded private key or `NULL` for none |
145 | 0 | { |
146 | 0 | bool ret = false; // Return value |
147 | 0 | char crtfile[1024], // Certificate filename |
148 | 0 | keyfile[1024], // Key filename |
149 | 0 | ktmfile[1024]; // Temporary key filename |
150 | | |
151 | | |
152 | | // Validate input... |
153 | 0 | if (credentials) |
154 | 0 | { |
155 | | // Make sure it looks like a PEM-encoded cert... |
156 | 0 | if (strncmp(credentials, "-----BEGIN CERTIFICATE-----", 27) || strstr(credentials, "-----END CERTIFICATE-----") == NULL) |
157 | 0 | return (false); |
158 | 0 | } |
159 | | |
160 | 0 | if (key) |
161 | 0 | { |
162 | | // Make sure it looks like a PEM-encoded private key... |
163 | 0 | if (strncmp(key, "-----BEGIN PRIVATE KEY-----", 27) || strstr(key, "-----END PRIVATE KEY-----") == NULL) |
164 | 0 | return (false); |
165 | 0 | } |
166 | | |
167 | | // Save or delete credentials... |
168 | 0 | http_make_path(crtfile, sizeof(crtfile), path, common_name, "crt"); |
169 | 0 | http_make_path(keyfile, sizeof(keyfile), path, common_name, "key"); |
170 | 0 | http_make_path(ktmfile, sizeof(ktmfile), path, common_name, "ktm"); |
171 | |
|
172 | 0 | if (!credentials && !key) |
173 | 0 | { |
174 | | // Delete credentials... |
175 | 0 | if (!unlink(crtfile) && !unlink(keyfile)) |
176 | 0 | ret = true; |
177 | 0 | else |
178 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), false); |
179 | 0 | } |
180 | 0 | else if (!credentials && key) |
181 | 0 | { |
182 | | // Bad arguments... |
183 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), false); |
184 | 0 | } |
185 | 0 | else if (!key && access(keyfile, 0) && access(ktmfile, 0)) |
186 | 0 | { |
187 | | // Missing key file... |
188 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), false); |
189 | 0 | } |
190 | 0 | else if (http_save_file(path, common_name, "crt", credentials)) |
191 | 0 | { |
192 | | // Certificate saved, save or rename key file as needed... |
193 | 0 | if (key) |
194 | 0 | ret = http_save_file(path, common_name, "key", key); |
195 | 0 | else if (!access(ktmfile, 0)) |
196 | 0 | ret = !rename(ktmfile, keyfile); |
197 | 0 | else |
198 | 0 | ret = true; |
199 | 0 | } |
200 | |
|
201 | 0 | return (ret); |
202 | 0 | } |
203 | | |
204 | | |
205 | | // |
206 | | // 'cupsSetServerCredentials()' - Set the default server credentials. |
207 | | // |
208 | | // Note: The server credentials are used by all threads in the running process. |
209 | | // This function is threadsafe. |
210 | | // |
211 | | |
212 | | bool // O - `true` on success, `false` on failure |
213 | | cupsSetServerCredentials( |
214 | | const char *path, // I - Directory path for certificate/key store or `NULL` for default |
215 | | const char *common_name, // I - Default common name for server |
216 | | bool auto_create) // I - `true` = automatically create self-signed certificates |
217 | 0 | { |
218 | 0 | char temp[1024]; // Default path buffer |
219 | | |
220 | |
|
221 | 0 | DEBUG_printf("cupsSetServerCredentials(path=\"%s\", common_name=\"%s\", auto_create=%d)", path, common_name, auto_create); |
222 | | |
223 | | // Use defaults as needed... |
224 | 0 | if (!path) |
225 | 0 | path = http_default_path(temp, sizeof(temp)); |
226 | | |
227 | | // Range check input... |
228 | 0 | if (!path || !common_name) |
229 | 0 | { |
230 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); |
231 | 0 | return (0); |
232 | 0 | } |
233 | | |
234 | 0 | cupsMutexLock(&tls_mutex); |
235 | | |
236 | | // Free old values... |
237 | 0 | if (tls_keypath) |
238 | 0 | _cupsStrFree(tls_keypath); |
239 | |
|
240 | 0 | if (tls_common_name) |
241 | 0 | _cupsStrFree(tls_common_name); |
242 | | |
243 | | // Save the new values... |
244 | 0 | tls_keypath = _cupsStrAlloc(path); |
245 | 0 | tls_auto_create = auto_create; |
246 | 0 | tls_common_name = _cupsStrAlloc(common_name); |
247 | |
|
248 | 0 | cupsMutexUnlock(&tls_mutex); |
249 | |
|
250 | 0 | return (1); |
251 | 0 | } |
252 | | |
253 | | |
254 | | // |
255 | | // '_httpTLSSetOptions()' - Set TLS protocol and cipher suite options. |
256 | | // |
257 | | |
258 | | void |
259 | | _httpTLSSetOptions(int options, // I - Options |
260 | | int min_version, // I - Minimum TLS version |
261 | | int max_version) // I - Maximum TLS version |
262 | 1 | { |
263 | 1 | if (!(options & _HTTP_TLS_SET_DEFAULT) || tls_options < 0) |
264 | 1 | { |
265 | 1 | tls_options = options; |
266 | 1 | tls_min_version = min_version; |
267 | 1 | tls_max_version = max_version; |
268 | 1 | } |
269 | 1 | } |
270 | | |
271 | | |
272 | | // |
273 | | // 'http_check_roots()' - Check whether the supplied credentials use a trusted root CA. |
274 | | // |
275 | | |
276 | | static bool // O - `true` if they use a trusted root, `false` otherwise |
277 | | http_check_roots(const char *creds) // I - Credentials |
278 | 0 | { |
279 | 0 | bool ret = false; // Return value |
280 | | |
281 | |
|
282 | | #ifdef __APPLE__ |
283 | | // Apple hides all of the keychain stuff (all deprecated) so the best we can |
284 | | // do is use the SecTrust API to evaluate the certificate... |
285 | | CFMutableArrayRef certs = NULL; // Certificates from credentials |
286 | | SecCertificateRef cert; |
287 | | char *tcreds = NULL, // Copy of credentials string |
288 | | *tstart, // Start of certificate data |
289 | | *tend, // End of certificate data |
290 | | *der = NULL; // DER-encoded fragment buffer |
291 | | size_t dersize, // Size of DER buffer |
292 | | derlen; // Length of DER data |
293 | | SecPolicyRef policy; // X.509 policy |
294 | | SecTrustRef trust; // Trust evaluator |
295 | | |
296 | | |
297 | | // Convert PEM-encoded credentials to an array of DER-encoded certificates... |
298 | | if ((tcreds = strdup(creds)) == NULL) |
299 | | goto done; |
300 | | |
301 | | if ((certs = CFArrayCreateMutable(kCFAllocatorDefault, /*capacity*/0, &kCFTypeArrayCallBacks)) == NULL) |
302 | | goto done; |
303 | | |
304 | | dersize = 3 * strlen(tcreds) / 4; |
305 | | if ((der = malloc(dersize)) == NULL) |
306 | | goto done; |
307 | | |
308 | | for (tstart = strstr(tcreds, "-----BEGIN CERTIFICATE-----\n"); tstart; tstart = strstr(tend, "-----BEGIN CERTIFICATE-----\n")) |
309 | | { |
310 | | // Find the end of the certificate data... |
311 | | tstart += 28; // Skip "-----BEGIN CERTIFICATE-----\n" |
312 | | if ((tend = strstr(tstart, "-----END CERTIFICATE-----\n")) == NULL) |
313 | | break; // Missing end... |
314 | | |
315 | | // Nul-terminate the cert data... |
316 | | *tend++ = '\0'; |
317 | | |
318 | | // Convert to DER format |
319 | | derlen = dersize; |
320 | | if (httpDecode64(der, &derlen, tstart, /*end*/NULL)) |
321 | | { |
322 | | // Create a CFData object for the data... |
323 | | CFDataRef data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)der, (CFIndex)derlen); |
324 | | |
325 | | if (data) |
326 | | { |
327 | | // Create a certificate from the DER data... |
328 | | if ((cert = SecCertificateCreateWithData(kCFAllocatorDefault, data)) != NULL) |
329 | | { |
330 | | // Add certificate to the array... |
331 | | CFArrayAppendValue(certs, cert); |
332 | | CFRelease(cert); |
333 | | } |
334 | | |
335 | | CFRelease(data); |
336 | | } |
337 | | } |
338 | | } |
339 | | |
340 | | // Test the certificate list against the macOS/iOS trust store... |
341 | | if ((policy = SecPolicyCreateBasicX509()) != NULL) |
342 | | { |
343 | | if (SecTrustCreateWithCertificates(certs, policy, &trust) == noErr) |
344 | | { |
345 | | ret = SecTrustEvaluateWithError(trust, NULL); |
346 | | CFRelease(trust); |
347 | | } |
348 | | |
349 | | CFRelease(policy); |
350 | | } |
351 | | |
352 | | done: |
353 | | |
354 | | free(tcreds); |
355 | | free(der); |
356 | | |
357 | | if (certs) |
358 | | CFRelease(certs); |
359 | | |
360 | | #else |
361 | 0 | size_t credslen; // Length of credentials string |
362 | 0 | const char *rcreds; // Current root credential |
363 | 0 | size_t rcredslen; // Length of current root credential |
364 | | |
365 | |
|
366 | 0 | cupsMutexLock(&tls_mutex); |
367 | | |
368 | | // Load root certificates as needed... |
369 | 0 | if (!tls_root_certs) |
370 | 0 | { |
371 | | // Load root certificates... |
372 | 0 | tls_root_certs = cupsArrayNew(/*cb*/NULL, /*cb_data*/NULL, /*hash_cb*/NULL, /*hash_size*/0, /*copy_cb*/NULL, /*free_cb*/NULL); |
373 | |
|
374 | | # ifdef _WIN32 |
375 | | int i; // Looping var |
376 | | HCERTSTORE store; // Certificate store |
377 | | CERT_CONTEXT *cert; // Current certificate |
378 | | |
379 | | // Add certificates in both the "ROOT" and "CA" stores... |
380 | | for (i = 0; i < 2; i ++) |
381 | | { |
382 | | if ((store = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, i ? L"CA" : L"ROOT")) == NULL) |
383 | | continue; |
384 | | |
385 | | // Loop through certificates... |
386 | | for (cert = CertEnumCertificatesInStore(store, NULL); cert; cert = CertEnumCertificatesInStore(store, cert)) |
387 | | { |
388 | | if (cert->dwCertEncodingType == X509_ASN_ENCODING) |
389 | | { |
390 | | // Convert DER to PEM and add to the list... |
391 | | char * pem = http_der_to_pem(cert->pbCertEncoded, cert->cbCertEncoded); |
392 | | |
393 | | if (pem) |
394 | | cupsArrayAdd(tls_root_certs, pem); |
395 | | } |
396 | | } |
397 | | |
398 | | CertCloseStore(store, 0); |
399 | | } |
400 | | |
401 | | # else |
402 | 0 | size_t i; // Looping var |
403 | 0 | cups_dir_t *dir; // Directory |
404 | 0 | cups_dentry_t *dent; // Directory entry |
405 | 0 | const char *ext; // Pointer to filename extension |
406 | 0 | static const char * const root_dirs[] = |
407 | 0 | { // Root certificate stores |
408 | 0 | "/etc/ssl/certs", |
409 | 0 | "/system/etc/security/cacerts/", |
410 | |
|
411 | 0 | }; |
412 | |
|
413 | 0 | for (i = 0, dir = NULL; i < (sizeof(root_dirs) / sizeof(root_dirs[0])); i ++) |
414 | 0 | { |
415 | 0 | if ((dir = cupsDirOpen(root_dirs[i])) != NULL) |
416 | 0 | break; |
417 | 0 | } |
418 | |
|
419 | 0 | if (dir) |
420 | 0 | { |
421 | 0 | while ((dent = cupsDirRead(dir)) != NULL) |
422 | 0 | { |
423 | 0 | if ((ext = strrchr(dent->filename, '.')) != NULL && !strcmp(ext, ".pem")) |
424 | 0 | { |
425 | 0 | char filename[1024], // Certificate filename |
426 | 0 | *cert; // Certificate data |
427 | 0 | int fd; // File descriptor |
428 | |
|
429 | 0 | snprintf(filename, sizeof(filename), "%s/%s", root_dirs[i], dent->filename); |
430 | 0 | if ((fd = open(filename, O_RDONLY)) >= 0) |
431 | 0 | { |
432 | 0 | if ((cert = calloc(1, (size_t)(dent->fileinfo.st_size + 1))) != NULL) |
433 | 0 | { |
434 | 0 | read(fd, cert, (size_t)dent->fileinfo.st_size); |
435 | 0 | cupsArrayAdd(tls_root_certs, cert); |
436 | 0 | } |
437 | |
|
438 | 0 | close(fd); |
439 | 0 | } |
440 | 0 | } |
441 | 0 | } |
442 | 0 | } |
443 | 0 | # endif // _WIN32 |
444 | 0 | } |
445 | | |
446 | | // Check all roots |
447 | 0 | credslen = strlen(creds); |
448 | |
|
449 | 0 | DEBUG_printf("4http_check_roots: %lu root certificates to check.", (unsigned long)cupsArrayGetCount(tls_root_certs)); |
450 | |
|
451 | 0 | for (rcreds = (const char *)cupsArrayGetFirst(tls_root_certs); rcreds && !ret; rcreds = (const char *)cupsArrayGetNext(tls_root_certs)) |
452 | 0 | { |
453 | | // Compare the root against the tail of the current credentials... |
454 | 0 | rcredslen = strlen(rcreds); |
455 | |
|
456 | 0 | if (credslen >= rcredslen && !strcmp(creds + (credslen - rcredslen), rcreds)) |
457 | 0 | ret = true; |
458 | 0 | } |
459 | | |
460 | | // Unlock access and return... |
461 | 0 | cupsMutexUnlock(&tls_mutex); |
462 | 0 | #endif // __APPLE__ |
463 | |
|
464 | 0 | return (ret); |
465 | 0 | } |
466 | | |
467 | | |
468 | | // |
469 | | // 'http_copy_file()' - Copy the contents of a file to a string. |
470 | | // |
471 | | |
472 | | static char * // O - Contents of file or `NULL` on error |
473 | | http_copy_file(const char *path, // I - Directory |
474 | | const char *common_name, // I - Common name |
475 | | const char *ext) // I - Extension |
476 | 0 | { |
477 | 0 | char *s = NULL; // String |
478 | 0 | int fd; // File descriptor |
479 | 0 | char defpath[1024], // Default path |
480 | 0 | filename[1024]; // Filename |
481 | 0 | struct stat fileinfo; // File information |
482 | | |
483 | |
|
484 | 0 | if (!common_name) |
485 | 0 | return (NULL); |
486 | | |
487 | 0 | if (!path) |
488 | 0 | path = http_default_path(defpath, sizeof(defpath)); |
489 | |
|
490 | 0 | if ((fd = open(http_make_path(filename, sizeof(filename), path, common_name, ext), O_RDONLY)) < 0) |
491 | 0 | return (NULL); |
492 | | |
493 | 0 | if (fstat(fd, &fileinfo)) |
494 | 0 | goto done; |
495 | | |
496 | 0 | if (fileinfo.st_size > 65536) |
497 | 0 | { |
498 | 0 | close(fd); |
499 | 0 | return (NULL); |
500 | 0 | } |
501 | | |
502 | 0 | if ((s = calloc(1, (size_t)fileinfo.st_size + 1)) == NULL) |
503 | 0 | { |
504 | 0 | close(fd); |
505 | 0 | return (NULL); |
506 | 0 | } |
507 | | |
508 | 0 | if (read(fd, s, (size_t)fileinfo.st_size) < 0) |
509 | 0 | { |
510 | 0 | free(s); |
511 | 0 | s = NULL; |
512 | 0 | } |
513 | |
|
514 | 0 | done: |
515 | |
|
516 | 0 | close(fd); |
517 | |
|
518 | 0 | return (s); |
519 | 0 | } |
520 | | |
521 | | |
522 | | // |
523 | | // 'http_default_path()' - Get the default credential store path. |
524 | | // |
525 | | |
526 | | static const char * // O - Path or NULL on error |
527 | | http_default_path( |
528 | | char *buffer, // I - Path buffer |
529 | | size_t bufsize) // I - Size of path buffer |
530 | 0 | { |
531 | 0 | _cups_globals_t *cg = _cupsGlobals(); |
532 | | // Pointer to library globals |
533 | | |
534 | |
|
535 | 0 | if (cg->userconfig) |
536 | 0 | { |
537 | 0 | snprintf(buffer, bufsize, "%s/ssl", cg->userconfig); |
538 | |
|
539 | 0 | if (!_cupsDirCreate(buffer, 0700)) |
540 | 0 | { |
541 | 0 | DEBUG_printf("1http_default_path: Failed to make directory '%s': %s", buffer, strerror(errno)); |
542 | 0 | return (NULL); |
543 | 0 | } |
544 | 0 | } |
545 | 0 | else |
546 | 0 | { |
547 | 0 | snprintf(buffer, bufsize, "%s/ssl", cg->sysconfig); |
548 | |
|
549 | 0 | if (!_cupsDirCreate(buffer, 0700)) |
550 | 0 | { |
551 | 0 | DEBUG_printf("1http_default_path: Failed to make directory '%s': %s", buffer, strerror(errno)); |
552 | 0 | return (NULL); |
553 | 0 | } |
554 | 0 | } |
555 | | |
556 | 0 | DEBUG_printf("1http_default_path: Using default path \"%s\".", buffer); |
557 | |
|
558 | 0 | return (buffer); |
559 | 0 | } |
560 | | |
561 | | |
562 | | // |
563 | | // 'http_default_san_cb()' - Validate a subjectAltName value. |
564 | | // |
565 | | |
566 | | static bool // O - `true` if OK, `false` otherwise |
567 | | http_default_san_cb( |
568 | | const char *common_name, // I - Common name value |
569 | | const char *subject_alt_name, // I - subjectAltName value |
570 | | void *data) // I - Callback data (unused) |
571 | 0 | { |
572 | 0 | size_t common_len; // Common name length |
573 | | |
574 | |
|
575 | 0 | (void)data; |
576 | |
|
577 | 0 | if (!_cups_strcasecmp(subject_alt_name, common_name) || !_cups_strcasecmp(subject_alt_name, "localhost")) |
578 | 0 | return (true); |
579 | | |
580 | 0 | common_len = strlen(common_name); |
581 | |
|
582 | 0 | return (!_cups_strncasecmp(subject_alt_name, common_name, common_len) && subject_alt_name[common_len] == '.'); |
583 | 0 | } |
584 | | |
585 | | |
586 | | #if defined(_WIN32) || defined(HAVE_GNUTLS) |
587 | | // |
588 | | // 'http_der_to_pem()' - Convert DER format certificate data to PEM. |
589 | | // |
590 | | |
591 | | static char * // O - PEM string |
592 | | http_der_to_pem( |
593 | | const unsigned char *der, // I - DER-encoded data |
594 | | size_t dersize) // I - Size of DER-encoded data |
595 | | { |
596 | | char *pem, // PEM-encoded string |
597 | | *pemptr; // Pointer into PEM-encoded string |
598 | | int col; // Current column |
599 | | size_t pemsize; // Size of PEM-encoded string |
600 | | static const char *base64 = // Base64 alphabet |
601 | | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
602 | | |
603 | | |
604 | | // Calculate the size, accounting for Base64 expansion, line wrapping at |
605 | | // column 64, and the BEGIN/END CERTIFICATE text... |
606 | | pemsize = 2 * dersize + /*"-----BEGIN CERTIFICATE-----\n"*/28 + /*"-----END CERTIFICATE-----\n"*/26 + 1; |
607 | | |
608 | | if ((pem = calloc(1, pemsize)) == NULL) |
609 | | return (NULL); |
610 | | |
611 | | cupsCopyString(pem, "-----BEGIN CERTIFICATE-----\n", pemsize); |
612 | | for (pemptr = pem + strlen(pem), col = 0; dersize > 0; der += 3) |
613 | | { |
614 | | // Encode the up to 3 characters as 4 Base64 numbers... |
615 | | switch (dersize) |
616 | | { |
617 | | case 1 : |
618 | | *pemptr ++ = base64[(der[0] & 255) >> 2]; |
619 | | *pemptr ++ = base64[((der[0] & 255) << 4) & 63]; |
620 | | *pemptr ++ = '='; |
621 | | *pemptr ++ = '='; |
622 | | dersize = 0; |
623 | | break; |
624 | | case 2 : |
625 | | *pemptr ++ = base64[(der[0] & 255) >> 2]; |
626 | | *pemptr ++ = base64[(((der[0] & 255) << 4) | ((der[1] & 255) >> 4)) & 63]; |
627 | | *pemptr ++ = base64[((der[1] & 255) << 2) & 63]; |
628 | | *pemptr ++ = '='; |
629 | | dersize = 0; |
630 | | break; |
631 | | default : |
632 | | *pemptr ++ = base64[(der[0] & 255) >> 2]; |
633 | | *pemptr ++ = base64[(((der[0] & 255) << 4) | ((der[1] & 255) >> 4)) & 63]; |
634 | | *pemptr ++ = base64[(((der[1] & 255) << 2) | ((der[2] & 255) >> 6)) & 63]; |
635 | | *pemptr ++ = base64[der[2] & 63]; |
636 | | dersize -= 3; |
637 | | break; |
638 | | } |
639 | | |
640 | | // Add a newline as needed... |
641 | | col += 4; |
642 | | if (col >= 64) |
643 | | { |
644 | | *pemptr++ = '\n'; |
645 | | col = 0; |
646 | | } |
647 | | } |
648 | | |
649 | | if (col > 0) |
650 | | *pemptr++ = '\n'; |
651 | | *pemptr = '\0'; |
652 | | |
653 | | cupsConcatString(pem, "-----END CERTIFICATE-----\n", pemsize); |
654 | | |
655 | | // Return the encoded string... |
656 | | return (pem); |
657 | | } |
658 | | #endif // _WIN32 || HAVE_GNUTLS |
659 | | |
660 | | |
661 | | // |
662 | | // 'http_make_path()' - Format a filename for a certificate or key file. |
663 | | // |
664 | | |
665 | | static const char * // O - Filename |
666 | | http_make_path( |
667 | | char *buffer, // I - Filename buffer |
668 | | size_t bufsize, // I - Size of buffer |
669 | | const char *dirname, // I - Directory |
670 | | const char *filename, // I - Filename (usually hostname) |
671 | | const char *ext) // I - Extension |
672 | 0 | { |
673 | 0 | char *bufptr, // Pointer into buffer |
674 | 0 | *bufend = buffer + bufsize - 1; // End of buffer |
675 | | |
676 | |
|
677 | 0 | snprintf(buffer, bufsize, "%s/", dirname); |
678 | 0 | bufptr = buffer + strlen(buffer); |
679 | |
|
680 | 0 | while (*filename && bufptr < bufend) |
681 | 0 | { |
682 | 0 | if (_cups_isalnum(*filename) || *filename == '-' || *filename == '.') |
683 | 0 | *bufptr++ = *filename; |
684 | 0 | else |
685 | 0 | *bufptr++ = '_'; |
686 | |
|
687 | 0 | filename ++; |
688 | 0 | } |
689 | |
|
690 | 0 | if (bufptr < bufend && filename[-1] != '.') |
691 | 0 | *bufptr++ = '.'; |
692 | |
|
693 | 0 | cupsCopyString(bufptr, ext, (size_t)(bufend - bufptr + 1)); |
694 | |
|
695 | 0 | return (buffer); |
696 | 0 | } |
697 | | |
698 | | |
699 | | // |
700 | | // 'http_save_file()' - Save a string to a file. |
701 | | // |
702 | | |
703 | | static bool // O - `true` on success, `false` on failure |
704 | | http_save_file(const char *path, // I - Directory path for certificate/key store or `NULL` for default |
705 | | const char *common_name, // I - Common name |
706 | | const char *ext, // I - Extension |
707 | | const char *value) // I - String value |
708 | 0 | { |
709 | 0 | char defpath[1024], // Default path |
710 | 0 | filename[1024]; // Output filename |
711 | 0 | int fd; // File descriptor |
712 | | |
713 | | |
714 | | // Range check input... |
715 | 0 | if (!common_name) |
716 | 0 | return (false); |
717 | | |
718 | | // Get default path as needed... |
719 | 0 | if (!path) |
720 | 0 | path = http_default_path(defpath, sizeof(defpath)); |
721 | |
|
722 | 0 | http_make_path(filename, sizeof(filename), path, common_name, ext); |
723 | |
|
724 | 0 | if (!value) |
725 | 0 | { |
726 | 0 | unlink(filename); |
727 | 0 | return (true); |
728 | 0 | } |
729 | | |
730 | 0 | if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644)) < 0) |
731 | 0 | return (false); |
732 | | |
733 | 0 | if (write(fd, value, strlen(value)) < 0) |
734 | 0 | { |
735 | 0 | close(fd); |
736 | 0 | unlink(filename); |
737 | 0 | return (false); |
738 | 0 | } |
739 | | |
740 | 0 | close(fd); |
741 | |
|
742 | 0 | return (true); |
743 | 0 | } |
744 | | |
745 | | |