Skip to content

Commit a842d01

Browse files
committed
Add 'install' sub-command to the cups-x509 command.
1 parent 20adff9 commit a842d01

4 files changed

Lines changed: 130 additions & 9 deletions

File tree

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ libcups v3.0.0 (YYYY-MM-DD)
88
- Added `cupsOAuthGetDeviceGrant`, `cupsOAuthGetJWKS`, and `cupsOAuthGetUserId`
99
APIs.
1010
- Added `httpGetCookieValue` and `httpGetSecurity` APIs.
11+
- Added an "install" sub-command to the `cups-x509` command.
1112
- Added a "--user-agent" option to the `ipptool` command.
1213
- Updated documentation (Issue #113)
1314
- Updated the `cupsOAuth` APIs to support sharing of some OAuth values between

doc/cups-x509.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ <h3 id="cups-x509-1.sub-commands.client-uri">Client Uri</h3>
229229
</p>
230230
<h3 id="cups-x509-1.sub-commands.csr-common-name">Csr Common-Name</h3>
231231
<p>Create a certificate signing request for the specified common name.
232+
</p>
233+
<h3 id="cups-x509-1.sub-commands.install-common-name-filename.crt-filename.key">Install Common-Name Filename.Crt [Filename.Key]</h3>
234+
<p>Installs a certificate and optional private key for the specified common name.
235+
The certificate and private key are PEM-encoded files.
232236
</p>
233237
<h3 id="cups-x509-1.sub-commands.server-common-nameport">Server Common-Name[:Port]</h3>
234238
<p>Run a HTTPS test server that echos back the resource path for every GET request.

man/cups-x509.1

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
.\" Licensed under Apache License v2.0. See the file "LICENSE" for more
77
.\" information.
88
.\"
9-
.TH cups-x509 1 "CUPS" "2025-05-05" "OpenPrinting"
9+
.TH cups-x509 1 "CUPS" "2025-10-21" "OpenPrinting"
1010
.SH NAME
1111
cups-x509 \- description
1212
.SH SYNOPSIS
@@ -132,6 +132,9 @@ Create a certificate for the specified common name.
132132
Connect to the specified URI and validate the server's certificate.
133133
.SS csr COMMON-NAME
134134
Create a certificate signing request for the specified common name.
135+
.SS install COMMON-NAME FILENAME.crt [FILENAME.key]
136+
Installs a certificate and optional private key for the specified common name.
137+
The certificate and private key are PEM-encoded files.
135138
.SS server COMMON-NAME[:PORT]
136139
Run a HTTPS test server that echos back the resource path for every GET request.
137140
If PORT is not specified, uses a port number from 8000 to 8999.

tools/cups-x509.c

Lines changed: 121 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
// cert COMMON-NAME Create a certificate.
1616
// client URI Connect to URI.
1717
// csr COMMON-NAME Create a certificate signing request.
18+
// install COMMON-NAME FILENAME.crt [FILENAME.key]
19+
// Install a certificate and (optional) private key.
1820
// server COMMON-NAME[:PORT] Run a HTTPS server (default port 8NNN.)
1921
// show COMMON-NAME Show stored credentials for COMMON-NAME.
2022
//
@@ -69,8 +71,10 @@ static int do_ca(const char *common_name, const char *csrfile, const char *root_
6971
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);
7072
static int do_client(const char *uri, bool pin, bool require_ca);
7173
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);
74+
static int do_install(const char *common_name, const char *crtfile, const char *keyfile);
7275
static int do_server(const char *host_port);
7376
static int do_show(const char *common_name);
77+
static char *get_file(const char *filename);
7478
static int usage(FILE *fp);
7579

7680

