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