Coverage Report

Created: 2026-10-28 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libcups/cups/tls.c
Line
Count
Source
1
//
2
// TLS routines for CUPS.
3
//
4
// Copyright © 2021-2026 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
48
49
//
50
// Local functions...
51
//
52
53
static char   *http_copy_file(const char *path, const char *common_name, const char *ext);
54
static const char *http_default_path(char *buffer, size_t bufsize);
55
static bool   http_default_san_cb(const char *common_name, const char *subject_alt_name, void *data);
56
#if defined(_WIN32) || defined(HAVE_GNUTLS)
57
static char   *http_der_to_pem(const unsigned char *der, size_t dersize);
58
#endif // _WIN32 || HAVE_GNUTLS
59
static const char *http_make_path(char *buffer, size_t bufsize, const char *dirname, const char *filename, const char *ext);
60
static bool   http_save_file(const char *path, const char *common_name, const char *ext, const char *value);
61
62
63
//
64
// Include platform-specific TLS code...
65
//
66
67
#ifdef HAVE_OPENSSL
68
#  include "tls-openssl.c"
69
#else // HAVE_GNUTLS
70
#  include "tls-gnutls.c"
71
#endif // HAVE_OPENSSL
72
73
74
//
75
// 'cupsCopyCredentials()' - Copy the X.509 certificate chain to a string.
76
//
77
78
char *          // O - PEM-encoded certificate(s)
79
cupsCopyCredentials(
80
    const char *path,     // I - Directory path for certificate/key store or `NULL` for default
81
    const char *common_name)    // I - Common name
82
0
{
83
0
  return (http_copy_file(path, common_name, "crt"));
84
0
}
85
86
87
//
88
// 'cupsCopyCredentialsKey()' - Copy the private key to a string.
89
//
90
91
char *          // O - PEM-encoded private key
92
cupsCopyCredentialsKey(
93
    const char *path,     // I - Directory path for certificate/key store or `NULL` for default
94
    const char *common_name)    // I - Common name
95
0
{
96
0
  char  *key = http_copy_file(path, common_name, "key");
97
          // Private key
98
99
0
  if (!key)
100
0
    key = http_copy_file(path, common_name, "ktm");
101
102
0
  return (key);
103
0
}
104
105
106
//
107
// 'cupsCopyCredentialsPublicKey()' - Copy the public key for a X.509 certificate request.
108
//
109
110
char *          // O - PEM-encoded public key
111
cupsCopyCredentialsPublicKey(
112
    const char *path,     // I - Directory path for certificate/key store or `NULL` for default
113
    const char *common_name)    // I - Common name
114
0
{
115
0
  return (http_copy_file(path, common_name, "pub"));
116
0
}
117
118
119
//
120
// 'cupsCopyCredentialsRequest()' - Copy the X.509 certificate signing request to a string.
121
//
122
123
char *          // O - PEM-encoded X.509 certificate signing request
124
cupsCopyCredentialsRequest(
125
    const char *path,     // I - Directory path for certificate/key store or `NULL` for default
126
    const char *common_name)    // I - Common name
127
0
{
128
0
  return (http_copy_file(path, common_name, "csr"));
129
0
}
130
131
132
//
133
// 'cupsSaveCredentials()' - Save the credentials associated with a printer/server.
134
//
135
// This function saves the the PEM-encoded X.509 certificate chain string and
136
// private key (if not `NULL`) to the directory "path" or, if "path" is `NULL`,
137
// in a per-user or system-wide (when running as root) certificate/key store.
138
//
139
140
bool          // O - `true` on success, `false` on failure
141
cupsSaveCredentials(
142
    const char *path,     // I - Directory path for certificate/key store or `NULL` for default
143
    const char *common_name,    // I - Common name for certificate
144
    const char *credentials,    // I - PEM-encoded certificate chain or `NULL` to remove
145
    const char *key)      // I - PEM-encoded private key or `NULL` for none
