]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
Add 'install' sub-command to cups-x509 command (Issue #1227)
authorMichael R Sweet <msweet@msweet.org>
Tue, 21 Oct 2025 16:09:03 +0000 (12:09 -0400)
committerMichael R Sweet <msweet@msweet.org>
Tue, 21 Oct 2025 16:09:03 +0000 (12:09 -0400)
CHANGES.md
doc/help/man-cups-x509.html
man/cups-x509.1
tools/cups-x509.c

index 9a5bf0c458fce734d4dca8cf7ae8bcba80b1abd8..0c968cbc81f49e94fda3c5365bac16cd6447c467 100644 (file)
@@ -40,6 +40,7 @@ Changes in CUPS v2.5b1 (YYYY-MM-DD)
   not specified (Issue #1217)
 - Added `print-as-raster` printer and job attributes for forcing rasterization
   (Issue #1282)
+- Added an "install" sub-command to the `cups-x509` command (Issue #1227)
 - Added a "--user-agent" option to the `ipptool` command.
 - Updated documentation (Issue #984, Issue #1086, Issue #1182)
 - Updated translations (Issue #1146, Issue #1161, Issue #1164)
index 987080fffe3f628e5a26e08e4898e0f147d05702..bf0e98755ef6eb78733f6798321ec3b8d23587de 100644 (file)
@@ -139,6 +139,10 @@ The preset &quot;default-ca&quot; specifies those uses required for a Certificat
 </p>
     <h3 id="cups-x509-1.sub-commands.csr-common-name">Csr Common-Name</h3>
 <p>Create a certificate signing request for the specified common name.
+</p>
+    <h3 id="cups-x509-1.sub-commands.install-common-name-filename.crt-filename.key">Install Common-Name Filename.Crt [Filename.Key]</h3>
+<p>Installs a certificate and optional private key for the specified common name.
+The certificate and private key are PEM-encoded files.
 </p>
     <h3 id="cups-x509-1.sub-commands.server-common-nameport">Server Common-Name[:Port]</h3>
 <p>Run a HTTPS test server that echos back the resource path for every GET request.
index 2695804c39f464d2277e413817a582d69d56c213..138f9be27c2e6d591bf74f3d6428a7da577c6561 100644 (file)
@@ -6,7 +6,7 @@
 .\" Licensed under Apache License v2.0.  See the file "LICENSE" for more
 .\" information.
 .\"
-.TH cups-x509 1 "CUPS" "2025-05-05" "OpenPrinting"
+.TH cups-x509 1 "CUPS" "2025-10-21" "OpenPrinting"
 .SH NAME
 cups-x509 \- description
 .SH SYNOPSIS
@@ -132,6 +132,9 @@ Create a certificate for the specified common name.
 Connect to the specified URI and validate the server's certificate.
 .SS csr COMMON-NAME
 Create a certificate signing request for the specified common name.
+.SS install COMMON-NAME FILENAME.crt [FILENAME.key]
+Installs a certificate and optional private key for the specified common name.
+The certificate and private key are PEM-encoded files.
 .SS server COMMON-NAME[:PORT]
 Run a HTTPS test server that echos back the resource path for every GET request.
 If PORT is not specified, uses a port number from 8000 to 8999.
index 482a647c137c496de3b8072a88a54e018b00b524..6c7727ad67bd13263f9ce9333390a316c871255f 100644 (file)
@@ -15,6 +15,8 @@
 //   cert COMMON-NAME           Create a certificate.
 //   client URI                 Connect to URI.
 //   csr COMMON-NAME            Create a certificate signing request.
+//   install COMMON-NAME FILENAME.crt [FILENAME.key]
+//                              Install a certificate and (optional) private key.
 //   server COMMON-NAME[:PORT]  Run a HTTPS server (default port 8NNN.)
 //   show COMMON-NAME           Show stored credentials for COMMON-NAME.
 //
@@ -68,8 +70,10 @@ static int   do_ca(const char *common_name, const char *csrfile, const char *root_
 static int     do_cert(bool ca_cert, cups_credpurpose_t purpose, cups_credtype_t type, cups_credusage_t keyusage, const char *organization, const char *org_unit, const char *locality, const char *state, const char *country, const char *root_name, const char *common_name, size_t num_alt_names, const char **alt_names, int days);
 static int     do_client(const char *uri, bool pin, bool require_ca);
 static int     do_csr(cups_credpurpose_t purpose, cups_credtype_t type, cups_credusage_t keyusage, const char *organization, const char *org_unit, const char *locality, const char *state, const char *country, const char *common_name, size_t num_alt_names, const char **alt_names);
+static int     do_install(const char *common_name, const char *crtfile, const char *keyfile);
 static int     do_server(const char *host_port);
 static int     do_show(const char *common_name);
+static char    *get_file(const char *filename);
 static int     usage(FILE *fp);
 
 
@@ -84,6 +88,8 @@ main(int  argc,                               // I - Number of command-line arguments
   int          i;                      // Looping var
   const char   *command = NULL,        // Command
                *arg = NULL,            // Argument for command
+               *crtfile = NULL,        // Certificate file for install command
+               *keyfile = NULL,        // Private key file for install command
                *opt,                   // Current option character
                *csrfile = NULL,        // Certificste signing request filename
                *root_name = NULL,      // Name of root certificate
@@ -355,6 +361,14 @@ main(int  argc,                            // I - Number of command-line arguments
     {
       arg = argv[i];
     }
+    else if (!crtfile && !strcmp(command, "install"))
+    {
+      crtfile = argv[i];
+    }
+    else if (!keyfile && !strcmp(command, "install"))
+    {
+      keyfile = argv[i];
+    }
     else
     {
       _cupsLangPrintf(stderr, _("%s: Unknown option '%s'."), "cups-x509", argv[i]);
@@ -362,9 +376,9 @@ main(int  argc,                             // I - Number of command-line arguments
     }
   }
 
-  if (!command || !arg)
+  if (!command || !arg || (!strcmp(command, "install") && !crtfile))
   {
-    _cupsLangPuts(stderr, _("cups-x509: Missing command argument."));
+    _cupsLangPuts(stderr, _("cups-x509: Missing sub-command argument."));
     return (usage(stderr));
   }
 
@@ -389,6 +403,10 @@ main(int  argc,                            // I - Number of command-line arguments
   {
     return (do_csr(purpose, type, keyusage, organization, org_unit, locality, state, country, arg, num_alt_names, alt_names));
   }
+  else if (!strcmp(command, "install"))
+  {
+    return (do_install(arg, crtfile, keyfile));
+  }
   else if (!strcmp(command, "server"))
   {
     return (do_server(arg));
@@ -644,6 +662,41 @@ do_csr(
 }
 
 
+//
+// 'do_install()' - Install a certificate.
+//
+
+static int                             // O - Exit status
+do_install(const char *common_name,    // I - Common name
+           const char *crtfile,                // I - Certificate filename
+           const char *keyfile)                // I - Private key filename or `NULL` if none
+{
+  int  ret = 1;                        // Exit status
+  char *crt,                           // Certificate string
+       *key;                           // Private key string
+
+
+  // Load the certificate and key, as needed...
+  crt = get_file(crtfile);
+  key = keyfile ? get_file(keyfile) : NULL;
+
+  if (!crt || (keyfile && !key))
+    goto done;
+
+  // Try saving them...
+  if (cupsSaveCredentials(/*path*/NULL, common_name, crt, key))
+    ret = 0;
+
+  // Free and return...
+  done:
+
+  free(crt);
+  free(key);
+
+  return (ret);
+}
+
+
 //
 // 'do_server()' - Test running a server.
 //
@@ -833,6 +886,64 @@ do_show(const char *common_name)   // I - Common name
 }
 
 
+//
+// 'get_file()' - Load a file into a string.
+//
+// Note: The string must be freed by the caller.
+//
+
+static char *                          // O - String or `NULL` on error
+get_file(const char *filename)         // I - File to load
+{
+  int          fd;                     // File descriptor
+  struct stat  info;                   // File information
+  char         *file = NULL;           // File string
+
+
+  if ((fd = open(filename, O_RDONLY)) < 0)
+  {
+    _cupsLangPrintf(stderr, _("%s: Unable to open '%s': %s"), "cups-x509", filename, strerror(errno));
+    return (NULL);
+  }
+
+  if (fstat(fd, &info))
+  {
+    _cupsLangPrintf(stderr, _("%s: Unable to access '%s': %s"), "cups-x509", filename, strerror(errno));
+    goto error;
+  }
+
+  if (info.st_size > 65536)
+  {
+    _cupsLangPrintf(stderr, _("%s: File '%s' is too large to load in memory."), "cups-x509", filename);
+    goto error;
+  }
+
+  if ((file = calloc(1, (size_t)info.st_size + 1)) == NULL)
+  {
+    _cupsLangPrintf(stderr, _("%s: Unable to allocate memory for '%s': %s"), "cups-x509", filename, strerror(errno));
+    goto error;
+  }
+
+  if (read(fd, file, (size_t)info.st_size) < 0)
+  {
+    _cupsLangPrintf(stderr, _("%s: Unable to read '%s': %s"), "cups-x509", filename, strerror(errno));
+    goto error;
+  }
+
+  close(fd);
+
+  return (file);
+
+  // If we get here something bad happened.
+  error:
+
+  free(file);
+  close(fd);
+
+  return (NULL);
+}
+
+
 //
 // 'usage()' - Show program usage...
 //
@@ -849,6 +960,8 @@ usage(FILE *out)                    // I - Output file (stdout or stderr)
   _cupsLangPuts(out, _("cert COMMON-NAME           Create a certificate."));
   _cupsLangPuts(out, _("client URI                 Connect to URI."));
   _cupsLangPuts(out, _("csr COMMON-NAME            Create a certificate signing request."));
+  _cupsLangPuts(out, _("install COMMON-NAME FILENAME.crt [FILENAME.key]\n"
+                       "                           Install a certificate and (optional) private key."));
   _cupsLangPuts(out, _("server COMMON-NAME[:PORT]  Run a HTTPS server (default port 8NNN.)"));
   _cupsLangPuts(out, _("show COMMON-NAME           Show stored credentials for COMMON-NAME."));
   _cupsLangPuts(out, "");