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