146
0
{
147
0
  bool  ret = false;      // Return value
148
0
  char  defpath[1024],      // Default path
149
0
  crtfile[1024],      // Certificate filename
150
0
  keyfile[1024],      // Key filename
151
0
  ktmfile[1024];      // Temporary key filename
152
153
154
  // Validate input...
155
0
  DEBUG_printf("cupsSaveCredentials(path=\"%s\", common_name=\"%s\", credentials=%p(%u), key=%p(%u))", path, common_name, (void *)credentials, credentials ? (unsigned)strlen(credentials) : 0, (void *)key, key ? (unsigned)strlen(key) : 0);
156
157
0
  if (!path)
158
0
    path = http_default_path(defpath, sizeof(defpath));
159
160
0
  if (credentials)
161
0
  {
162
    // Make sure it looks like a PEM-encoded cert...
163
0
    if (strncmp(credentials, "-----BEGIN CERTIFICATE-----", 27) || strstr(credentials, "-----END CERTIFICATE-----") == NULL)
164
0
    {
165
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad certificate."), true);
166
0
      return (false);
167
0
    }
168
0
  }
169
170
0
  if (key)
171
0
  {
172
    // Make sure it looks like a PEM-encoded private key...
173
0
    if (strncmp(key, "-----BEGIN PRIVATE KEY-----", 27) || strstr(key, "-----END PRIVATE KEY-----") == NULL)
174
0
    {
175
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad private key."), true);
176
0
      return (false);
177
0
    }
178
0
  }
179
180
  // Save or delete credentials...
181
0
  http_make_path(crtfile, sizeof(crtfile), path, common_name, "crt");
182
0
  http_make_path(keyfile, sizeof(keyfile), path, common_name, "key");
183
0
  http_make_path(ktmfile, sizeof(ktmfile), path, common_name, "ktm");
184
185
0
  DEBUG_printf("1cupsSaveCredentials: crtfile=\"%s\"", crtfile);
186
0
  DEBUG_printf("1cupsSaveCredentials: keyfile=\"%s\"", keyfile);
187
0
  DEBUG_printf("1cupsSaveCredentials: ktmfile=\"%s\"", ktmfile);
188
189
0
  if (!credentials && !key)
190
0
  {
191
    // Delete credentials...
192
0
    if (!unlink(crtfile) && !unlink(keyfile))
193
0
      ret = true;
194
0
    else
195
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), false);
196
0
  }
197
0
  else if (!credentials && key)
198
0
  {
199
    // Bad arguments...
200
0
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), false);
201
0
  }
202
0
  else if (!key && access(keyfile, 0) && access(ktmfile, 0))
203
0
  {
204
    // Missing key file...
205
0
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), false);
206
207
0
    DEBUG_printf("1cupsSaveCredentials: access(\"%s\", 0)=%d", keyfile, access(keyfile, 0));
208
0
    DEBUG_printf("1cupsSaveCredentials: access(\"%s\", 0)=%d", ktmfile, access(ktmfile, 0));
209
0
  }
210
0
  else if (http_save_file(path, common_name, "crt", credentials))
211
0
  {
212
    // Certificate saved, save or rename key file as needed...
213
0
    if (key)
214
0
    {
215
0
      ret = http_save_file(path, common_name, "key", key);
216
0
    }
217
0
    else if (!access(ktmfile, 0) && rename(ktmfile, keyfile))
218
0
    {
219
0
      _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), false);
220
221
0
      DEBUG_printf("1cupsSaveCredentials: rename(\"%s\", \"%s\") failed.", ktmfile, keyfile);
222
0
    }
223
0
    else
224
0
    {
225
0
      ret = true;
226
0
    }
227
0
  }
228
229
0
  return (ret);
230
0
}
231
232
233
//
234
// 'cupsSetServerCredentials()' - Set the default server credentials.
235
//
236
// Note: The server credentials are used by all threads in the running process.
237
// This function is threadsafe.
238
//
239
240
bool          // O - `true` on success, `false` on failure
241
cupsSetServerCredentials(
242
    const char *path,     // I - Directory path for certificate/key store or `NULL` for default
243
    const char *common_name,    // I - Default common name for server
244
    bool       auto_create)   // I - `true` = automatically create self-signed certificates