@@ -85,6 +89,8 @@ main(int argc, // I - Number of command-line arguments
8589
int i; // Looping var
8690
const char *command = NULL, // Command
8791
*arg = NULL, // Argument for command
92+
*crtfile = NULL, // Certificate file for install command
93+
*keyfile = NULL, // Private key file for install command
8894
*opt, // Current option character
8995
*csrfile = NULL, // Certificste signing request filename
9096
*root_name = NULL, // Name of root certificate
@@ -356,14 +362,22 @@ main(int argc, // I - Number of command-line arguments
356362
{
357363
arg = argv[i];
358364
}
365+
else if (!crtfile && !strcmp(command, "install"))
366+
{
367+
crtfile = argv[i];
368+
}
369+
else if (!keyfile && !strcmp(command, "install"))
370+
{
371+
keyfile = argv[i];
372+
}
359373
else
360374
{
361375
cupsLangPrintf(stderr, _("%s: Unknown option '%s'."), "cups-x509", argv[i]);
362376
return (usage(stderr));
363377
}
364378
}
365379

366-
if (!command || !arg)
380+
if (!command || !arg || (!strcmp(command, "install") && !crtfile))
367381
{
368382
cupsLangPuts(stderr, _("cups-x509: Missing sub-command argument."));
369383
return (usage(stderr));
@@ -390,6 +404,10 @@ main(int argc, // I - Number of command-line arguments
390404
{
391405
return (do_csr(purpose, type, keyusage, organization, org_unit, locality, state, country, arg, num_alt_names, alt_names));
392406
}
407+
else if (!strcmp(command, "install"))
408+
{
409+
return (do_install(arg, crtfile, keyfile));
410+
}
393411
else if (!strcmp(command, "server"))
394412
{
395413
return (do_server(arg));
@@ -645,6 +663,41 @@ do_csr(
645663
}
646664

647665

666+
//
667+
// 'do_install()' - Install a certificate.
668+
//
669+
670+
static int // O - Exit status
671+
do_install(const char *common_name, // I - Common name
672+
const char *crtfile, // I - Certificate filename
673+
const char *keyfile) // I - Private key filename or `NULL` if none
674+
{
675+
int ret = 1; // Exit status
676+
char *crt, // Certificate string
677+
*key; // Private key string
678+
679+
680+
// Load the certificate and key, as needed...
681+
crt = get_file(crtfile);
682+
key = keyfile ? get_file(keyfile) : NULL;
683+
684+
if (!crt || (keyfile && !key))
685+
goto done;
686+
687+
// Try saving them...
688+
if (cupsSaveCredentials(/*path*/NULL, common_name, crt, key))
689+
ret = 0;
690+
691+
// Free and return...
692+
done:
693+
694+
free(crt);
695+
free(key);
696+
697+
return (ret);
698+
}
699+
700+
648701
//
649702
// 'do_server()' - Test running a server.
650703
//
@@ -834,6 +887,64 @@ do_show(const char *common_name) // I - Common name
834887
}
835888

836889

890+
//
891+
// 'get_file()' - Load a file into a string.
892+
//
893+
// Note: The string must be freed by the caller.
894+
//
895+
896+
static char * // O - String or `NULL` on error
897+
get_file(const char *filename) // I - File to load
898+
{
899+
int fd; // File descriptor
900+
struct stat info; // File information
901+
char *file = NULL; // File string
902+
903+
904+
if ((fd = open(filename, O_RDONLY)) < 0)
905+
{
906+
cupsLangPrintf(stderr, _("%s: Unable to open '%s': %s"), "cups-x509", filename, strerror(errno));
907+
return (NULL);
908+
}
909+
910+
if (fstat(fd, &info))
911+
{
912+
cupsLangPrintf(stderr, _("%s: Unable to access '%s': %s"), "cups-x509", filename, strerror(errno));
913+
goto error;
914+
}
915+
916+
if (info.st_size > 65536)
917+
{
918+
cupsLangPrintf(stderr, _("%s: File '%s' is too large to load in memory."), "cups-x509", filename);
919+
goto error;
920+
}
921+
922+
if ((file = calloc(1, (size_t)info.st_size + 1)) == NULL)
923+
{
924+
cupsLangPrintf(stderr, _("%s: Unable to allocate memory for '%s': %s"), "cups-x509", filename, strerror(errno));
925+
goto error;
926+
}
927+
928+
if (read(fd, file, (size_t)info.st_size) < 0)
929+
{
930+
cupsLangPrintf(stderr, _("%s: Unable to read '%s': %s"), "cups-x509", filename, strerror(errno));
931+
goto error;
932+
}
933+
934+
close(fd);
935+
936+
return (file);
937+
938+
// If we get here something bad happened.
939+
error:
940+
941+
free(file);
942+
close(fd);
943+
944+
return (NULL);
945+
}
946+
947+
837948
//
838949
// 'usage()' - Show program usage...
839950
//
@@ -845,13 +956,15 @@ usage(FILE *out) // I - Output file (stdout or stderr)
845956
cupsLangPuts(out, "");
846957
cupsLangPuts(out, _("Sub-Commands:"));
847958
cupsLangPuts(out, "");
848-
cupsLangPuts(out, _("ca COMMON-NAME Sign a CSR to produce a certificate."));
849-
cupsLangPuts(out, _("cacert COMMON-NAME Create a CA certificate."));
850-
cupsLangPuts(out, _("cert COMMON-NAME Create a certificate."));
851-
cupsLangPuts(out, _("client URI Connect to URI."));
852-
cupsLangPuts(out, _("csr COMMON-NAME Create a certificate signing request."));
853-
cupsLangPuts(out, _("server COMMON-NAME[:PORT] Run a HTTPS server (default port 8NNN.)"));
854-
cupsLangPuts(out, _("show COMMON-NAME Show stored credentials for COMMON-NAME."));
959+
cupsLangPuts(out, _("ca COMMON-NAME Sign a CSR to produce a certificate."));
960+
cupsLangPuts(out, _("cacert COMMON-NAME Create a CA certificate."));
961+
cupsLangPuts(out, _("cert COMMON-NAME Create a certificate."));
962+
cupsLangPuts(out, _("client URI Connect to URI."));
963+
cupsLangPuts(out, _("csr COMMON-NAME Create a certificate signing request."));
964+
cupsLangPuts(out, _("install COMMON-NAME FILENAME.crt [FILENAME.key]\n"
965+
" Install a certificate and (optional) private key."));
966+
cupsLangPuts(out, _("server COMMON-NAME[:PORT] Run a HTTPS server (default port 8NNN.)"));
967+
cupsLangPuts(out, _("show COMMON-NAME Show stored credentials for COMMON-NAME."));
855968
cupsLangPuts(out, "");
856969
cupsLangPuts(out, _("Options:"));
857970
cupsLangPuts(out, _(""));

0 commit comments

Comments
 (0)