245
0
{
246
0
  char  temp[1024];     // Default path buffer
247
248
249
0
  DEBUG_printf("cupsSetServerCredentials(path=\"%s\", common_name=\"%s\", auto_create=%d)", path, common_name, auto_create);
250
251
  // Use defaults as needed...
252
0
  if (!path)
253
0
    path = http_default_path(temp, sizeof(temp));
254
255
  // Range check input...
256
0
  if (!path || !common_name)
257
0
  {
258
0
    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
259
0
    return (0);
260
0
  }
261
262
0
  cupsMutexLock(&tls_mutex);
263
264
  // Free old values...
265
0
  if (tls_keypath)
266
0
    _cupsStrFree(tls_keypath);
267
268
0
  if (tls_common_name)
269
0
    _cupsStrFree(tls_common_name);
270
271
  // Save the new values...
272
0
  tls_keypath     = _cupsStrAlloc(path);
273
0
  tls_auto_create = auto_create;
274
0
  tls_common_name = _cupsStrAlloc(common_name);
275
276
0
  cupsMutexUnlock(&tls_mutex);
277
278
0
  return (1);
279
0
}
280
281
282
//
283
// '_httpTLSSetOptions()' - Set TLS protocol and cipher suite options.
284
//
285
286
void
287
_httpTLSSetOptions(int options,   // I - Options
288
                   int min_version, // I - Minimum TLS version
289
                   int max_version) // I - Maximum TLS version
290
1
{
291
1
  if (!(options & _HTTP_TLS_SET_DEFAULT) || tls_options < 0)
292
1
  {
293
1
    tls_options     = options;
294
1
    tls_min_version = min_version;
295
1
    tls_max_version = max_version;
296
1
  }
297
1
}
298
299
300
//
301
// 'http_copy_file()' - Copy the contents of a file to a string.
302
//
303
304
static char *       // O - Contents of file or `NULL` on error
305
http_copy_file(const char *path,  // I - Directory
306
               const char *common_name, // I - Common name
307
               const char *ext)   // I - Extension
308
0
{
309
0
  char    *s = NULL;   // String
310
0
  int   fd;     // File descriptor
311
0
  char    defpath[1024],    // Default path
312
0
    filename[1024];   // Filename
313
0
  struct stat fileinfo;   // File information
314
315
316
0
  DEBUG_printf("3http_copy_file(path=\"%s\", common_name=\"%s\", ext=\"%s\")", path, common_name, ext);
317
318
0
  if (!common_name)
319
0
    return (NULL);
320
321
0
  if (!path)
322
0
    path = http_default_path(defpath, sizeof(defpath));
323
324
0
  if ((fd = open(http_make_path(filename, sizeof(filename), path, common_name, ext), O_RDONLY)) < 0)
325
0
  {
326
0
    DEBUG_printf("4http_copy_file: open() failed: %s", strerror(errno));
327
0
    return (NULL);
328
0
  }
329
330
0
  if (fstat(fd, &fileinfo))
331
0
  {
332
0
    DEBUG_printf("4http_copy_file: fstat() failed: %s", strerror(errno));
333
0
    goto done;
334
0
  }
335
336
0
  DEBUG_printf("4http_copy_file: st_size=%lu", (unsigned long)fileinfo.st_size);
337
338
0
  if (fileinfo.st_size > 65536)
339
0
  {
340
0
    close(fd);
341
0
    return (NULL);
342
0
  }
343
344
0
  if ((s = calloc(1, (size_t)fileinfo.st_size + 1)) == NULL)
345
0
  {
346
0
    DEBUG_printf("4http_copy_file: calloc() failed: %s", strerror(errno));
347
0
    close(fd);
348
0
    return (NULL);
349
0
  }
350
351
0
  if (read(fd, s, (size_t)fileinfo.st_size) < 0)
352
0
  {
353
0
    DEBUG_printf("4http_copy_file: read() failed: %s", strerror(errno));
354
0
    free(s);
355
0
    s = NULL;
356
0
  }
357
358
0
  done:
359
360
0
  close(fd);
361
362
0
  DEBUG_printf("4http_copy_file: Returning \"%s\"", s);
363
364
0
  return (s);
365
0
}
366
367
368
//
369
// 'http_default_path()' - Get the default credential store path.
370
//
371
372
static const char *     // O - Path or NULL on error
373
http_default_path(
374
    char   *buffer,     // I - Path buffer
375
    size_t bufsize)     // I - Size of path buffer
376
0
{
377
0
  _cups_globals_t *cg = _cupsGlobals();
378
          // Pointer to library globals
379
380
381
0
  if (cg->userconfig)
382
0
  {
383
0
    snprintf(buffer, bufsize, "%s/ssl", cg->userconfig);
384
385
0
    if (!_cupsDirCreate(buffer, 0700))
386
0
    {
387
0
      DEBUG_printf("4http_default_path: Failed to make directory '%s': %s", buffer, strerror(errno));
388
0
      return (NULL);
389
0
    }
390
0
  }
391
0
  else
392
0
  {
393
0
    snprintf(buffer, bufsize, "%s/ssl", cg->sysconfig);
394
395
0
    if (!_cupsDirCreate(buffer, 0700))
396
0
    {
397
0
      DEBUG_printf("4http_default_path: Failed to make directory '%s': %s", buffer, strerror(errno));
398
0
      return (NULL);
399
0
    }
400
0
  }
401
402
0
  DEBUG_printf("4http_default_path: Using default path \"%s\".", buffer);
403
404
0
  return (buffer);
405
0
}
406
407
408
//
409
// 'http_default_san_cb()' - Validate a subjectAltName value.
410
//
411
412
static bool       // O - `true` if OK, `false` otherwise
413
http_default_san_cb(
414
    const char *common_name,    // I - Common name value
415
    const char *subject_alt_name, // I - subjectAltName value
416
    void       *data)     // I - Callback data (unused)
417
0
{
418
0
  size_t  common_len;   // Common name length
419
420
421
0
  (void)data;
422
423
0
  if (!_cups_strcasecmp(subject_alt_name, common_name) || !_cups_strcasecmp(subject_alt_name, "localhost"))
424
0
    return (true);
425
426
0
  common_len = strlen(common_name);
427
428
0
  return (!_cups_strncasecmp(subject_alt_name, common_name, common_len) && subject_alt_name[common_len] == '.');
429
0
}
430
431
432
#if defined(_WIN32) || defined(HAVE_GNUTLS)
433
//
434
// 'http_der_to_pem()' - Convert DER format certificate data to PEM.
435
//
436
437
static char *       // O - PEM string
438
http_der_to_pem(
439
     const unsigned char *der,    // I - DER-encoded data
440
     size_t              dersize) // I - Size of DER-encoded data
441
{
442
  char    *pem,     // PEM-encoded string
443
    *pemptr;    // Pointer into PEM-encoded string
444
  int   col;      // Current column
445
  size_t  pemsize;    // Size of PEM-encoded string
446
  static const char *base64 =   // Base64 alphabet
447
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
448
449
450
  // Calculate the size, accounting for Base64 expansion, line wrapping at
451
  // column 64, and the BEGIN/END CERTIFICATE text...
452
  pemsize = 2 * dersize + /*"-----BEGIN CERTIFICATE-----\n"*/28 + /*"-----END CERTIFICATE-----\n"*/26 + 1;
453
454
  if ((pem = calloc(1, pemsize)) == NULL)
455
    return (NULL);
456
457
  cupsCopyString(pem, "-----BEGIN CERTIFICATE-----\n", pemsize);
458
  for (pemptr = pem + strlen(pem), col = 0; dersize > 0; der += 3)
459
  {
460
    // Encode the up to 3 characters as 4 Base64 numbers...
461
    switch (dersize)
462
    {
463
      case 1 :
464
          *pemptr ++ = base64[(der[0] & 255) >> 2];
465
    *pemptr ++ = base64[((der[0] & 255) << 4) & 63];
466
    *pemptr ++ = '=';
467
    *pemptr ++ = '=';
468
    dersize = 0;
469
          break;
470
      case 2 :
471
          *pemptr ++ = base64[(der[0] & 255) >> 2];
472
    *pemptr ++ = base64[(((der[0] & 255) << 4) | ((der[1] & 255) >> 4)) & 63];
473
    *pemptr ++ = base64[((der[1] & 255) << 2) & 63];
474
    *pemptr ++ = '=';
475
    dersize = 0;
476
          break;
477
      default :
478
          *pemptr ++ = base64[(der[0] & 255) >> 2];
479
    *pemptr ++ = base64[(((der[0] & 255) << 4) | ((der[1] & 255) >> 4)) & 63];
480
    *pemptr ++ = base64[(((der[1] & 255) << 2) | ((der[2] & 255) >> 6)) & 63];
481
    *pemptr ++ = base64[der[2] & 63];
482
    dersize -= 3;
483
          break;
484
    }
485
486
    // Add a newline as needed...
487
    col += 4;
488
    if (col >= 64)
489
    {
490
      *pemptr++ = '\n';
491
      col = 0;
492
    }
493
  }
494
495
  if (col > 0)
496
    *pemptr++ = '\n';
497
  *pemptr = '\0';
498
499
  cupsConcatString(pem, "-----END CERTIFICATE-----\n", pemsize);
500
501
  // Return the encoded string...
502
  return (pem);
503
}
504
#endif // _WIN32 || HAVE_GNUTLS
505
506
507
//
508
// 'http_make_path()' - Format a filename for a certificate or key file.
509
//
510
511
static const char *     // O - Filename
512
http_make_path(
513
    char       *buffer,     // I - Filename buffer
514
    size_t     bufsize,     // I - Size of buffer
515
    const char *dirname,    // I - Directory
516
    const char *filename,   // I - Filename (usually hostname)
517
    const char *ext)      // I - Extension
518
0
{
519
0
  char  *bufptr,      // Pointer into buffer
520
0
  *bufend = buffer + bufsize - 1; // End of buffer
521
522
523
0
  DEBUG_printf("3http_make_path(buffer=%p, bufsize=%u, dirname=\"%s\", filename=\"%s\", ext=\"%s\")", (void *)buffer, (unsigned)bufsize, dirname, filename, ext);
524
525
0
  snprintf(buffer, bufsize, "%s/", dirname);
526
0
  bufptr = buffer + strlen(buffer);
527
528
0
  while (*filename && bufptr < bufend)
529
0
  {
530
0
    if (_cups_isalnum(*filename) || *filename == '-' || *filename == '.')
531
0
      *bufptr++ = *filename;
532
0
    else
533
0
      *bufptr++ = '_';
534
535
0
    filename ++;
536
0
  }
537
538
0
  if (bufptr < bufend && filename[-1] != '.')
539
0
    *bufptr++ = '.';
540
541
0
  cupsCopyString(bufptr, ext, (size_t)(bufend - bufptr + 1));
542
543
0
  DEBUG_printf("4http_make_path: Returning \"%s\".", buffer);
544
545
0
  return (buffer);
546
0
}
547
548
549
//
550
// 'http_save_file()' - Save a string to a file.
551
//
552
553
static bool       // O - `true` on success, `false` on failure
554
http_save_file(const char *path,  // I - Directory path for certificate/key store or `NULL` for default
555
               const char *common_name, // I - Common name
556
               const char *ext,   // I - Extension
557
         const char *value) // I - String value
558
0
{
559
0
  char  defpath[1024],      // Default path
560
0
  filename[1024];     // Output filename
561
0
  int fd;       // File descriptor
562
563
564
  // Range check input...
565
0
  if (!common_name)
566
0
    return (false);
567
568
  // Get default path as needed...
569
0
  if (!path)
570
0
    path = http_default_path(defpath, sizeof(defpath));
571
572
0
  http_make_path(filename, sizeof(filename), path, common_name, ext);
573
574
0
  if (!value)
575
0
  {
576
0
    unlink(filename);
577
0
    return (true);
578
0
  }
579
580
0
  if ((fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, (strcmp(ext, "key") && strcmp(ext, "ktm")) ? 0644 : 0600)) < 0)
581
0
    return (false);
582
583
0
  if (write(fd, value, strlen(value)) < 0)
584
0
  {
585
0
    close(fd);
586
0
    unlink(filename);
587
0
    return (false);
588
0
  }
589
590
0
  close(fd);
591
592
  return (true);
593
0
}
594
595