From efb425046d8e333ef94752aa1b737f116137cb57 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 11 Apr 2023 16:46:39 -0400 Subject: [PATCH 01/31] Initial prototypes for new X.509 certificate support (Issue #53) --- cups/cups.h | 4 +++- cups/tls-openssl.c | 25 ++++++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/cups/cups.h b/cups/cups.h index 6450230ea..60afe5f54 100644 --- a/cups/cups.h +++ b/cups/cups.h @@ -336,7 +336,8 @@ extern const char *cupsLocalizeDestMedia(http_t *http, cups_dest_t *dest, cups_d extern const char *cupsLocalizeDestOption(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, const char *option) _CUPS_PUBLIC; extern const char *cupsLocalizeDestValue(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, const char *option, const char *value) _CUPS_PUBLIC; -extern int cupsMakeServerCredentials(const char *path, const char *common_name, int num_alt_names, const char **alt_names, time_t expiration_date) _CUPS_PUBLIC; +extern bool cupsMakeServerCredentials(const char *path, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *root_name, const char *common_name, int num_alt_names, const char **alt_names, time_t expiration_date) _CUPS_PUBLIC; +extern char *cupsMakeServerRequest(const char *path, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, int num_alt_names, const char **alt_names) _CUPS_PUBLIC; extern char *cupsNotifySubject(cups_lang_t *lang, ipp_t *event) _CUPS_PUBLIC; extern char *cupsNotifyText(cups_lang_t *lang, ipp_t *event) _CUPS_PUBLIC; @@ -349,6 +350,7 @@ extern ssize_t cupsReadResponseData(http_t *http, char *buffer, size_t length) extern size_t cupsRemoveDest(const char *name, const char *instance, size_t num_dests, cups_dest_t **dests) _CUPS_PUBLIC; extern size_t cupsRemoveOption(const char *name, size_t num_options, cups_option_t **options) _CUPS_PUBLIC; +extern bool cupsSaveServerCredentials(const char *path, const char *pem) _CUPS_PUBLIC; extern http_status_t cupsSendRequest(http_t *http, ipp_t *request, const char *resource, size_t length) _CUPS_PUBLIC; extern void cupsSetOAuthCB(cups_oauth_cb_t cb, void *data) _CUPS_PUBLIC; extern void cupsSetClientCertCB(cups_client_cert_cb_t cb, void *user_data) _CUPS_PUBLIC; diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index 258b4eee7..4fc17ff48 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -64,9 +64,15 @@ static int tls_options = -1,/* Options for TLS connections */ * 'cupsMakeServerCredentials()' - Make a self-signed certificate and private key pair. */ -int // O - 1 on success, 0 on failure +bool // O - `true` on success, `false` on failure cupsMakeServerCredentials( - const char *path, // I - Path to keychain/directory + const char *path, // I - Path to keychain/directory or `NULL` for default + const char *organization, // I - Organization or `NULL` to use common name + const char *org_unit, // I - Organizational unit or `NULL` for none + const char *locality, // I - City/town or `NULL` for "Unknown" + const char *state_province, // I - State/province or `NULL` for "Unknown" + const char *country, // I - Country or `NULL` for locale-based default + const char *root_name, // I - Root certificate/domain name or `NULL` for site/self-signed const char *common_name, // I - Common name int num_alt_names, // I - Number of subject alternate names const char **alt_names, // I - Subject Alternate Names @@ -177,16 +183,19 @@ cupsMakeServerCredentials( language = cupsLangDefault(); langname = cupsLangGetName(language); name = X509_NAME_new(); - if (strlen(langname) == 5) + if (country) + X509_NAME_add_entry_by_txt(name, SN_countryName, MBSTRING_ASC, (unsigned char *)country, -1, -1, 0); + else if (strlen(langname) == 5) X509_NAME_add_entry_by_txt(name, SN_countryName, MBSTRING_ASC, (unsigned char *)langname + 3, -1, -1, 0); else X509_NAME_add_entry_by_txt(name, SN_countryName, MBSTRING_ASC, (unsigned char *)"US", -1, -1, 0); X509_NAME_add_entry_by_txt(name, SN_commonName, MBSTRING_ASC, (unsigned char *)common_name, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, SN_organizationName, MBSTRING_ASC, (unsigned char *)common_name, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, SN_organizationalUnitName, MBSTRING_ASC, (unsigned char *)"Unknown", -1, -1, 0); - X509_NAME_add_entry_by_txt(name, SN_stateOrProvinceName, MBSTRING_ASC, (unsigned char *)"Unknown", -1, -1, 0); - X509_NAME_add_entry_by_txt(name, SN_localityName, MBSTRING_ASC, (unsigned char *)"Unknown", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, SN_organizationName, MBSTRING_ASC, (unsigned char *)(organization ? organization : common_name), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, SN_organizationalUnitName, MBSTRING_ASC, (unsigned char *)(org_unit ? org_unit : ""), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, SN_stateOrProvinceName, MBSTRING_ASC, (unsigned char *)(state_province ? state_province : "Unknown"), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, SN_localityName, MBSTRING_ASC, (unsigned char *)(locality ? locality : "Unknown"), -1, -1, 0); + // TODO set issuer name to CA subject name (X509_get_subject_name) X509_set_issuer_name(cert, name); X509_set_subject_name(cert, name); X509_NAME_free(name); @@ -218,6 +227,7 @@ cupsMakeServerCredentials( } // Add extensions that are required to make Chrome happy... + // TODO: Support creating CA certs http_x509_add_ext(cert, NID_basic_constraints, "critical,CA:FALSE,pathlen:0"); http_x509_add_ext(cert, NID_key_usage, "critical,digitalSignature,keyEncipherment"); http_x509_add_ext(cert, NID_ext_key_usage, "1.3.6.1.5.5.7.3.1"); @@ -225,6 +235,7 @@ cupsMakeServerCredentials( http_x509_add_ext(cert, NID_authority_key_identifier, "keyid,issuer"); X509_set_version(cert, 2); // v3 + // TODO: Sign with CA key when available X509_sign(cert, pkey, EVP_sha256()); // Save them... From 3da4b2f67d1818cd12c4cad18151bde152df4c8d Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Fri, 14 Apr 2023 10:12:11 -0400 Subject: [PATCH 02/31] Save work on new cupsMakeCredentials. --- cups/cups.h | 6 +- cups/http.h | 4 +- cups/testcreds.c | 4 +- cups/testhttp.c | 2 +- cups/tls-openssl.c | 377 +++++++++++++++++++++++++-------------------- 5 files changed, 222 insertions(+), 171 deletions(-) diff --git a/cups/cups.h b/cups/cups.h index 60afe5f54..1383713f8 100644 --- a/cups/cups.h +++ b/cups/cups.h @@ -336,8 +336,8 @@ extern const char *cupsLocalizeDestMedia(http_t *http, cups_dest_t *dest, cups_d extern const char *cupsLocalizeDestOption(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, const char *option) _CUPS_PUBLIC; extern const char *cupsLocalizeDestValue(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, const char *option, const char *value) _CUPS_PUBLIC; -extern bool cupsMakeServerCredentials(const char *path, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *root_name, const char *common_name, int num_alt_names, const char **alt_names, time_t expiration_date) _CUPS_PUBLIC; -extern char *cupsMakeServerRequest(const char *path, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, int num_alt_names, const char **alt_names) _CUPS_PUBLIC; +extern bool cupsMakeServerCredentials(const char *path, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *root_name, bool ca_cert, const char *common_name, size_t num_alt_names, const char **alt_names, time_t expiration_date) _CUPS_PUBLIC; +extern char *cupsMakeServerRequest(const char *path, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, size_t num_alt_names, const char **alt_names) _CUPS_PUBLIC; extern char *cupsNotifySubject(cups_lang_t *lang, ipp_t *event) _CUPS_PUBLIC; extern char *cupsNotifyText(cups_lang_t *lang, ipp_t *event) _CUPS_PUBLIC; @@ -361,7 +361,7 @@ extern void cupsSetEncryption(http_encryption_t e) _CUPS_PUBLIC; extern void cupsSetPasswordCB(cups_password_cb_t cb, void *user_data) _CUPS_PUBLIC; extern void cupsSetServer(const char *server) _CUPS_PUBLIC; extern void cupsSetServerCertCB(cups_server_cert_cb_t cb, void *user_data) _CUPS_PUBLIC; -extern int cupsSetServerCredentials(const char *path, const char *common_name, int auto_create) _CUPS_PUBLIC; +extern bool cupsSetServerCredentials(const char *path, const char *common_name, bool auto_create) _CUPS_PUBLIC; extern void cupsSetUser(const char *user) _CUPS_PUBLIC; extern void cupsSetUserAgent(const char *user_agent) _CUPS_PUBLIC; extern http_status_t cupsStartDestDocument(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, int job_id, const char *docname, const char *format, size_t num_options, cups_option_t *options, bool last_document) _CUPS_PUBLIC; diff --git a/cups/http.h b/cups/http.h index 8f9dfbc9b..559e52aff 100644 --- a/cups/http.h +++ b/cups/http.h @@ -458,9 +458,9 @@ extern void httpClose(http_t *http) _CUPS_PUBLIC; extern void httpClearCookie(http_t *http) _CUPS_PUBLIC; extern bool httpCompareCredentials(cups_array_t *cred1, cups_array_t *cred2) _CUPS_PUBLIC; extern http_t *httpConnect(const char *host, int port, http_addrlist_t *addrlist, int family, http_encryption_t encryption, bool blocking, int msec, int *cancel) _CUPS_PUBLIC; -extern int httpCopyCredentials(http_t *http, cups_array_t **credentials) _CUPS_PUBLIC; +extern bool httpCopyCredentials(http_t *http, cups_array_t **credentials) _CUPS_PUBLIC; extern cups_array_t *httpCreateCredentials(const void *data, size_t datalen) _CUPS_PUBLIC; -extern int httpCredentialsAreValidForName(cups_array_t *credentials, const char *common_name); +extern bool httpCredentialsAreValidForName(cups_array_t *credentials, const char *common_name); extern time_t httpCredentialsGetExpiration(cups_array_t *credentials) _CUPS_PUBLIC; extern http_trust_t httpCredentialsGetTrust(cups_array_t *credentials, const char *common_name) _CUPS_PUBLIC; extern size_t httpCredentialsString(cups_array_t *credentials, char *buffer, size_t bufsize) _CUPS_PUBLIC; diff --git a/cups/testcreds.c b/cups/testcreds.c index deacbe03a..2ea765f5a 100644 --- a/cups/testcreds.c +++ b/cups/testcreds.c @@ -82,7 +82,7 @@ main(int argc, /* I - Number of command-line arguments */ else printf(" Trust: %s (%s)\n", trusts[trust], cupsLastErrorString()); printf(" Expiration: %s\n", httpGetDateString(httpCredentialsGetExpiration(hcreds), datestr, sizeof(datestr))); - printf(" IsValidName: %d\n", httpCredentialsAreValidForName(hcreds, hostname)); + printf(" IsValidName: %s\n", httpCredentialsAreValidForName(hcreds, hostname) ? "true" : "false"); printf(" String: \"%s\"\n", hinfo); httpFreeCredentials(hcreds); @@ -109,7 +109,7 @@ main(int argc, /* I - Number of command-line arguments */ printf(" Certificate Count: %u\n", (unsigned)cupsArrayGetCount(tcreds)); printf(" Expiration: %s\n", httpGetDateString(httpCredentialsGetExpiration(tcreds), datestr, sizeof(datestr))); - printf(" IsValidName: %d\n", httpCredentialsAreValidForName(tcreds, hostname)); + printf(" IsValidName: %s\n", httpCredentialsAreValidForName(tcreds, hostname) ? "true" : "false"); printf(" String: \"%s\"\n", tinfo); httpFreeCredentials(tcreds); diff --git a/cups/testhttp.c b/cups/testhttp.c index 60afda9ba..161062683 100644 --- a/cups/testhttp.c +++ b/cups/testhttp.c @@ -675,7 +675,7 @@ main(int argc, /* I - Number of command-line arguments */ printf("Count: %u\n", (unsigned)cupsArrayGetCount(creds)); printf("Trust: %s\n", trusts[trust]); printf("Expiration: %s\n", httpGetDateString(httpCredentialsGetExpiration(creds), expstr, sizeof(expstr))); - printf("IsValidName: %d\n", httpCredentialsAreValidForName(creds, hostname)); + printf("IsValidName: %s\n", httpCredentialsAreValidForName(creds, hostname) ? "true" : "false"); printf("String: \"%s\"\n", info); printf("LoadCredentials: %s\n", httpLoadCredentials(NULL, &lcreds, hostname) ? "true" : "false"); diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index 4fc17ff48..ecf743d47 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -1,28 +1,24 @@ -/* - * TLS support code for CUPS using OpenSSL/LibreSSL. - * - * Copyright © 2020-2022 by OpenPrinting - * Copyright © 2007-2019 by Apple Inc. - * Copyright © 1997-2007 by Easy Software Products, all rights reserved. - * - * Licensed under Apache License v2.0. See the file "LICENSE" for more - * information. - */ - -/**** This file is included from tls.c ****/ - -/* - * Include necessary headers... - */ +// +// TLS support code for CUPS using OpenSSL/LibreSSL. +// +// Note: This file is included from tls.c +// +// Copyright © 2020-2023 by OpenPrinting +// Copyright © 2007-2019 by Apple Inc. +// Copyright © 1997-2007 by Easy Software Products, all rights reserved. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// #include #include #define USE_EC 0 // Set to 1 to generate EC certs -/* - * Local functions... - */ +// +// Local functions... +// static long http_bio_ctrl(BIO *h, int cmd, long arg1, void *arg2); static int http_bio_free(BIO *data); @@ -37,14 +33,14 @@ static time_t http_get_date(X509 *cert, int which); //static void http_load_crl(void); static const char *http_make_path(char *buffer, size_t bufsize, const char *dirname, const char *filename, const char *ext); static bool http_x509_add_ext(X509 *cert, int nid, const char *value); -static void http_x509_add_san(X509 *cert, const char *name); +static void http_x509_add_san(GENERAL_NAMES *gens, const char *name); -/* - * Local globals... - */ +// +// Local globals... +// -static int tls_auto_create = 0; +static bool tls_auto_create = false; /* Auto-create self-signed certs? */ static BIO_METHOD *tls_bio_method = NULL; /* OpenSSL BIO method */ @@ -73,12 +69,13 @@ cupsMakeServerCredentials( const char *state_province, // I - State/province or `NULL` for "Unknown" const char *country, // I - Country or `NULL` for locale-based default const char *root_name, // I - Root certificate/domain name or `NULL` for site/self-signed + bool ca_cert, // I - Make CA certificate? const char *common_name, // I - Common name - int num_alt_names, // I - Number of subject alternate names + size_t num_alt_names, // I - Number of subject alternate names const char **alt_names, // I - Subject Alternate Names time_t expiration_date) // I - Expiration date { - int result = 0; // Return value + bool result = false; // Return value EVP_PKEY *pkey; // Private key #if defined(EVP_PKEY_EC) && USE_EC EC_KEY *ec; // EC key @@ -86,6 +83,10 @@ cupsMakeServerCredentials( RSA *rsa; // RSA key pair #endif // EVP_PKEY_EC && USE_EC X509 *cert; // Certificate + X509 *root_cert = NULL; // Root certificate, if any + EVP_PKEY *root_key = NULL; // Root private key, if any + char root_crtfile[1024], // Path to root certificate + root_keyfile[1024]; // Path to root private key cups_lang_t *language; // Default language info const char *langname; // Language name time_t curtime; // Current time @@ -97,9 +98,10 @@ cupsMakeServerCredentials( char temp[1024], // Temporary directory name crtfile[1024], // Certificate filename keyfile[1024]; // Private key filename + GENERAL_NAMES *gens; // Names for SubjectAltName certificate extension - DEBUG_printf(("cupsMakeServerCredentials(path=\"%s\", common_name=\"%s\", num_alt_names=%d, alt_names=%p, expiration_date=%d)", path, common_name, num_alt_names, alt_names, (int)expiration_date)); + DEBUG_printf(("cupsMakeServerCredentials(path=\"%s\", organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", common_name=\"%s\", num_alt_names=%u, alt_names=%p, expiration_date=%ld)", path, organization, org_unit, locality, state_province, country, common_name, (unsigned)num_alt_names, alt_names, (long)expiration_date)); // Filenames... if (!path) @@ -108,7 +110,7 @@ cupsMakeServerCredentials( if (!path || !common_name) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); - return (0); + return (false); } http_make_path(crtfile, sizeof(crtfile), path, common_name, "crt"); @@ -121,13 +123,13 @@ cupsMakeServerCredentials( if ((ec = EC_KEY_new_by_curve_name(NID_secp384r1)) == NULL) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create key pair."), 1); - return (0); + return (false); } #else if ((rsa = RSA_generate_key(3072, RSA_F4, NULL, NULL)) == NULL) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create key pair."), 1); - return (0); + return (false); } #endif // EVP_PKEY_EC && USE_EC @@ -140,7 +142,7 @@ cupsMakeServerCredentials( #endif // EVP_PKEY_EC && USE_EC _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create private key."), 1); - return (0); + return (false); } #if defined(EVP_PKEY_EC) && USE_EC @@ -158,7 +160,7 @@ cupsMakeServerCredentials( { EVP_PKEY_free(pkey); _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create X.509 certificate."), 1); - return (0); + return (false); } curtime = time(NULL); @@ -180,6 +182,33 @@ cupsMakeServerCredentials( X509_set_pubkey(cert, pkey); + // Try loading a root certificate... + http_make_path(root_crtfile, sizeof(root_crtfile), path, root_name ? root_name : "_site_", "crt"); + http_make_path(root_keyfile, sizeof(root_keyfile), path, root_name ? root_name : "_site_", "key"); + + if (!ca_cert && !access(root_crtfile, 0) && !access(root_keyfile, 0)) + { + if ((bio = BIO_new_file(root_crtfile, "rb")) != NULL) + { + PEM_read_bio_X509(bio, &root_cert, /*cb*/NULL, /*u*/NULL); + BIO_free(bio); + + if ((bio = BIO_new_file(root_keyfile, "rb")) != NULL) + { + PEM_read_bio_PrivateKey(bio, &root_key, /*cb*/NULL, /*u*/NULL); + BIO_free(bio); + } + + if (!root_key) + { + // Only use root certificate if we have the key... + X509_free(root_cert); + root_cert = NULL; + } + } + + } + language = cupsLangDefault(); langname = cupsLangGetName(language); name = X509_NAME_new(); @@ -196,47 +225,64 @@ cupsMakeServerCredentials( X509_NAME_add_entry_by_txt(name, SN_localityName, MBSTRING_ASC, (unsigned char *)(locality ? locality : "Unknown"), -1, -1, 0); // TODO set issuer name to CA subject name (X509_get_subject_name) - X509_set_issuer_name(cert, name); + if (root_cert) + X509_set_issuer_name(cert, X509_get_subject_name(root_cert)); + else + X509_set_issuer_name(cert, name); X509_set_subject_name(cert, name); X509_NAME_free(name); - http_x509_add_san(cert, common_name); - if (!strstr(common_name, ".local")) + gens = sk_GENERAL_NAME_new_null(); + http_x509_add_san(gens, common_name); + if (strstr(common_name, ".local") == NULL && !ca_cert) { // Add common_name.local to the list, too... char localname[256], // hostname.local *localptr; // Pointer into localname - cupsCopyString(localname, common_name, sizeof(localname)); + strlcpy(localname, common_name, sizeof(localname)); if ((localptr = strchr(localname, '.')) != NULL) *localptr = '\0'; - cupsConcatString(localname, ".local", sizeof(localname)); + strlcat(localname, ".local", sizeof(localname)); - http_x509_add_san(cert, localname); + http_x509_add_san(gens, localname); } if (num_alt_names > 0) { - int i; // Looping var... + size_t i; // Looping var... for (i = 0; i < num_alt_names; i ++) { if (strcmp(alt_names[i], "localhost")) - http_x509_add_san(cert, alt_names[i]); + http_x509_add_san(gens, alt_names[i]); } } + // Add extension with DNS names and free buffer for GENERAL_NAME + X509_add1_ext_i2d(cert, NID_subject_alt_name, gens, 0, X509V3_ADD_DEFAULT); + sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); + // Add extensions that are required to make Chrome happy... - // TODO: Support creating CA certs - http_x509_add_ext(cert, NID_basic_constraints, "critical,CA:FALSE,pathlen:0"); - http_x509_add_ext(cert, NID_key_usage, "critical,digitalSignature,keyEncipherment"); + if (ca_cert) + { + http_x509_add_ext(cert, NID_basic_constraints, "critical,CA:TRUE,pathlen:0"); + http_x509_add_ext(cert, NID_key_usage, "critical,cRLSign,digitalSignature,keyCertSign"); + } + else + { + http_x509_add_ext(cert, NID_basic_constraints, "critical,CA:FALSE,pathlen:0"); + http_x509_add_ext(cert, NID_key_usage, "critical,digitalSignature,keyEncipherment"); + } http_x509_add_ext(cert, NID_ext_key_usage, "1.3.6.1.5.5.7.3.1"); http_x509_add_ext(cert, NID_subject_key_identifier, "hash"); http_x509_add_ext(cert, NID_authority_key_identifier, "keyid,issuer"); X509_set_version(cert, 2); // v3 - // TODO: Sign with CA key when available - X509_sign(cert, pkey, EVP_sha256()); + if (root_key) + X509_sign(cert, root_key, EVP_sha256()); + else + X509_sign(cert, pkey, EVP_sha256()); // Save them... if ((bio = BIO_new_file(keyfile, "wb")) == NULL) @@ -267,9 +313,12 @@ cupsMakeServerCredentials( goto done; } + if (root_cert) + PEM_write_bio_X509(bio, root_cert); + BIO_free(bio); - result = 1; + result = true; DEBUG_puts("1cupsMakeServerCredentials: Successfully created credentials."); // Cleanup... @@ -278,22 +327,27 @@ cupsMakeServerCredentials( X509_free(cert); EVP_PKEY_free(pkey); + if (root_cert) + X509_free(root_cert); + if (root_key) + EVP_PKEY_free(root_key); + return (result); } -/* - * 'cupsSetServerCredentials()' - Set the default server credentials. - * - * Note: The server credentials are used by all threads in the running process. - * This function is threadsafe. - */ +// +// 'cupsSetServerCredentials()' - Set the default server credentials. +// +// Note: The server credentials are used by all threads in the running process. +// This function is threadsafe. +// -int // O - 1 on success, 0 on failure +bool // O - `true` on success, `false` on failure cupsSetServerCredentials( const char *path, // I - Path to keychain/directory const char *common_name, // I - Default common name for server - int auto_create) // I - 1 = automatically create self-signed certificates + bool auto_create) // I - `true` = automatically create self-signed certificates { char temp[1024]; // Default path buffer @@ -343,12 +397,12 @@ cupsSetServerCredentials( } -/* - * 'httpCopyCredentials()' - Copy the credentials associated with the peer in - * an encrypted connection. - */ +// +// 'httpCopyCredentials()' - Copy the credentials associated with the peer in +// an encrypted connection. +// -int // O - Status of call (0 = success) +bool // O - Status of call (`true` = success) httpCopyCredentials( http_t *http, // I - Connection to server cups_array_t **credentials) // O - Array of credentials @@ -362,7 +416,7 @@ httpCopyCredentials( *credentials = NULL; if (!http || !http->tls || !credentials) - return (-1); + return (false); *credentials = cupsArrayNew(NULL, NULL, NULL, 0, NULL, NULL); chain = SSL_get_peer_cert_chain(http->tls); @@ -397,13 +451,13 @@ httpCopyCredentials( } } - return (0); + return (true); } -/* - * '_httpCreateCredentials()' - Create credentials in the internal format. - */ +// +// '_httpCreateCredentials()' - Create credentials in the internal format. +// http_tls_credentials_t // O - Internal credentials _httpCreateCredentials( @@ -415,9 +469,9 @@ _httpCreateCredentials( } -/* - * '_httpFreeCredentials()' - Free internal credentials. - */ +// +// '_httpFreeCredentials()' - Free internal credentials. +// void _httpFreeCredentials( @@ -427,23 +481,24 @@ _httpFreeCredentials( } -/* - * 'httpCredentialsAreValidForName()' - Return whether the credentials are valid for the given name. - */ +// +// 'httpCredentialsAreValidForName()' - Return whether the credentials are valid +// for the given name. +// -int // O - 1 if valid, 0 otherwise +bool // O - `true` if valid, `false` otherwise httpCredentialsAreValidForName( cups_array_t *credentials, // I - Credentials const char *common_name) // I - Name to check { X509 *cert; // Certificate - int result = 0; // Result + bool result = false; // Result cert = http_create_credential((http_credential_t *)cupsArrayGetFirst(credentials)); if (cert) { - result = X509_check_host(cert, common_name, strlen(common_name), 0, NULL); + result = X509_check_host(cert, common_name, strlen(common_name), 0, NULL) != 0; X509_free(cert); } @@ -452,9 +507,9 @@ httpCredentialsAreValidForName( } -/* - * 'httpCredentialsGetTrust()' - Return the trust of credentials. - */ +// +// 'httpCredentialsGetTrust()' - Return the trust of credentials. +// http_trust_t // O - Level of trust httpCredentialsGetTrust( @@ -596,9 +651,9 @@ httpCredentialsGetTrust( } -/* - * 'httpCredentialsGetExpiration()' - Return the expiration date of the credentials. - */ +// +// 'httpCredentialsGetExpiration()' - Return the expiration date of the credentials. +// time_t // O - Expiration date of credentials httpCredentialsGetExpiration( @@ -618,9 +673,9 @@ httpCredentialsGetExpiration( } -/* - * 'httpCredentialsString()' - Return a string representing the credentials. - */ +// +// 'httpCredentialsString()' - Return a string representing the credentials. +// size_t // O - Total size of credentials string httpCredentialsString( @@ -706,9 +761,9 @@ httpCredentialsString( } -/* - * 'httpLoadCredentials()' - Load X.509 credentials from a keychain file. - */ +// +// 'httpLoadCredentials()' - Load X.509 credentials from a keychain file. +// bool // O - `true` on success, `false` on error httpLoadCredentials( @@ -724,7 +779,7 @@ httpLoadCredentials( size_t alloc_data = 0, // Bytes allocated num_data = 0, // Bytes used decoded; // Bytes decoded - int in_certificate = 0; + bool in_certificate = false; // In a certificate? @@ -750,25 +805,19 @@ httpLoadCredentials( { if (in_certificate) { - /* - * Missing END CERTIFICATE... - */ - + // Missing END CERTIFICATE... httpFreeCredentials(*credentials); *credentials = NULL; break; } - in_certificate = 1; + in_certificate = true; } else if (!strcmp(line, "-----END CERTIFICATE-----")) { if (!in_certificate || !num_data) { - /* - * Missing data... - */ - + // Missing data... httpFreeCredentials(*credentials); *credentials = NULL; break; @@ -785,7 +834,7 @@ httpLoadCredentials( } num_data = 0; - in_certificate = 0; + in_certificate = false; } else if (in_certificate) { @@ -823,10 +872,7 @@ httpLoadCredentials( if (in_certificate) { - /* - * Missing END CERTIFICATE... - */ - + // Missing END CERTIFICATE... httpFreeCredentials(*credentials); *credentials = NULL; } @@ -838,9 +884,9 @@ httpLoadCredentials( } -/* - * 'httpSaveCredentials()' - Save X.509 credentials to a keychain file. - */ +// +// 'httpSaveCredentials()' - Save X.509 credentials to a keychain file. +// bool // O - `true` on success, `false` on error httpSaveCredentials( @@ -893,9 +939,9 @@ httpSaveCredentials( } -/* - * '_httpTLSInitialize()' - Initialize the TLS stack. - */ +// +// '_httpTLSInitialize()' - Initialize the TLS stack. +// void _httpTLSInitialize(void) @@ -904,9 +950,9 @@ _httpTLSInitialize(void) } -/* - * '_httpTLSPending()' - Return the number of pending TLS-encrypted bytes. - */ +// +// '_httpTLSPending()' - Return the number of pending TLS-encrypted bytes. +// size_t // O - Bytes available _httpTLSPending(http_t *http) // I - HTTP connection @@ -915,9 +961,9 @@ _httpTLSPending(http_t *http) // I - HTTP connection } -/* - * '_httpTLSRead()' - Read from a SSL/TLS connection. - */ +// +// '_httpTLSRead()' - Read from a SSL/TLS connection. +// int // O - Bytes read _httpTLSRead(http_t *http, // I - Connection to server @@ -933,9 +979,9 @@ _httpTLSRead(http_t *http, // I - Connection to server } -/* - * '_httpTLSSetOptions()' - Set TLS protocol and cipher suite options. - */ +// +// '_httpTLSSetOptions()' - Set TLS protocol and cipher suite options. +// void _httpTLSSetOptions(int options, // I - Options @@ -951,11 +997,11 @@ _httpTLSSetOptions(int options, // I - Options } -/* - * '_httpTLSStart()' - Set up SSL/TLS support on a connection. - */ +// +// '_httpTLSStart()' - Set up SSL/TLS support on a connection. +// -bool /* O - `true` on success, `false` on failure */ +bool // O - `true` on success, `false` on failure _httpTLSStart(http_t *http) // I - Connection to server { BIO *bio; // Basic input/output context @@ -1096,7 +1142,7 @@ _httpTLSStart(http_t *http) // I - Connection to server { DEBUG_printf(("4_httpTLSStart: Auto-create credentials for \"%s\".", cn)); - if (!cupsMakeServerCredentials(tls_keypath, cn, 0, NULL, time(NULL) + 3650 * 86400)) + if (!cupsMakeServerCredentials(tls_keypath, NULL, NULL, NULL, NULL, NULL, NULL, false, cn, 0, NULL, time(NULL) + 3650 * 86400)) { DEBUG_puts("4_httpTLSStart: cupsMakeServerCredentials failed."); http->error = errno = EINVAL; @@ -1219,9 +1265,9 @@ _httpTLSStart(http_t *http) // I - Connection to server } -/* - * '_httpTLSStop()' - Shut down SSL/TLS on a connection. - */ +// +// '_httpTLSStop()' - Shut down SSL/TLS on a connection. +// void _httpTLSStop(http_t *http) // I - Connection to server @@ -1239,9 +1285,9 @@ _httpTLSStop(http_t *http) // I - Connection to server } -/* - * '_httpTLSWrite()' - Write to a SSL/TLS connection. - */ +// +// '_httpTLSWrite()' - Write to a SSL/TLS connection. +// int // O - Bytes written _httpTLSWrite(http_t *http, // I - Connection to server @@ -1252,9 +1298,9 @@ _httpTLSWrite(http_t *http, // I - Connection to server } -/* - * 'http_bio_ctrl()' - Control the HTTP connection. - */ +// +// 'http_bio_ctrl()' - Control the HTTP connection. +// static long // O - Result/data http_bio_ctrl(BIO *h, // I - BIO data @@ -1296,9 +1342,9 @@ http_bio_ctrl(BIO *h, // I - BIO data } -/* - * 'http_bio_free()' - Free OpenSSL data. - */ +// +// 'http_bio_free()' - Free OpenSSL data. +// static int // O - 1 on success, 0 on failure http_bio_free(BIO *h) // I - BIO data @@ -1315,9 +1361,9 @@ http_bio_free(BIO *h) // I - BIO data } -/* - * 'http_bio_new()' - Initialize an OpenSSL BIO structure. - */ +// +// 'http_bio_new()' - Initialize an OpenSSL BIO structure. +// static int // O - 1 on success, 0 on failure http_bio_new(BIO *h) // I - BIO data @@ -1334,9 +1380,9 @@ http_bio_new(BIO *h) // I - BIO data } -/* - * 'http_bio_puts()' - Send a string for OpenSSL. - */ +// +// 'http_bio_puts()' - Send a string for OpenSSL. +// static int // O - Bytes written http_bio_puts(BIO *h, // I - BIO data @@ -1352,9 +1398,9 @@ http_bio_puts(BIO *h, // I - BIO data } -/* - * 'http_bio_read()' - Read data for OpenSSL. - */ +// +// 'http_bio_read()' - Read data for OpenSSL. +// static int // O - Bytes read http_bio_read(BIO *h, // I - BIO data @@ -1393,9 +1439,9 @@ http_bio_read(BIO *h, // I - BIO data } -/* - * 'http_bio_write()' - Write data for OpenSSL. - */ +// +// 'http_bio_write()' - Write data for OpenSSL. +// static int // O - Bytes written http_bio_write(BIO *h, // I - BIO data @@ -1414,9 +1460,9 @@ http_bio_write(BIO *h, // I - BIO data } -/* - * 'http_create_credential()' - Create a single credential in the internal format. - */ +// +// 'http_create_credential()' - Create a single credential in the internal format. +// static X509 * // O - Certificate http_create_credential( @@ -1440,9 +1486,9 @@ http_create_credential( } -/* - * 'http_default_path()' - Get the default credential store path. - */ +// +// 'http_default_path()' - Get the default credential store path. +// static const char * // O - Path or NULL on error http_default_path( @@ -1538,9 +1584,9 @@ http_get_date(X509 *cert, // I - Certificate #if 0 -/* - * 'http_load_crl()' - Load the certificate revocation list, if any. - */ +// +// 'http_load_crl()' - Load the certificate revocation list, if any. +// static void http_load_crl(void) @@ -1634,9 +1680,9 @@ http_load_crl(void) #endif // 0 -/* - * 'http_make_path()' - Format a filename for a certificate or key file. - */ +// +// 'http_make_path()' - Format a filename for a certificate or key file. +// static const char * // O - Filename http_make_path( @@ -1713,17 +1759,22 @@ http_x509_add_ext(X509 *cert, // I - Certificate // -// 'http_x509_add_san()' - Add a subjectAltName extension to an X.509 certificate. +// 'http_x509_add_san()' - Add a subjectAltName to GENERAL_NAMES used for +// the extension to an X.509 certificate. // static void -http_x509_add_san(X509 *cert, // I - Certificate - const char *name) // I - Hostname +http_x509_add_san(GENERAL_NAMES *gens, // I - Concatenation of DNS names + const char *name) // I - Hostname { - char dns_name[1024]; // DNS: prefixed hostname + GENERAL_NAME *gen_dns = GENERAL_NAME_new(); + // DNS: name + ASN1_IA5STRING *ia5 = ASN1_IA5STRING_new(); + // Hostname string - // The subjectAltName value for DNS names starts with a DNS: prefix... - snprintf(dns_name, sizeof(dns_name), "DNS:%s", name); - http_x509_add_ext(cert, NID_subject_alt_name, dns_name); + // Set the strings and push it on the GENERAL_NAMES list... + ASN1_STRING_set(ia5, name, strlen(name)); + GENERAL_NAME_set0_value(gen_dns, GEN_DNS, ia5); + sk_GENERAL_NAME_push(gens, gen_dns); } From 6b6c8205b916c6f67fa474aba98f89145cd803f0 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Fri, 14 Apr 2023 13:09:44 -0400 Subject: [PATCH 03/31] Add credential type argument to cupsMakeServerCredentials. --- cups/cups.h | 97 +++++++++++++++++---------------- cups/tls-openssl.c | 130 ++++++++++++++++++++++++++++----------------- 2 files changed, 133 insertions(+), 94 deletions(-) diff --git a/cups/cups.h b/cups/cups.h index 1383713f8..38e5a22e9 100644 --- a/cups/cups.h +++ b/cups/cups.h @@ -42,44 +42,6 @@ extern "C" { # define CUPS_LENGTH_VARIABLE (ssize_t)0 # define CUPS_TIMEOUT_DEFAULT 0 -typedef enum cups_whichjobs_e // Which jobs for @link cupsGetJobs@ -{ - CUPS_WHICHJOBS_ALL = -1, // All jobs - CUPS_WHICHJOBS_ACTIVE, // Pending/held/processing jobs - CUPS_WHICHJOBS_COMPLETED // Completed/canceled/aborted jobs -} cups_whichjobs_t; - -// Flags for cupsConnectDest and cupsEnumDests -# define CUPS_DEST_FLAGS_NONE 0x00 - // No flags are set -# define CUPS_DEST_FLAGS_UNCONNECTED 0x01 - // There is no connection -# define CUPS_DEST_FLAGS_MORE 0x02 - // There are more destinations -# define CUPS_DEST_FLAGS_REMOVED 0x04 - // The destination has gone away -# define CUPS_DEST_FLAGS_ERROR 0x08 - // An error occurred -# define CUPS_DEST_FLAGS_RESOLVING 0x10 - // The destination address is being resolved -# define CUPS_DEST_FLAGS_CONNECTING 0x20 - // A connection is being established -# define CUPS_DEST_FLAGS_CANCELED 0x40 - // Operation was canceled -# define CUPS_DEST_FLAGS_DEVICE 0x80 - // For @link cupsConnectDest@: Connect to device - -// Flags for cupsGetDestMediaByName/Size -# define CUPS_MEDIA_FLAGS_DEFAULT 0x00 - // Find the closest size supported by the printer -# define CUPS_MEDIA_FLAGS_BORDERLESS 0x01 - // Find a borderless size -# define CUPS_MEDIA_FLAGS_DUPLEX 0x02 - // Find a size compatible with 2-sided printing -# define CUPS_MEDIA_FLAGS_EXACT 0x04 - // Find an exact match for the size -# define CUPS_MEDIA_FLAGS_READY 0x08 - // If the printer supports media sensing, find the size amongst the "ready" media. // Options and values # define CUPS_COPIES "copies" @@ -148,8 +110,9 @@ typedef enum cups_whichjobs_e // Which jobs for @link cupsGetJobs@ # define CUPS_PRINT_COLOR_MODE_SUPPORTED "print-color-mode-supported" # define CUPS_PRINT_COLOR_MODE_AUTO "auto" -# define CUPS_PRINT_COLOR_MODE_MONOCHROME "monochrome" +# define CUPS_PRINT_COLOR_MODE_BI_LEVEL "bi-level" # define CUPS_PRINT_COLOR_MODE_COLOR "color" +# define CUPS_PRINT_COLOR_MODE_MONOCHROME "monochrome" # define CUPS_PRINT_QUALITY "print-quality" # define CUPS_PRINT_QUALITY_SUPPORTED "print-quality-supported" @@ -170,9 +133,43 @@ typedef enum cups_whichjobs_e // Which jobs for @link cupsGetJobs@ // Types and structures... // -typedef unsigned cups_ptype_t; // Printer type/capability bits -enum cups_ptype_e // Printer type/capability bit constants -{ // Not a typedef'd enum so we can OR +typedef enum cups_credtype_e // X.509 credential types +{ + CUPS_CREDTYPE_DEFAULT, // Default type + CUPS_CREDTYPE_RSA_2048_SHA256, // RSA with 2048-bit keys and SHA-256 hash + CUPS_CREDTYPE_RSA_3072_SHA256, // RSA with 3072-bit keys and SHA-256 hash + CUPS_CREDTYPE_RSA_4096_SHA256, // RSA with 4096-bit keys and SHA-256 hash + CUPS_CREDTYPE_ECDSA_P256_SHA256, // ECDSA using the P-256 curve with SHA-256 hash + CUPS_CREDTYPE_ECDSA_P384_SHA256, // ECDSA using the P-384 curve with SHA-256 hash + CUPS_CREDTYPE_ECDSA_P521_SHA256 // ECDSA using the P-521 curve with SHA-256 hash +} cups_credtype_t; + +enum cups_dest_flags_e // Flags for @link cupsConnectDest@ and @link cupsEnumDests@ +{ + CUPS_DEST_FLAGS_NONE = 0x00, // No flags are set + CUPS_DEST_FLAGS_UNCONNECTED = 0x01, // There is no connection + CUPS_DEST_FLAGS_MORE = 0x02, // There are more destinations + CUPS_DEST_FLAGS_REMOVED = 0x04, // The destination has gone away + CUPS_DEST_FLAGS_ERROR = 0x08, // An error occurred + CUPS_DEST_FLAGS_RESOLVING = 0x10, // The destination address is being resolved + CUPS_DEST_FLAGS_CONNECTING = 0x20, // A connection is being established + CUPS_DEST_FLAGS_CANCELED = 0x40, // Operation was canceled + CUPS_DEST_FLAGS_DEVICE = 0x80 // For @link cupsConnectDest@: Connect to device +}; +typedef unsigned cups_dest_flags_t; // Combined flags for @link cupsConnectDest@ and @link cupsEnumDests@ + +enum cups_media_flags_e // Flags for @link cupsGetDestMediaByName@ and @link cupsGetDestMediaBySize@ +{ + CUPS_MEDIA_FLAGS_DEFAULT = 0x00, // Find the closest size supported by the printer + CUPS_MEDIA_FLAGS_BORDERLESS = 0x01, // Find a borderless size + CUPS_MEDIA_FLAGS_DUPLEX = 0x02, // Find a size compatible with 2-sided printing + CUPS_MEDIA_FLAGS_EXACT = 0x04, // Find an exact match for the size + CUPS_MEDIA_FLAGS_READY = 0x08 // If the printer supports media sensing, find the size amongst the "ready" media. +}; +typedef unsigned cups_media_flags_t; // Combined flags for @link cupsGetDestMediaByName@ and @link cupsGetDestMediaBySize@ + +enum cups_ptype_e // Printer type/capability flags +{ CUPS_PRINTER_LOCAL = 0x0000, // Local printer or class CUPS_PRINTER_CLASS = 0x0001, // Printer class CUPS_PRINTER_REMOTE = 0x0002, // Remote printer or class @@ -201,6 +198,14 @@ enum cups_ptype_e // Printer type/capability bit constants CUPS_PRINTER_MFP = 0x4000000, // Printer with scanning capabilities CUPS_PRINTER_OPTIONS = 0x6fffc // ~(CLASS | REMOTE | IMPLICIT | DEFAULT | FAX | REJECTING | DELETE | NOT_SHARED | AUTHENTICATED | COMMANDS | DISCOVERED) @private@ }; +typedef unsigned cups_ptype_t; // Combined printer type/capability flags + +typedef enum cups_whichjobs_e // Which jobs for @link cupsGetJobs@ +{ + CUPS_WHICHJOBS_ALL = -1, // All jobs + CUPS_WHICHJOBS_ACTIVE, // Pending/held/processing jobs + CUPS_WHICHJOBS_COMPLETED // Completed/canceled/aborted jobs +} cups_whichjobs_t; typedef struct cups_option_s //// Printer Options { @@ -235,7 +240,7 @@ typedef struct cups_job_s // Job time_t processing_time; // Time the job was processed } cups_job_t; -typedef struct cups_size_s //// Media Size +typedef struct cups_size_s //// Media information { char media[128], // Media name to use color[128], // Media color (blank for any/auto) @@ -252,7 +257,7 @@ typedef struct cups_size_s //// Media Size typedef bool (*cups_client_cert_cb_t)(http_t *http, void *tls, cups_array_t *distinguished_names, void *user_data); // Client credentials callback -typedef bool (*cups_dest_cb_t)(void *user_data, unsigned flags, cups_dest_t *dest); +typedef bool (*cups_dest_cb_t)(void *user_data, cups_dest_flags_t flags, cups_dest_t *dest); // Destination enumeration callback typedef const char *(*cups_oauth_cb_t)(http_t *http, const char *realm, const char *scope, const char *resource, void *user_data); @@ -336,8 +341,8 @@ extern const char *cupsLocalizeDestMedia(http_t *http, cups_dest_t *dest, cups_d extern const char *cupsLocalizeDestOption(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, const char *option) _CUPS_PUBLIC; extern const char *cupsLocalizeDestValue(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, const char *option, const char *value) _CUPS_PUBLIC; -extern bool cupsMakeServerCredentials(const char *path, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *root_name, bool ca_cert, const char *common_name, size_t num_alt_names, const char **alt_names, time_t expiration_date) _CUPS_PUBLIC; -extern char *cupsMakeServerRequest(const char *path, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, size_t num_alt_names, const char **alt_names) _CUPS_PUBLIC; +extern bool cupsMakeServerCredentials(const char *path, cups_credtype_t type, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *root_name, bool ca_cert, const char *common_name, size_t num_alt_names, const char **alt_names, time_t expiration_date) _CUPS_PUBLIC; +extern char *cupsMakeServerRequest(const char *path, cups_credtype_t type, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, size_t num_alt_names, const char **alt_names) _CUPS_PUBLIC; extern char *cupsNotifySubject(cups_lang_t *lang, ipp_t *event) _CUPS_PUBLIC; extern char *cupsNotifyText(cups_lang_t *lang, ipp_t *event) _CUPS_PUBLIC; diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index ecf743d47..ef35fa247 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -13,7 +13,6 @@ #include #include -#define USE_EC 0 // Set to 1 to generate EC certs // @@ -41,47 +40,58 @@ static void http_x509_add_san(GENERAL_NAMES *gens, const char *name); // static bool tls_auto_create = false; - /* Auto-create self-signed certs? */ + // Auto-create self-signed certs? static BIO_METHOD *tls_bio_method = NULL; - /* OpenSSL BIO method */ + // OpenSSL BIO method static char *tls_common_name = NULL; - /* Default common name */ -//static X509_CRL *tls_crl = NULL;/* Certificate revocation list */ + // Default common name +//static X509_CRL *tls_crl = NULL;// Certificate revocation list static char *tls_keypath = NULL; - /* Server cert keychain path */ + // Server cert keychain path static cups_mutex_t tls_mutex = CUPS_MUTEX_INITIALIZER; - /* Mutex for keychain/certs */ -static int tls_options = -1,/* Options for TLS connections */ + // Mutex for keychain/certs +static int tls_options = -1,// Options for TLS connections tls_min_version = _HTTP_TLS_1_2, tls_max_version = _HTTP_TLS_MAX; -/* - * 'cupsMakeServerCredentials()' - Make a self-signed certificate and private key pair. - */ +// +// 'cupsMakeServerCredentials()' - Make an X.509 certificate and private key pair. +// +// This function creates an X.509 certificate and private key pair. The +// certificate and key are stored in the directory "path" or, if "path" is +// `NULL`, in a per-user or system-wide (when running as root) certificate/key +// store. The generated certificate is signed by the named root certificate or, +// if "root_name" is `NULL`, a site-wide default root certificate. When +// "root_name" is `NULL` and there is no site-wide default root certificate, a +// self-signed certificate is generated instead. +// +// The type of certificate depends on the version of the TLS library in use but +// will be either 3072-bit RSA or 384-bit ECDSA with SHA-256 signature. +// bool // O - `true` on success, `false` on failure cupsMakeServerCredentials( - const char *path, // I - Path to keychain/directory or `NULL` for default - const char *organization, // I - Organization or `NULL` to use common name - const char *org_unit, // I - Organizational unit or `NULL` for none - const char *locality, // I - City/town or `NULL` for "Unknown" - const char *state_province, // I - State/province or `NULL` for "Unknown" - const char *country, // I - Country or `NULL` for locale-based default - const char *root_name, // I - Root certificate/domain name or `NULL` for site/self-signed - bool ca_cert, // I - Make CA certificate? - const char *common_name, // I - Common name - size_t num_alt_names, // I - Number of subject alternate names - const char **alt_names, // I - Subject Alternate Names - time_t expiration_date) // I - Expiration date + const char *path, // I - Directory path or `NULL` for default + cups_credtype_t type, // I - Type of certificate/keys to generate + const char *organization, // I - Organization or `NULL` to use common name + const char *org_unit, // I - Organizational unit or `NULL` for none + const char *locality, // I - City/town or `NULL` for "Unknown" + const char *state_province, // I - State/province or `NULL` for "Unknown" + const char *country, // I - Country or `NULL` for locale-based default + const char *root_name, // I - Root certificate/domain name or `NULL` for site/self-signed + bool ca_cert, // I - Make CA certificate? + const char *common_name, // I - Common name + size_t num_alt_names, // I - Number of subject alternate names + const char **alt_names, // I - Subject Alternate Names + time_t expiration_date) // I - Expiration date { bool result = false; // Return value EVP_PKEY *pkey; // Private key -#if defined(EVP_PKEY_EC) && USE_EC - EC_KEY *ec; // EC key -#else - RSA *rsa; // RSA key pair -#endif // EVP_PKEY_EC && USE_EC +#if defined(EVP_PKEY_EC) + EC_KEY *ec = NULL; // EC key +#endif // EVP_PKEY_EC + RSA *rsa = NULL; // RSA key pair X509 *cert; // Certificate X509 *root_cert = NULL; // Root certificate, if any EVP_PKEY *root_key = NULL; // Root private key, if any @@ -119,37 +129,62 @@ cupsMakeServerCredentials( // Create the encryption key... DEBUG_puts("1cupsMakeServerCredentials: Creating key pair."); -#if defined(EVP_PKEY_EC) && USE_EC - if ((ec = EC_KEY_new_by_curve_name(NID_secp384r1)) == NULL) + switch (type) { - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create key pair."), 1); - return (false); +#if defined(EVP_PKEY_EC) + case CUPS_CREDTYPE_ECDSA_P256_SHA256 : + ec = EC_KEY_new_by_curve_name(NID_secp256k1); + break; + + case CUPS_CREDTYPE_ECDSA_P384_SHA256 : + ec = EC_KEY_new_by_curve_name(NID_secp384r1); + break; + + case CUPS_CREDTYPE_ECDSA_P521_SHA256 : + ec = EC_KEY_new_by_curve_name(NID_secp521r1); + break; +#endif // EVP_PKEY_EC && USE_EC + + case CUPS_CREDTYPE_RSA_2048_SHA256 : + rsa = RSA_generate_key(2048, RSA_F4, NULL, NULL); + break; + + default : + case CUPS_CREDTYPE_RSA_3072_SHA256 : + rsa = RSA_generate_key(3072, RSA_F4, NULL, NULL); + break; + + case CUPS_CREDTYPE_RSA_4096_SHA256 : + rsa = RSA_generate_key(4096, RSA_F4, NULL, NULL); + break; } -#else - if ((rsa = RSA_generate_key(3072, RSA_F4, NULL, NULL)) == NULL) + + if (!rsa && !ec) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create key pair."), 1); return (false); } -#endif // EVP_PKEY_EC && USE_EC if ((pkey = EVP_PKEY_new()) == NULL) { -#if defined(EVP_PKEY_EC) && USE_EC - EC_KEY_free(ec); -#else - RSA_free(rsa); +#if defined(EVP_PKEY_EC) + if (ec) + EC_KEY_free(ec); #endif // EVP_PKEY_EC && USE_EC + if (rsa) + RSA_free(rsa); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create private key."), 1); return (false); } -#if defined(EVP_PKEY_EC) && USE_EC - EVP_PKEY_assign_EC_KEY(pkey, ec); -#else +#if defined(EVP_PKEY_EC) + if (ec) + EVP_PKEY_assign_EC_KEY(pkey, ec); + else +#endif // EVP_PKEY_EC EVP_PKEY_assign_RSA(pkey, rsa); -#endif // EVP_PKEY_EC && USE_EC DEBUG_puts("1cupsMakeServerCredentials: Key pair created."); @@ -224,7 +259,6 @@ cupsMakeServerCredentials( X509_NAME_add_entry_by_txt(name, SN_stateOrProvinceName, MBSTRING_ASC, (unsigned char *)(state_province ? state_province : "Unknown"), -1, -1, 0); X509_NAME_add_entry_by_txt(name, SN_localityName, MBSTRING_ASC, (unsigned char *)(locality ? locality : "Unknown"), -1, -1, 0); - // TODO set issuer name to CA subject name (X509_get_subject_name) if (root_cert) X509_set_issuer_name(cert, X509_get_subject_name(root_cert)); else @@ -545,8 +579,8 @@ httpCredentialsGetTrust( if (tcreds) { - char credentials_str[1024], /* String for incoming credentials */ - tcreds_str[1024]; /* String for saved credentials */ + char credentials_str[1024], // String for incoming credentials + tcreds_str[1024]; // String for saved credentials httpCredentialsString(credentials, credentials_str, sizeof(credentials_str)); httpCredentialsString(tcreds, tcreds_str, sizeof(tcreds_str)); @@ -849,7 +883,7 @@ httpLoadCredentials( else if ((num_data + strlen(line)) >= alloc_data) { unsigned char *tdata = realloc(data, alloc_data + 1024); - /* Expanded buffer */ + // Expanded buffer if (!tdata) { @@ -1142,7 +1176,7 @@ _httpTLSStart(http_t *http) // I - Connection to server { DEBUG_printf(("4_httpTLSStart: Auto-create credentials for \"%s\".", cn)); - if (!cupsMakeServerCredentials(tls_keypath, NULL, NULL, NULL, NULL, NULL, NULL, false, cn, 0, NULL, time(NULL) + 3650 * 86400)) + if (!cupsMakeServerCredentials(tls_keypath, CUPS_CREDTYPE_DEFAULT, NULL, NULL, NULL, NULL, NULL, NULL, false, cn, 0, NULL, time(NULL) + 3650 * 86400)) { DEBUG_puts("4_httpTLSStart: cupsMakeServerCredentials failed."); http->error = errno = EINVAL; From d630e9e4bb18193aafc195060c02ffccec4b60fc Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Fri, 14 Apr 2023 16:23:48 -0400 Subject: [PATCH 04/31] Save work. --- cups/cups.h | 4 +- cups/tls-openssl.c | 503 +++++++++++++++++++++++++++++++++------------ 2 files changed, 368 insertions(+), 139 deletions(-) diff --git a/cups/cups.h b/cups/cups.h index 38e5a22e9..43e0dcf5a 100644 --- a/cups/cups.h +++ b/cups/cups.h @@ -133,7 +133,7 @@ extern "C" { // Types and structures... // -typedef enum cups_credtype_e // X.509 credential types +typedef enum cups_credtype_e // X.509 credential types for @link cupsMakeServerCredentials@ and @link cupsMakeServerRequest@ { CUPS_CREDTYPE_DEFAULT, // Default type CUPS_CREDTYPE_RSA_2048_SHA256, // RSA with 2048-bit keys and SHA-256 hash @@ -355,7 +355,7 @@ extern ssize_t cupsReadResponseData(http_t *http, char *buffer, size_t length) extern size_t cupsRemoveDest(const char *name, const char *instance, size_t num_dests, cups_dest_t **dests) _CUPS_PUBLIC; extern size_t cupsRemoveOption(const char *name, size_t num_options, cups_option_t **options) _CUPS_PUBLIC; -extern bool cupsSaveServerCredentials(const char *path, const char *pem) _CUPS_PUBLIC; +extern bool cupsSaveServerCredentials(const char *path, const char *common_name, const char *pem) _CUPS_PUBLIC; extern http_status_t cupsSendRequest(http_t *http, ipp_t *request, const char *resource, size_t length) _CUPS_PUBLIC; extern void cupsSetOAuthCB(cups_oauth_cb_t cb, void *data) _CUPS_PUBLIC; extern void cupsSetClientCertCB(cups_client_cert_cb_t cb, void *user_data) _CUPS_PUBLIC; diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index ef35fa247..c07fc3d83 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -19,6 +19,8 @@ // Local functions... // +static void http_add_san(GENERAL_NAMES *gens, const char *name); + static long http_bio_ctrl(BIO *h, int cmd, long arg1, void *arg2); static int http_bio_free(BIO *data); static int http_bio_new(BIO *h); @@ -27,12 +29,19 @@ static int http_bio_read(BIO *h, char *buf, int size); static int http_bio_write(BIO *h, const char *buf, int num); static X509 *http_create_credential(http_credential_t *credential); +static X509_NAME *http_create_name(const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name); +static EVP_PKEY *http_create_key(cups_credtype_t type); +static GENERAL_NAMES *http_create_san(const char *common_name, size_t num_alt_names, const char **alt_names); + static const char *http_default_path(char *buffer, size_t bufsize); + static time_t http_get_date(X509 *cert, int which); + //static void http_load_crl(void); + static const char *http_make_path(char *buffer, size_t bufsize, const char *dirname, const char *filename, const char *ext); + static bool http_x509_add_ext(X509 *cert, int nid, const char *value); -static void http_x509_add_san(GENERAL_NAMES *gens, const char *name); // @@ -66,13 +75,10 @@ static int tls_options = -1,// Options for TLS connections // "root_name" is `NULL` and there is no site-wide default root certificate, a // self-signed certificate is generated instead. // -// The type of certificate depends on the version of the TLS library in use but -// will be either 3072-bit RSA or 384-bit ECDSA with SHA-256 signature. -// bool // O - `true` on success, `false` on failure cupsMakeServerCredentials( - const char *path, // I - Directory path or `NULL` for default + const char *path, // I - Directory path for certificate/key store or `NULL` for default cups_credtype_t type, // I - Type of certificate/keys to generate const char *organization, // I - Organization or `NULL` to use common name const char *org_unit, // I - Organizational unit or `NULL` for none @@ -87,18 +93,12 @@ cupsMakeServerCredentials( time_t expiration_date) // I - Expiration date { bool result = false; // Return value - EVP_PKEY *pkey; // Private key -#if defined(EVP_PKEY_EC) - EC_KEY *ec = NULL; // EC key -#endif // EVP_PKEY_EC - RSA *rsa = NULL; // RSA key pair + EVP_PKEY *pkey; // Key pair X509 *cert; // Certificate X509 *root_cert = NULL; // Root certificate, if any EVP_PKEY *root_key = NULL; // Root private key, if any char root_crtfile[1024], // Path to root certificate root_keyfile[1024]; // Path to root private key - cups_lang_t *language; // Default language info - const char *langname; // Language name time_t curtime; // Current time X509_NAME *name; // Subject/issuer name ASN1_INTEGER *serial; // Serial number @@ -111,7 +111,7 @@ cupsMakeServerCredentials( GENERAL_NAMES *gens; // Names for SubjectAltName certificate extension - DEBUG_printf(("cupsMakeServerCredentials(path=\"%s\", organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", common_name=\"%s\", num_alt_names=%u, alt_names=%p, expiration_date=%ld)", path, organization, org_unit, locality, state_province, country, common_name, (unsigned)num_alt_names, alt_names, (long)expiration_date)); + DEBUG_printf(("cupsMakeServerCredentials(path=\"%s\", type=%d, organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", root_name=\"%s\", ca_cert=%s, common_name=\"%s\", num_alt_names=%u, alt_names=%p, expiration_date=%ld)", path, type, organization, org_unit, locality, state_province, country, root_name, ca_cert ? "true" : "false", common_name, (unsigned)num_alt_names, alt_names, (long)expiration_date)); // Filenames... if (!path) @@ -129,62 +129,8 @@ cupsMakeServerCredentials( // Create the encryption key... DEBUG_puts("1cupsMakeServerCredentials: Creating key pair."); - switch (type) - { -#if defined(EVP_PKEY_EC) - case CUPS_CREDTYPE_ECDSA_P256_SHA256 : - ec = EC_KEY_new_by_curve_name(NID_secp256k1); - break; - - case CUPS_CREDTYPE_ECDSA_P384_SHA256 : - ec = EC_KEY_new_by_curve_name(NID_secp384r1); - break; - - case CUPS_CREDTYPE_ECDSA_P521_SHA256 : - ec = EC_KEY_new_by_curve_name(NID_secp521r1); - break; -#endif // EVP_PKEY_EC && USE_EC - - case CUPS_CREDTYPE_RSA_2048_SHA256 : - rsa = RSA_generate_key(2048, RSA_F4, NULL, NULL); - break; - - default : - case CUPS_CREDTYPE_RSA_3072_SHA256 : - rsa = RSA_generate_key(3072, RSA_F4, NULL, NULL); - break; - - case CUPS_CREDTYPE_RSA_4096_SHA256 : - rsa = RSA_generate_key(4096, RSA_F4, NULL, NULL); - break; - } - - if (!rsa && !ec) - { - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create key pair."), 1); - return (false); - } - - if ((pkey = EVP_PKEY_new()) == NULL) - { -#if defined(EVP_PKEY_EC) - if (ec) - EC_KEY_free(ec); -#endif // EVP_PKEY_EC && USE_EC - - if (rsa) - RSA_free(rsa); - - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create private key."), 1); + if ((pkey = http_create_key(type)) == NULL) return (false); - } - -#if defined(EVP_PKEY_EC) - if (ec) - EVP_PKEY_assign_EC_KEY(pkey, ec); - else -#endif // EVP_PKEY_EC - EVP_PKEY_assign_RSA(pkey, rsa); DEBUG_puts("1cupsMakeServerCredentials: Key pair created."); @@ -244,20 +190,7 @@ cupsMakeServerCredentials( } - language = cupsLangDefault(); - langname = cupsLangGetName(language); - name = X509_NAME_new(); - if (country) - X509_NAME_add_entry_by_txt(name, SN_countryName, MBSTRING_ASC, (unsigned char *)country, -1, -1, 0); - else if (strlen(langname) == 5) - X509_NAME_add_entry_by_txt(name, SN_countryName, MBSTRING_ASC, (unsigned char *)langname + 3, -1, -1, 0); - else - X509_NAME_add_entry_by_txt(name, SN_countryName, MBSTRING_ASC, (unsigned char *)"US", -1, -1, 0); - X509_NAME_add_entry_by_txt(name, SN_commonName, MBSTRING_ASC, (unsigned char *)common_name, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, SN_organizationName, MBSTRING_ASC, (unsigned char *)(organization ? organization : common_name), -1, -1, 0); - X509_NAME_add_entry_by_txt(name, SN_organizationalUnitName, MBSTRING_ASC, (unsigned char *)(org_unit ? org_unit : ""), -1, -1, 0); - X509_NAME_add_entry_by_txt(name, SN_stateOrProvinceName, MBSTRING_ASC, (unsigned char *)(state_province ? state_province : "Unknown"), -1, -1, 0); - X509_NAME_add_entry_by_txt(name, SN_localityName, MBSTRING_ASC, (unsigned char *)(locality ? locality : "Unknown"), -1, -1, 0); + name = http_create_name(organization, org_unit, locality, state_province, country, common_name); if (root_cert) X509_set_issuer_name(cert, X509_get_subject_name(root_cert)); @@ -266,48 +199,29 @@ cupsMakeServerCredentials( X509_set_subject_name(cert, name); X509_NAME_free(name); - gens = sk_GENERAL_NAME_new_null(); - http_x509_add_san(gens, common_name); - if (strstr(common_name, ".local") == NULL && !ca_cert) - { - // Add common_name.local to the list, too... - char localname[256], // hostname.local - *localptr; // Pointer into localname - - strlcpy(localname, common_name, sizeof(localname)); - if ((localptr = strchr(localname, '.')) != NULL) - *localptr = '\0'; - strlcat(localname, ".local", sizeof(localname)); - - http_x509_add_san(gens, localname); - } - - if (num_alt_names > 0) - { - size_t i; // Looping var... - - for (i = 0; i < num_alt_names; i ++) - { - if (strcmp(alt_names[i], "localhost")) - http_x509_add_san(gens, alt_names[i]); - } - } - - // Add extension with DNS names and free buffer for GENERAL_NAME - X509_add1_ext_i2d(cert, NID_subject_alt_name, gens, 0, X509V3_ADD_DEFAULT); - sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); - - // Add extensions that are required to make Chrome happy... if (ca_cert) { + // Add extensions that are required to make Chrome happy... http_x509_add_ext(cert, NID_basic_constraints, "critical,CA:TRUE,pathlen:0"); http_x509_add_ext(cert, NID_key_usage, "critical,cRLSign,digitalSignature,keyCertSign"); } else { + // Add extension with DNS names and free buffer for GENERAL_NAME + if ((gens = http_create_san(common_name, num_alt_names, alt_names)) == NULL) + { + // TODO: Set error? + goto done; + } + + X509_add1_ext_i2d(cert, NID_subject_alt_name, gens, 0, X509V3_ADD_DEFAULT); + sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); + + // Add extensions that are required to make Chrome happy... http_x509_add_ext(cert, NID_basic_constraints, "critical,CA:FALSE,pathlen:0"); http_x509_add_ext(cert, NID_key_usage, "critical,digitalSignature,keyEncipherment"); } + http_x509_add_ext(cert, NID_ext_key_usage, "1.3.6.1.5.5.7.3.1"); http_x509_add_ext(cert, NID_subject_key_identifier, "hash"); http_x509_add_ext(cert, NID_authority_key_identifier, "keyid,issuer"); @@ -370,6 +284,168 @@ cupsMakeServerCredentials( } +// +// 'cupsMakeServerRequest()' - Make an X.509 Certificate Signing Request. +// +// This function creates an X.509 certificate signing request (CSR) and +// associated private key. The CSR and key are stored in the directory "path" +// or, if "path" is `NULL`, in a per-user or system-wide (when running as root) +// certificate/key store. +// +// The CSR is returned as a string that must be freed using the `free` function. +// + +char * // O - PEM-encoded certificate signing request +cupsMakeServerRequest( + const char *path, // I - Directory path for certificate/key store or `NULL` for default + cups_credtype_t type, // I - Type of certificate/keys to generate + const char *organization, // I - Organization or `NULL` to use common name + const char *org_unit, // I - Organizational unit or `NULL` for none + const char *locality, // I - City/town or `NULL` for "Unknown" + const char *state_province, // I - State/province or `NULL` for "Unknown" + const char *country, // I - Country or `NULL` for locale-based default + const char *common_name, // I - Common name + size_t num_alt_names, // I - Number of subject alternate names + const char **alt_names) // I - Subject Alternate Names +{ + char *result = NULL; // Return value + EVP_PKEY *pkey; // Key pair + X509_REQ *csr; // Certificate signing request + X509_NAME *name; // Subject/issuer name + BIO *bio; // Output file + char temp[1024], // Temporary directory name + csrfile[1024], // Certificate signing request filename + keyfile[1024]; // Private key filename +// GENERAL_NAMES *gens; // Names for SubjectAltName certificate extension + + + DEBUG_printf(("cupsMakeServerRequest(path=\"%s\", type=%d, organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", common_name=\"%s\", num_alt_names=%u, alt_names=%p)", path, type, organization, org_unit, locality, state_province, country, common_name, (unsigned)num_alt_names, alt_names)); + + // Filenames... + if (!path) + path = http_default_path(temp, sizeof(temp)); + + if (!path || !common_name) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); + return (false); + } + + http_make_path(csrfile, sizeof(csrfile), path, common_name, "csr"); + http_make_path(keyfile, sizeof(keyfile), path, common_name, "key"); + + // Create the encryption key... + DEBUG_puts("1cupsMakeServerRequest: Creating key pair."); + + if ((pkey = http_create_key(type)) == NULL) + return (false); + + DEBUG_puts("1cupsMakeServerRequest: Key pair created."); + + // Create the X.509 certificate... + DEBUG_puts("1cupsMakeServerRequest: Generating self-signed X.509 certificate."); + + if ((csr = X509_REQ_new()) == NULL) + { + EVP_PKEY_free(pkey); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create X.509 certificate signing request."), 1); + return (NULL); + } + + X509_REQ_set_pubkey(csr, pkey); + + if ((name = http_create_name(organization, org_unit, locality, state_province, country, common_name)) == NULL) + goto done; + + X509_REQ_set_subject_name(csr, name); + X509_NAME_free(name); + + // Add extension with DNS names and free buffer for GENERAL_NAME +// if ((gens = http_create_san(common_name, num_alt_names, alt_names)) == NULL) +// { +// // TODO: Set error? +// goto done; +// } + + // TODO: Make this STACKOF and use X509_REQ_add_extensions +// X509_REQ_add1_ext_i2d(csr, NID_subject_alt_name, gens, 0, X509V3_ADD_DEFAULT); +// sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); + + X509_REQ_sign(csr, pkey, EVP_sha256()); + + // Save them... + if ((bio = BIO_new_file(keyfile, "wb")) == NULL) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); + goto done; + } + + if (!PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, NULL, NULL)) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to write private key."), 1); + BIO_free(bio); + goto done; + } + + BIO_free(bio); + + if ((bio = BIO_new_file(csrfile, "wb")) == NULL) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); + goto done; + } + + if (!PEM_write_bio_X509_REQ(bio, csr)) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to write X.509 certificate signing request."), 1); + BIO_free(bio); + goto done; + } + + BIO_free(bio); + + // TODO: Copy CSR to returned string + result = strdup(""); + DEBUG_puts("1cupsMakeServerRequest: Successfully created signing request."); + + // Cleanup... + done: + + X509_REQ_free(csr); + EVP_PKEY_free(pkey); + + return (result); +} + + +// +// 'cupsSaveServerCredentials()' - Save the X.509 certificate chain associated +// with a server. +// +// This function saves the the PEM-encoded X.509 certificate chain string to +// the directory "path" or, if "path" is `NULL`, in a per-user or system-wide +// (when running as root) certificate/key store. The "common_name" value must +// match the value supplied when the @link cupsMakeServerRequest@ function was +// called to obtain the certificate signing request (CSR). The saved +// certificate is paired with the private key that was generated for the CSR, +// allowing it to be used for encryption and signing. +// + +extern bool // O - `true` on success, `false` on failure +cupsSaveServerCredentials( + const char *path, // I - Directory path for certificate/key store or `NULL` for default + const char *common_name, // I - Common name for certificate + const char *pem) // I - PEM-encoded certificate chain +{ + // TODO: Implement cupsSaveServerCredentials + (void)path; + (void)common_name; + (void)pem; + + return (false); +} + + // // 'cupsSetServerCredentials()' - Set the default server credentials. // @@ -379,7 +455,7 @@ cupsMakeServerCredentials( bool // O - `true` on success, `false` on failure cupsSetServerCredentials( - const char *path, // I - Path to keychain/directory + const char *path, // I - Directory path for certificate/key store or `NULL` for default const char *common_name, // I - Default common name for server bool auto_create) // I - `true` = automatically create self-signed certificates { @@ -1332,6 +1408,28 @@ _httpTLSWrite(http_t *http, // I - Connection to server } +// +// 'http_add_san()' - Add a subjectAltName to GENERAL_NAMES used for +// the extension to an X.509 certificate/signing request. +// + +static void +http_add_san(GENERAL_NAMES *gens, // I - Concatenation of DNS names + const char *name) // I - Hostname +{ + GENERAL_NAME *gen_dns = GENERAL_NAME_new(); + // DNS: name + ASN1_IA5STRING *ia5 = ASN1_IA5STRING_new(); + // Hostname string + + + // Set the strings and push it on the GENERAL_NAMES list... + ASN1_STRING_set(ia5, name, strlen(name)); + GENERAL_NAME_set0_value(gen_dns, GEN_DNS, ia5); + sk_GENERAL_NAME_push(gens, gen_dns); +} + + // // 'http_bio_ctrl()' - Control the HTTP connection. // @@ -1520,6 +1618,159 @@ http_create_credential( } +// +// 'http_create_key()' - Create a suitable key pair for a certificate/signing request. +// + +static EVP_PKEY * // O - Key pair +http_create_key(cups_credtype_t type) // I - Type of key +{ + EVP_PKEY *pkey; // Key pair +#if defined(EVP_PKEY_EC) + EC_KEY *ec = NULL; // EC key +#endif // EVP_PKEY_EC + RSA *rsa = NULL; // RSA key pair + + + switch (type) + { +#if defined(EVP_PKEY_EC) + case CUPS_CREDTYPE_ECDSA_P256_SHA256 : + ec = EC_KEY_new_by_curve_name(NID_secp256k1); + break; + + case CUPS_CREDTYPE_ECDSA_P384_SHA256 : + ec = EC_KEY_new_by_curve_name(NID_secp384r1); + break; + + case CUPS_CREDTYPE_ECDSA_P521_SHA256 : + ec = EC_KEY_new_by_curve_name(NID_secp521r1); + break; +#endif // EVP_PKEY_EC && USE_EC + + case CUPS_CREDTYPE_RSA_2048_SHA256 : + rsa = RSA_generate_key(2048, RSA_F4, NULL, NULL); + break; + + default : + case CUPS_CREDTYPE_RSA_3072_SHA256 : + rsa = RSA_generate_key(3072, RSA_F4, NULL, NULL); + break; + + case CUPS_CREDTYPE_RSA_4096_SHA256 : + rsa = RSA_generate_key(4096, RSA_F4, NULL, NULL); + break; + } + + if (!rsa && !ec) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create key pair."), 1); + return (NULL); + } + + if ((pkey = EVP_PKEY_new()) == NULL) + { +#if defined(EVP_PKEY_EC) + if (ec) + EC_KEY_free(ec); +#endif // EVP_PKEY_EC && USE_EC + + if (rsa) + RSA_free(rsa); + + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create private key."), 1); + return (NULL); + } + +#if defined(EVP_PKEY_EC) + if (ec) + EVP_PKEY_assign_EC_KEY(pkey, ec); + else +#endif // EVP_PKEY_EC + EVP_PKEY_assign_RSA(pkey, rsa); + + return (pkey); +} + + +// +// 'http_create_name()' - Create an X.509 name value for a certificate/signing request. +// + +static X509_NAME * // O - X.509 name value +http_create_name( + const char *organization, // I - Organization or `NULL` to use common name + const char *org_unit, // I - Organizational unit or `NULL` for none + const char *locality, // I - City/town or `NULL` for "Unknown" + const char *state_province, // I - State/province or `NULL` for "Unknown" + const char *country, // I - Country or `NULL` for locale-based default + const char *common_name) // I - Common name +{ + X509_NAME *name; // Subject/issuer name + cups_lang_t *language; // Default language info + const char *langname; // Language name + + + language = cupsLangDefault(); + langname = cupsLangGetName(language); + name = X509_NAME_new(); + if (country) + X509_NAME_add_entry_by_txt(name, SN_countryName, MBSTRING_ASC, (unsigned char *)country, -1, -1, 0); + else if (strlen(langname) == 5) + X509_NAME_add_entry_by_txt(name, SN_countryName, MBSTRING_ASC, (unsigned char *)langname + 3, -1, -1, 0); + else + X509_NAME_add_entry_by_txt(name, SN_countryName, MBSTRING_ASC, (unsigned char *)"US", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, SN_commonName, MBSTRING_ASC, (unsigned char *)common_name, -1, -1, 0); + X509_NAME_add_entry_by_txt(name, SN_organizationName, MBSTRING_ASC, (unsigned char *)(organization ? organization : common_name), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, SN_organizationalUnitName, MBSTRING_ASC, (unsigned char *)(org_unit ? org_unit : ""), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, SN_stateOrProvinceName, MBSTRING_ASC, (unsigned char *)(state_province ? state_province : "Unknown"), -1, -1, 0); + X509_NAME_add_entry_by_txt(name, SN_localityName, MBSTRING_ASC, (unsigned char *)(locality ? locality : "Unknown"), -1, -1, 0); + + return (name); +} + + +// +// 'http_create_san()' - Create a list of subjectAltName values for a certificate/signing request. +// + +static GENERAL_NAMES * // O - List of subjectAltName values +http_create_san( + const char *common_name, // I - Common name + size_t num_alt_names, // I - Number of alternate names + const char **alt_names) // I - List of alternate names +{ + GENERAL_NAMES *gens; // List of subjectAltName values + size_t i; // Looping var + + + gens = sk_GENERAL_NAME_new_null(); + + http_add_san(gens, common_name); + if (strstr(common_name, ".local") == NULL) + { + // Add common_name.local to the list, too... + char localname[256], // hostname.local + *localptr; // Pointer into localname + + strlcpy(localname, common_name, sizeof(localname)); + if ((localptr = strchr(localname, '.')) != NULL) + *localptr = '\0'; + strlcat(localname, ".local", sizeof(localname)); + + http_add_san(gens, localname); + } + + for (i = 0; i < num_alt_names; i ++) + { + if (strcmp(alt_names[i], "localhost")) + http_add_san(gens, alt_names[i]); + } + + return (gens); +} + + // // 'http_default_path()' - Get the default credential store path. // @@ -1790,25 +2041,3 @@ http_x509_add_ext(X509 *cert, // I - Certificate return (ret); } - - -// -// 'http_x509_add_san()' - Add a subjectAltName to GENERAL_NAMES used for -// the extension to an X.509 certificate. -// - -static void -http_x509_add_san(GENERAL_NAMES *gens, // I - Concatenation of DNS names - const char *name) // I - Hostname -{ - GENERAL_NAME *gen_dns = GENERAL_NAME_new(); - // DNS: name - ASN1_IA5STRING *ia5 = ASN1_IA5STRING_new(); - // Hostname string - - - // Set the strings and push it on the GENERAL_NAMES list... - ASN1_STRING_set(ia5, name, strlen(name)); - GENERAL_NAME_set0_value(gen_dns, GEN_DNS, ia5); - sk_GENERAL_NAME_push(gens, gen_dns); -} From e7a984bd99f9b2a0992c84005911c37a4aba23f5 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Fri, 21 Apr 2023 23:45:00 -0400 Subject: [PATCH 05/31] Fix some Coverity issues. --- tools/ippeveprinter.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/ippeveprinter.c b/tools/ippeveprinter.c index a19dcc989..6f6dc3dae 100644 --- a/tools/ippeveprinter.c +++ b/tools/ippeveprinter.c @@ -1586,7 +1586,7 @@ create_printer( * Extract up to 3 icons... */ - for (i = 1, iconsptr = strchr(icons, ','); iconsptr && i < 3; i ++, iconsptr = strchr(iconsptr, ',')) + for (i = 1, iconsptr = strchr(printer->icons, ','); iconsptr && i < 3; i ++, iconsptr = strchr(iconsptr, ',')) { *iconsptr++ = '\0'; printer->icons[i] = iconsptr; @@ -5995,7 +5995,10 @@ process_job(ippeve_job_t *job) /* I - Job */ } if (mystdout < 0) - mystdout = open("/dev/null", O_WRONLY | O_BINARY); + { + if ((mystdout = open("/dev/null", O_WRONLY | O_BINARY)) < 0) + fprintf(stderr, "[Job %d] Unable to redirect command output to /dev/null: %s", job->id, strerror(errno)); + } if (pipe(mypipe)) { From 8a70202a99f14600ccf363c35698994d6bd82556 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Sun, 23 Apr 2023 17:45:19 -0400 Subject: [PATCH 06/31] Fix compile error --- tools/ippeveprinter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ippeveprinter.c b/tools/ippeveprinter.c index 6f6dc3dae..a384d7adb 100644 --- a/tools/ippeveprinter.c +++ b/tools/ippeveprinter.c @@ -1586,7 +1586,7 @@ create_printer( * Extract up to 3 icons... */ - for (i = 1, iconsptr = strchr(printer->icons, ','); iconsptr && i < 3; i ++, iconsptr = strchr(iconsptr, ',')) + for (i = 1, iconsptr = strchr(printer->icons[0], ','); iconsptr && i < 3; i ++, iconsptr = strchr(iconsptr, ',')) { *iconsptr++ = '\0'; printer->icons[i] = iconsptr; From 9e3f27277693bd7ba49633c5914f0b11ed7d22ba Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Thu, 4 May 2023 12:27:32 -0400 Subject: [PATCH 07/31] Rework OpenSSL X.509 extension handling. --- cups/cups.h | 150 +++--- cups/tls-gnutls.c | 1101 ++++++++++++++------------------------------ cups/tls-openssl.c | 799 ++++++++++---------------------- cups/tls.c | 583 ++++++++++++++++++++++- 4 files changed, 1231 insertions(+), 1402 deletions(-) diff --git a/cups/cups.h b/cups/cups.h index 43e0dcf5a..c455672b1 100644 --- a/cups/cups.h +++ b/cups/cups.h @@ -133,74 +133,101 @@ extern "C" { // Types and structures... // -typedef enum cups_credtype_e // X.509 credential types for @link cupsMakeServerCredentials@ and @link cupsMakeServerRequest@ +enum cups_credpurpose_e //// X.509 credential purposes { - CUPS_CREDTYPE_DEFAULT, // Default type - CUPS_CREDTYPE_RSA_2048_SHA256, // RSA with 2048-bit keys and SHA-256 hash - CUPS_CREDTYPE_RSA_3072_SHA256, // RSA with 3072-bit keys and SHA-256 hash - CUPS_CREDTYPE_RSA_4096_SHA256, // RSA with 4096-bit keys and SHA-256 hash - CUPS_CREDTYPE_ECDSA_P256_SHA256, // ECDSA using the P-256 curve with SHA-256 hash - CUPS_CREDTYPE_ECDSA_P384_SHA256, // ECDSA using the P-384 curve with SHA-256 hash - CUPS_CREDTYPE_ECDSA_P521_SHA256 // ECDSA using the P-521 curve with SHA-256 hash + CUPS_CREDPURPOSE_SERVER_AUTH = 0x01, // serverAuth + CUPS_CREDPURPOSE_CLIENT_AUTH = 0x02, // clientAuth + CUPS_CREDPURPOSE_CODE_SIGNING = 0x04, // codeSigning + CUPS_CREDPURPOSE_EMAIL_PROTECTION = 0x08, // emailProtection + CUPS_CREDPURPOSE_TIME_STAMPING = 0x10, // timeStamping + CUPS_CREDPURPOSE_OCSP_SIGNING = 0x20 // OCSPSigning +}; +typedef unsigned cups_credpurpose_t; //// Combined X.509 credential purposes for @link cupsCreateCredentials@ and @link cupsCreateCredentialsRequest@ + +typedef enum cups_credtype_e //// X.509 credential types for @link cupsCreateCredentials@ and @link cupsCreateCredentialsRequest@ +{ + CUPS_CREDTYPE_DEFAULT, // Default type + CUPS_CREDTYPE_RSA_2048_SHA256, // RSA with 2048-bit keys and SHA-256 hash + CUPS_CREDTYPE_RSA_3072_SHA256, // RSA with 3072-bit keys and SHA-256 hash + CUPS_CREDTYPE_RSA_4096_SHA256, // RSA with 4096-bit keys and SHA-256 hash + CUPS_CREDTYPE_ECDSA_P256_SHA256, // ECDSA using the P-256 curve with SHA-256 hash + CUPS_CREDTYPE_ECDSA_P384_SHA256, // ECDSA using the P-384 curve with SHA-256 hash + CUPS_CREDTYPE_ECDSA_P521_SHA256 // ECDSA using the P-521 curve with SHA-256 hash } cups_credtype_t; -enum cups_dest_flags_e // Flags for @link cupsConnectDest@ and @link cupsEnumDests@ +enum cups_credusage_e //// X.509 keyUsage flags { - CUPS_DEST_FLAGS_NONE = 0x00, // No flags are set - CUPS_DEST_FLAGS_UNCONNECTED = 0x01, // There is no connection - CUPS_DEST_FLAGS_MORE = 0x02, // There are more destinations - CUPS_DEST_FLAGS_REMOVED = 0x04, // The destination has gone away - CUPS_DEST_FLAGS_ERROR = 0x08, // An error occurred - CUPS_DEST_FLAGS_RESOLVING = 0x10, // The destination address is being resolved - CUPS_DEST_FLAGS_CONNECTING = 0x20, // A connection is being established - CUPS_DEST_FLAGS_CANCELED = 0x40, // Operation was canceled - CUPS_DEST_FLAGS_DEVICE = 0x80 // For @link cupsConnectDest@: Connect to device + CUPS_CREDUSAGE_DIGITAL_SIGNATURE = 0x001, // digitalSignature + CUPS_CREDUSAGE_NON_REPUDIATION = 0x002, // nonRepudiation/contentCommitment + CUPS_CREDUSAGE_KEY_ENCIPHERMENT = 0x004, // keyEncipherment + CUPS_CREDUSAGE_DATA_ENCIPHERMENT = 0x008, // dataEncipherment + CUPS_CREDUSAGE_KEY_AGREEMENT = 0x010, // keyAgreement + CUPS_CREDUSAGE_KEY_CERT_SIGN = 0x020, // keyCertSign + CUPS_CREDUSAGE_CRL_SIGN = 0x040, // cRLSign + CUPS_CREDUSAGE_ENCIPHER_ONLY = 0x080, // encipherOnly + CUPS_CREDUSAGE_DECIPHER_ONLY = 0x100, // decipherOnly + CUPS_CREDUSAGE_DEFAULT_CA = 0x061, // Defaults for CA certs + CUPS_CREDUSAGE_DEFAULT_TLS = 0x005 // Defaults for TLS certs }; -typedef unsigned cups_dest_flags_t; // Combined flags for @link cupsConnectDest@ and @link cupsEnumDests@ +typedef unsigned cups_credusage_t; //// Combined X.509 keyUsage flags for @link cupsCreateCredentials@ and @link cupsCreateCredentialsRequest@ -enum cups_media_flags_e // Flags for @link cupsGetDestMediaByName@ and @link cupsGetDestMediaBySize@ +enum cups_dest_flags_e //// Flags for @link cupsConnectDest@ and @link cupsEnumDests@ { - CUPS_MEDIA_FLAGS_DEFAULT = 0x00, // Find the closest size supported by the printer - CUPS_MEDIA_FLAGS_BORDERLESS = 0x01, // Find a borderless size - CUPS_MEDIA_FLAGS_DUPLEX = 0x02, // Find a size compatible with 2-sided printing - CUPS_MEDIA_FLAGS_EXACT = 0x04, // Find an exact match for the size - CUPS_MEDIA_FLAGS_READY = 0x08 // If the printer supports media sensing, find the size amongst the "ready" media. + CUPS_DEST_FLAGS_NONE = 0x00, // No flags are set + CUPS_DEST_FLAGS_UNCONNECTED = 0x01, // There is no connection + CUPS_DEST_FLAGS_MORE = 0x02, // There are more destinations + CUPS_DEST_FLAGS_REMOVED = 0x04, // The destination has gone away + CUPS_DEST_FLAGS_ERROR = 0x08, // An error occurred + CUPS_DEST_FLAGS_RESOLVING = 0x10, // The destination address is being resolved + CUPS_DEST_FLAGS_CONNECTING = 0x20, // A connection is being established + CUPS_DEST_FLAGS_CANCELED = 0x40, // Operation was canceled + CUPS_DEST_FLAGS_DEVICE = 0x80 // For @link cupsConnectDest@: Connect to device }; -typedef unsigned cups_media_flags_t; // Combined flags for @link cupsGetDestMediaByName@ and @link cupsGetDestMediaBySize@ +typedef unsigned cups_dest_flags_t; //// Combined flags for @link cupsConnectDest@ and @link cupsEnumDests@ -enum cups_ptype_e // Printer type/capability flags +enum cups_media_flags_e //// Flags for @link cupsGetDestMediaByName@ and @link cupsGetDestMediaBySize@ { - CUPS_PRINTER_LOCAL = 0x0000, // Local printer or class - CUPS_PRINTER_CLASS = 0x0001, // Printer class - CUPS_PRINTER_REMOTE = 0x0002, // Remote printer or class - CUPS_PRINTER_BW = 0x0004, // Can do B&W printing - CUPS_PRINTER_COLOR = 0x0008, // Can do color printing - CUPS_PRINTER_DUPLEX = 0x0010, // Can do two-sided printing - CUPS_PRINTER_STAPLE = 0x0020, // Can staple output - CUPS_PRINTER_COPIES = 0x0040, // Can do copies in hardware - CUPS_PRINTER_COLLATE = 0x0080, // Can quickly collate copies - CUPS_PRINTER_PUNCH = 0x0100, // Can punch output - CUPS_PRINTER_COVER = 0x0200, // Can cover output - CUPS_PRINTER_BIND = 0x0400, // Can bind output - CUPS_PRINTER_SORT = 0x0800, // Can sort output - CUPS_PRINTER_SMALL = 0x1000, // Can print on Letter/Legal/A4-size media - CUPS_PRINTER_MEDIUM = 0x2000, // Can print on Tabloid/B/C/A3/A2-size media - CUPS_PRINTER_LARGE = 0x4000, // Can print on D/E/A1/A0-size media - CUPS_PRINTER_VARIABLE = 0x8000, // Can print on rolls and custom-size media - CUPS_PRINTER_DEFAULT = 0x20000, // Default printer on network - CUPS_PRINTER_FAX = 0x40000, // Fax queue - CUPS_PRINTER_REJECTING = 0x80000, // Printer is rejecting jobs - CUPS_PRINTER_NOT_SHARED = 0x200000, // Printer is not shared - CUPS_PRINTER_AUTHENTICATED = 0x400000,// Printer requires authentication - CUPS_PRINTER_COMMANDS = 0x800000, // Printer supports maintenance commands - CUPS_PRINTER_DISCOVERED = 0x1000000, // Printer was discovered - CUPS_PRINTER_SCANNER = 0x2000000, // Scanner-only device - CUPS_PRINTER_MFP = 0x4000000, // Printer with scanning capabilities - CUPS_PRINTER_OPTIONS = 0x6fffc // ~(CLASS | REMOTE | IMPLICIT | DEFAULT | FAX | REJECTING | DELETE | NOT_SHARED | AUTHENTICATED | COMMANDS | DISCOVERED) @private@ + CUPS_MEDIA_FLAGS_DEFAULT = 0x00, // Find the closest size supported by the printer + CUPS_MEDIA_FLAGS_BORDERLESS = 0x01, // Find a borderless size + CUPS_MEDIA_FLAGS_DUPLEX = 0x02, // Find a size compatible with 2-sided printing + CUPS_MEDIA_FLAGS_EXACT = 0x04, // Find an exact match for the size + CUPS_MEDIA_FLAGS_READY = 0x08 // If the printer supports media sensing, find the size amongst the "ready" media. }; -typedef unsigned cups_ptype_t; // Combined printer type/capability flags +typedef unsigned cups_media_flags_t; //// Combined flags for @link cupsGetDestMediaByName@ and @link cupsGetDestMediaBySize@ -typedef enum cups_whichjobs_e // Which jobs for @link cupsGetJobs@ +enum cups_ptype_e //// Printer type/capability flags +{ + CUPS_PRINTER_LOCAL = 0x0000, // Local printer or class + CUPS_PRINTER_CLASS = 0x0001, // Printer class + CUPS_PRINTER_REMOTE = 0x0002, // Remote printer or class + CUPS_PRINTER_BW = 0x0004, // Can do B&W printing + CUPS_PRINTER_COLOR = 0x0008, // Can do color printing + CUPS_PRINTER_DUPLEX = 0x0010, // Can do two-sided printing + CUPS_PRINTER_STAPLE = 0x0020, // Can staple output + CUPS_PRINTER_COPIES = 0x0040, // Can do copies in hardware + CUPS_PRINTER_COLLATE = 0x0080, // Can quickly collate copies + CUPS_PRINTER_PUNCH = 0x0100, // Can punch output + CUPS_PRINTER_COVER = 0x0200, // Can cover output + CUPS_PRINTER_BIND = 0x0400, // Can bind output + CUPS_PRINTER_SORT = 0x0800, // Can sort output + CUPS_PRINTER_SMALL = 0x1000, // Can print on Letter/Legal/A4-size media + CUPS_PRINTER_MEDIUM = 0x2000, // Can print on Tabloid/B/C/A3/A2-size media + CUPS_PRINTER_LARGE = 0x4000, // Can print on D/E/A1/A0-size media + CUPS_PRINTER_VARIABLE = 0x8000, // Can print on rolls and custom-size media + CUPS_PRINTER_DEFAULT = 0x20000, // Default printer on network + CUPS_PRINTER_FAX = 0x40000, // Fax queue + CUPS_PRINTER_REJECTING = 0x80000, // Printer is rejecting jobs + CUPS_PRINTER_NOT_SHARED = 0x200000, // Printer is not shared + CUPS_PRINTER_AUTHENTICATED = 0x400000, // Printer requires authentication + CUPS_PRINTER_COMMANDS = 0x800000, // Printer supports maintenance commands + CUPS_PRINTER_DISCOVERED = 0x1000000, // Printer was discovered + CUPS_PRINTER_SCANNER = 0x2000000, // Scanner-only device + CUPS_PRINTER_MFP = 0x4000000, // Printer with scanning capabilities + CUPS_PRINTER_OPTIONS = 0x6fffc // ~(CLASS | REMOTE | IMPLICIT | DEFAULT | FAX | REJECTING | DELETE | NOT_SHARED | AUTHENTICATED | COMMANDS | DISCOVERED) @private@ +}; +typedef unsigned cups_ptype_t; //// Combined printer type/capability flags + +typedef enum cups_whichjobs_e //// Which jobs for @link cupsGetJobs@ { CUPS_WHICHJOBS_ALL = -1, // All jobs CUPS_WHICHJOBS_ACTIVE, // Pending/held/processing jobs @@ -284,10 +311,15 @@ extern bool cupsCheckDestSupported(http_t *http, cups_dest_t *dest, cups_dinfo_ extern ipp_status_t cupsCloseDestJob(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, int job_id) _CUPS_PUBLIC; extern size_t cupsConcatString(char *dst, const char *src, size_t dstsize) _CUPS_PUBLIC; extern http_t *cupsConnectDest(cups_dest_t *dest, unsigned flags, int msec, int *cancel, char *resource, size_t resourcesize, cups_dest_cb_t cb, void *user_data) _CUPS_PUBLIC; +extern char *cupsCopyCredentials(const char *path, const char *common_name) _CUPS_PUBLIC; +extern char *cupsCopyCredentialsKey(const char *path, const char *common_name) _CUPS_PUBLIC; +extern char *cupsCopyCredentialsRequest(const char *path, const char *common_name) _CUPS_PUBLIC; extern size_t cupsCopyDest(cups_dest_t *dest, size_t num_dests, cups_dest_t **dests) _CUPS_PUBLIC; extern cups_dinfo_t *cupsCopyDestInfo(http_t *http, cups_dest_t *dest) _CUPS_PUBLIC; extern int cupsCopyDestConflicts(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, size_t num_options, cups_option_t *options, const char *new_option, const char *new_value, size_t *num_conflicts, cups_option_t **conflicts, size_t *num_resolved, cups_option_t **resolved) _CUPS_PUBLIC; extern size_t cupsCopyString(char *dst, const char *src, size_t dstsize) _CUPS_PUBLIC; +extern bool cupsCreateCredentials(const char *path, bool ca_cert, cups_credpurpose_t purpose, cups_credtype_t type, cups_credusage_t usage, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, size_t num_alt_names, const char **alt_names, const char *root_name, time_t expiration_date) _CUPS_PUBLIC; +extern bool cupsCreateCredentialsRequest(const char *path, cups_credpurpose_t purpose, cups_credtype_t type, cups_credusage_t usage, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, size_t num_alt_names, const char **alt_names) _CUPS_PUBLIC; extern ipp_status_t cupsCreateDestJob(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, int *job_id, const char *title, size_t num_options, cups_option_t *options) _CUPS_PUBLIC; extern int cupsDoAuthentication(http_t *http, const char *method, const char *resource) _CUPS_PUBLIC; @@ -341,9 +373,6 @@ extern const char *cupsLocalizeDestMedia(http_t *http, cups_dest_t *dest, cups_d extern const char *cupsLocalizeDestOption(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, const char *option) _CUPS_PUBLIC; extern const char *cupsLocalizeDestValue(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, const char *option, const char *value) _CUPS_PUBLIC; -extern bool cupsMakeServerCredentials(const char *path, cups_credtype_t type, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *root_name, bool ca_cert, const char *common_name, size_t num_alt_names, const char **alt_names, time_t expiration_date) _CUPS_PUBLIC; -extern char *cupsMakeServerRequest(const char *path, cups_credtype_t type, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, size_t num_alt_names, const char **alt_names) _CUPS_PUBLIC; - extern char *cupsNotifySubject(cups_lang_t *lang, ipp_t *event) _CUPS_PUBLIC; extern char *cupsNotifyText(cups_lang_t *lang, ipp_t *event) _CUPS_PUBLIC; @@ -355,7 +384,7 @@ extern ssize_t cupsReadResponseData(http_t *http, char *buffer, size_t length) extern size_t cupsRemoveDest(const char *name, const char *instance, size_t num_dests, cups_dest_t **dests) _CUPS_PUBLIC; extern size_t cupsRemoveOption(const char *name, size_t num_options, cups_option_t **options) _CUPS_PUBLIC; -extern bool cupsSaveServerCredentials(const char *path, const char *common_name, const char *pem) _CUPS_PUBLIC; +extern bool cupsSaveCredentials(const char *path, const char *common_name, const char *credentials, const char *key) _CUPS_PUBLIC; extern http_status_t cupsSendRequest(http_t *http, ipp_t *request, const char *resource, size_t length) _CUPS_PUBLIC; extern void cupsSetOAuthCB(cups_oauth_cb_t cb, void *data) _CUPS_PUBLIC; extern void cupsSetClientCertCB(cups_client_cert_cb_t cb, void *user_data) _CUPS_PUBLIC; @@ -369,6 +398,7 @@ extern void cupsSetServerCertCB(cups_server_cert_cb_t cb, void *user_data) _CUP extern bool cupsSetServerCredentials(const char *path, const char *common_name, bool auto_create) _CUPS_PUBLIC; extern void cupsSetUser(const char *user) _CUPS_PUBLIC; extern void cupsSetUserAgent(const char *user_agent) _CUPS_PUBLIC; +extern bool cupsSignCredentialsRequest(const char *path, const char *common_name, const char *request, const char *root_name, time_t expiration_date) _CUPS_PUBLIC; extern http_status_t cupsStartDestDocument(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, int job_id, const char *docname, const char *format, size_t num_options, cups_option_t *options, bool last_document) _CUPS_PUBLIC; extern int cupsTempFd(const char *prefix, const char *suffix, char *filename, size_t len) _CUPS_PUBLIC; extern cups_file_t *cupsTempFile(const char *prefix, const char *suffix, char *filename, size_t len) _CUPS_PUBLIC; diff --git a/cups/tls-gnutls.c b/cups/tls-gnutls.c index 92f856e54..ba05da168 100644 --- a/cups/tls-gnutls.c +++ b/cups/tls-gnutls.c @@ -1,88 +1,78 @@ -/* - * TLS support code for CUPS using GNU TLS. - * - * Copyright © 2020-2022 by OpenPrinting - * Copyright © 2007-2019 by Apple Inc. - * Copyright © 1997-2007 by Easy Software Products, all rights reserved. - * - * Licensed under Apache License v2.0. See the file "LICENSE" for more - * information. - */ - -/**** This file is included from tls.c ****/ - -/* - * Include necessary headers... - */ - -#include - - -/* - * Local globals... - */ - -static int tls_auto_create = 0; - /* Auto-create self-signed certs? */ -static char *tls_common_name = NULL; - /* Default common name */ -static gnutls_x509_crl_t tls_crl = NULL;/* Certificate revocation list */ -static char *tls_keypath = NULL; - /* Server cert keychain path */ -static cups_mutex_t tls_mutex = CUPS_MUTEX_INITIALIZER; - /* Mutex for keychain/certs */ -static int tls_options = -1,/* Options for TLS connections */ - tls_min_version = _HTTP_TLS_1_0, - tls_max_version = _HTTP_TLS_MAX; - - -/* - * Local functions... - */ - -static gnutls_x509_crt_t http_gnutls_create_credential(http_credential_t *credential); -static const char *http_gnutls_default_path(char *buffer, size_t bufsize); -static void http_gnutls_load_crl(void); -static const char *http_gnutls_make_path(char *buffer, size_t bufsize, const char *dirname, const char *filename, const char *ext); -static ssize_t http_gnutls_read(gnutls_transport_ptr_t ptr, void *data, size_t length); -static ssize_t http_gnutls_write(gnutls_transport_ptr_t ptr, const void *data, size_t length); - - -/* - * 'cupsMakeServerCredentials()' - Make a self-signed certificate and private key pair. - */ - -int /* O - 1 on success, 0 on failure */ -cupsMakeServerCredentials( - const char *path, /* I - Path to keychain/directory */ - const char *common_name, /* I - Common name */ - int num_alt_names, /* I - Number of subject alternate names */ - const char **alt_names, /* I - Subject Alternate Names */ - time_t expiration_date) /* I - Expiration date */ -{ - gnutls_x509_crt_t crt; /* Self-signed certificate */ - gnutls_x509_privkey_t key; /* Encryption private key */ - char temp[1024], /* Temporary directory name */ - crtfile[1024], /* Certificate filename */ - keyfile[1024]; /* Private key filename */ - cups_lang_t *language; /* Default language info */ - const char *langname; /* Default language name */ - cups_file_t *fp; /* Key/cert file */ - unsigned char buffer[8192]; /* Buffer for x509 data */ - size_t bytes; /* Number of bytes of data */ - unsigned char serial[4]; /* Serial number buffer */ - time_t curtime; /* Current time */ - int result; /* Result of GNU TLS calls */ - - - DEBUG_printf(("cupsMakeServerCredentials(path=\"%s\", common_name=\"%s\", num_alt_names=%d, alt_names=%p, expiration_date=%d)", path, common_name, num_alt_names, alt_names, (int)expiration_date)); - - /* - * Filenames... - */ +// +// TLS support code for CUPS using GNU TLS. +// +// Copyright © 2020-2023 by OpenPrinting +// Copyright © 2007-2019 by Apple Inc. +// Copyright © 1997-2007 by Easy Software Products, all rights reserved. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +//// This file is included from tls.c + + +// +// Local functions... +// + +static gnutls_x509_crt_t gnutls_create_credential(http_credential_t *credential); +static gnutls_x509_privkey_t gnutls_create_key(cups_credtype_t type); +static void gnutls_load_crl(void); +static ssize_t gnutls_read(gnutls_transport_ptr_t ptr, void *data, size_t length); +static ssize_t gnutls_write(gnutls_transport_ptr_t ptr, const void *data, size_t length); + + +// +// 'cupsCreateCredentials()' - Make an X.509 certificate and private key pair. +// +// This function creates an X.509 certificate and private key pair. The +// certificate and key are stored in the directory "path" or, if "path" is +// `NULL`, in a per-user or system-wide (when running as root) certificate/key +// store. The generated certificate is signed by the named root certificate or, +// if "root_name" is `NULL`, a site-wide default root certificate. When +// "root_name" is `NULL` and there is no site-wide default root certificate, a +// self-signed certificate is generated instead. +// +bool // O - `true` on success, `false` on error +cupsCreateCredentials( + const char *path, // I - Directory path for certificate/key store or `NULL` for default + bool ca_cert, // I - `true` to create a CA certificate, `false` for a client/server certificate + cups_credpurpose_t purpose, // I - Credential purposes + cups_credtype_t type, // I - Credential type + cups_credusage_t usage, // I - Credential usages + const char *organization, // I - Organization or `NULL` to use common name + const char *org_unit, // I - Organizational unit or `NULL` for none + const char *locality, // I - City/town or `NULL` for "Unknown" + const char *state_province, // I - State/province or `NULL` for "Unknown" + const char *country, // I - Country or `NULL` for locale-based default + const char *common_name, // I - Common name + size_t num_alt_names, // I - Number of subject alternate names + const char **alt_names, // I - Subject Alternate Names + const char *root_name, // I - Root certificate/domain name or `NULL` for site/self-signed + time_t expiration_date) // I - Expiration date +{ + gnutls_x509_crt_t crt; // Self-signed certificate + gnutls_x509_privkey_t key; // Encryption private key + char temp[1024], // Temporary directory name + crtfile[1024], // Certificate filename + keyfile[1024]; // Private key filename + cups_lang_t *language; // Default language info + const char *langname; // Default language name + cups_file_t *fp; // Key/cert file + unsigned char buffer[8192]; // Buffer for x509 data + size_t bytes; // Number of bytes of data + unsigned char serial[4]; // Serial number buffer + time_t curtime; // Current time + int result; // Result of GNU TLS calls + + + DEBUG_printf(("cupsCreateCredentials(path=\"%s\", ca_cert=%s, purpose=0x%x, type=%d, usage=0x%x, organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", common_name=\"%s\", num_alt_names=%u, alt_names=%p, root_name=\"%s\", expiration_date=%ld)", path, ca_cert ? "true" : "false", purpose, type, usage, organization, org_unit, locality, state_province, country, common_name, (unsigned)num_alt_names, alt_names, root_name, (long)expiration_date)); + + // Filenames... if (!path) - path = http_gnutls_default_path(temp, sizeof(temp)); + path = http_default_path(temp, sizeof(temp)); if (!path || !common_name) { @@ -90,52 +80,42 @@ cupsMakeServerCredentials( return (0); } - http_gnutls_make_path(crtfile, sizeof(crtfile), path, common_name, "crt"); - http_gnutls_make_path(keyfile, sizeof(keyfile), path, common_name, "key"); + http_make_path(crtfile, sizeof(crtfile), path, common_name, "crt"); + http_make_path(keyfile, sizeof(keyfile), path, common_name, "key"); - /* - * Create the encryption key... - */ + // Create the encryption key... + DEBUG_puts("1cupsCreateCredentials: Creating key pair."); - DEBUG_puts("1cupsMakeServerCredentials: Creating key pair."); + key = gnutls_create_key(type); - gnutls_x509_privkey_init(&key); - gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 3072, 0); - - DEBUG_puts("1cupsMakeServerCredentials: Key pair created."); - - /* - * Save it... - */ + DEBUG_puts("1cupsCreateCredentials: Key pair created."); + // Save it... bytes = sizeof(buffer); if ((result = gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) { - DEBUG_printf(("1cupsMakeServerCredentials: Unable to export private key: %s", gnutls_strerror(result))); + DEBUG_printf(("1cupsCreateCredentials: Unable to export private key: %s", gnutls_strerror(result))); _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); gnutls_x509_privkey_deinit(key); return (0); } else if ((fp = cupsFileOpen(keyfile, "w")) != NULL) { - DEBUG_printf(("1cupsMakeServerCredentials: Writing private key to \"%s\".", keyfile)); + DEBUG_printf(("1cupsCreateCredentials: Writing private key to \"%s\".", keyfile)); cupsFileWrite(fp, (char *)buffer, bytes); cupsFileClose(fp); } else { - DEBUG_printf(("1cupsMakeServerCredentials: Unable to create private key file \"%s\": %s", keyfile, strerror(errno))); + DEBUG_printf(("1cupsCreateCredentials: Unable to create private key file \"%s\": %s", keyfile, strerror(errno))); _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); gnutls_x509_privkey_deinit(key); return (0); } - /* - * Create the self-signed certificate... - */ - - DEBUG_puts("1cupsMakeServerCredentials: Generating self-signed X.509 certificate."); + // Create the self-signed certificate... + DEBUG_puts("1cupsCreateCredentials: Generating self-signed X.509 certificate."); language = cupsLangDefault(); langname = cupsLangGetName(language); @@ -155,7 +135,7 @@ cupsMakeServerCredentials( gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, "Unknown", 7); gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, "Unknown", 7); gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_LOCALITY_NAME, 0, "Unknown", 7); -/* gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_PKCS9_EMAIL, 0, ServerAdmin, strlen(ServerAdmin));*/ +// gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_PKCS9_EMAIL, 0, ServerAdmin, strlen(ServerAdmin)); gnutls_x509_crt_set_key(crt, key); gnutls_x509_crt_set_serial(crt, serial, sizeof(serial)); gnutls_x509_crt_set_activation_time(crt, curtime); @@ -164,11 +144,8 @@ cupsMakeServerCredentials( gnutls_x509_crt_set_subject_alt_name(crt, GNUTLS_SAN_DNSNAME, common_name, (unsigned)strlen(common_name), GNUTLS_FSAN_SET); if (!strchr(common_name, '.')) { - /* - * Add common_name.local to the list, too... - */ - - char localname[256]; /* hostname.local */ + // Add common_name.local to the list, too... + char localname[256]; // hostname.local snprintf(localname, sizeof(localname), "%s.local", common_name); gnutls_x509_crt_set_subject_alt_name(crt, GNUTLS_SAN_DNSNAME, localname, (unsigned)strlen(localname), GNUTLS_FSAN_APPEND); @@ -176,7 +153,7 @@ cupsMakeServerCredentials( gnutls_x509_crt_set_subject_alt_name(crt, GNUTLS_SAN_DNSNAME, "localhost", 9, GNUTLS_FSAN_APPEND); if (num_alt_names > 0) { - int i; /* Looping var */ + size_t i; // Looping var for (i = 0; i < num_alt_names; i ++) { @@ -196,14 +173,11 @@ cupsMakeServerCredentials( gnutls_x509_crt_sign(crt, crt, key); - /* - * Save it... - */ - + // Save it... bytes = sizeof(buffer); if ((result = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) { - DEBUG_printf(("1cupsMakeServerCredentials: Unable to export public key and X.509 certificate: %s", gnutls_strerror(result))); + DEBUG_printf(("1cupsCreateCredentials: Unable to export public key and X.509 certificate: %s", gnutls_strerror(result))); _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); gnutls_x509_crt_deinit(crt); gnutls_x509_privkey_deinit(key); @@ -211,105 +185,41 @@ cupsMakeServerCredentials( } else if ((fp = cupsFileOpen(crtfile, "w")) != NULL) { - DEBUG_printf(("1cupsMakeServerCredentials: Writing public key and X.509 certificate to \"%s\".", crtfile)); + DEBUG_printf(("1cupsCreateCredentials: Writing public key and X.509 certificate to \"%s\".", crtfile)); cupsFileWrite(fp, (char *)buffer, bytes); cupsFileClose(fp); } else { - DEBUG_printf(("1cupsMakeServerCredentials: Unable to create public key and X.509 certificate file \"%s\": %s", crtfile, strerror(errno))); + DEBUG_printf(("1cupsCreateCredentials: Unable to create public key and X.509 certificate file \"%s\": %s", crtfile, strerror(errno))); _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); gnutls_x509_crt_deinit(crt); gnutls_x509_privkey_deinit(key); return (0); } - /* - * Cleanup... - */ - + // Cleanup... gnutls_x509_crt_deinit(crt); gnutls_x509_privkey_deinit(key); - DEBUG_puts("1cupsMakeServerCredentials: Successfully created credentials."); - - return (1); -} - - -/* - * 'cupsSetServerCredentials()' - Set the default server credentials. - * - * Note: The server credentials are used by all threads in the running process. - * This function is threadsafe. - */ - -int /* O - 1 on success, 0 on failure */ -cupsSetServerCredentials( - const char *path, /* I - Path to keychain/directory */ - const char *common_name, /* I - Default common name for server */ - int auto_create) /* I - 1 = automatically create self-signed certificates */ -{ - char temp[1024]; /* Default path buffer */ - - - DEBUG_printf(("cupsSetServerCredentials(path=\"%s\", common_name=\"%s\", auto_create=%d)", path, common_name, auto_create)); - - /* - * Use defaults as needed... - */ - - if (!path) - path = http_gnutls_default_path(temp, sizeof(temp)); - - /* - * Range check input... - */ - - if (!path || !common_name) - { - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); - return (0); - } - - cupsMutexLock(&tls_mutex); - - /* - * Free old values... - */ - - if (tls_keypath) - _cupsStrFree(tls_keypath); - - if (tls_common_name) - _cupsStrFree(tls_common_name); - - /* - * Save the new values... - */ - - tls_keypath = _cupsStrAlloc(path); - tls_auto_create = auto_create; - tls_common_name = _cupsStrAlloc(common_name); - - cupsMutexUnlock(&tls_mutex); + DEBUG_puts("1cupsCreateCredentials: Successfully created credentials."); return (1); } -/* - * 'httpCopyCredentials()' - Copy the credentials associated with the peer in - * an encrypted connection. - */ +// +// 'httpCopyCredentials()' - Copy the credentials associated with the peer in +// an encrypted connection. +// -int /* O - Status of call (0 = success) */ +int // O - Status of call (0 = success) httpCopyCredentials( - http_t *http, /* I - Connection to server */ - cups_array_t **credentials) /* O - Array of credentials */ + http_t *http, // I - Connection to server + cups_array_t **credentials) // O - Array of credentials { - unsigned count; /* Number of certificates */ - const gnutls_datum_t *certs; /* Certificates */ + unsigned count; // Number of certificates + const gnutls_datum_t *certs; // Certificates DEBUG_printf(("httpCopyCredentials(http=%p, credentials=%p)", http, credentials)); @@ -339,13 +249,13 @@ httpCopyCredentials( } -/* - * '_httpCreateCredentials()' - Create credentials in the internal format. - */ +// +// '_httpCreateCredentials()' - Create credentials in the internal format. +// -http_tls_credentials_t /* O - Internal credentials */ +http_tls_credentials_t // O - Internal credentials _httpCreateCredentials( - cups_array_t *credentials) /* I - Array of credentials */ + cups_array_t *credentials) // I - Array of credentials { (void)credentials; @@ -353,32 +263,20 @@ _httpCreateCredentials( } -/* - * '_httpFreeCredentials()' - Free internal credentials. - */ +// +// 'httpCredentialsAreValidForName()' - Return whether the credentials are valid for the given name. +// -void -_httpFreeCredentials( - http_tls_credentials_t credentials) /* I - Internal credentials */ -{ - (void)credentials; -} - - -/* - * 'httpCredentialsAreValidForName()' - Return whether the credentials are valid for the given name. - */ - -int /* O - 1 if valid, 0 otherwise */ +int // O - 1 if valid, 0 otherwise httpCredentialsAreValidForName( - cups_array_t *credentials, /* I - Credentials */ - const char *common_name) /* I - Name to check */ + cups_array_t *credentials, // I - Credentials + const char *common_name) // I - Name to check { - gnutls_x509_crt_t cert; /* Certificate */ - int result = 0; /* Result */ + gnutls_x509_crt_t cert; // Certificate + int result = 0; // Result - cert = http_gnutls_create_credential((http_credential_t *)cupsArrayGetFirst(credentials)); + cert = gnutls_create_credential((http_credential_t *)cupsArrayGetFirst(credentials)); if (cert) { result = gnutls_x509_crt_check_hostname(cert, common_name) != 0; @@ -386,11 +284,11 @@ httpCredentialsAreValidForName( if (result) { gnutls_x509_crl_iter_t iter = NULL; - /* Iterator */ - unsigned char cserial[1024], /* Certificate serial number */ - rserial[1024]; /* Revoked serial number */ - size_t cserial_size, /* Size of cert serial number */ - rserial_size; /* Size of revoked serial number */ + // Iterator + unsigned char cserial[1024], // Certificate serial number + rserial[1024]; // Revoked serial number + size_t cserial_size, // Size of cert serial number + rserial_size; // Size of revoked serial number cupsMutexLock(&tls_mutex); @@ -424,21 +322,21 @@ httpCredentialsAreValidForName( } -/* - * 'httpCredentialsGetTrust()' - Return the trust of credentials. - */ +// +// 'httpCredentialsGetTrust()' - Return the trust of credentials. +// -http_trust_t /* O - Level of trust */ +http_trust_t // O - Level of trust httpCredentialsGetTrust( - cups_array_t *credentials, /* I - Credentials */ - const char *common_name) /* I - Common name for trust lookup */ + cups_array_t *credentials, // I - Credentials + const char *common_name) // I - Common name for trust lookup { http_trust_t trust = HTTP_TRUST_OK; - /* Trusted? */ - gnutls_x509_crt_t cert; /* Certificate */ - cups_array_t *tcreds = NULL; /* Trusted credentials */ + // Trusted? + gnutls_x509_crt_t cert; // Certificate + cups_array_t *tcreds = NULL; // Trusted credentials _cups_globals_t *cg = _cupsGlobals(); - /* Per-thread globals */ + // Per-thread globals if (!common_name) @@ -447,7 +345,7 @@ httpCredentialsGetTrust( return (HTTP_TRUST_UNKNOWN); } - if ((cert = http_gnutls_create_credential((http_credential_t *)cupsArrayGetFirst(credentials))) == NULL) + if ((cert = gnutls_create_credential((http_credential_t *)cupsArrayGetFirst(credentials))) == NULL) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create credentials from array."), 1); return (HTTP_TRUST_UNKNOWN); @@ -456,66 +354,48 @@ httpCredentialsGetTrust( if (cg->any_root < 0) { _cupsSetDefaults(); - http_gnutls_load_crl(); + gnutls_load_crl(); } - /* - * Look this common name up in the default keychains... - */ - + // Look this common name up in the default keychains... httpLoadCredentials(NULL, &tcreds, common_name); if (tcreds) { - char credentials_str[1024], /* String for incoming credentials */ - tcreds_str[1024]; /* String for saved credentials */ + char credentials_str[1024], // String for incoming credentials + tcreds_str[1024]; // String for saved credentials httpCredentialsString(credentials, credentials_str, sizeof(credentials_str)); httpCredentialsString(tcreds, tcreds_str, sizeof(tcreds_str)); if (strcmp(credentials_str, tcreds_str)) { - /* - * Credentials don't match, let's look at the expiration date of the new - * credentials and allow if the new ones have a later expiration... - */ - + // Credentials don't match, let's look at the expiration date of the new + // credentials and allow if the new ones have a later expiration... if (!cg->trust_first) { - /* - * Do not trust certificates on first use... - */ - + // Do not trust certificates on first use... _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Trust on first use is disabled."), 1); trust = HTTP_TRUST_INVALID; } else if (httpCredentialsGetExpiration(credentials) <= httpCredentialsGetExpiration(tcreds)) { - /* - * The new credentials are not newly issued... - */ - + // The new credentials are not newly issued... _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("New credentials are older than stored credentials."), 1); trust = HTTP_TRUST_INVALID; } else if (!httpCredentialsAreValidForName(credentials, common_name)) { - /* - * The common name does not match the issued certificate... - */ - + // The common name does not match the issued certificate... _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("New credentials are not valid for name."), 1); trust = HTTP_TRUST_INVALID; } else if (httpCredentialsGetExpiration(tcreds) < time(NULL)) { - /* - * Save the renewed credentials... - */ - + // Save the renewed credentials... trust = HTTP_TRUST_RENEWED; httpSaveCredentials(NULL, credentials, common_name); @@ -531,33 +411,24 @@ httpCredentialsGetTrust( } else if (!cg->trust_first) { - /* - * See if we have a site CA certificate we can compare... - */ - + // See if we have a site CA certificate we can compare... if (!httpLoadCredentials(NULL, &tcreds, "site")) { if (cupsArrayGetCount(credentials) != (cupsArrayGetCount(tcreds) + 1)) { - /* - * Certificate isn't directly generated from the CA cert... - */ - + // Certificate isn't directly generated from the CA cert... trust = HTTP_TRUST_INVALID; } else { - /* - * Do a tail comparison of the two certificates... - */ + // Do a tail comparison of the two certificates... + http_credential_t *a, *b; // Certificates - http_credential_t *a, *b; /* Certificates */ - - for (a = (http_credential_t *)cupsArrayGetFirst(tcreds), b = (http_credential_t *)cupsArrayGetElement(credentials, 1); - a && b; - a = (http_credential_t *)cupsArrayGetNext(tcreds), b = (http_credential_t *)cupsArrayGetNext(credentials)) + for (a = (http_credential_t *)cupsArrayGetFirst(tcreds), b = (http_credential_t *)cupsArrayGetElement(credentials, 1); a && b; a = (http_credential_t *)cupsArrayGetNext(tcreds), b = (http_credential_t *)cupsArrayGetNext(credentials)) + { if (a->datalen != b->datalen || memcmp(a->data, b->data, a->datalen)) break; + } if (a || b) trust = HTTP_TRUST_INVALID; @@ -575,7 +446,7 @@ httpCredentialsGetTrust( if (trust == HTTP_TRUST_OK && !cg->expired_certs) { - time_t curtime; /* Current date/time */ + time_t curtime; // Current date/time time(&curtime); if (curtime < gnutls_x509_crt_get_activation_time(cert) || @@ -598,19 +469,19 @@ httpCredentialsGetTrust( } -/* - * 'httpCredentialsGetExpiration()' - Return the expiration date of the credentials. - */ +// +// 'httpCredentialsGetExpiration()' - Return the expiration date of the credentials. +// -time_t /* O - Expiration date of credentials */ +time_t // O - Expiration date of credentials httpCredentialsGetExpiration( - cups_array_t *credentials) /* I - Credentials */ + cups_array_t *credentials) // I - Credentials { - gnutls_x509_crt_t cert; /* Certificate */ - time_t result = 0; /* Result */ + gnutls_x509_crt_t cert; // Certificate + time_t result = 0; // Result - cert = http_gnutls_create_credential((http_credential_t *)cupsArrayGetFirst(credentials)); + cert = gnutls_create_credential((http_credential_t *)cupsArrayGetFirst(credentials)); if (cert) { result = gnutls_x509_crt_get_expiration_time(cert); @@ -621,18 +492,18 @@ httpCredentialsGetExpiration( } -/* - * 'httpCredentialsString()' - Return a string representing the credentials. - */ +// +// 'httpCredentialsString()' - Return a string representing the credentials. +// -size_t /* O - Total size of credentials string */ +size_t // O - Total size of credentials string httpCredentialsString( - cups_array_t *credentials, /* I - Credentials */ - char *buffer, /* I - Buffer or @code NULL@ */ - size_t bufsize) /* I - Size of buffer */ + cups_array_t *credentials, // I - Credentials + char *buffer, // I - Buffer or @code NULL@ + size_t bufsize) // I - Size of buffer { - http_credential_t *first; /* First certificate */ - gnutls_x509_crt_t cert; /* Certificate */ + http_credential_t *first; // First certificate + gnutls_x509_crt_t cert; // Certificate DEBUG_printf(("httpCredentialsString(credentials=%p, buffer=%p, bufsize=" CUPS_LLFMT ")", credentials, buffer, CUPS_LLCAST bufsize)); @@ -644,15 +515,15 @@ httpCredentialsString( *buffer = '\0'; if ((first = (http_credential_t *)cupsArrayGetFirst(credentials)) != NULL && - (cert = http_gnutls_create_credential(first)) != NULL) + (cert = gnutls_create_credential(first)) != NULL) { - char name[256], /* Common name associated with cert */ - issuer[256]; /* Issuer associated with cert */ - size_t len; /* Length of string */ - time_t expiration; /* Expiration date of cert */ + char name[256], // Common name associated with cert + issuer[256]; // Issuer associated with cert + size_t len; // Length of string + time_t expiration; // Expiration date of cert char expstr[256]; // Expiration date as string */ - int sigalg; /* Signature algorithm */ - unsigned char md5_digest[16]; /* MD5 result */ + int sigalg; // Signature algorithm + unsigned char md5_digest[16]; // MD5 result len = sizeof(name) - 1; if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, name, &len) >= 0) @@ -682,214 +553,39 @@ httpCredentialsString( } -/* - * 'httpLoadCredentials()' - Load X.509 credentials from a keychain file. - */ - -bool // O - `true` on success, `false` on error -httpLoadCredentials( - const char *path, /* I - Keychain/PKCS#12 path */ - cups_array_t **credentials, /* IO - Credentials */ - const char *common_name) /* I - Common name for credentials */ -{ - cups_file_t *fp; /* Certificate file */ - char filename[1024], /* filename.crt */ - temp[1024], /* Temporary string */ - line[256]; /* Base64-encoded line */ - unsigned char *data = NULL; /* Buffer for cert data */ - size_t alloc_data = 0, /* Bytes allocated */ - num_data = 0; /* Bytes used */ - size_t decoded; /* Bytes decoded */ - int in_certificate = 0; - /* In a certificate? */ - - - if (credentials) - *credentials = NULL; - - if (!credentials || !common_name) - return (false); - - if (!path) - path = http_gnutls_default_path(temp, sizeof(temp)); - if (!path) - return (false); - - http_gnutls_make_path(filename, sizeof(filename), path, common_name, "crt"); - - if ((fp = cupsFileOpen(filename, "r")) == NULL) - return (false); - - while (cupsFileGets(fp, line, sizeof(line))) - { - if (!strcmp(line, "-----BEGIN CERTIFICATE-----")) - { - if (in_certificate) - { - /* - * Missing END CERTIFICATE... - */ - - httpFreeCredentials(*credentials); - *credentials = NULL; - break; - } - - in_certificate = 1; - } - else if (!strcmp(line, "-----END CERTIFICATE-----")) - { - if (!in_certificate || !num_data) - { - /* - * Missing data... - */ - - httpFreeCredentials(*credentials); - *credentials = NULL; - break; - } - - if (!*credentials) - *credentials = cupsArrayNew(NULL, NULL, NULL, 0, NULL, NULL); - - if (httpAddCredential(*credentials, data, num_data)) - { - httpFreeCredentials(*credentials); - *credentials = NULL; - break; - } - - num_data = 0; - in_certificate = 0; - } - else if (in_certificate) - { - if (alloc_data == 0) - { - data = malloc(2048); - alloc_data = 2048; - - if (!data) - break; - } - else if ((num_data + strlen(line)) >= alloc_data) - { - unsigned char *tdata = realloc(data, alloc_data + 1024); - /* Expanded buffer */ - - if (!tdata) - { - httpFreeCredentials(*credentials); - *credentials = NULL; - break; - } - - data = tdata; - alloc_data += 1024; - } - - decoded = (size_t)(alloc_data - num_data); - httpDecode64((char *)data + num_data, &decoded, line, NULL); - num_data += (size_t)decoded; - } - } - - cupsFileClose(fp); - - if (in_certificate) - { - /* - * Missing END CERTIFICATE... - */ - - httpFreeCredentials(*credentials); - *credentials = NULL; - } - - if (data) - free(data); - - return (*credentials != NULL); -} - - -/* - * 'httpSaveCredentials()' - Save X.509 credentials to a keychain file. - */ +// +// '_httpFreeCredentials()' - Free internal credentials. +// -bool // O - `true` on success, `false` on error -httpSaveCredentials( - const char *path, /* I - Keychain/PKCS#12 path */ - cups_array_t *credentials, /* I - Credentials */ - const char *common_name) /* I - Common name for credentials */ +void +_httpFreeCredentials( + http_tls_credentials_t credentials) // I - Internal credentials { - cups_file_t *fp; /* Certificate file */ - char filename[1024], /* filename.crt */ - nfilename[1024],/* filename.crt.N */ - temp[1024], /* Temporary string */ - line[256]; /* Base64-encoded line */ - const unsigned char *ptr; /* Pointer into certificate */ - ssize_t remaining; /* Bytes left */ - http_credential_t *cred; /* Current credential */ - - - if (!credentials || !common_name) - return (false); - - if (!path) - path = http_gnutls_default_path(temp, sizeof(temp)); - if (!path) - return (false); - - http_gnutls_make_path(filename, sizeof(filename), path, common_name, "crt"); - snprintf(nfilename, sizeof(nfilename), "%s.N", filename); - - if ((fp = cupsFileOpen(nfilename, "w")) == NULL) - return (false); - - fchmod(cupsFileNumber(fp), 0600); - - for (cred = (http_credential_t *)cupsArrayGetFirst(credentials); - cred; - cred = (http_credential_t *)cupsArrayGetNext(credentials)) - { - cupsFilePuts(fp, "-----BEGIN CERTIFICATE-----\n"); - for (ptr = cred->data, remaining = (ssize_t)cred->datalen; remaining > 0; remaining -= 45, ptr += 45) - { - httpEncode64(line, sizeof(line), (char *)ptr, remaining > 45 ? 45 : (size_t)remaining, false); - cupsFilePrintf(fp, "%s\n", line); - } - cupsFilePuts(fp, "-----END CERTIFICATE-----\n"); - } - - cupsFileClose(fp); - - return (!rename(nfilename, filename)); + (void)credentials; } -/* - * 'http_gnutls_create_credential()' - Create a single credential in the internal format. - */ +// +// 'gnutls_create_credential()' - Create a single credential in the internal format. +// -static gnutls_x509_crt_t /* O - Certificate */ -http_gnutls_create_credential( - http_credential_t *credential) /* I - Credential */ +static gnutls_x509_crt_t // O - Certificate +gnutls_create_credential( + http_credential_t *credential) // I - Credential { - int result; /* Result from GNU TLS */ - gnutls_x509_crt_t cert; /* Certificate */ - gnutls_datum_t datum; /* Data record */ + int result; // Result from GNU TLS + gnutls_x509_crt_t cert; // Certificate + gnutls_datum_t datum; // Data record - DEBUG_printf(("3http_gnutls_create_credential(credential=%p)", credential)); + DEBUG_printf(("3gnutls_create_credential(credential=%p)", credential)); if (!credential) return (NULL); if ((result = gnutls_x509_crt_init(&cert)) < 0) { - DEBUG_printf(("4http_gnutls_create_credential: init error: %s", gnutls_strerror(result))); + DEBUG_printf(("4gnutls_create_credential: init error: %s", gnutls_strerror(result))); return (NULL); } @@ -898,7 +594,7 @@ http_gnutls_create_credential( if ((result = gnutls_x509_crt_import(cert, &datum, GNUTLS_X509_FMT_DER)) < 0) { - DEBUG_printf(("4http_gnutls_create_credential: import error: %s", gnutls_strerror(result))); + DEBUG_printf(("4gnutls_create_credential: import error: %s", gnutls_strerror(result))); gnutls_x509_crt_deinit(cert); return (NULL); @@ -908,79 +604,72 @@ http_gnutls_create_credential( } -/* - * 'http_gnutls_default_path()' - Get the default credential store path. - */ +// +// 'gnutls_create_key()' - Create a private key. +// -static const char * /* O - Path or NULL on error */ -http_gnutls_default_path(char *buffer,/* I - Path buffer */ - size_t bufsize)/* I - Size of path buffer */ +static gnutls_x509_privkey_t // O - Private key +gnutls_create_key(cups_credtype_t type) // I - Type of key { - _cups_globals_t *cg = _cupsGlobals(); - /* Pointer to library globals */ + gnutls_x509_privkey_t key; // Private key - if (cg->userconfig) - { - if (mkdir(cg->userconfig, 0755) && errno != EEXIST) - { - DEBUG_printf(("1http_gnutls_default_path: Failed to make directory '%s': %s", cg->userconfig, strerror(errno))); - return (NULL); - } - - snprintf(buffer, bufsize, "%s/ssl", cg->userconfig); + gnutls_x509_privkey_init(&key); - if (mkdir(buffer, 0700) && errno != EEXIST) - { - DEBUG_printf(("1http_gnutls_default_path: Failed to make directory '%s': %s", buffer, strerror(errno))); - return (NULL); - } - } - else + switch (type) { - if (mkdir(cg->sysconfig, 0755) && errno != EEXIST) - { - DEBUG_printf(("1http_gnutls_default_path: Failed to make directory '%s': %s", cg->sysconfig, strerror(errno))); - return (NULL); - } - - snprintf(buffer, bufsize, "%s/ssl", cg->sysconfig); - - if (mkdir(buffer, 0700) && errno != EEXIST) - { - DEBUG_printf(("1http_gnutls_default_path: Failed to make directory '%s': %s", buffer, strerror(errno))); - return (NULL); - } + case CUPS_CREDTYPE_ECDSA_P256_SHA256 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_ECDSA, GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1), 0); + break; + + case CUPS_CREDTYPE_ECDSA_P384_SHA256 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_ECDSA, GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP384R1), 0); + break; + + case CUPS_CREDTYPE_ECDSA_P521_SHA256 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_ECDSA, GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP521R1), 0); + break; + + case CUPS_CREDTYPE_RSA_2048_SHA256 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 2048, 0); + break; + + default : + case CUPS_CREDTYPE_RSA_3072_SHA256 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 3072, 0); + break; + + case CUPS_CREDTYPE_RSA_4096_SHA256 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 4096, 0); + break; } - DEBUG_printf(("1http_gnutls_default_path: Using default path \"%s\".", buffer)); - - return (buffer); + return (key); } -/* - * 'http_gnutls_load_crl()' - Load the certificate revocation list, if any. - */ +// +// 'gnutls_load_crl()' - Load the certificate revocation list, if any. +// static void -http_gnutls_load_crl(void) +gnutls_load_crl(void) { cupsMutexLock(&tls_mutex); if (!gnutls_x509_crl_init(&tls_crl)) { - cups_file_t *fp; /* CRL file */ - char filename[1024], /* site.crl */ - line[256]; /* Base64-encoded line */ - unsigned char *data = NULL; /* Buffer for cert data */ - size_t alloc_data = 0, /* Bytes allocated */ - num_data = 0; /* Bytes used */ - size_t decoded; /* Bytes decoded */ - gnutls_datum_t datum; /* Data record */ + cups_file_t *fp; // CRL file + char filename[1024], // site.crl + line[256]; // Base64-encoded line + unsigned char *data = NULL; // Buffer for cert data + size_t alloc_data = 0, // Bytes allocated + num_data = 0; // Bytes used + size_t decoded; // Bytes decoded + gnutls_datum_t datum; // Data record - http_gnutls_make_path(filename, sizeof(filename), CUPS_SERVERROOT, "site", "crl"); + http_make_path(filename, sizeof(filename), CUPS_SERVERROOT, "site", "crl"); if ((fp = cupsFileOpen(filename, "r")) != NULL) { @@ -990,10 +679,7 @@ http_gnutls_load_crl(void) { if (num_data) { - /* - * Missing END X509 CRL... - */ - + // Missing END X509 CRL... break; } } @@ -1001,10 +687,7 @@ http_gnutls_load_crl(void) { if (!num_data) { - /* - * Missing data... - */ - + // Missing data... break; } @@ -1028,7 +711,7 @@ http_gnutls_load_crl(void) else if ((num_data + strlen(line)) >= alloc_data) { unsigned char *tdata = realloc(data, alloc_data + 1024); - /* Expanded buffer */ + // Expanded buffer if (!tdata) break; @@ -1054,68 +737,27 @@ http_gnutls_load_crl(void) } -/* - * 'http_gnutls_make_path()' - Format a filename for a certificate or key file. - */ +// +// 'gnutls_read()' - Read function for the GNU TLS library. +// -static const char * /* O - Filename */ -http_gnutls_make_path( - char *buffer, /* I - Filename buffer */ - size_t bufsize, /* I - Size of buffer */ - const char *dirname, /* I - Directory */ - const char *filename, /* I - Filename (usually hostname) */ - const char *ext) /* I - Extension */ +static ssize_t // O - Number of bytes read or -1 on error +gnutls_read( + gnutls_transport_ptr_t ptr, // I - Connection to server + void *data, // I - Buffer + size_t length) // I - Number of bytes to read { - char *bufptr, /* Pointer into buffer */ - *bufend = buffer + bufsize - 1; /* End of buffer */ - + http_t *http; // HTTP connection + ssize_t bytes; // Bytes read - snprintf(buffer, bufsize, "%s/", dirname); - bufptr = buffer + strlen(buffer); - while (*filename && bufptr < bufend) - { - if (_cups_isalnum(*filename) || *filename == '-' || *filename == '.') - *bufptr++ = *filename; - else - *bufptr++ = '_'; - - filename ++; - } - - if (bufptr < bufend) - *bufptr++ = '.'; - - cupsCopyString(bufptr, ext, (size_t)(bufend - bufptr + 1)); - - return (buffer); -} - - -/* - * 'http_gnutls_read()' - Read function for the GNU TLS library. - */ - -static ssize_t /* O - Number of bytes read or -1 on error */ -http_gnutls_read( - gnutls_transport_ptr_t ptr, /* I - Connection to server */ - void *data, /* I - Buffer */ - size_t length) /* I - Number of bytes to read */ -{ - http_t *http; /* HTTP connection */ - ssize_t bytes; /* Bytes read */ - - - DEBUG_printf(("5http_gnutls_read(ptr=%p, data=%p, length=%d)", ptr, data, (int)length)); + DEBUG_printf(("5gnutls_read(ptr=%p, data=%p, length=%d)", ptr, data, (int)length)); http = (http_t *)ptr; if (!http->blocking || http->timeout_value > 0.0) { - /* - * Make sure we have data before we read... - */ - + // Make sure we have data before we read... while (!_httpWait(http, http->wait_value, 0)) { if (http->timeout_cb && (*http->timeout_cb)(http, http->timeout_data)) @@ -1127,79 +769,73 @@ http_gnutls_read( } bytes = recv(http->fd, data, length, 0); - DEBUG_printf(("5http_gnutls_read: bytes=%d", (int)bytes)); + DEBUG_printf(("5gnutls_read: bytes=%d", (int)bytes)); return (bytes); } -/* - * 'http_gnutls_write()' - Write function for the GNU TLS library. - */ +// +// 'gnutls_write()' - Write function for the GNU TLS library. +// -static ssize_t /* O - Number of bytes written or -1 on error */ -http_gnutls_write( - gnutls_transport_ptr_t ptr, /* I - Connection to server */ - const void *data, /* I - Data buffer */ - size_t length) /* I - Number of bytes to write */ +static ssize_t // O - Number of bytes written or -1 on error +gnutls_write( + gnutls_transport_ptr_t ptr, // I - Connection to server + const void *data, // I - Data buffer + size_t length) // I - Number of bytes to write { - ssize_t bytes; /* Bytes written */ + ssize_t bytes; // Bytes written - DEBUG_printf(("5http_gnutls_write(ptr=%p, data=%p, length=%d)", ptr, data, + DEBUG_printf(("5gnutls_write(ptr=%p, data=%p, length=%d)", ptr, data, (int)length)); bytes = send(((http_t *)ptr)->fd, data, length, 0); - DEBUG_printf(("5http_gnutls_write: bytes=%d", (int)bytes)); + DEBUG_printf(("5gnutls_write: bytes=%d", (int)bytes)); return (bytes); } -/* - * '_httpTLSInitialize()' - Initialize the TLS stack. - */ +// +// '_httpTLSInitialize()' - Initialize the TLS stack. +// void _httpTLSInitialize(void) { - /* - * Initialize GNU TLS... - */ - + // Initialize GNU TLS... gnutls_global_init(); } -/* - * '_httpTLSPending()' - Return the number of pending TLS-encrypted bytes. - */ +// +// '_httpTLSPending()' - Return the number of pending TLS-encrypted bytes. +// -size_t /* O - Bytes available */ -_httpTLSPending(http_t *http) /* I - HTTP connection */ +size_t // O - Bytes available +_httpTLSPending(http_t *http) // I - HTTP connection { return (gnutls_record_check_pending(http->tls)); } -/* - * '_httpTLSRead()' - Read from a SSL/TLS connection. - */ +// +// '_httpTLSRead()' - Read from a SSL/TLS connection. +// -int /* O - Bytes read */ -_httpTLSRead(http_t *http, /* I - Connection to server */ - char *buf, /* I - Buffer to store data */ - int len) /* I - Length of buffer */ +int // O - Bytes read +_httpTLSRead(http_t *http, // I - Connection to server + char *buf, // I - Buffer to store data + int len) // I - Length of buffer { - ssize_t result; /* Return value */ + ssize_t result; // Return value result = gnutls_record_recv(http->tls, buf, (size_t)len); if (result < 0 && !errno) { - /* - * Convert GNU TLS error to errno value... - */ - + // Convert GNU TLS error to errno value... switch (result) { case GNUTLS_E_INTERRUPTED : @@ -1222,43 +858,25 @@ _httpTLSRead(http_t *http, /* I - Connection to server */ } -/* - * '_httpTLSSetOptions()' - Set TLS protocol and cipher suite options. - */ - -void -_httpTLSSetOptions(int options, /* I - Options */ - int min_version, /* I - Minimum TLS version */ - int max_version) /* I - Maximum TLS version */ -{ - if (!(options & _HTTP_TLS_SET_DEFAULT) || tls_options < 0) - { - tls_options = options; - tls_min_version = min_version; - tls_max_version = max_version; - } -} - - -/* - * '_httpTLSStart()' - Set up SSL/TLS support on a connection. - */ +// +// '_httpTLSStart()' - Set up SSL/TLS support on a connection. +// -bool /* O - `true` on success, `false` on failure */ -_httpTLSStart(http_t *http) /* I - Connection to server */ +bool // O - `true` on success, `false` on failure +_httpTLSStart(http_t *http) // I - Connection to server { - char hostname[256], /* Hostname */ - *hostptr; /* Pointer into hostname */ - int status; /* Status of handshake */ + char hostname[256], // Hostname + *hostptr; // Pointer into hostname + int status; // Status of handshake gnutls_certificate_credentials_t *credentials; - /* TLS credentials */ + // TLS credentials char priority_string[2048]; - /* Priority string */ - int version; /* Current version */ - double old_timeout; /* Old timeout value */ - http_timeout_cb_t old_cb; /* Old timeout callback */ - void *old_data; /* Old timeout data */ - static const char * const versions[] =/* SSL/TLS versions */ + // Priority string + int version; // Current version + double old_timeout; // Old timeout value + http_timeout_cb_t old_cb; // Old timeout callback + void *old_data; // Old timeout data + static const char * const versions[] =// SSL/TLS versions { "VERS-SSL3.0", "VERS-TLS1.0", @@ -1324,23 +942,16 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ if (http->mode == _HTTP_MODE_CLIENT) { - /* - * Client: get the hostname to use for TLS... - */ - + // Client: get the hostname to use for TLS... if (httpAddrIsLocalhost(http->hostaddr)) { cupsCopyString(hostname, "localhost", sizeof(hostname)); } else { - /* - * Otherwise make sure the hostname we have does not end in a trailing dot. - */ - + // Otherwise make sure the hostname we have does not end in a trailing dot. cupsCopyString(hostname, http->hostname, sizeof(hostname)); - if ((hostptr = hostname + strlen(hostname) - 1) >= hostname && - *hostptr == '.') + if ((hostptr = hostname + strlen(hostname) - 1) >= hostname && *hostptr == '.') *hostptr = '\0'; } @@ -1348,10 +959,7 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ } else { - /* - * Server: get certificate and private key... - */ - + // Server: get certificate and private key... char crtfile[1024], // Certificate file keyfile[1024]; // Private key file const char *cn, // Common name to lookup @@ -1360,20 +968,14 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ if (http->fields[HTTP_FIELD_HOST]) { - /* - * Use hostname for TLS upgrade... - */ - + // Use hostname for TLS upgrade... cupsCopyString(hostname, http->fields[HTTP_FIELD_HOST], sizeof(hostname)); } else { - /* - * Resolve hostname from connection address... - */ - - http_addr_t addr; /* Connection address */ - socklen_t addrlen; /* Length of address */ + // Resolve hostname from connection address... + http_addr_t addr; // Connection address + socklen_t addrlen; // Length of address addrlen = sizeof(addr); if (getsockname(http->fd, (struct sockaddr *)&addr, &addrlen)) @@ -1382,7 +984,9 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ hostname[0] = '\0'; } else if (httpAddrIsLocalhost(&addr)) + { hostname[0] = '\0'; + } else { httpAddrLookup(&addr, hostname, sizeof(hostname)); @@ -1391,7 +995,7 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ } if (isdigit(hostname[0] & 255) || hostname[0] == '[') - hostname[0] = '\0'; /* Don't allow numeric addresses */ + hostname[0] = '\0'; // Don't allow numeric addresses cupsMutexLock(&tls_mutex); @@ -1403,13 +1007,13 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ if (cn) { // First look in the CUPS keystore... - http_gnutls_make_path(crtfile, sizeof(crtfile), tls_keypath, cn, "crt"); - http_gnutls_make_path(keyfile, sizeof(keyfile), tls_keypath, cn, "key"); + http_make_path(crtfile, sizeof(crtfile), tls_keypath, cn, "crt"); + http_make_path(keyfile, sizeof(keyfile), tls_keypath, cn, "key"); if (access(crtfile, R_OK) || access(keyfile, R_OK)) { // No CUPS-managed certs, look for CA certs... - char cacrtfile[1024], cakeyfile[1024]; /* CA cert files */ + char cacrtfile[1024], cakeyfile[1024]; // CA cert files snprintf(cacrtfile, sizeof(cacrtfile), "/etc/letsencrypt/live/%s/fullchain.pem", cn); snprintf(cakeyfile, sizeof(cakeyfile), "/etc/letsencrypt/live/%s/privkey.pem", cn); @@ -1440,9 +1044,9 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ { DEBUG_printf(("4_httpTLSStart: Auto-create credentials for \"%s\".", cn)); - if (!cupsMakeServerCredentials(tls_keypath, cn, 0, NULL, time(NULL) + 3650 * 86400)) + if (!cupsCreateCredentials(tls_keypath, cn, 0, NULL, time(NULL) + 3650 * 86400)) { - DEBUG_puts("4_httpTLSStart: cupsMakeServerCredentials failed."); + DEBUG_puts("4_httpTLSStart: cupsCreateCredentials failed."); http->error = errno = EINVAL; http->status = HTTP_STATUS_ERROR; _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create server credentials."), 1); @@ -1482,10 +1086,7 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ if (tls_max_version < _HTTP_TLS_MAX) { - /* - * Require specific TLS versions... - */ - + // Require specific TLS versions... cupsConcatString(priority_string, ":-VERS-TLS-ALL", sizeof(priority_string)); for (version = tls_min_version; version <= tls_max_version; version ++) { @@ -1495,18 +1096,12 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ } else if (tls_min_version == _HTTP_TLS_SSL3) { - /* - * Allow all versions of TLS and SSL/3.0... - */ - + // Allow all versions of TLS and SSL/3.0... cupsConcatString(priority_string, ":+VERS-TLS-ALL:+VERS-SSL3.0", sizeof(priority_string)); } else { - /* - * Require a minimum version... - */ - + // Require a minimum version... cupsConcatString(priority_string, ":+VERS-TLS-ALL", sizeof(priority_string)); for (version = 0; version < tls_min_version; version ++) { @@ -1529,24 +1124,21 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ gnutls_priority_set_direct(http->tls, priority_string, NULL); #else - gnutls_priority_t priority; /* Priority */ + gnutls_priority_t priority; // Priority gnutls_priority_init(&priority, priority_string, NULL); gnutls_priority_set(http->tls, priority); gnutls_priority_deinit(priority); -#endif /* HAVE_GNUTLS_PRIORITY_SET_DIRECT */ +#endif // HAVE_GNUTLS_PRIORITY_SET_DIRECT gnutls_transport_set_ptr(http->tls, (gnutls_transport_ptr_t)http); - gnutls_transport_set_pull_function(http->tls, http_gnutls_read); + gnutls_transport_set_pull_function(http->tls, gnutls_read); #ifdef HAVE_GNUTLS_TRANSPORT_SET_PULL_TIMEOUT_FUNCTION gnutls_transport_set_pull_timeout_function(http->tls, (gnutls_pull_timeout_func)httpWait); -#endif /* HAVE_GNUTLS_TRANSPORT_SET_PULL_TIMEOUT_FUNCTION */ - gnutls_transport_set_push_function(http->tls, http_gnutls_write); - - /* - * Enforce a minimum timeout of 10 seconds for the TLS handshake... - */ +#endif // HAVE_GNUTLS_TRANSPORT_SET_PULL_TIMEOUT_FUNCTION + gnutls_transport_set_push_function(http->tls, gnutls_write); + // Enforce a minimum timeout of 10 seconds for the TLS handshake... old_timeout = http->timeout_value; old_cb = http->timeout_cb; old_data = http->timeout_data; @@ -1557,10 +1149,7 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ httpSetTimeout(http, 10.0, NULL, NULL); } - /* - * Do the TLS handshake... - */ - + // Do the TLS handshake... while ((status = gnutls_handshake(http->tls)) != GNUTLS_E_SUCCESS) { DEBUG_printf(("5_httpStartTLS: gnutls_handshake returned %d (%s)", @@ -1584,10 +1173,7 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ } } - /* - * Restore the previous timeout settings... - */ - + // Restore the previous timeout settings... httpSetTimeout(http, old_timeout, old_cb, old_data); http->tls_credentials = credentials; @@ -1596,14 +1182,14 @@ _httpTLSStart(http_t *http) /* I - Connection to server */ } -/* - * '_httpTLSStop()' - Shut down SSL/TLS on a connection. - */ +// +// '_httpTLSStop()' - Shut down SSL/TLS on a connection. +// void -_httpTLSStop(http_t *http) /* I - Connection to server */ +_httpTLSStop(http_t *http) // I - Connection to server { - int error; /* Error code */ + int error; // Error code error = gnutls_bye(http->tls, http->mode == _HTTP_MODE_CLIENT ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR); @@ -1622,16 +1208,16 @@ _httpTLSStop(http_t *http) /* I - Connection to server */ } -/* - * '_httpTLSWrite()' - Write to a SSL/TLS connection. - */ +// +// '_httpTLSWrite()' - Write to a SSL/TLS connection. +// -int /* O - Bytes written */ -_httpTLSWrite(http_t *http, /* I - Connection to server */ - const char *buf, /* I - Buffer holding data */ - int len) /* I - Length of buffer */ +int // O - Bytes written +_httpTLSWrite(http_t *http, // I - Connection to server + const char *buf, // I - Buffer holding data + int len) // I - Length of buffer { - ssize_t result; /* Return value */ + ssize_t result; // Return value DEBUG_printf(("5_httpTLSWrite(http=%p, buf=%p, len=%d)", http, buf, len)); @@ -1640,10 +1226,7 @@ _httpTLSWrite(http_t *http, /* I - Connection to server */ if (result < 0 && !errno) { - /* - * Convert GNU TLS error to errno value... - */ - + // Convert GNU TLS error to errno value... switch (result) { case GNUTLS_E_INTERRUPTED : diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index c07fc3d83..172e82f89 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -11,7 +11,6 @@ // information. // -#include #include @@ -19,7 +18,6 @@ // Local functions... // -static void http_add_san(GENERAL_NAMES *gens, const char *name); static long http_bio_ctrl(BIO *h, int cmd, long arg1, void *arg2); static int http_bio_free(BIO *data); @@ -28,44 +26,46 @@ static int http_bio_puts(BIO *h, const char *str); static int http_bio_read(BIO *h, char *buf, int size); static int http_bio_write(BIO *h, const char *buf, int num); -static X509 *http_create_credential(http_credential_t *credential); -static X509_NAME *http_create_name(const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name); -static EVP_PKEY *http_create_key(cups_credtype_t type); -static GENERAL_NAMES *http_create_san(const char *common_name, size_t num_alt_names, const char **alt_names); - -static const char *http_default_path(char *buffer, size_t bufsize); - -static time_t http_get_date(X509 *cert, int which); - -//static void http_load_crl(void); - -static const char *http_make_path(char *buffer, size_t bufsize, const char *dirname, const char *filename, const char *ext); - -static bool http_x509_add_ext(X509 *cert, int nid, const char *value); +static bool openssl_add_ext(STACK_OF(X509_EXTENSION) *exts, int nid, const char *value); +static X509 *openssl_create_credential(http_credential_t *credential); +static X509_NAME *openssl_create_name(const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name); +static EVP_PKEY *openssl_create_key(cups_credtype_t type); +static bool openssl_create_san(STACK_OF(X509_EXTENSION) *exts, const char *common_name, size_t num_alt_names, const char **alt_names); +static time_t openssl_get_date(X509 *cert, int which); +//static void openssl_load_crl(void); // // Local globals... // -static bool tls_auto_create = false; - // Auto-create self-signed certs? static BIO_METHOD *tls_bio_method = NULL; // OpenSSL BIO method -static char *tls_common_name = NULL; - // Default common name -//static X509_CRL *tls_crl = NULL;// Certificate revocation list -static char *tls_keypath = NULL; - // Server cert keychain path -static cups_mutex_t tls_mutex = CUPS_MUTEX_INITIALIZER; - // Mutex for keychain/certs -static int tls_options = -1,// Options for TLS connections - tls_min_version = _HTTP_TLS_1_2, - tls_max_version = _HTTP_TLS_MAX; - - -// -// 'cupsMakeServerCredentials()' - Make an X.509 certificate and private key pair. +static const char * const tls_purpose_oids[] = +{ // OIDs for each key purpose value + "1.3.6.1.5.5.7.3.1", // serverAuth + "1.3.6.1.5.5.7.3.2", // clientAuth + "1.3.6.1.5.5.7.3.3", // codeSigning + "1.3.6.1.5.5.7.3.4", // emailProtection + "1.3.6.1.5.5.7.3.8", // timeStamping + "1.3.6.1.5.5.7.3.9" // OCSPSigning +}; +static const char * const tls_usage_strings[] = +{ // Strings for each key usage value + "digitalSignature", + "nonRepudiation", + "keyEncipherment", + "dataEncipherment", + "keyAgreement", + "keyCertSign", + "cRLSign", + "encipherOnly", + "decipherOnly" +}; + + +// +// 'cupsCreateCredentials()' - Make an X.509 certificate and private key pair. // // This function creates an X.509 certificate and private key pair. The // certificate and key are stored in the directory "path" or, if "path" is @@ -77,20 +77,22 @@ static int tls_options = -1,// Options for TLS connections // bool // O - `true` on success, `false` on failure -cupsMakeServerCredentials( - const char *path, // I - Directory path for certificate/key store or `NULL` for default - cups_credtype_t type, // I - Type of certificate/keys to generate - const char *organization, // I - Organization or `NULL` to use common name - const char *org_unit, // I - Organizational unit or `NULL` for none - const char *locality, // I - City/town or `NULL` for "Unknown" - const char *state_province, // I - State/province or `NULL` for "Unknown" - const char *country, // I - Country or `NULL` for locale-based default - const char *root_name, // I - Root certificate/domain name or `NULL` for site/self-signed - bool ca_cert, // I - Make CA certificate? - const char *common_name, // I - Common name - size_t num_alt_names, // I - Number of subject alternate names - const char **alt_names, // I - Subject Alternate Names - time_t expiration_date) // I - Expiration date +cupsCreateCredentials( + const char *path, // I - Directory path for certificate/key store or `NULL` for default + bool ca_cert, // I - `true` to create a CA certificate, `false` for a client/server certificate + cups_credpurpose_t purpose, // I - Credential purposes + cups_credtype_t type, // I - Credential type + cups_credusage_t usage, // I - Credential usages + const char *organization, // I - Organization or `NULL` to use common name + const char *org_unit, // I - Organizational unit or `NULL` for none + const char *locality, // I - City/town or `NULL` for "Unknown" + const char *state_province, // I - State/province or `NULL` for "Unknown" + const char *country, // I - Country or `NULL` for locale-based default + const char *common_name, // I - Common name + size_t num_alt_names, // I - Number of subject alternate names + const char **alt_names, // I - Subject Alternate Names + const char *root_name, // I - Root certificate/domain name or `NULL` for site/self-signed + time_t expiration_date) // I - Expiration date { bool result = false; // Return value EVP_PKEY *pkey; // Key pair @@ -105,13 +107,18 @@ cupsMakeServerCredentials( ASN1_TIME *notBefore, // Initial date *notAfter; // Expiration date BIO *bio; // Output file - char temp[1024], // Temporary directory name + char temp[1024], // Temporary string + *tempptr, // Pointer into temporary string crtfile[1024], // Certificate filename keyfile[1024]; // Private key filename - GENERAL_NAMES *gens; // Names for SubjectAltName certificate extension + STACK_OF(X509_EXTENSION) *exts; // Extensions + X509_EXTENSION *ext; // Current extension + unsigned i; // Looping var + cups_credpurpose_t purpose_bit; // Current purpose + cups_credusage_t usage_bit; // Current usage - DEBUG_printf(("cupsMakeServerCredentials(path=\"%s\", type=%d, organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", root_name=\"%s\", ca_cert=%s, common_name=\"%s\", num_alt_names=%u, alt_names=%p, expiration_date=%ld)", path, type, organization, org_unit, locality, state_province, country, root_name, ca_cert ? "true" : "false", common_name, (unsigned)num_alt_names, alt_names, (long)expiration_date)); + DEBUG_printf(("cupsCreateCredentials(path=\"%s\", ca_cert=%s, purpose=0x%x, type=%d, usage=0x%x, organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", common_name=\"%s\", num_alt_names=%u, alt_names=%p, root_name=\"%s\", expiration_date=%ld)", path, ca_cert ? "true" : "false", purpose, type, usage, organization, org_unit, locality, state_province, country, common_name, (unsigned)num_alt_names, alt_names, root_name, (long)expiration_date)); // Filenames... if (!path) @@ -127,15 +134,15 @@ cupsMakeServerCredentials( http_make_path(keyfile, sizeof(keyfile), path, common_name, "key"); // Create the encryption key... - DEBUG_puts("1cupsMakeServerCredentials: Creating key pair."); + DEBUG_puts("1cupsCreateCredentials: Creating key pair."); - if ((pkey = http_create_key(type)) == NULL) + if ((pkey = openssl_create_key(type)) == NULL) return (false); - DEBUG_puts("1cupsMakeServerCredentials: Key pair created."); + DEBUG_puts("1cupsCreateCredentials: Key pair created."); // Create the X.509 certificate... - DEBUG_puts("1cupsMakeServerCredentials: Generating self-signed X.509 certificate."); + DEBUG_puts("1cupsCreateCredentials: Generating self-signed X.509 certificate."); if ((cert = X509_new()) == NULL) { @@ -190,7 +197,7 @@ cupsMakeServerCredentials( } - name = http_create_name(organization, org_unit, locality, state_province, country, common_name); + name = openssl_create_name(organization, org_unit, locality, state_province, country, common_name); if (root_cert) X509_set_issuer_name(cert, X509_get_subject_name(root_cert)); @@ -199,32 +206,56 @@ cupsMakeServerCredentials( X509_set_subject_name(cert, name); X509_NAME_free(name); + exts = sk_X509_EXTENSION_new_null(); + if (ca_cert) { // Add extensions that are required to make Chrome happy... - http_x509_add_ext(cert, NID_basic_constraints, "critical,CA:TRUE,pathlen:0"); - http_x509_add_ext(cert, NID_key_usage, "critical,cRLSign,digitalSignature,keyCertSign"); + openssl_add_ext(exts, NID_basic_constraints, "critical,CA:TRUE,pathlen:0"); } else { // Add extension with DNS names and free buffer for GENERAL_NAME - if ((gens = http_create_san(common_name, num_alt_names, alt_names)) == NULL) - { - // TODO: Set error? + if (!openssl_create_san(exts, common_name, num_alt_names, alt_names)) goto done; - } - - X509_add1_ext_i2d(cert, NID_subject_alt_name, gens, 0, X509V3_ADD_DEFAULT); - sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); // Add extensions that are required to make Chrome happy... - http_x509_add_ext(cert, NID_basic_constraints, "critical,CA:FALSE,pathlen:0"); - http_x509_add_ext(cert, NID_key_usage, "critical,digitalSignature,keyEncipherment"); + openssl_add_ext(exts, NID_basic_constraints, "critical,CA:FALSE,pathlen:0"); } - http_x509_add_ext(cert, NID_ext_key_usage, "1.3.6.1.5.5.7.3.1"); - http_x509_add_ext(cert, NID_subject_key_identifier, "hash"); - http_x509_add_ext(cert, NID_authority_key_identifier, "keyid,issuer"); + cupsCopyString(temp, "critical", sizeof(temp)); + for (tempptr = temp + strlen(temp), i = 0, usage_bit = CUPS_CREDUSAGE_DIGITAL_SIGNATURE; i < (sizeof(tls_usage_strings) / sizeof(tls_usage_strings[0])); i ++, usage_bit *= 2) + { + if (!(usage & usage_bit)) + continue; + + snprintf(tempptr, sizeof(temp) - (size_t)(tempptr - temp), ",%s", tls_usage_strings[i]); + + tempptr += strlen(tempptr); + } + openssl_add_ext(exts, NID_key_usage, temp); + + temp[0] = '\0'; + for (tempptr = temp, i = 0, purpose_bit = CUPS_CREDPURPOSE_SERVER_AUTH; i < (sizeof(tls_purpose_oids) / sizeof(tls_purpose_oids[0])); i ++, purpose_bit *= 2) + { + if (!(purpose & purpose_bit)) + continue; + + if (tempptr == temp) + cupsCopyString(temp, tls_purpose_oids[i], sizeof(temp)); + else + snprintf(tempptr, sizeof(temp) - (size_t)(tempptr - temp), ",%s", tls_purpose_oids[i]); + + tempptr += strlen(tempptr); + } + openssl_add_ext(exts, NID_ext_key_usage, temp); + + openssl_add_ext(exts, NID_subject_key_identifier, "hash"); + openssl_add_ext(exts, NID_authority_key_identifier, "keyid,issuer"); + + while ((ext = sk_X509_EXTENSION_pop(exts)) != NULL) + X509_add_ext(cert, ext, -1); + X509_set_version(cert, 2); // v3 if (root_key) @@ -267,7 +298,7 @@ cupsMakeServerCredentials( BIO_free(bio); result = true; - DEBUG_puts("1cupsMakeServerCredentials: Successfully created credentials."); + DEBUG_puts("1cupsCreateCredentials: Successfully created credentials."); // Cleanup... done: @@ -285,28 +316,28 @@ cupsMakeServerCredentials( // -// 'cupsMakeServerRequest()' - Make an X.509 Certificate Signing Request. +// 'cupsCreateCredentialsRequest()' - Make an X.509 Certificate Signing Request. // // This function creates an X.509 certificate signing request (CSR) and // associated private key. The CSR and key are stored in the directory "path" // or, if "path" is `NULL`, in a per-user or system-wide (when running as root) // certificate/key store. // -// The CSR is returned as a string that must be freed using the `free` function. -// -char * // O - PEM-encoded certificate signing request -cupsMakeServerRequest( - const char *path, // I - Directory path for certificate/key store or `NULL` for default - cups_credtype_t type, // I - Type of certificate/keys to generate - const char *organization, // I - Organization or `NULL` to use common name - const char *org_unit, // I - Organizational unit or `NULL` for none - const char *locality, // I - City/town or `NULL` for "Unknown" - const char *state_province, // I - State/province or `NULL` for "Unknown" - const char *country, // I - Country or `NULL` for locale-based default - const char *common_name, // I - Common name - size_t num_alt_names, // I - Number of subject alternate names - const char **alt_names) // I - Subject Alternate Names +bool // O - `true` on success, `false` on error +cupsCreateCredentialsRequest( + const char *path, // I - Directory path for certificate/key store or `NULL` for default + cups_credpurpose_t purpose, // I - Credential purposes + cups_credtype_t type, // I - Credential type + cups_credusage_t usage, // I - Credential usages + const char *organization, // I - Organization or `NULL` to use common name + const char *org_unit, // I - Organizational unit or `NULL` for none + const char *locality, // I - City/town or `NULL` for "Unknown" + const char *state_province, // I - State/province or `NULL` for "Unknown" + const char *country, // I - Country or `NULL` for locale-based default + const char *common_name, // I - Common name + size_t num_alt_names, // I - Number of subject alternate names + const char **alt_names) // I - Subject Alternate Names { char *result = NULL; // Return value EVP_PKEY *pkey; // Key pair @@ -314,12 +345,16 @@ cupsMakeServerRequest( X509_NAME *name; // Subject/issuer name BIO *bio; // Output file char temp[1024], // Temporary directory name + *tempptr, // Pointer into temporary string csrfile[1024], // Certificate signing request filename keyfile[1024]; // Private key filename -// GENERAL_NAMES *gens; // Names for SubjectAltName certificate extension + STACK_OF(X509_EXTENSION) *exts; // Extensions + unsigned i; // Looping var + cups_credpurpose_t purpose_bit; // Current purpose + cups_credusage_t usage_bit; // Current usage - DEBUG_printf(("cupsMakeServerRequest(path=\"%s\", type=%d, organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", common_name=\"%s\", num_alt_names=%u, alt_names=%p)", path, type, organization, org_unit, locality, state_province, country, common_name, (unsigned)num_alt_names, alt_names)); + DEBUG_printf(("cupsCreateCredentialsRequest(path=\"%s\", purpose=0x%x, type=%d, usage=0x%x, organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", common_name=\"%s\", num_alt_names=%u, alt_names=%p)", path, purpose, type, usage, organization, org_unit, locality, state_province, country, common_name, (unsigned)num_alt_names, alt_names)); // Filenames... if (!path) @@ -335,15 +370,15 @@ cupsMakeServerRequest( http_make_path(keyfile, sizeof(keyfile), path, common_name, "key"); // Create the encryption key... - DEBUG_puts("1cupsMakeServerRequest: Creating key pair."); + DEBUG_puts("1cupsCreateCredentialsRequest: Creating key pair."); - if ((pkey = http_create_key(type)) == NULL) + if ((pkey = openssl_create_key(type)) == NULL) return (false); - DEBUG_puts("1cupsMakeServerRequest: Key pair created."); + DEBUG_puts("1cupsCreateCredentialsRequest: Key pair created."); // Create the X.509 certificate... - DEBUG_puts("1cupsMakeServerRequest: Generating self-signed X.509 certificate."); + DEBUG_puts("1cupsCreateCredentialsRequest: Generating self-signed X.509 certificate."); if ((csr = X509_REQ_new()) == NULL) { @@ -354,25 +389,50 @@ cupsMakeServerRequest( X509_REQ_set_pubkey(csr, pkey); - if ((name = http_create_name(organization, org_unit, locality, state_province, country, common_name)) == NULL) + if ((name = openssl_create_name(organization, org_unit, locality, state_province, country, common_name)) == NULL) goto done; X509_REQ_set_subject_name(csr, name); X509_NAME_free(name); // Add extension with DNS names and free buffer for GENERAL_NAME -// if ((gens = http_create_san(common_name, num_alt_names, alt_names)) == NULL) -// { -// // TODO: Set error? -// goto done; -// } + exts = sk_X509_EXTENSION_new_null(); + + if (!openssl_create_san(exts, common_name, num_alt_names, alt_names)) + goto done; + + cupsCopyString(temp, "critical", sizeof(temp)); + for (tempptr = temp + strlen(temp), i = 0, usage_bit = CUPS_CREDUSAGE_DIGITAL_SIGNATURE; i < (sizeof(tls_usage_strings) / sizeof(tls_usage_strings[0])); i ++, usage_bit *= 2) + { + if (!(usage & usage_bit)) + continue; + + snprintf(tempptr, sizeof(temp) - (size_t)(tempptr - temp), ",%s", tls_usage_strings[i]); + + tempptr += strlen(tempptr); + } + openssl_add_ext(exts, NID_key_usage, temp); - // TODO: Make this STACKOF and use X509_REQ_add_extensions -// X509_REQ_add1_ext_i2d(csr, NID_subject_alt_name, gens, 0, X509V3_ADD_DEFAULT); -// sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); + temp[0] = '\0'; + for (tempptr = temp, i = 0, purpose_bit = CUPS_CREDPURPOSE_SERVER_AUTH; i < (sizeof(tls_purpose_oids) / sizeof(tls_purpose_oids[0])); i ++, purpose_bit *= 2) + { + if (!(purpose & purpose_bit)) + continue; + + if (tempptr == temp) + cupsCopyString(temp, tls_purpose_oids[i], sizeof(temp)); + else + snprintf(tempptr, sizeof(temp) - (size_t)(tempptr - temp), ",%s", tls_purpose_oids[i]); + + tempptr += strlen(tempptr); + } + openssl_add_ext(exts, NID_ext_key_usage, temp); + X509_REQ_add_extensions(csr, exts); X509_REQ_sign(csr, pkey, EVP_sha256()); + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); + // Save them... if ((bio = BIO_new_file(keyfile, "wb")) == NULL) { @@ -406,7 +466,7 @@ cupsMakeServerRequest( // TODO: Copy CSR to returned string result = strdup(""); - DEBUG_puts("1cupsMakeServerRequest: Successfully created signing request."); + DEBUG_puts("1cupsCreateCredentialsRequest: Successfully created signing request."); // Cleanup... done: @@ -419,94 +479,27 @@ cupsMakeServerRequest( // -// 'cupsSaveServerCredentials()' - Save the X.509 certificate chain associated -// with a server. -// -// This function saves the the PEM-encoded X.509 certificate chain string to -// the directory "path" or, if "path" is `NULL`, in a per-user or system-wide -// (when running as root) certificate/key store. The "common_name" value must -// match the value supplied when the @link cupsMakeServerRequest@ function was -// called to obtain the certificate signing request (CSR). The saved -// certificate is paired with the private key that was generated for the CSR, -// allowing it to be used for encryption and signing. +// 'cupsSignCredentialsRequest()' - Sign an X.509 certificate signing request to produce an X.509 certificate chain. // -extern bool // O - `true` on success, `false` on failure -cupsSaveServerCredentials( +bool // O - `true` on success, `false` on failure +cupsSignCredentialsRequest( const char *path, // I - Directory path for certificate/key store or `NULL` for default - const char *common_name, // I - Common name for certificate - const char *pem) // I - PEM-encoded certificate chain + const char *common_name, // I - Common name to use + const char *request, // I - PEM-encoded CSR + const char *root_name, // I - Root certificate + time_t expiration_date) // I - Certificate expiration date { - // TODO: Implement cupsSaveServerCredentials (void)path; (void)common_name; - (void)pem; + (void)request; + (void)root_name; + (void)expiration_date; return (false); } -// -// 'cupsSetServerCredentials()' - Set the default server credentials. -// -// Note: The server credentials are used by all threads in the running process. -// This function is threadsafe. -// - -bool // O - `true` on success, `false` on failure -cupsSetServerCredentials( - const char *path, // I - Directory path for certificate/key store or `NULL` for default - const char *common_name, // I - Default common name for server - bool auto_create) // I - `true` = automatically create self-signed certificates -{ - char temp[1024]; // Default path buffer - - - DEBUG_printf(("cupsSetServerCredentials(path=\"%s\", common_name=\"%s\", auto_create=%d)", path, common_name, auto_create)); - - /* - * Use defaults as needed... - */ - - if (!path) - path = http_default_path(temp, sizeof(temp)); - - /* - * Range check input... - */ - - if (!path || !common_name) - { - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); - return (0); - } - - cupsMutexLock(&tls_mutex); - - /* - * Free old values... - */ - - if (tls_keypath) - _cupsStrFree(tls_keypath); - - if (tls_common_name) - _cupsStrFree(tls_common_name); - - /* - * Save the new values... - */ - - tls_keypath = _cupsStrAlloc(path); - tls_auto_create = auto_create; - tls_common_name = _cupsStrAlloc(common_name); - - cupsMutexUnlock(&tls_mutex); - - return (1); -} - - // // 'httpCopyCredentials()' - Copy the credentials associated with the peer in // an encrypted connection. @@ -579,18 +572,6 @@ _httpCreateCredentials( } -// -// '_httpFreeCredentials()' - Free internal credentials. -// - -void -_httpFreeCredentials( - http_tls_credentials_t credentials) // I - Internal credentials -{ - X509_free(credentials); -} - - // // 'httpCredentialsAreValidForName()' - Return whether the credentials are valid // for the given name. @@ -605,7 +586,7 @@ httpCredentialsAreValidForName( bool result = false; // Result - cert = http_create_credential((http_credential_t *)cupsArrayGetFirst(credentials)); + cert = openssl_create_credential((http_credential_t *)cupsArrayGetFirst(credentials)); if (cert) { result = X509_check_host(cert, common_name, strlen(common_name), 0, NULL) != 0; @@ -638,7 +619,7 @@ httpCredentialsGetTrust( return (HTTP_TRUST_UNKNOWN); } - if ((cert = http_create_credential((http_credential_t *)cupsArrayGetFirst(credentials))) == NULL) + if ((cert = openssl_create_credential((http_credential_t *)cupsArrayGetFirst(credentials))) == NULL) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create credentials from array."), 1); return (HTTP_TRUST_UNKNOWN); @@ -647,7 +628,7 @@ httpCredentialsGetTrust( if (cg->any_root < 0) { _cupsSetDefaults(); -// http_load_crl(); +// openssl_load_crl(); } // Look this common name up in the default keychains... @@ -742,7 +723,7 @@ httpCredentialsGetTrust( time_t curtime; // Current date/time time(&curtime); - if (curtime < http_get_date(cert, 0) || curtime > http_get_date(cert, 1)) + if (curtime < openssl_get_date(cert, 0) || curtime > openssl_get_date(cert, 1)) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Credentials have expired."), 1); trust = HTTP_TRUST_EXPIRED; @@ -773,9 +754,9 @@ httpCredentialsGetExpiration( X509 *cert; // Certificate - if ((cert = http_create_credential((http_credential_t *)cupsArrayGetFirst(credentials))) != NULL) + if ((cert = openssl_create_credential((http_credential_t *)cupsArrayGetFirst(credentials))) != NULL) { - result = http_get_date(cert, 1); + result = openssl_get_date(cert, 1); X509_free(cert); } @@ -806,7 +787,7 @@ httpCredentialsString( *buffer = '\0'; first = (http_credential_t *)cupsArrayGetFirst(credentials); - cert = http_create_credential(first); + cert = openssl_create_credential(first); if (cert) { @@ -820,7 +801,7 @@ httpCredentialsString( X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, name, sizeof(name)); X509_NAME_get_text_by_NID(X509_get_issuer_name(cert), NID_commonName, issuer, sizeof(issuer)); - expiration = http_get_date(cert, 1); + expiration = openssl_get_date(cert, 1); switch (X509_get_signature_nid(cert)) { @@ -872,180 +853,14 @@ httpCredentialsString( // -// 'httpLoadCredentials()' - Load X.509 credentials from a keychain file. -// - -bool // O - `true` on success, `false` on error -httpLoadCredentials( - const char *path, // I - Keychain/PKCS#12 path - cups_array_t **credentials, // IO - Credentials - const char *common_name) // I - Common name for credentials -{ - cups_file_t *fp; // Certificate file - char filename[1024], // filename.crt - temp[1024], // Temporary string - line[256]; // Base64-encoded line - unsigned char *data = NULL; // Buffer for cert data - size_t alloc_data = 0, // Bytes allocated - num_data = 0, // Bytes used - decoded; // Bytes decoded - bool in_certificate = false; - // In a certificate? - - - if (credentials) - *credentials = NULL; - - if (!credentials || !common_name) - return (false); - - if (!path) - path = http_default_path(temp, sizeof(temp)); - if (!path) - return (false); - - http_make_path(filename, sizeof(filename), path, common_name, "crt"); - - if ((fp = cupsFileOpen(filename, "r")) == NULL) - return (false); - - while (cupsFileGets(fp, line, sizeof(line))) - { - if (!strcmp(line, "-----BEGIN CERTIFICATE-----")) - { - if (in_certificate) - { - // Missing END CERTIFICATE... - httpFreeCredentials(*credentials); - *credentials = NULL; - break; - } - - in_certificate = true; - } - else if (!strcmp(line, "-----END CERTIFICATE-----")) - { - if (!in_certificate || !num_data) - { - // Missing data... - httpFreeCredentials(*credentials); - *credentials = NULL; - break; - } - - if (!*credentials) - *credentials = cupsArrayNew(NULL, NULL, NULL, 0, NULL, NULL); - - if (httpAddCredential(*credentials, data, num_data)) - { - httpFreeCredentials(*credentials); - *credentials = NULL; - break; - } - - num_data = 0; - in_certificate = false; - } - else if (in_certificate) - { - if (alloc_data == 0) - { - data = malloc(2048); - alloc_data = 2048; - - if (!data) - break; - } - else if ((num_data + strlen(line)) >= alloc_data) - { - unsigned char *tdata = realloc(data, alloc_data + 1024); - // Expanded buffer - - if (!tdata) - { - httpFreeCredentials(*credentials); - *credentials = NULL; - break; - } - - data = tdata; - alloc_data += 1024; - } - - decoded = alloc_data - num_data; - httpDecode64((char *)data + num_data, &decoded, line, NULL); - num_data += (size_t)decoded; - } - } - - cupsFileClose(fp); - - if (in_certificate) - { - // Missing END CERTIFICATE... - httpFreeCredentials(*credentials); - *credentials = NULL; - } - - if (data) - free(data); - - return (*credentials != NULL); -} - - -// -// 'httpSaveCredentials()' - Save X.509 credentials to a keychain file. +// '_httpFreeCredentials()' - Free internal credentials. // -bool // O - `true` on success, `false` on error -httpSaveCredentials( - const char *path, // I - Keychain/PKCS#12 path - cups_array_t *credentials, // I - Credentials - const char *common_name) // I - Common name for credentials +void +_httpFreeCredentials( + http_tls_credentials_t credentials) // I - Internal credentials { - cups_file_t *fp; // Certificate file - char filename[1024], // filename.crt - nfilename[1024],// filename.crt.N - temp[1024], // Temporary string - line[256]; // Base64-encoded line - const unsigned char *ptr; // Pointer into certificate - ssize_t remaining; // Bytes left - http_credential_t *cred; // Current credential - - - if (!credentials || !common_name) - return (false); - - if (!path) - path = http_default_path(temp, sizeof(temp)); - if (!path) - return (false); - - http_make_path(filename, sizeof(filename), path, common_name, "crt"); - snprintf(nfilename, sizeof(nfilename), "%s.N", filename); - - if ((fp = cupsFileOpen(nfilename, "w")) == NULL) - return (false); - -#ifndef _WIN32 - fchmod(cupsFileNumber(fp), 0600); -#endif // !_WIN32 - - for (cred = (http_credential_t *)cupsArrayGetFirst(credentials); cred; cred = (http_credential_t *)cupsArrayGetNext(credentials)) - { - cupsFilePuts(fp, "-----BEGIN CERTIFICATE-----\n"); - for (ptr = cred->data, remaining = (ssize_t)cred->datalen; remaining > 0; remaining -= 45, ptr += 45) - { - httpEncode64(line, sizeof(line), (char *)ptr, remaining > 45 ? 45 : (size_t)remaining, false); - cupsFilePrintf(fp, "%s\n", line); - } - cupsFilePuts(fp, "-----END CERTIFICATE-----\n"); - } - - cupsFileClose(fp); - - return (!rename(nfilename, filename)); + X509_free(credentials); } @@ -1089,24 +904,6 @@ _httpTLSRead(http_t *http, // I - Connection to server } -// -// '_httpTLSSetOptions()' - Set TLS protocol and cipher suite options. -// - -void -_httpTLSSetOptions(int options, // I - Options - int min_version, // I - Minimum TLS version - int max_version) // I - Maximum TLS version -{ - if (!(options & _HTTP_TLS_SET_DEFAULT) || tls_options < 0) - { - tls_options = options; - tls_min_version = min_version; - tls_max_version = max_version; - } -} - - // // '_httpTLSStart()' - Set up SSL/TLS support on a connection. // @@ -1252,9 +1049,9 @@ _httpTLSStart(http_t *http) // I - Connection to server { DEBUG_printf(("4_httpTLSStart: Auto-create credentials for \"%s\".", cn)); - if (!cupsMakeServerCredentials(tls_keypath, CUPS_CREDTYPE_DEFAULT, NULL, NULL, NULL, NULL, NULL, NULL, false, cn, 0, NULL, time(NULL) + 3650 * 86400)) + if (!cupsCreateCredentials(tls_keypath, false, CUPS_CREDPURPOSE_SERVER_AUTH, CUPS_CREDTYPE_DEFAULT, CUPS_CREDUSAGE_DEFAULT_TLS, NULL, NULL, NULL, NULL, NULL, cn, 0, NULL, NULL, time(NULL) + 3650 * 86400)) { - DEBUG_puts("4_httpTLSStart: cupsMakeServerCredentials failed."); + DEBUG_puts("4_httpTLSStart: cupsCreateCredentials failed."); http->error = errno = EINVAL; http->status = HTTP_STATUS_ERROR; _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create server credentials."), 1); @@ -1408,28 +1205,6 @@ _httpTLSWrite(http_t *http, // I - Connection to server } -// -// 'http_add_san()' - Add a subjectAltName to GENERAL_NAMES used for -// the extension to an X.509 certificate/signing request. -// - -static void -http_add_san(GENERAL_NAMES *gens, // I - Concatenation of DNS names - const char *name) // I - Hostname -{ - GENERAL_NAME *gen_dns = GENERAL_NAME_new(); - // DNS: name - ASN1_IA5STRING *ia5 = ASN1_IA5STRING_new(); - // Hostname string - - - // Set the strings and push it on the GENERAL_NAMES list... - ASN1_STRING_set(ia5, name, strlen(name)); - GENERAL_NAME_set0_value(gen_dns, GEN_DNS, ia5); - sk_GENERAL_NAME_push(gens, gen_dns); -} - - // // 'http_bio_ctrl()' - Control the HTTP connection. // @@ -1593,11 +1368,39 @@ http_bio_write(BIO *h, // I - BIO data // -// 'http_create_credential()' - Create a single credential in the internal format. +// 'openssl_add_ext()' - Add an extension. +// + +static bool // O - `true` on success, `false` on error +openssl_add_ext( + STACK_OF(X509_EXTENSION) *exts, // I - Stack of extensions + int nid, // I - Extension ID + const char *value) // I - Value +{ + X509_EXTENSION *ext = NULL; // Extension + + + DEBUG_printf(("3openssl_add_ext(exts=%p, nid=%d, value=\"%s\")", (void *)exts, nid, value)); + + // Create and add the extension... + if ((ext = X509V3_EXT_conf_nid(/*conf*/NULL, /*ctx*/NULL, nid, value)) == NULL) + { + DEBUG_puts("4openssl_add_ext: Unable to create extension, returning false."); + return (false); + } + + sk_X509_EXTENSION_push(exts, ext); + + return (true); +} + + +// +// 'openssl_create_credential()' - Create a single credential in the internal format. // static X509 * // O - Certificate -http_create_credential( +openssl_create_credential( http_credential_t *credential) // I - Credential { X509 *cert = NULL; // Certificate @@ -1619,11 +1422,11 @@ http_create_credential( // -// 'http_create_key()' - Create a suitable key pair for a certificate/signing request. +// 'openssl_create_key()' - Create a suitable key pair for a certificate/signing request. // static EVP_PKEY * // O - Key pair -http_create_key(cups_credtype_t type) // I - Type of key +openssl_create_key(cups_credtype_t type) // I - Type of key { EVP_PKEY *pkey; // Key pair #if defined(EVP_PKEY_EC) @@ -1694,11 +1497,11 @@ http_create_key(cups_credtype_t type) // I - Type of key // -// 'http_create_name()' - Create an X.509 name value for a certificate/signing request. +// 'openssl_create_name()' - Create an X.509 name value for a certificate/signing request. // static X509_NAME * // O - X.509 name value -http_create_name( +openssl_create_name( const char *organization, // I - Organization or `NULL` to use common name const char *org_unit, // I - Organizational unit or `NULL` for none const char *locality, // I - City/town or `NULL` for "Unknown" @@ -1731,104 +1534,60 @@ http_create_name( // -// 'http_create_san()' - Create a list of subjectAltName values for a certificate/signing request. +// 'openssl_create_san()' - Create a list of subjectAltName values for a certificate/signing request. // -static GENERAL_NAMES * // O - List of subjectAltName values -http_create_san( +static bool // O - `true` on success, `false` otherwise +openssl_create_san( + STACK_OF(X509_EXTENSION) *exts, // I - Extensions const char *common_name, // I - Common name size_t num_alt_names, // I - Number of alternate names const char **alt_names) // I - List of alternate names { - GENERAL_NAMES *gens; // List of subjectAltName values + char temp[2048], // Temporary string + *tempptr; // Pointer into temporary string size_t i; // Looping var - gens = sk_GENERAL_NAME_new_null(); + // Add the common name + snprintf(temp, sizeof(temp), "dns:%s", common_name); + tempptr = temp + strlen(temp); - http_add_san(gens, common_name); if (strstr(common_name, ".local") == NULL) { // Add common_name.local to the list, too... char localname[256], // hostname.local *localptr; // Pointer into localname - strlcpy(localname, common_name, sizeof(localname)); + cupsCopyString(localname, common_name, sizeof(localname)); if ((localptr = strchr(localname, '.')) != NULL) *localptr = '\0'; - strlcat(localname, ".local", sizeof(localname)); - http_add_san(gens, localname); + snprintf(tempptr, sizeof(temp) - (size_t)(tempptr - temp), ",dns:%s.local", localname); + tempptr += strlen(tempptr); } + // Add any alternate names... for (i = 0; i < num_alt_names; i ++) { if (strcmp(alt_names[i], "localhost")) - http_add_san(gens, alt_names[i]); - } - - return (gens); -} - - -// -// 'http_default_path()' - Get the default credential store path. -// - -static const char * // O - Path or NULL on error -http_default_path( - char *buffer, // I - Path buffer - size_t bufsize) // I - Size of path buffer -{ - _cups_globals_t *cg = _cupsGlobals(); - // Pointer to library globals - - - if (cg->userconfig) - { - if (mkdir(cg->userconfig, 0755) && errno != EEXIST) - { - DEBUG_printf(("1http_default_path: Failed to make directory '%s': %s", cg->userconfig, strerror(errno))); - return (NULL); - } - - snprintf(buffer, bufsize, "%s/ssl", cg->userconfig); - - if (mkdir(buffer, 0700) && errno != EEXIST) - { - DEBUG_printf(("1http_default_path: Failed to make directory '%s': %s", buffer, strerror(errno))); - return (NULL); - } - } - else - { - if (mkdir(cg->sysconfig, 0755) && errno != EEXIST) { - DEBUG_printf(("1http_default_path: Failed to make directory '%s': %s", cg->sysconfig, strerror(errno))); - return (NULL); - } - - snprintf(buffer, bufsize, "%s/ssl", cg->sysconfig); - - if (mkdir(buffer, 0700) && errno != EEXIST) - { - DEBUG_printf(("1http_default_path: Failed to make directory '%s': %s", buffer, strerror(errno))); - return (NULL); + snprintf(tempptr, sizeof(temp) - (size_t)(tempptr - temp), ",dns:%s", alt_names[i]); + tempptr += strlen(tempptr); } } - DEBUG_printf(("1http_default_path: Using default path \"%s\".", buffer)); - - return (buffer); + // Return the stack + return (openssl_add_ext(exts, NID_subject_alt_name, temp)); } // -// 'http_get_date()' - Get the notBefore or notAfter date of a certificate. +// 'openssl_get_date()' - Get the notBefore or notAfter date of a certificate. // static time_t // O - UNIX time in seconds -http_get_date(X509 *cert, // I - Certificate +openssl_get_date(X509 *cert, // I - Certificate int which) // I - 0 for notBefore, 1 for notAfter { unsigned char *expiration; // Expiration date of cert @@ -1870,11 +1629,11 @@ http_get_date(X509 *cert, // I - Certificate #if 0 // -// 'http_load_crl()' - Load the certificate revocation list, if any. +// 'openssl_load_crl()' - Load the certificate revocation list, if any. // static void -http_load_crl(void) +openssl_load_crl(void) { cupsMutexLock(&tls_mutex); @@ -1963,81 +1722,3 @@ http_load_crl(void) cupsMutexUnlock(&tls_mutex); } #endif // 0 - - -// -// 'http_make_path()' - Format a filename for a certificate or key file. -// - -static const char * // O - Filename -http_make_path( - char *buffer, // I - Filename buffer - size_t bufsize, // I - Size of buffer - const char *dirname, // I - Directory - const char *filename, // I - Filename (usually hostname) - const char *ext) // I - Extension -{ - char *bufptr, // Pointer into buffer - *bufend = buffer + bufsize - 1; // End of buffer - - - snprintf(buffer, bufsize, "%s/", dirname); - bufptr = buffer + strlen(buffer); - - while (*filename && bufptr < bufend) - { - if (_cups_isalnum(*filename) || *filename == '-' || *filename == '.') - *bufptr++ = *filename; - else - *bufptr++ = '_'; - - filename ++; - } - - if (bufptr < bufend && filename[-1] != '.') - *bufptr++ = '.'; - - cupsCopyString(bufptr, ext, (size_t)(bufend - bufptr + 1)); - - return (buffer); -} - - -// -// 'http_x509_add_ext()' - Add an extension to a certificate. -// - -static bool -http_x509_add_ext(X509 *cert, // I - Certificate - int nid, // I - Extension ID - const char *value) // I - Value -{ - bool ret; // Return value - X509_EXTENSION *ex = NULL; // Extension - X509V3_CTX ctx; // Certificate context - - - DEBUG_printf(("3http_x509_add_ext(cert=%p, nid=%d, value=\"%s\")", (void *)cert, nid, value)); - - // Don't use a configuration database... - X509V3_set_ctx_nodb(&ctx); - - // Self-signed certificates use the same issuer and subject... - X509V3_set_ctx(&ctx, /*issuer*/cert, /*subject*/cert, /*req*/NULL, /*crl*/NULL, /*flags*/0); - - // Create and add the extension... - if ((ex = X509V3_EXT_conf_nid(/*conf*/NULL, &ctx, nid, value)) == NULL) - { - DEBUG_puts("4http_x509_add_ext: Unable to create extension, returning false."); - return (false); - } - - ret = X509_add_ext(cert, ex, -1) != 0; - - DEBUG_printf(("4http_x509_add_ext: X509_add_ext returned %s.", ret ? "true" : "false")); - - // Free the extension and return... - X509_EXTENSION_free(ex); - - return (ret); -} diff --git a/cups/tls.c b/cups/tls.c index 165d55874..1ad53348e 100644 --- a/cups/tls.c +++ b/cups/tls.c @@ -1,22 +1,19 @@ -/* - * TLS routines for CUPS. - * - * Copyright © 2021-2022 by OpenPrinting. - * Copyright @ 2007-2014 by Apple Inc. - * Copyright @ 1997-2007 by Easy Software Products, all rights reserved. - * - * Licensed under Apache License v2.0. See the file "LICENSE" for more - * information. - */ - -/* - * Include necessary headers... - */ +// +// TLS routines for CUPS. +// +// Copyright © 2021-2023 by OpenPrinting. +// Copyright @ 2007-2014 by Apple Inc. +// Copyright @ 1997-2007 by Easy Software Products, all rights reserved. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// #include "cups-private.h" #include "debug-internal.h" #include #include +#include #ifdef _WIN32 # include #else @@ -24,17 +21,555 @@ # include # include # include -#endif /* _WIN32 */ +#endif // _WIN32 -/* - * Include platform-specific TLS code... - */ +// +// Local globals... +// -#ifdef HAVE_TLS -# ifdef HAVE_OPENSSL +static bool tls_auto_create = false; + // Auto-create self-signed certs? +static char *tls_common_name = NULL; + // Default common name +static char *tls_keypath = NULL; + // Certificate store path +static cups_mutex_t tls_mutex = CUPS_MUTEX_INITIALIZER; + // Mutex for certificates +static int tls_options = -1,// Options for TLS connections + tls_min_version = _HTTP_TLS_1_2, + tls_max_version = _HTTP_TLS_MAX; + + +// +// Local functions... +// + +static char *http_copy_file(const char *path, const char *common_name, const char *ext); +static const char *http_default_path(char *buffer, size_t bufsize); +static const char *http_make_path(char *buffer, size_t bufsize, const char *dirname, const char *filename, const char *ext); +static bool http_save_file(const char *path, const char *common_name, const char *ext, const char *value); + + +// +// Include platform-specific TLS code... +// + +#ifdef HAVE_OPENSSL # include "tls-openssl.c" -# elif defined(HAVE_GNUTLS) -# include "tls-gnutls.c" -# endif /* HAVE_OPENSSL */ -#endif /* HAVE_TLS */ +#else // HAVE_GNUTLS +# include "tls-gnutls.c" +#endif /* HAVE_OPENSSL */ + + +// +// 'cupsCopyCredentials()' - Copy the X.509 certificate chain to a string. +// + +char * +cupsCopyCredentials( + const char *path, // I - Directory path for certificate/key store or `NULL` for default + const char *common_name) // I - Common name +{ + return (http_copy_file(path, common_name, "crt")); +} + + +// +// 'cupsCopyCredentialsKey()' - Copy the private key to a string. +// + +char * +cupsCopyCredentialsKey( + const char *path, // I - Directory path for certificate/key store or `NULL` for default + const char *common_name) // I - Common name +{ + return (http_copy_file(path, common_name, "key")); +} + + +// +// 'cupsCopyCredentialsRequest()' - Copy the X.509 certificate signing request to a string. +// + +char * +cupsCopyCredentialsRequest( + const char *path, // I - Directory path for certificate/key store or `NULL` for default + const char *common_name) // I - Common name +{ + return (http_copy_file(path, common_name, "csr")); +} + + +// +// 'cupsSaveServerCredentials()' - Save the X.509 certificate chain associated +// with a server. +// +// This function saves the the PEM-encoded X.509 certificate chain string to +// the directory "path" or, if "path" is `NULL`, in a per-user or system-wide +// (when running as root) certificate/key store. The "common_name" value must +// match the value supplied when the @link cupsMakeServerRequest@ function was +// called to obtain the certificate signing request (CSR). The saved +// certificate is paired with the private key that was generated for the CSR, +// allowing it to be used for encryption and signing. +// + +extern bool // O - `true` on success, `false` on failure +cupsSaveCredentials( + const char *path, // I - Directory path for certificate/key store or `NULL` for default + const char *common_name, // I - Common name for certificate + const char *credentials, // I - PEM-encoded certificate chain + const char *key) // I - PEM-encoded private key or `NULL` for none +{ + if (http_save_file(path, common_name, "crt", credentials)) + { + if (key) + return (http_save_file(path, common_name, "key", key)); + else + return (true); + } + + return (false); +} + + +// +// 'cupsSetServerCredentials()' - Set the default server credentials. +// +// Note: The server credentials are used by all threads in the running process. +// This function is threadsafe. +// + +bool // O - `true` on success, `false` on failure +cupsSetServerCredentials( + const char *path, // I - Directory path for certificate/key store or `NULL` for default + const char *common_name, // I - Default common name for server + bool auto_create) // I - `true` = automatically create self-signed certificates +{ + char temp[1024]; // Default path buffer + + + DEBUG_printf(("cupsSetServerCredentials(path=\"%s\", common_name=\"%s\", auto_create=%d)", path, common_name, auto_create)); + + /* + * Use defaults as needed... + */ + + if (!path) + path = http_default_path(temp, sizeof(temp)); + + /* + * Range check input... + */ + + if (!path || !common_name) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); + return (0); + } + + cupsMutexLock(&tls_mutex); + + /* + * Free old values... + */ + + if (tls_keypath) + _cupsStrFree(tls_keypath); + + if (tls_common_name) + _cupsStrFree(tls_common_name); + + /* + * Save the new values... + */ + + tls_keypath = _cupsStrAlloc(path); + tls_auto_create = auto_create; + tls_common_name = _cupsStrAlloc(common_name); + + cupsMutexUnlock(&tls_mutex); + + return (1); +} + + +// +// 'httpLoadCredentials()' - Load X.509 credentials from a keychain file. +// + +bool // O - `true` on success, `false` on error +httpLoadCredentials( + const char *path, // I - Keychain/PKCS#12 path + cups_array_t **credentials, // IO - Credentials + const char *common_name) // I - Common name for credentials +{ + cups_file_t *fp; // Certificate file + char filename[1024], // filename.crt + temp[1024], // Temporary string + line[256]; // Base64-encoded line + unsigned char *data = NULL; // Buffer for cert data + size_t alloc_data = 0, // Bytes allocated + num_data = 0, // Bytes used + decoded; // Bytes decoded + bool in_certificate = false; + // In a certificate? + + + if (credentials) + *credentials = NULL; + + if (!credentials || !common_name) + return (false); + + if (!path) + path = http_default_path(temp, sizeof(temp)); + if (!path) + return (false); + + http_make_path(filename, sizeof(filename), path, common_name, "crt"); + + if ((fp = cupsFileOpen(filename, "r")) == NULL) + return (false); + + while (cupsFileGets(fp, line, sizeof(line))) + { + if (!strcmp(line, "-----BEGIN CERTIFICATE-----")) + { + if (in_certificate) + { + // Missing END CERTIFICATE... + httpFreeCredentials(*credentials); + *credentials = NULL; + break; + } + + in_certificate = true; + } + else if (!strcmp(line, "-----END CERTIFICATE-----")) + { + if (!in_certificate || !num_data) + { + // Missing data... + httpFreeCredentials(*credentials); + *credentials = NULL; + break; + } + + if (!*credentials) + *credentials = cupsArrayNew(NULL, NULL, NULL, 0, NULL, NULL); + + if (httpAddCredential(*credentials, data, num_data)) + { + httpFreeCredentials(*credentials); + *credentials = NULL; + break; + } + + num_data = 0; + in_certificate = false; + } + else if (in_certificate) + { + if (alloc_data == 0) + { + data = malloc(2048); + alloc_data = 2048; + + if (!data) + break; + } + else if ((num_data + strlen(line)) >= alloc_data) + { + unsigned char *tdata = realloc(data, alloc_data + 1024); + // Expanded buffer + + if (!tdata) + { + httpFreeCredentials(*credentials); + *credentials = NULL; + break; + } + + data = tdata; + alloc_data += 1024; + } + + decoded = alloc_data - num_data; + httpDecode64((char *)data + num_data, &decoded, line, NULL); + num_data += (size_t)decoded; + } + } + + cupsFileClose(fp); + + if (in_certificate) + { + // Missing END CERTIFICATE... + httpFreeCredentials(*credentials); + *credentials = NULL; + } + + if (data) + free(data); + + return (*credentials != NULL); +} + + +// +// 'httpSaveCredentials()' - Save X.509 credentials to a keychain file. +// + +bool // O - `true` on success, `false` on error +httpSaveCredentials( + const char *path, // I - Keychain/PKCS#12 path + cups_array_t *credentials, // I - Credentials + const char *common_name) // I - Common name for credentials +{ + cups_file_t *fp; // Certificate file + char filename[1024], // filename.crt + nfilename[1024],// filename.crt.N + temp[1024], // Temporary string + line[256]; // Base64-encoded line + const unsigned char *ptr; // Pointer into certificate + ssize_t remaining; // Bytes left + http_credential_t *cred; // Current credential + + + if (!credentials || !common_name) + return (false); + + if (!path) + path = http_default_path(temp, sizeof(temp)); + if (!path) + return (false); + + http_make_path(filename, sizeof(filename), path, common_name, "crt"); + snprintf(nfilename, sizeof(nfilename), "%s.N", filename); + + if ((fp = cupsFileOpen(nfilename, "w")) == NULL) + return (false); + +#ifndef _WIN32 + fchmod(cupsFileNumber(fp), 0600); +#endif // !_WIN32 + + for (cred = (http_credential_t *)cupsArrayGetFirst(credentials); cred; cred = (http_credential_t *)cupsArrayGetNext(credentials)) + { + cupsFilePuts(fp, "-----BEGIN CERTIFICATE-----\n"); + for (ptr = cred->data, remaining = (ssize_t)cred->datalen; remaining > 0; remaining -= 45, ptr += 45) + { + httpEncode64(line, sizeof(line), (char *)ptr, remaining > 45 ? 45 : (size_t)remaining, false); + cupsFilePrintf(fp, "%s\n", line); + } + cupsFilePuts(fp, "-----END CERTIFICATE-----\n"); + } + + cupsFileClose(fp); + + return (!rename(nfilename, filename)); +} + + +// +// '_httpTLSSetOptions()' - Set TLS protocol and cipher suite options. +// + +void +_httpTLSSetOptions(int options, // I - Options + int min_version, // I - Minimum TLS version + int max_version) // I - Maximum TLS version +{ + if (!(options & _HTTP_TLS_SET_DEFAULT) || tls_options < 0) + { + tls_options = options; + tls_min_version = min_version; + tls_max_version = max_version; + } +} + + +// +// 'http_copy_file()' - Copy the contents of a file to a string. +// + +static char * +http_copy_file(const char *path, // I - Directory + const char *common_name, // I - Common name + const char *ext) // I - Extension +{ + char *s = NULL; // String + int fd; // File descriptor + char defpath[1024], // Default path + filename[1024]; // Filename + struct stat fileinfo; // File information + + + if (!common_name) + return (NULL); + + if (!path) + path = http_default_path(defpath, sizeof(defpath)); + + if ((fd = open(http_make_path(filename, sizeof(filename), path, common_name, ext), O_RDONLY)) < 0) + return (NULL); + + if (fstat(fd, &fileinfo)) + goto done; + + if (fileinfo.st_size > 65536) + { + close(fd); + return (NULL); + } + + if ((s = calloc(1, (size_t)fileinfo.st_size + 1)) == NULL) + { + close(fd); + return (NULL); + } + + if (read(fd, s, (size_t)fileinfo.st_size) < 0) + { + free(s); + s = NULL; + } + + done: + + close(fd); + + return (s); +} + + +// +// 'http_default_path()' - Get the default credential store path. +// + +static const char * // O - Path or NULL on error +http_default_path( + char *buffer, // I - Path buffer + size_t bufsize) // I - Size of path buffer +{ + _cups_globals_t *cg = _cupsGlobals(); + // Pointer to library globals + + + if (cg->userconfig) + { + if (mkdir(cg->userconfig, 0755) && errno != EEXIST) + { + DEBUG_printf(("1http_default_path: Failed to make directory '%s': %s", cg->userconfig, strerror(errno))); + return (NULL); + } + + snprintf(buffer, bufsize, "%s/ssl", cg->userconfig); + + if (mkdir(buffer, 0700) && errno != EEXIST) + { + DEBUG_printf(("1http_default_path: Failed to make directory '%s': %s", buffer, strerror(errno))); + return (NULL); + } + } + else + { + if (mkdir(cg->sysconfig, 0755) && errno != EEXIST) + { + DEBUG_printf(("1http_default_path: Failed to make directory '%s': %s", cg->sysconfig, strerror(errno))); + return (NULL); + } + + snprintf(buffer, bufsize, "%s/ssl", cg->sysconfig); + + if (mkdir(buffer, 0700) && errno != EEXIST) + { + DEBUG_printf(("1http_default_path: Failed to make directory '%s': %s", buffer, strerror(errno))); + return (NULL); + } + } + + DEBUG_printf(("1http_default_path: Using default path \"%s\".", buffer)); + + return (buffer); +} + + +// +// 'http_make_path()' - Format a filename for a certificate or key file. +// + +static const char * // O - Filename +http_make_path( + char *buffer, // I - Filename buffer + size_t bufsize, // I - Size of buffer + const char *dirname, // I - Directory + const char *filename, // I - Filename (usually hostname) + const char *ext) // I - Extension +{ + char *bufptr, // Pointer into buffer + *bufend = buffer + bufsize - 1; // End of buffer + + + snprintf(buffer, bufsize, "%s/", dirname); + bufptr = buffer + strlen(buffer); + + while (*filename && bufptr < bufend) + { + if (_cups_isalnum(*filename) || *filename == '-' || *filename == '.') + *bufptr++ = *filename; + else + *bufptr++ = '_'; + + filename ++; + } + + if (bufptr < bufend && filename[-1] != '.') + *bufptr++ = '.'; + + cupsCopyString(bufptr, ext, (size_t)(bufend - bufptr + 1)); + + return (buffer); +} + + +// +// 'http_save_file()' - Save a string to a file. +// + +static bool // O - `true` on success, `false` on failure +http_save_file(const char *path, // I - Directory path for certificate/key store or `NULL` for default + const char *common_name, // I - Common name + const char *ext, // I - Extension + const char *value) // I - String value +{ + char defpath[1024], // Default path + filename[1024]; // Output filename + int fd; // File descriptor + + + // Range check input... + if (!common_name || !value) + return (false); + + // Get default path as needed... + if (!path) + path = http_default_path(defpath, sizeof(defpath)); + + if ((fd = open(http_make_path(filename, sizeof(filename), path, common_name, ext), O_CREAT | O_WRONLY | O_TRUNC, 0644)) < 0) + return (false); + + if (write(fd, value, strlen(value)) < 0) + { + close(fd); + unlink(filename); + return (false); + } + + close(fd); + + return (true); +} + + From ff12bf3dede813fe8c4667f4dc392e42974f7bc5 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Thu, 4 May 2023 18:01:21 -0400 Subject: [PATCH 08/31] Stub out a new unit test/test command for the credentials APIs. --- cups/testcreds.c | 594 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 518 insertions(+), 76 deletions(-) diff --git a/cups/testcreds.c b/cups/testcreds.c index 2ea765f5a..43dc98c3a 100644 --- a/cups/testcreds.c +++ b/cups/testcreds.c @@ -1,105 +1,472 @@ -/* - * HTTP credentials test program for CUPS. - * - * Copyright © 2022 by OpenPrinting. - * Copyright © 2007-2016 by Apple Inc. - * Copyright © 1997-2006 by Easy Software Products. - * - * Licensed under Apache License v2.0. See the file "LICENSE" for more - * information. - */ - -/* - * Include necessary headers... - */ +// +// X.509 credentials test program for CUPS. +// +// Copyright © 2022-2023 by OpenPrinting. +// Copyright © 2007-2016 by Apple Inc. +// Copyright © 1997-2006 by Easy Software Products. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// +// Usage: testcreds [OPTIONS] [SUB-COMMAND] [ARGUMENT] +// +// Sub-Commands: +// +// ca COMMON-NAME Sign a CSR to produce a certificate. +// cacert COMMON-NAME Create a CA certificate. +// cert COMMON-NAME Create a certificate. +// client URI Connect to URI. +// csr COMMON-NAME Create a certificate signing request. +// server COMMON-NAME[:PORT] Run a HTTPS server (default port 8NNN.) +// show COMMON-NAME Show stored credentials for COMMON-NAME. +// +// Options: +// +// -C COUNTRY Set country. +// -L LOCALITY Set locality name. +// -O ORGANIZATION Set organization name. +// -S STATE Set state. +// -U ORGANIZATIONAL-UNIT Set organizational unit name. +// -a SUBJECT-ALT-NAME Add a subjectAltName. +// -d DAYS Set expiration date in days. +// -p PURPOSE Comma-delimited certificate purpose (serverAuth, +// clientAuth, codeSigning, emailProtection, +// timeStamping, OCSPSigning) +// -r ROOT-NAME Name of root certificate +// -t TYPE Certificate type (rsa-2048, rsa-3072, rsa-4096, +// ecdsa-p256, ecdsa-p384, ecdsa-p521) +// -u USAGE Comma-delimited key usage (digitalSignature, +// nonRepudiation, keyEncipherment, +// dataEncipherment, keyAgreement, keyCertSign, +// cRLSign, encipherOnly, decipherOnly, default-ca, +// default-tls) +// #include "cups-private.h" +#include "test-internal.h" -/* - * 'main()' - Main entry. - */ +// +// Local functions... +// -int /* O - Exit status */ -main(int argc, /* I - Number of command-line arguments */ - char *argv[]) /* I - Command-line arguments */ -{ - http_t *http; /* HTTP connection */ - char scheme[HTTP_MAX_URI], /* Scheme from URI */ - hostname[HTTP_MAX_URI], /* Hostname from URI */ - username[HTTP_MAX_URI], /* Username:password from URI */ - resource[HTTP_MAX_URI]; /* Resource from URI */ - int port; /* Port number from URI */ - http_trust_t trust; /* Trust evaluation for connection */ - cups_array_t *hcreds, /* Credentials from connection */ - *tcreds; /* Credentials from trust store */ - char hinfo[1024], /* String for connection credentials */ - tinfo[1024], /* String for trust store credentials */ - datestr[256]; // Date string - static const char *trusts[] = /* Trust strings */ - { "OK", "Invalid", "Changed", "Expired", "Renewed", "Unknown" }; +static int test_ca(const char *arg); +static int test_cacert(const char *arg); +static int test_cert(const char *arg); +static int test_client(const char *arg); +static int test_csr(const char *arg); +static int test_server(const char *arg); +static int test_show(const char *arg); +static int usage(FILE *fp); - /* - * Check command-line... - */ +// +// 'main()' - Main entry. +// - if (argc != 2) - { - puts("Usage: ./testcreds hostname"); - puts(" ./testcreds https://hostname[:port]"); - return (1); - } +int // O - Exit status +main(int argc, // I - Number of command-line arguments + char *argv[]) // I - Command-line arguments +{ + int i; // Looping var + const char *subcommand = NULL, // Sub-command + *arg = NULL, // Argument for sub-command + *opt, // Current option character + *root_name = NULL, // Name of root certificate + *organization = NULL, // Organization + *org_unit = NULL, // Organizational unit + *locality = NULL, // Locality + *state = NULL, // State/province + *country = NULL, // Country + *alt_names[100]; // Subject alternate names + size_t num_alt_names = 0; + int days; // Days until expiration + cups_credpurpose_t purpose = CUPS_CREDPURPOSE_SERVER_AUTH; + // Certificate purpose + cups_credtype_t type = CUPS_CREDTYPE_DEFAULT; + // Certificate type + cups_credusage_t keyusage = CUPS_CREDUSAGE_DEFAULT_TLS; + // Key usage - if (!strncmp(argv[1], "https://", 8)) - { - /* - * Connect to the host and validate credentials... - */ - if (httpSeparateURI(HTTP_URI_CODING_MOST, argv[1], scheme, sizeof(scheme), username, sizeof(username), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK) + // Check command-line... + for (i = 1; i < argc; i ++) + { + if (!strcmp(argv[i], "--help")) { - printf("ERROR: Bad URI \"%s\".\n", argv[1]); - return (1); + return (usage(stdout)); } - - if ((http = httpConnect(hostname, port, NULL, AF_UNSPEC, HTTP_ENCRYPTION_ALWAYS, 1, 30000, NULL)) == NULL) + else if (!strncmp(argv[i], "--", 2)) { - printf("ERROR: Unable to connect to \"%s\" on port %d: %s\n", hostname, port, cupsLastErrorString()); - return (1); + fprintf(stderr, "testcreds: Unknown option '%s'.\n", argv[i]); + return (usage(stderr)); } - - puts("HTTP Credentials:"); - if (httpCopyCredentials(http, &hcreds)) + else if (argv[i][0] == '-') { - trust = httpCredentialsGetTrust(hcreds, hostname); + for (opt = argv[i] + 1; *opt; opt ++) + { + switch (*opt) + { + case 'C' : // -C COUNTRY + i ++; + if (i >= argc) + { + fputs("testcreds: Missing country after '-C'.\n", stderr); + return (usage(stderr)); + } + country = argv[i]; + break; + + case 'L' : // -L LOCALITY + i ++; + if (i >= argc) + { + fputs("testcreds: Missing locality/city/town after '-L'.\n", stderr); + return (usage(stderr)); + } + locality = argv[i]; + break; + + case 'O' : // -O ORGANIZATION + i ++; + if (i >= argc) + { + fputs("testcreds: Missing organization after '-O'.\n", stderr); + return (usage(stderr)); + } + organization = argv[i]; + break; + + case 'S' : // -S STATE + i ++; + if (i >= argc) + { + fputs("testcreds: Missing state/province after '-S'.\n", stderr); + return (usage(stderr)); + } + state = argv[i]; + break; - httpCredentialsString(hcreds, hinfo, sizeof(hinfo)); + case 'U' : // -U ORGANIZATIONAL-UNIT + i ++; + if (i >= argc) + { + fputs("testcreds: Missing organizational unit after '-U'.\n", stderr); + return (usage(stderr)); + } + org_unit = argv[i]; + break; - printf(" Certificate Count: %u\n", (unsigned)cupsArrayGetCount(hcreds)); - if (trust == HTTP_TRUST_OK) - puts(" Trust: OK"); - else - printf(" Trust: %s (%s)\n", trusts[trust], cupsLastErrorString()); - printf(" Expiration: %s\n", httpGetDateString(httpCredentialsGetExpiration(hcreds), datestr, sizeof(datestr))); - printf(" IsValidName: %s\n", httpCredentialsAreValidForName(hcreds, hostname) ? "true" : "false"); - printf(" String: \"%s\"\n", hinfo); + case 'a' : // -a SUBJECT-ALT-NAME + i ++; + if (i >= argc) + { + fputs("testcreds: Missing subjectAltName after '-a'.\n", stderr); + return (usage(stderr)); + } + if (num_alt_names >= (sizeof(alt_names) / sizeof(alt_names[0]))) + { + fputs("testcreds: Too many subjectAltName values.\n", stderr); + return (1); + } + alt_names[num_alt_names ++] = argv[i]; + break; - httpFreeCredentials(hcreds); + case 'd' : // -d DAYS + i ++; + if (i >= argc) + { + fputs("testcreds: Missing expiration days after '-d'.\n", stderr); + return (usage(stderr)); + } + if ((days = atoi(argv[i])) <= 0) + { + fprintf(stderr, "testcreds: Bad DAYS value '%s' after '-d'.\n", argv[i]); + return (1); + } + break; + + case 'p' : // -p PURPOSE + i ++; + if (i >= argc) + { + fputs("testcreds: Missing purpose after '-p'.\n", stderr); + return (usage(stderr)); + } + purpose = 0; + if (strstr(argv[i], "serverAuth")) + purpose |= CUPS_CREDPURPOSE_SERVER_AUTH; + if (strstr(argv[i], "clientAuth")) + purpose |= CUPS_CREDPURPOSE_CLIENT_AUTH; + if (strstr(argv[i], "codeSigning")) + purpose |= CUPS_CREDPURPOSE_CODE_SIGNING; + if (strstr(argv[i], "emailProtection")) + purpose |= CUPS_CREDPURPOSE_EMAIL_PROTECTION; + if (strstr(argv[i], "timeStamping")) + purpose |= CUPS_CREDPURPOSE_TIME_STAMPING; + if (strstr(argv[i], "OCSPSigning")) + purpose |= CUPS_CREDPURPOSE_OCSP_SIGNING; + if (purpose == 0) + { + fprintf(stderr, "testcreds: Bad purpose '%s'.\n", argv[i]); + return (usage(stderr)); + } + break; + + case 'r' : // -r ROOT-NAME + i ++; + if (i >= argc) + { + fputs("testcreds: Missing root name after '-r'.\n", stderr); + return (usage(stderr)); + } + root_name = argv[i]; + break; + + case 't' : // -t TYPE + i ++; + if (i >= argc) + { + fputs("testcreds: Missing certificate type after '-t'.\n", stderr); + return (usage(stderr)); + } + if (!strcmp(argv[i], "default")) + { + type = CUPS_CREDTYPE_DEFAULT; + } + else if (!strcmp(argv[i], "rsa-2048")) + { + type = CUPS_CREDTYPE_RSA_2048_SHA256; + } + else if (!strcmp(argv[i], "rsa-3072")) + { + type = CUPS_CREDTYPE_RSA_3072_SHA256; + } + else if (!strcmp(argv[i], "rsa-4096")) + { + type = CUPS_CREDTYPE_RSA_4096_SHA256; + } + else if (!strcmp(argv[i], "ecdsa-p256")) + { + type = CUPS_CREDTYPE_ECDSA_P256_SHA256; + } + else if (!strcmp(argv[i], "ecdsa-p384")) + { + type = CUPS_CREDTYPE_ECDSA_P384_SHA256; + } + else if (!strcmp(argv[i], "ecdsa-p521")) + { + type = CUPS_CREDTYPE_ECDSA_P521_SHA256; + } + else + { + fprintf(stderr, "testcreds: Bad certificate type '%s'.\n", argv[i]); + return (usage(stderr)); + } + break; + + case 'u' : // -u USAGE + i ++; + if (i >= argc) + { + fputs("testcreds: Missing key usage after '-u'.\n", stderr); + return (usage(stderr)); + } + keyusage = 0; + if (strstr(argv[i], "default-ca")) + keyusage = CUPS_CREDUSAGE_DEFAULT_CA; + if (strstr(argv[i], "default-tls")) + keyusage = CUPS_CREDUSAGE_DEFAULT_TLS; + if (strstr(argv[i], "digitalSignature")) + keyusage |= CUPS_CREDUSAGE_DIGITAL_SIGNATURE; + if (strstr(argv[i], "nonRepudiation")) + keyusage |= CUPS_CREDUSAGE_NON_REPUDIATION; + if (strstr(argv[i], "keyEncipherment")) + keyusage |= CUPS_CREDUSAGE_KEY_ENCIPHERMENT; + if (strstr(argv[i], "dataEncipherment")) + keyusage |= CUPS_CREDUSAGE_DATA_ENCIPHERMENT; + if (strstr(argv[i], "keyAgreement")) + keyusage |= CUPS_CREDUSAGE_KEY_AGREEMENT; + if (strstr(argv[i], "keyCertSign")) + keyusage |= CUPS_CREDUSAGE_KEY_CERT_SIGN; + if (strstr(argv[i], "cRLSign")) + keyusage |= CUPS_CREDUSAGE_CRL_SIGN; + if (strstr(argv[i], "encipherOnly")) + keyusage |= CUPS_CREDUSAGE_ENCIPHER_ONLY; + if (strstr(argv[i], "decipherOnly")) + keyusage |= CUPS_CREDUSAGE_DECIPHER_ONLY; + if (keyusage == 0) + { + fprintf(stderr, "testcreds: Bad key usage '%s'.\n", argv[i]); + return (usage(stderr)); + } + break; + + default : + fprintf(stderr, "testcreds: Unknown option '-%c'.\n", *opt); + return (usage(stderr)); + } + } + } + else if (!subcommand) + { + subcommand = argv[i]; + } + else if (!arg) + { + arg = argv[i]; } else - puts(" Not present (error)."); + { + fprintf(stderr, "testcreds: Unknown option '%s'.\n", argv[i]); + return (usage(stderr)); + } + } + + if (!subcommand) + { + fputs("testcreds: Missing sub-command.\n", stderr); + return (usage(stderr)); + } + else if (!arg) + { + fputs("testcreds: Missing sub-command argument.\n", stderr); + return (usage(stderr)); + } - puts(""); + // Run the corresponding sub-command... + if (!strcmp(subcommand, "ca")) + { + return (test_ca(arg)); + } + else if (!strcmp(subcommand, "cacert")) + { + return (test_cacert(arg)); + } + else if (!strcmp(subcommand, "cert")) + { + return (test_cert(arg)); + } + else if (!strcmp(subcommand, "client")) + { + return (test_client(arg)); + } + else if (!strcmp(subcommand, "csr")) + { + return (test_csr(arg)); + } + else if (!strcmp(subcommand, "server")) + { + return (test_server(arg)); + } + else if (!strcmp(subcommand, "show")) + { + return (test_show(arg)); } else { - /* - * Load stored credentials... - */ + fprintf(stderr, "testcreds: Unknown sub-command '%s'.\n", subcommand); + return (usage(stderr)); + } +} + + +// +// 'test_ca()' - Test generating a certificate from a CSR. +// + +static int // O - Exit status +test_ca(const char *arg) // I - Common name +{ + (void)arg; + return (1); +} + + +// +// 'test_cacert()' - Test creating a CA certificate. +// + +static int // O - Exit status +test_cacert(const char *arg) // I - Common name +{ + (void)arg; + return (1); +} + + +// +// 'test_cert()' - Test creating a self-signed certificate. +// + +static int // O - Exit status +test_cert(const char *arg) // I - Common name +{ + (void)arg; + return (1); +} + + +// +// 'test_client()' - Test connecting to a HTTPS server. +// + +static int // O - Exit status +test_client(const char *arg) // I - URI +{ + http_t *http; // HTTP connection + char scheme[HTTP_MAX_URI], // Scheme from URI + hostname[HTTP_MAX_URI], // Hostname from URI + username[HTTP_MAX_URI], // Username:password from URI + resource[HTTP_MAX_URI]; // Resource from URI + int port; // Port number from URI + http_trust_t trust; // Trust evaluation for connection + cups_array_t *hcreds, // Credentials from connection + *tcreds; // Credentials from trust store + char hinfo[1024], // String for connection credentials + tinfo[1024], // String for trust store credentials + datestr[256]; // Date string + static const char *trusts[] = // Trust strings + { "OK", "Invalid", "Changed", "Expired", "Renewed", "Unknown" }; + + + // Connect to the host and validate credentials... + if (httpSeparateURI(HTTP_URI_CODING_MOST, arg, scheme, sizeof(scheme), username, sizeof(username), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK) + { + fprintf(stderr, "testcreds: Bad URI '%s'.\n", arg); + return (1); + } + + if ((http = httpConnect(hostname, port, NULL, AF_UNSPEC, HTTP_ENCRYPTION_ALWAYS, 1, 30000, NULL)) == NULL) + { + fprintf(stderr, "testcreds: Unable to connect to '%s' on port %d: %s\n", hostname, port, cupsLastErrorString()); + return (1); + } + + puts("TLS Server Credentials:"); + if (httpCopyCredentials(http, &hcreds)) + { + trust = httpCredentialsGetTrust(hcreds, hostname); + + httpCredentialsString(hcreds, hinfo, sizeof(hinfo)); + + printf(" Certificate Count: %u\n", (unsigned)cupsArrayGetCount(hcreds)); + if (trust == HTTP_TRUST_OK) + puts(" Trust: OK"); + else + printf(" Trust: %s (%s)\n", trusts[trust], cupsLastErrorString()); + printf(" Expiration: %s\n", httpGetDateString(httpCredentialsGetExpiration(hcreds), datestr, sizeof(datestr))); + printf(" IsValidName: %s\n", httpCredentialsAreValidForName(hcreds, hostname) ? "true" : "false"); + printf(" String: \"%s\"\n", hinfo); - cupsCopyString(hostname, argv[1], sizeof(hostname)); + httpFreeCredentials(hcreds); } + else + { + puts(" Not present (error)."); + } + + puts(""); printf("Trust Store for \"%s\":\n", hostname); @@ -115,7 +482,82 @@ main(int argc, /* I - Number of command-line arguments */ httpFreeCredentials(tcreds); } else + { puts(" Not present."); + } return (0); } + + +// +// 'test_csr()' - Test creating a certificate signing request. +// + +static int // O - Exit status +test_csr(const char *arg) // I - Common name +{ + (void)arg; + return (1); +} + + +// +// 'test_server()' - Test running a server. +// + +static int // O - Exit status +test_server(const char *arg) // I - Hostname/port +{ + (void)arg; + return (1); +} + + +// +// 'test_show()' - Test showing stored certificates. +// + +static int // O - Exit status +test_show(const char *arg) // I - Common name +{ + (void)arg; + return (1); +} + + +// +// 'usage()' - Show program usage... +// + +static int // O - Exit code +usage(FILE *fp) // I - Output file (stdout or stderr) +{ + fputs("Usage: testcreds [OPTIONS] [SUB-COMMAND] [ARGUMENT]\n", fp); + fputs("\n", fp); + fputs("Sub-Commands:\n", fp); + fputs("\n", fp); + fputs(" ca COMMON-NAME Sign a CSR to produce a certificate.\n", fp); + fputs(" cacert COMMON-NAME Create a CA certificate.\n", fp); + fputs(" cert COMMON-NAME Create a certificate.\n", fp); + fputs(" client URI Connect to URI.\n", fp); + fputs(" csr COMMON-NAME Create a certificate signing request.\n", fp); + fputs(" server COMMON-NAME[:PORT] Run a HTTPS server (default port 8NNN.)\n", fp); + fputs(" show COMMON-NAME Show stored credentials for COMMON-NAME.\n", fp); + fputs("\n", fp); + fputs("Options:\n", fp); + fputs("\n", fp); + fputs(" -C COUNTRY Set country.\n", fp); + fputs(" -L LOCALITY Set locality name.\n", fp); + fputs(" -O ORGANIZATION Set organization name.\n", fp); + fputs(" -S STATE Set state.\n", fp); + fputs(" -U ORGANIZATIONAL-UNIT Set organizational unit name.\n", fp); + fputs(" -a SUBJECT-ALT-NAME Add a subjectAltName.\n", fp); + fputs(" -d DAYS Set expiration date in days.\n", fp); + fputs(" -p PURPOSE Comma-delimited certificate purpose (serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, OCSPSigning)\n", fp); + fputs(" -r ROOT-NAME Name of root certificate\n", fp); + fputs(" -t TYPE Certificate type (rsa-2048, rsa-3072, rsa-4096, ecdsa-p256, ecdsa-p384, ecdsa-p521)\n", fp); + fputs(" -u USAGE Comma-delimited key usage (digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign, encipherOnly, decipherOnly, default-ca, default-tls)\n", fp); + + return (fp == stderr); +} From 101006e50c0981b67e412b7da18582517489a83e Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Sun, 7 May 2023 17:19:49 -0400 Subject: [PATCH 09/31] More cert test stuff. --- cups/testcreds.c | 220 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 166 insertions(+), 54 deletions(-) diff --git a/cups/testcreds.c b/cups/testcreds.c index 43dc98c3a..31556afe7 100644 --- a/cups/testcreds.c +++ b/cups/testcreds.c @@ -44,19 +44,27 @@ #include "cups-private.h" #include "test-internal.h" +#include + + +// +// Constants... +// + +#define TEST_CERT_PATH ".testssl" // // Local functions... // -static int test_ca(const char *arg); -static int test_cacert(const char *arg); -static int test_cert(const char *arg); -static int test_client(const char *arg); -static int test_csr(const char *arg); -static int test_server(const char *arg); -static int test_show(const char *arg); +static int do_unit_tests(void); +static int test_ca(const char *common_name, const char *root_name, int days); +static int test_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 test_client(const char *uri); +static int test_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 test_server(const char *host_port); +static int test_show(const char *common_name); static int usage(FILE *fp); @@ -80,7 +88,7 @@ main(int argc, // I - Number of command-line arguments *country = NULL, // Country *alt_names[100]; // Subject alternate names size_t num_alt_names = 0; - int days; // Days until expiration + int days = 0; // Days until expiration cups_credpurpose_t purpose = CUPS_CREDPURPOSE_SERVER_AUTH; // Certificate purpose cups_credtype_t type = CUPS_CREDTYPE_DEFAULT; @@ -323,10 +331,14 @@ main(int argc, // I - Number of command-line arguments } } + // Make certificate directory + if (access(TEST_CERT_PATH, 0)) + mkdir(TEST_CERT_PATH, 0700); + + // Do unit tests or sub-command... if (!subcommand) { - fputs("testcreds: Missing sub-command.\n", stderr); - return (usage(stderr)); + return (do_unit_tests()); } else if (!arg) { @@ -337,15 +349,15 @@ main(int argc, // I - Number of command-line arguments // Run the corresponding sub-command... if (!strcmp(subcommand, "ca")) { - return (test_ca(arg)); + return (test_ca(arg, root_name, days)); } else if (!strcmp(subcommand, "cacert")) { - return (test_cacert(arg)); + return (test_cert(true, purpose, type, keyusage, organization, org_unit, locality, state, country, root_name, arg, num_alt_names, alt_names, days)); } else if (!strcmp(subcommand, "cert")) { - return (test_cert(arg)); + return (test_cert(false, purpose, type, keyusage, organization, org_unit, locality, state, country, root_name, arg, num_alt_names, alt_names, days)); } else if (!strcmp(subcommand, "client")) { @@ -353,7 +365,7 @@ main(int argc, // I - Number of command-line arguments } else if (!strcmp(subcommand, "csr")) { - return (test_csr(arg)); + return (test_csr(purpose, type, keyusage, organization, org_unit, locality, state, country, arg, num_alt_names, alt_names)); } else if (!strcmp(subcommand, "server")) { @@ -372,25 +384,29 @@ main(int argc, // I - Number of command-line arguments // -// 'test_ca()' - Test generating a certificate from a CSR. +// 'do_unit_tests()' - Do unit tests. // static int // O - Exit status -test_ca(const char *arg) // I - Common name +do_unit_tests(void) { - (void)arg; return (1); } // -// 'test_cacert()' - Test creating a CA certificate. +// 'test_ca()' - Test generating a certificate from a CSR. // static int // O - Exit status -test_cacert(const char *arg) // I - Common name +test_ca(const char *common_name, // I - Common name + const char *root_name, // I - Root certificate name + int days) // I - Number of days { - (void)arg; + (void)common_name; + (void)root_name; + (void)days; + return (1); } @@ -400,10 +416,55 @@ test_cacert(const char *arg) // I - Common name // static int // O - Exit status -test_cert(const char *arg) // I - Common name +test_cert( + bool ca_cert, // I - `true` for a CA certificate, `false` for a regular one + cups_credpurpose_t purpose, // I - Certificate purpose + cups_credtype_t type, // I - Certificate type + cups_credusage_t keyusage, // I - Key usage + const char *organization, // I - Organization + const char *org_unit, // I - Organizational unit + const char *locality, // I - Locality (city/town/etc.) + const char *state, // I - State/province + const char *country, // I - Country + const char *root_name, // I - Root certificate name + const char *common_name, // I - Common name + size_t num_alt_names, // I - Number of subjectAltName's + const char **alt_names, // I - subjectAltName's + int days) // I - Number of days until expiration { - (void)arg; - return (1); + char *cert, // Certificate + *key; // Private key + + + if (!cupsCreateCredentials(TEST_CERT_PATH, ca_cert, purpose, type, keyusage, organization, org_unit, locality, state, country, common_name, num_alt_names, alt_names, root_name, time(NULL) + days * 86400)) + { + fprintf(stderr, "testcreds: Unable to create certificate (%s)\n", cupsLastErrorString()); + return (1); + } + + if ((cert = cupsCopyCredentials(TEST_CERT_PATH, common_name)) != NULL) + { + puts(cert); + free(cert); + } + else + { + fprintf(stderr, "testcreds: Unable to get generated certificate for '%s'.\n", common_name); + return (1); + } + + if ((key = cupsCopyCredentialsKey(TEST_CERT_PATH, common_name)) != NULL) + { + puts(key); + free(key); + } + else + { + fprintf(stderr, "testcreds: Unable to get generated private key for '%s'.\n", common_name); + return (1); + } + + return (0); } @@ -412,7 +473,7 @@ test_cert(const char *arg) // I - Common name // static int // O - Exit status -test_client(const char *arg) // I - URI +test_client(const char *uri) // I - URI { http_t *http; // HTTP connection char scheme[HTTP_MAX_URI], // Scheme from URI @@ -421,19 +482,17 @@ test_client(const char *arg) // I - URI resource[HTTP_MAX_URI]; // Resource from URI int port; // Port number from URI http_trust_t trust; // Trust evaluation for connection - cups_array_t *hcreds, // Credentials from connection - *tcreds; // Credentials from trust store + cups_array_t *hcreds; // Credentials from connection char hinfo[1024], // String for connection credentials - tinfo[1024], // String for trust store credentials datestr[256]; // Date string static const char *trusts[] = // Trust strings { "OK", "Invalid", "Changed", "Expired", "Renewed", "Unknown" }; // Connect to the host and validate credentials... - if (httpSeparateURI(HTTP_URI_CODING_MOST, arg, scheme, sizeof(scheme), username, sizeof(username), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK) + if (httpSeparateURI(HTTP_URI_CODING_MOST, uri, scheme, sizeof(scheme), username, sizeof(username), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK) { - fprintf(stderr, "testcreds: Bad URI '%s'.\n", arg); + fprintf(stderr, "testcreds: Bad URI '%s'.\n", uri); return (1); } @@ -468,22 +527,46 @@ test_client(const char *arg) // I - URI puts(""); - printf("Trust Store for \"%s\":\n", hostname); + return (test_show(hostname)); +} - if (httpLoadCredentials(NULL, &tcreds, hostname)) - { - httpCredentialsString(tcreds, tinfo, sizeof(tinfo)); - printf(" Certificate Count: %u\n", (unsigned)cupsArrayGetCount(tcreds)); - printf(" Expiration: %s\n", httpGetDateString(httpCredentialsGetExpiration(tcreds), datestr, sizeof(datestr))); - printf(" IsValidName: %s\n", httpCredentialsAreValidForName(tcreds, hostname) ? "true" : "false"); - printf(" String: \"%s\"\n", tinfo); +// +// 'test_csr()' - Test creating a certificate signing request. +// - httpFreeCredentials(tcreds); +static int // O - Exit status +test_csr( + cups_credpurpose_t purpose, // I - Certificate purpose + cups_credtype_t type, // I - Certificate type + cups_credusage_t keyusage, // I - Key usage + const char *organization, // I - Organization + const char *org_unit, // I - Organizational unit + const char *locality, // I - Locality (city/town/etc.) + const char *state, // I - State/province + const char *country, // I - Country + const char *common_name, // I - Common name + size_t num_alt_names, // I - Number of subjectAltName's + const char **alt_names) // I - subjectAltName's +{ + char *csr; // Certificate request + + + if (!cupsCreateCredentialsRequest(TEST_CERT_PATH, purpose, type, keyusage, organization, org_unit, locality, state, country, common_name, num_alt_names, alt_names)) + { + fprintf(stderr, "testcreds: Unable to create certificate request (%s)\n", cupsLastErrorString()); + return (1); + } + + if ((csr = cupsCopyCredentialsRequest(TEST_CERT_PATH, common_name)) != NULL) + { + puts(csr); + free(csr); } else { - puts(" Not present."); + fprintf(stderr, "testcreds: Unable to get generated certificate request for '%s'.\n", common_name); + return (1); } return (0); @@ -491,25 +574,32 @@ test_client(const char *arg) // I - URI // -// 'test_csr()' - Test creating a certificate signing request. +// 'test_server()' - Test running a server. // static int // O - Exit status -test_csr(const char *arg) // I - Common name +test_server(const char *host_port) // I - Hostname/port { - (void)arg; - return (1); -} + char host[256], // Hostname + *hostptr; // Pointer into hostname + int port; // Port number -// -// 'test_server()' - Test running a server. -// + cupsCopyString(host, host_port, sizeof(host)); + if ((hostptr = strrchr(host, ':')) != NULL) + { + // Extract the port number from the argument... + *hostptr++ = '\0'; + port = atoi(hostptr); + } + else + { + // Use the default port 8NNN where NNN is the bottom 3 digits of the UID... + port = 8000 + (int)getuid() % 1000; + } + + printf("Listening for connections on port %d...\n", port); -static int // O - Exit status -test_server(const char *arg) // I - Hostname/port -{ - (void)arg; return (1); } @@ -519,10 +609,32 @@ test_server(const char *arg) // I - Hostname/port // static int // O - Exit status -test_show(const char *arg) // I - Common name +test_show(const char *common_name) // I - Common name { - (void)arg; - return (1); + cups_array_t *tcreds; // Credentials from trust store + char tinfo[1024], // String for trust store credentials + datestr[256]; // Date string + + + printf("Trust Store for \"%s\":\n", common_name); + + if (httpLoadCredentials(NULL, &tcreds, common_name)) + { + httpCredentialsString(tcreds, tinfo, sizeof(tinfo)); + + printf(" Certificate Count: %u\n", (unsigned)cupsArrayGetCount(tcreds)); + printf(" Expiration: %s\n", httpGetDateString(httpCredentialsGetExpiration(tcreds), datestr, sizeof(datestr))); + printf(" IsValidName: %s\n", httpCredentialsAreValidForName(tcreds, common_name) ? "true" : "false"); + printf(" String: \"%s\"\n", tinfo); + + httpFreeCredentials(tcreds); + } + else + { + puts(" Not present."); + } + + return (0); } From 79d237d969927c3c587b2af0d9acb011247ae2ad Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Sun, 7 May 2023 18:01:32 -0400 Subject: [PATCH 10/31] Separate 'unittests' target, drop 'local' target. --- Makedefs.in | 8 -------- Makefile | 13 ++++++++++++- configure | 15 --------------- configure.ac | 9 --------- cups/Makefile | 7 +++++++ doc/Makefile | 9 ++++++++- examples/Makefile | 7 +++++++ man/Makefile | 8 ++++---- tools/Makefile | 9 ++++----- 9 files changed, 42 insertions(+), 43 deletions(-) diff --git a/Makedefs.in b/Makedefs.in index a8edba1ef..8824d7981 100644 --- a/Makedefs.in +++ b/Makedefs.in @@ -49,14 +49,6 @@ INSTALL_LIB = $(INSTALL) -c -m 755 INSTALL_MAN = $(INSTALL) -c -m 444 -# -# Cross-compilation support: "local" target is used for any tools that are -# built and run locally. -# - -LOCALTARGET = @LOCALTARGET@ - - # # Libraries... # diff --git a/Makefile b/Makefile index 2e8353f97..76acc66bc 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # # Top-level Makefile for libcups. # -# Copyright © 2020-2022 by OpenPrinting +# Copyright © 2020-2023 by OpenPrinting # # Licensed under Apache License v2.0. See the file "LICENSE" for more # information. @@ -100,6 +100,17 @@ uninstall: done +# +# Build all unit tests... +# + +unittests: all + for dir in $(DIRS); do\ + echo "======== unittests in $$dir ========";\ + (cd $$dir ; $(MAKE) $(MFLAGS) unittests) || exit 1;\ + done + + # # Test everything... # diff --git a/configure b/configure index fb9c87b08..70b354830 100755 --- a/configure +++ b/configure @@ -684,7 +684,6 @@ CC OPTIM DSOFLAGS CODE_SIGN -LOCALTARGET host_os host_vendor host_cpu @@ -2558,20 +2557,6 @@ then : fi -if test "$build" = "$host" -then : - - # No, build local targets - LOCALTARGET="local" - -else $as_nop - - # Yes, don't build local targets - LOCALTARGET="" - -fi - - for ac_prog in codesign true do # Extract the first word of "$ac_prog", so it can be a program name with args. diff --git a/configure.ac b/configure.ac index f04930282..fba17aa9e 100644 --- a/configure.ac +++ b/configure.ac @@ -46,15 +46,6 @@ AS_IF([test "x$host_os_version" = x], [ dnl Determine whether we are cross-compiling... -AS_IF([test "$build" = "$host"], [ - # No, build local targets - LOCALTARGET="local" -], [ - # Yes, don't build local targets - LOCALTARGET="" -]) -AC_SUBST([LOCALTARGET]) - AC_PATH_PROGS([CODE_SIGN], [codesign true]) diff --git a/cups/Makefile b/cups/Makefile index 62d12b36a..c4a876a7f 100644 --- a/cups/Makefile +++ b/cups/Makefile @@ -218,6 +218,13 @@ fuzz: $(UNITTARGETS) AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=yes AFL_NO_UI=yes afl-fuzz -n -i json-afl-in -o json-afl-out -V 120 -- ./testjson 2>>fuzz.log +# +# Make unit tests... +# + +unittests: $(UNITTARGETS) + + # # Remove object and target files... # diff --git a/doc/Makefile b/doc/Makefile index 495a2ad8a..5d00c7da5 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -37,12 +37,19 @@ libs: # -# Make unit tests... +# Run unit tests... # test: +# +# Make unit tests... +# + +unittests: + + # # Remove all generated files... # diff --git a/examples/Makefile b/examples/Makefile index bdda03ded..0f5395dd7 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -104,6 +104,13 @@ all: libs: +# +# Run unit tests... +# + +test: + + # # Make unit tests... # diff --git a/man/Makefile b/man/Makefile index 355c302b0..1ca31e5ad 100644 --- a/man/Makefile +++ b/man/Makefile @@ -1,7 +1,7 @@ # # Man page makefile for libcups. # -# Copyright © 2021 by OpenPrinting. +# Copyright © 2021-2023 by OpenPrinting. # Copyright © 2007-2019 by Apple Inc. # Copyright © 1993-2006 by Easy Software Products. # @@ -33,7 +33,7 @@ all: $(LOCALTARGET) # -# Make unit tests... +# Run unit tests... # test: @@ -85,10 +85,10 @@ uninstall: # -# Local programs (not built when cross-compiling...) +# Unit test programs (not built when cross-compiling...) # -local: +unittests: # diff --git a/tools/Makefile b/tools/Makefile index af2eadc96..f1fac06a7 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -25,8 +25,7 @@ IPPTOOLS = \ ippfind \ ipptool TARGETS = \ - $(IPPTOOLS) \ - $(LOCALTARGET) + $(IPPTOOLS) # @@ -100,17 +99,17 @@ uninstall: # -# Local programs (not built when cross-compiling...) +# Unit test programs (not built when cross-compiling...) # -local: ippeveprinter-static ippfind-static ipptool-static +unittests: ippeveprinter-static ippfind-static ipptool-static # # Test everything... # -test: local +test: unittests ./run-tests.sh From 677e1bd60186491d7d035d53da7b88722a4dfdda Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Mon, 8 May 2023 08:56:12 -0400 Subject: [PATCH 11/31] Fix up subjectAltName support. --- cups/testcreds.c | 2 +- cups/tls-openssl.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cups/testcreds.c b/cups/testcreds.c index 31556afe7..a71617377 100644 --- a/cups/testcreds.c +++ b/cups/testcreds.c @@ -88,7 +88,7 @@ main(int argc, // I - Number of command-line arguments *country = NULL, // Country *alt_names[100]; // Subject alternate names size_t num_alt_names = 0; - int days = 0; // Days until expiration + int days = 365; // Days until expiration cups_credpurpose_t purpose = CUPS_CREDPURPOSE_SERVER_AUTH; // Certificate purpose cups_credtype_t type = CUPS_CREDTYPE_DEFAULT; diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index 172e82f89..815e3bdab 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -1550,7 +1550,7 @@ openssl_create_san( // Add the common name - snprintf(temp, sizeof(temp), "dns:%s", common_name); + snprintf(temp, sizeof(temp), "DNS:%s", common_name); tempptr = temp + strlen(temp); if (strstr(common_name, ".local") == NULL) @@ -1563,7 +1563,7 @@ openssl_create_san( if ((localptr = strchr(localname, '.')) != NULL) *localptr = '\0'; - snprintf(tempptr, sizeof(temp) - (size_t)(tempptr - temp), ",dns:%s.local", localname); + snprintf(tempptr, sizeof(temp) - (size_t)(tempptr - temp), ",DNS:%s.local", localname); tempptr += strlen(tempptr); } @@ -1572,7 +1572,7 @@ openssl_create_san( { if (strcmp(alt_names[i], "localhost")) { - snprintf(tempptr, sizeof(temp) - (size_t)(tempptr - temp), ",dns:%s", alt_names[i]); + snprintf(tempptr, sizeof(temp) - (size_t)(tempptr - temp), ",DNS:%s", alt_names[i]); tempptr += strlen(tempptr); } } From 2d570f332d944ee406f9e12313f5519bed9c45f3 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Mon, 8 May 2023 08:58:19 -0400 Subject: [PATCH 12/31] Don't use _cupsStrAlloc for globals - causes recursion. --- cups/cups-private.h | 4 ++-- cups/globals.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cups/cups-private.h b/cups/cups-private.h index 4584c531e..6822d017f 100644 --- a/cups/cups-private.h +++ b/cups/cups-private.h @@ -76,8 +76,8 @@ typedef struct _cups_globals_s /**** CUPS global state data ****/ { /* Multiple places... */ const char *datadir, // Data directory (CUPS_DATADIR environment var) - *sysconfig, // System config files (CUPS_SERVERROOT environment var) - *userconfig; // User-specific config files (various environment vars) + *sysconfig; // System config files (CUPS_SERVERROOT environment var) + char *userconfig; // User-specific config files #ifndef _WIN32 #define PW_BUF_SIZE 16384 /* As per glibc manual page */ char pw_buf[PW_BUF_SIZE]; diff --git a/cups/globals.c b/cups/globals.c index f9400ad0a..949120962 100644 --- a/cups/globals.c +++ b/cups/globals.c @@ -360,7 +360,8 @@ cups_globals_alloc(void) } # endif // __APPLE__ - cg->userconfig = _cupsStrAlloc(temp); + // Can't use _cupsStrAlloc since it causes a loop with debug logging enabled + cg->userconfig = strdup(temp); #endif // _WIN32 return (cg); @@ -393,14 +394,13 @@ cups_globals_free(_cups_globals_t *cg) // I - Pointer to global data httpClose(cg->http); -#ifdef HAVE_TLS _httpFreeCredentials(cg->tls_credentials); -#endif // HAVE_TLS cupsFileClose(cg->stdio_files[0]); cupsFileClose(cg->stdio_files[1]); cupsFileClose(cg->stdio_files[2]); + free(cg->userconfig); free(cg->raster_error.start); free(cg); } From db2cd3db47f211b10eb4639f54daaff44cf62af2 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Wed, 10 May 2023 19:07:04 -0400 Subject: [PATCH 13/31] Fix testpwg. --- configure | 29 +- configure.ac | 13 +- cups/Makefile | 9 +- cups/pwg-media.c | 18 +- cups/testpwg.c | 638 ++++-------------- xcode/libcups.xcodeproj/project.pbxproj | 15 +- .../xcshareddata/xcschemes/Tests.xcscheme | 2 +- .../xcshareddata/xcschemes/fuzzipp.xcscheme | 2 +- .../xcshareddata/xcschemes/ippevepcl.xcscheme | 2 +- .../xcshareddata/xcschemes/ippeveps.xcscheme | 2 +- .../xcshareddata/xcschemes/libcups.xcscheme | 2 +- .../xcshareddata/xcschemes/testcreds.xcscheme | 2 +- .../xcshareddata/xcschemes/testlang.xcscheme | 2 +- .../xcshareddata/xcschemes/testpwg.xcscheme | 2 +- .../xcschemes/testtestpage.xcscheme | 2 +- 15 files changed, 188 insertions(+), 552 deletions(-) diff --git a/configure b/configure index 70b354830..c16cb2bc1 100755 --- a/configure +++ b/configure @@ -744,7 +744,7 @@ enable_static enable_shared enable_debug enable_maintainer -enable_sanitizer +with_sanitizer with_dsoflags with_ldflags with_domainsocket @@ -1385,13 +1385,14 @@ Optional Features: --disable-shared do not install shared library --enable-debug turn on debugging, default=no --enable-maintainer turn on maintainer mode, default=no - --enable-sanitizer build with AddressSanitizer, default=no Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-dnssd=LIBRARY set DNS-SD library (auto, avahi, mdnsresponder) --with-tls=... use gnutls or openssl/libressl for TLS support + --with-sanitizer build with address, leak, memory, thread, or + undefined sanitizer, default=no --with-dsoflags=... Specify additional DSOFLAGS --with-ldflags=... Specify additional LDFLAGS --with-domainsocket set unix domain socket name @@ -5202,12 +5203,24 @@ then : enableval=$enable_maintainer; fi -# Check whether --enable-sanitizer was given. -if test ${enable_sanitizer+y} + +# Check whether --with-sanitizer was given. +if test ${with_sanitizer+y} then : - enableval=$enable_sanitizer; + withval=$with_sanitizer; fi +if test "x$with_sanitizer" = x +then : + + with_sanitizer="address" + +elif test "$with_sanitizer" != address -a "$with_sanitizer" != leak -a "$with_sanitizer" != memory -a "$with_sanitizer" != no -a "$with_sanitizer" != thread -a "$with_sanitizer" != undefined +then : + + as_fn_error $? "Unsupported --with-sanitizer value \"$with_sanitizer\" specified." "$LINENO" 5 + +fi if test x$enable_debug = xyes then : @@ -5233,11 +5246,11 @@ WARNINGS="" if test -n "$GCC" then : - if test x$enable_sanitizer = xyes + if test x$with_sanitizer != xno then : - # Use -fsanitize=address with debugging... - OPTIM="$OPTIM -fsanitize=address" + # Use -fsanitize=FOO with debugging... + OPTIM="$OPTIM -fsanitize=$with_sanitizer" elif echo "$CPPFLAGS $CFLAGS" | grep -q _FORTIFY_SOURCE then : diff --git a/configure.ac b/configure.ac index fba17aa9e..899e31c62 100644 --- a/configure.ac +++ b/configure.ac @@ -454,7 +454,12 @@ AC_SUBST([LIBCUPS_STATIC]) dnl Extra compiler options... AC_ARG_ENABLE([debug], AS_HELP_STRING([--enable-debug], [turn on debugging, default=no])) AC_ARG_ENABLE([maintainer], AS_HELP_STRING([--enable-maintainer], [turn on maintainer mode, default=no])) -AC_ARG_ENABLE([sanitizer], AS_HELP_STRING([--enable-sanitizer], [build with AddressSanitizer, default=no])) +AC_ARG_WITH([sanitizer], AS_HELP_STRING([--with-sanitizer], [build with address, leak, memory, thread, or undefined sanitizer, default=no])) +AS_IF([test "x$with_sanitizer" = x], [ + with_sanitizer="address" +], [test "$with_sanitizer" != address -a "$with_sanitizer" != leak -a "$with_sanitizer" != memory -a "$with_sanitizer" != no -a "$with_sanitizer" != thread -a "$with_sanitizer" != undefined], [ + AC_MSG_ERROR([Unsupported --with-sanitizer value "$with_sanitizer" specified.]) +]) AS_IF([test x$enable_debug = xyes], [ CSFLAGS="" @@ -473,9 +478,9 @@ WARNINGS="" AC_SUBST([WARNINGS]) AS_IF([test -n "$GCC"], [ - AS_IF([test x$enable_sanitizer = xyes], [ - # Use -fsanitize=address with debugging... - OPTIM="$OPTIM -fsanitize=address" + AS_IF([test x$with_sanitizer != xno], [ + # Use -fsanitize=FOO with debugging... + OPTIM="$OPTIM -fsanitize=$with_sanitizer" ], [echo "$CPPFLAGS $CFLAGS" | grep -q _FORTIFY_SOURCE], [ # Don't add _FORTIFY_SOURCE if it is already there ], [ diff --git a/cups/Makefile b/cups/Makefile index c4a876a7f..9d44aa5d3 100644 --- a/cups/Makefile +++ b/cups/Makefile @@ -74,6 +74,7 @@ TESTOBJS = \ testjson.o \ testjwt.o \ testoptions.o \ + testpwg.o \ testraster.o \ testtestpage.o \ testthreads.o \ @@ -83,7 +84,6 @@ OBJS = \ $(TESTOBJS) # testlang.o \ -# testpwg.o \ # @@ -156,13 +156,13 @@ UNITTARGETS = \ testjson \ testjwt \ testoptions \ + testpwg \ testraster \ testtestpage \ testthreads \ tlscheck # testlang \ -# testpwg \ TARGETS = \ $(LIBTARGETS) @@ -181,6 +181,7 @@ all: $(TARGETS) test: $(UNITTARGETS) rm -f test.log + date >test.log echo Running array API tests... ./testarray 2>>test.log # if test `uname` != Darwin; then \ @@ -207,6 +208,8 @@ test: $(UNITTARGETS) ./testjwt 2>>test.log echo Running option API tests... ./testoptions 2>>test.log + echo Running PWG API tests... + ./testpwg 2>>test.log echo Running raster API tests... ./testraster 2>>test.log ./testtestpage 2>>test.log @@ -579,8 +582,6 @@ testpwg: testpwg.o $(LIBCUPS_STATIC) test.ppd echo Linking $@... $(CC) $(LDFLAGS) $(OPTIM) -o $@ testpwg.o $(LIBCUPS_STATIC) $(LIBS) $(CODE_SIGN) $(CSFLAGS) $@ - echo Running PWG API tests... - ./testpwg test.ppd # diff --git a/cups/pwg-media.c b/cups/pwg-media.c index 4d329b606..42cb91d35 100644 --- a/cups/pwg-media.c +++ b/cups/pwg-media.c @@ -1,7 +1,7 @@ /* * PWG media name API implementation for CUPS. * - * Copyright © 2022 by OpenPrinting. + * Copyright © 2022-2023 by OpenPrinting. * Copyright © 2009-2019 by Apple Inc. * * Licensed under Apache License v2.0. See the file "LICENSE" for more @@ -970,16 +970,18 @@ pwg_format_inches(char *buf, // I - Buffer size_t bufsize, // I - Size of buffer int val) // I - Value in hundredths of millimeters { - int thousandths, // Thousandths of inches - integer, // Integer portion + int integer, // Integer portion fraction; // Fractional portion - // Convert hundredths of millimeters to thousandths of inches and round to - // the nearest thousandth. - thousandths = (val * 1000 + 1270) / 2540; - integer = thousandths / 1000; - fraction = thousandths % 1000; + // Convert hundredths of millimeters to inches and thousandths. + integer = val / 2540; + fraction = ((val % 2540) * 1000 + 1270) / 2540; + if (fraction >= 1000) + { + integer ++; + fraction -= 1000; + } // Format as a pair of integers (avoids locale stuff), avoiding trailing zeros... if (fraction == 0) diff --git a/cups/testpwg.c b/cups/testpwg.c index 6473c8cf9..feacdb1e0 100644 --- a/cups/testpwg.c +++ b/cups/testpwg.c @@ -1,565 +1,167 @@ -/* - * PWG unit test program for CUPS. - * - * Copyright 2009-2016 by Apple Inc. - * - * Licensed under Apache License v2.0. See the file "LICENSE" for more information. - */ +// +// PWG unit test program for CUPS. +// +// Copyright © 2023 by OpenPrinting. +// Copyright © 2009-2016 by Apple Inc. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more information. +// /* * Include necessary headers... */ -#include "ppd-private.h" -#include "file-private.h" - - -/* - * Local functions... - */ +#include "cups.h" +#include "pwg-private.h" +#include "test-internal.h" +#include -static int test_pagesize(_ppd_cache_t *pc, ppd_file_t *ppd, - const char *ppdsize); -static int test_ppd_cache(_ppd_cache_t *pc, ppd_file_t *ppd); +// +// 'main()' - Main entry. +// -/* - * 'main()' - Main entry. - */ - -int /* O - Exit status */ -main(int argc, /* I - Number of command-line args */ - char *argv[]) /* I - Command-line arguments */ +int // O - Exit status +main(int argc, // I - Number of command-line args + char *argv[]) // I - Command-line arguments { - int status; /* Status of tests (0 = success, 1 = fail) */ - const char *ppdfile; /* PPD filename */ - ppd_file_t *ppd; /* PPD file */ - _ppd_cache_t *pc; /* PPD cache and PWG mapping data */ - const pwg_media_t *pwgmedia; /* PWG media size */ - size_t i, /* Looping var */ - num_media; /* Number of media sizes */ - const pwg_media_t *mediatable; /* Media size table */ - int dupmedia = 0; /* Duplicate media sizes? */ - - - status = 0; + const pwg_media_t *pwg; // PWG media size + size_t i, // Looping var + num_media; // Number of media sizes + const pwg_media_t *mediatable; // Media size table - if (argc < 2 || argc > 3) - { - puts("Usage: ./testpwg filename.ppd [jobfile]"); - return (1); - } - - ppdfile = argv[1]; - - printf("ppdOpenFile(%s): ", ppdfile); - if ((ppd = ppdOpenFile(ppdfile)) == NULL) - { - ppd_status_t err; /* Last error in file */ - int line; /* Line number in file */ - - - err = ppdLastError(&line); - - printf("FAIL (%s on line %d)\n", ppdErrorString(err), line); - - return (1); - } - else - puts("PASS"); - fputs("_ppdCacheCreateWithPPD(ppd): ", stdout); - if ((pc = _ppdCacheCreateWithPPD(ppd)) == NULL) - { - puts("FAIL"); - status ++; - } - else + if (argc > 1) { - puts("PASS"); - status += test_ppd_cache(pc, ppd); + // Decode media sizes... + int j; // Looping var - if (argc == 3) + for (j = 1; j < argc; j ++) { - /* - * Test PageSize mapping code. - */ - - int fd; /* Job file descriptor */ - const char *pagesize; /* PageSize value */ - ipp_t *job; /* Job attributes */ - ipp_attribute_t *media; /* Media attribute */ + int width, length; // Width and length - if ((fd = open(argv[2], O_RDONLY)) >= 0) - { - job = ippNew(); - ippReadFile(fd, job); - close(fd); + if (isdigit(argv[j][0] & 255) && sscanf(argv[j], "%d,%d", &width, &length) == 2) + pwg = pwgMediaForSize(width, length); + else if ((pwg = pwgMediaForLegacy(argv[j])) == NULL) + pwg = pwgMediaForPWG(argv[j]); - if ((media = ippFindAttribute(job, "media", IPP_TAG_ZERO)) != NULL && - media->value_tag != IPP_TAG_NAME && - media->value_tag != IPP_TAG_KEYWORD) - media = NULL; - - if (media) - printf("_ppdCacheGetPageSize(media=%s): ", - media->values[0].string.text); - else - fputs("_ppdCacheGetPageSize(media-col): ", stdout); - - fflush(stdout); - - if ((pagesize = _ppdCacheGetPageSize(pc, job, NULL, NULL)) == NULL) - { - puts("FAIL (Not Found)"); - status = 1; - } - else if (media && _cups_strcasecmp(pagesize, media->values[0].string.text)) - { - printf("FAIL (Got \"%s\", Expected \"%s\")\n", pagesize, - media->values[0].string.text); - status = 1; - } - else - printf("PASS (%s)\n", pagesize); - - ippDelete(job); - } + if (pwg) + printf("%s = %s (%.2fx%.2fmm", argv[j], pwg->pwg, pwg->width * 0.01, pwg->length * 0.01); else - { - perror(argv[2]); - status = 1; - } + printf("%s = BAD\n", argv[j]); } - /* - * _ppdCacheDestroy should never fail... - */ - - fputs("_ppdCacheDestroy(pc): ", stdout); - _ppdCacheDestroy(pc); - puts("PASS"); + return (0); } - fputs("pwgMediaForPWG(\"iso_a4_210x297mm\"): ", stdout); - if ((pwgmedia = pwgMediaForPWG("iso_a4_210x297mm")) == NULL) - { - puts("FAIL (not found)"); - status ++; - } - else if (strcmp(pwgmedia->pwg, "iso_a4_210x297mm")) - { - printf("FAIL (%s)\n", pwgmedia->pwg); - status ++; - } - else if (pwgmedia->width != 21000 || pwgmedia->length != 29700) - { - printf("FAIL (%dx%d)\n", pwgmedia->width, pwgmedia->length); - status ++; - } + testBegin("pwgMediaForPWG(\"iso_a4_210x297mm\")"); + if ((pwg = pwgMediaForPWG("iso_a4_210x297mm")) == NULL) + testEndMessage(false, "not found"); + else if (strcmp(pwg->pwg, "iso_a4_210x297mm")) + testEndMessage(false, "%s", pwg->pwg); + else if (pwg->width != 21000 || pwg->length != 29700) + testEndMessage(false, "%dx%d", pwg->width, pwg->length); else - puts("PASS"); + testEnd(true); - fputs("pwgMediaForPWG(\"roll_max_36.1025x3622.0472in\"): ", stdout); - if ((pwgmedia = pwgMediaForPWG("roll_max_36.1025x3622.0472in")) == NULL) - { - puts("FAIL (not found)"); - status ++; - } - else if (pwgmedia->width != 91700 || pwgmedia->length != 9199999) - { - printf("FAIL (%dx%d)\n", pwgmedia->width, pwgmedia->length); - status ++; - } + testBegin("pwgMediaForPWG(\"roll_max_36.1025x3622.0472in\")"); + if ((pwg = pwgMediaForPWG("roll_max_36.1025x3622.0472in")) == NULL) + testEndMessage(false, "not found"); + else if (pwg->width != 91700 || pwg->length != 9199999) + testEndMessage(false, "%dx%d", pwg->width, pwg->length); else - printf("PASS (%dx%d)\n", pwgmedia->width, pwgmedia->length); + testEndMessage(true, "%dx%d", pwg->width, pwg->length); - fputs("pwgMediaForPWG(\"disc_test_10x100mm\"): ", stdout); - if ((pwgmedia = pwgMediaForPWG("disc_test_10x100mm")) == NULL) - { - puts("FAIL (not found)"); - status ++; - } - else if (pwgmedia->width != 10000 || pwgmedia->length != 10000) - { - printf("FAIL (%dx%d)\n", pwgmedia->width, pwgmedia->length); - status ++; - } + testBegin("pwgMediaForPWG(\"disc_test_10x100mm\")"); + if ((pwg = pwgMediaForPWG("disc_test_10x100mm")) == NULL) + testEndMessage(false, "not found"); + else if (pwg->width != 10000 || pwg->length != 10000) + testEndMessage(false, "%dx%d", pwg->width, pwg->length); else - printf("PASS (%dx%d)\n", pwgmedia->width, pwgmedia->length); - - fputs("pwgMediaForLegacy(\"na-letter\"): ", stdout); - if ((pwgmedia = pwgMediaForLegacy("na-letter")) == NULL) - { - puts("FAIL (not found)"); - status ++; - } - else if (strcmp(pwgmedia->pwg, "na_letter_8.5x11in")) - { - printf("FAIL (%s)\n", pwgmedia->pwg); - status ++; - } - else if (pwgmedia->width != 21590 || pwgmedia->length != 27940) - { - printf("FAIL (%dx%d)\n", pwgmedia->width, pwgmedia->length); - status ++; - } + testEndMessage(true, "%dx%d", pwg->width, pwg->length); + + testBegin("pwgMediaForLegacy(\"na-letter\")"); + if ((pwg = pwgMediaForLegacy("na-letter")) == NULL) + testEndMessage(false, "not found"); + else if (strcmp(pwg->pwg, "na_letter_8.5x11in")) + testEndMessage(false, "%s", pwg->pwg); + else if (pwg->width != 21590 || pwg->length != 27940) + testEndMessage(false, "%dx%d", pwg->width, pwg->length); else - puts("PASS"); - - fputs("pwgMediaForPPD(\"4x6\"): ", stdout); - if ((pwgmedia = pwgMediaForPPD("4x6")) == NULL) - { - puts("FAIL (not found)"); - status ++; - } - else if (strcmp(pwgmedia->pwg, "na_index-4x6_4x6in")) - { - printf("FAIL (%s)\n", pwgmedia->pwg); - status ++; - } - else if (pwgmedia->width != 10160 || pwgmedia->length != 15240) - { - printf("FAIL (%dx%d)\n", pwgmedia->width, pwgmedia->length); - status ++; - } + testEnd(true); + + testBegin("pwgMediaForPPD(\"4x6\")"); + if ((pwg = pwgMediaForPPD("4x6")) == NULL) + testEndMessage(false, "not found"); + else if (strcmp(pwg->pwg, "na_index-4x6_4x6in")) + testEndMessage(false, "%s", pwg->pwg); + else if (pwg->width != 10160 || pwg->length != 15240) + testEndMessage(false, "%dx%d", pwg->width, pwg->length); else - puts("PASS"); - - fputs("pwgMediaForPPD(\"10x15cm\"): ", stdout); - if ((pwgmedia = pwgMediaForPPD("10x15cm")) == NULL) - { - puts("FAIL (not found)"); - status ++; - } - else if (strcmp(pwgmedia->pwg, "om_100x150mm_100x150mm")) - { - printf("FAIL (%s)\n", pwgmedia->pwg); - status ++; - } - else if (pwgmedia->width != 10000 || pwgmedia->length != 15000) - { - printf("FAIL (%dx%d)\n", pwgmedia->width, pwgmedia->length); - status ++; - } + testEnd(true); + + testBegin("pwgMediaForPPD(\"10x15cm\")"); + if ((pwg = pwgMediaForPPD("10x15cm")) == NULL) + testEndMessage(false, "not found"); + else if (strcmp(pwg->pwg, "om_100x150mm_100x150mm")) + testEndMessage(false, "%s", pwg->pwg); + else if (pwg->width != 10000 || pwg->length != 15000) + testEndMessage(false, "%dx%d", pwg->width, pwg->length); else - puts("PASS"); - - fputs("pwgMediaForPPD(\"Custom.10x15cm\"): ", stdout); - if ((pwgmedia = pwgMediaForPPD("Custom.10x15cm")) == NULL) - { - puts("FAIL (not found)"); - status ++; - } - else if (strcmp(pwgmedia->pwg, "custom_10x15cm_100x150mm")) - { - printf("FAIL (%s)\n", pwgmedia->pwg); - status ++; - } - else if (pwgmedia->width != 10000 || pwgmedia->length != 15000) - { - printf("FAIL (%dx%d)\n", pwgmedia->width, pwgmedia->length); - status ++; - } + testEnd(true); + + testBegin("pwgMediaForPPD(\"Custom.10x15cm\")"); + if ((pwg = pwgMediaForPPD("Custom.10x15cm")) == NULL) + testEndMessage(false, "not found"); + else if (strcmp(pwg->pwg, "custom_10x15cm_100x150mm")) + testEndMessage(false, "%s", pwg->pwg); + else if (pwg->width != 10000 || pwg->length != 15000) + testEndMessage(false, "%dx%d", pwg->width, pwg->length); else - puts("PASS"); + testEnd(true); - fputs("pwgMediaForSize(29700, 42000): ", stdout); - if ((pwgmedia = pwgMediaForSize(29700, 42000)) == NULL) - { - puts("FAIL (not found)"); - status ++; - } - else if (strcmp(pwgmedia->pwg, "iso_a3_297x420mm")) - { - printf("FAIL (%s)\n", pwgmedia->pwg); - status ++; - } + testBegin("pwgMediaForSize(29700, 42000)"); + if ((pwg = pwgMediaForSize(29700, 42000)) == NULL) + testEndMessage(false, "not found"); + else if (strcmp(pwg->pwg, "iso_a3_297x420mm")) + testEndMessage(false, "%s", pwg->pwg); else - puts("PASS"); + testEnd(true); - fputs("pwgMediaForSize(9842, 19050): ", stdout); - if ((pwgmedia = pwgMediaForSize(9842, 19050)) == NULL) - { - puts("FAIL (not found)"); - status ++; - } - else if (strcmp(pwgmedia->pwg, "na_monarch_3.875x7.5in")) - { - printf("FAIL (%s)\n", pwgmedia->pwg); - status ++; - } + testBegin("pwgMediaForSize(9842, 19050)"); + if ((pwg = pwgMediaForSize(9842, 19050)) == NULL) + testEndMessage(false, "not found"); + else if (strcmp(pwg->pwg, "na_monarch_3.875x7.5in")) + testEndMessage(false, "%s", pwg->pwg); else - printf("PASS (%s)\n", pwgmedia->pwg); + testEndMessage(true, "%s", pwg->pwg); - fputs("pwgMediaForSize(9800, 19000): ", stdout); - if ((pwgmedia = pwgMediaForSize(9800, 19000)) == NULL) - { - puts("FAIL (not found)"); - status ++; - } - else if (strcmp(pwgmedia->pwg, "jpn_you6_98x190mm")) - { - printf("FAIL (%s)\n", pwgmedia->pwg); - status ++; - } + testBegin("pwgMediaForSize(9800, 19000)"); + if ((pwg = pwgMediaForSize(9800, 19000)) == NULL) + testEndMessage(false, "not found"); + else if (strcmp(pwg->pwg, "jpn_you6_98x190mm")) + testEndMessage(false, "%s", pwg->pwg); else - printf("PASS (%s)\n", pwgmedia->pwg); + testEndMessage(true, "%s", pwg->pwg); - fputs("Duplicate size test: ", stdout); - for (mediatable = _pwgMediaTable(&num_media); - num_media > 1; - num_media --, mediatable ++) + testBegin("Duplicate size test"); + for (mediatable = _pwgMediaTable(&num_media); num_media > 1; num_media --, mediatable ++) { - for (i = num_media - 1, pwgmedia = mediatable + 1; i > 0; i --, pwgmedia ++) + for (i = num_media - 1, pwg = mediatable + 1; i > 0; i --, pwg ++) { - if (pwgmedia->width == mediatable->width && - pwgmedia->length == mediatable->length) + if (pwg->width == mediatable->width && pwg->length == mediatable->length) { - if (!dupmedia) - { - dupmedia = 1; - status ++; - puts("FAIL"); - } - - printf(" %s and %s have the same dimensions (%dx%d)\n", - pwgmedia->pwg, mediatable->pwg, pwgmedia->width, - pwgmedia->length); + testEndMessage(false, "%s and %s have the same dimensions (%dx%d)", pwg->pwg, mediatable->pwg, pwg->width, pwg->length); + break; } } - } - if (!dupmedia) - puts("PASS"); - - - return (status); -} - - -/* - * 'test_pagesize()' - Test the PWG mapping functions. - */ - -static int /* O - 1 on failure, 0 on success */ -test_pagesize(_ppd_cache_t *pc, /* I - PWG mapping data */ - ppd_file_t *ppd, /* I - PPD file */ - const char *ppdsize) /* I - PPD page size */ -{ - int status = 0; /* Return status */ - ipp_t *job; /* Job attributes */ - const char *pagesize; /* PageSize value */ - - - if (ppdPageSize(ppd, ppdsize)) - { - printf("_ppdCacheGetPageSize(keyword=%s): ", ppdsize); - fflush(stdout); - - if ((pagesize = _ppdCacheGetPageSize(pc, NULL, ppdsize, NULL)) == NULL) - { - puts("FAIL (Not Found)"); - status = 1; - } - else if (_cups_strcasecmp(pagesize, ppdsize)) - { - printf("FAIL (Got \"%s\", Expected \"%s\")\n", pagesize, ppdsize); - status = 1; - } - else - puts("PASS"); - job = ippNew(); - ippAddString(job, IPP_TAG_JOB, IPP_TAG_KEYWORD, "media", NULL, ppdsize); - - printf("_ppdCacheGetPageSize(media=%s): ", ppdsize); - fflush(stdout); - - if ((pagesize = _ppdCacheGetPageSize(pc, job, NULL, NULL)) == NULL) - { - puts("FAIL (Not Found)"); - status = 1; - } - else if (_cups_strcasecmp(pagesize, ppdsize)) - { - printf("FAIL (Got \"%s\", Expected \"%s\")\n", pagesize, ppdsize); - status = 1; - } - else - puts("PASS"); - - ippDelete(job); + if (i > 0) + break; } - return (status); -} - - -/* - * 'test_ppd_cache()' - Test the PPD cache functions. - */ - -static int /* O - 1 on failure, 0 on success */ -test_ppd_cache(_ppd_cache_t *pc, /* I - PWG mapping data */ - ppd_file_t *ppd) /* I - PPD file */ -{ - int i, /* Looping var */ - status = 0; /* Return status */ - _ppd_cache_t *pc2; /* Loaded data */ - pwg_size_t *size, /* Size from original */ - *size2; /* Size from saved */ - pwg_map_t *map, /* Map from original */ - *map2; /* Map from saved */ - - - /* - * Verify that we can write and read back the same data... - */ - - fputs("_ppdCacheWriteFile(test.pwg): ", stdout); - if (!_ppdCacheWriteFile(pc, "test.pwg", NULL)) - { - puts("FAIL"); - status ++; - } - else - puts("PASS"); - - fputs("_ppdCacheCreateWithFile(test.pwg): ", stdout); - if ((pc2 = _ppdCacheCreateWithFile("test.pwg", NULL)) == NULL) - { - puts("FAIL"); - status ++; - } - else - { - // TODO: FINISH ADDING ALL VALUES IN STRUCTURE - if (pc2->num_sizes != pc->num_sizes) - { - if (!status) - puts("FAIL"); - - printf(" SAVED num_sizes=%d, ORIG num_sizes=%d\n", pc2->num_sizes, - pc->num_sizes); - - status ++; - } - else - { - for (i = pc->num_sizes, size = pc->sizes, size2 = pc2->sizes; - i > 0; - i --, size ++, size2 ++) - { - if (strcmp(size2->map.pwg, size->map.pwg) || - strcmp(size2->map.ppd, size->map.ppd) || - size2->width != size->width || - size2->length != size->length || - size2->left != size->left || - size2->bottom != size->bottom || - size2->right != size->right || - size2->top != size->top) - { - if (!status) - puts("FAIL"); - - if (strcmp(size->map.pwg, size2->map.pwg)) - printf(" SAVED size->map.pwg=\"%s\", ORIG " - "size->map.pwg=\"%s\"\n", size2->map.pwg, size->map.pwg); - - if (strcmp(size2->map.ppd, size->map.ppd)) - printf(" SAVED size->map.ppd=\"%s\", ORIG " - "size->map.ppd=\"%s\"\n", size2->map.ppd, size->map.ppd); - - if (size2->width != size->width) - printf(" SAVED size->width=%d, ORIG size->width=%d\n", - size2->width, size->width); - - if (size2->length != size->length) - printf(" SAVED size->length=%d, ORIG size->length=%d\n", - size2->length, size->length); - - if (size2->left != size->left) - printf(" SAVED size->left=%d, ORIG size->left=%d\n", - size2->left, size->left); - - if (size2->bottom != size->bottom) - printf(" SAVED size->bottom=%d, ORIG size->bottom=%d\n", - size2->bottom, size->bottom); - - if (size2->right != size->right) - printf(" SAVED size->right=%d, ORIG size->right=%d\n", - size2->right, size->right); - - if (size2->top != size->top) - printf(" SAVED size->top=%d, ORIG size->top=%d\n", - size2->top, size->top); - - status ++; - break; - } - } - - for (i = pc->num_sources, map = pc->sources, map2 = pc2->sources; - i > 0; - i --, map ++, map2 ++) - { - if (strcmp(map2->pwg, map->pwg) || - strcmp(map2->ppd, map->ppd)) - { - if (!status) - puts("FAIL"); - - if (strcmp(map->pwg, map2->pwg)) - printf(" SAVED source->pwg=\"%s\", ORIG source->pwg=\"%s\"\n", - map2->pwg, map->pwg); - - if (strcmp(map2->ppd, map->ppd)) - printf(" SAVED source->ppd=\"%s\", ORIG source->ppd=\"%s\"\n", - map2->ppd, map->ppd); - - status ++; - break; - } - } - - for (i = pc->num_types, map = pc->types, map2 = pc2->types; - i > 0; - i --, map ++, map2 ++) - { - if (strcmp(map2->pwg, map->pwg) || - strcmp(map2->ppd, map->ppd)) - { - if (!status) - puts("FAIL"); - - if (strcmp(map->pwg, map2->pwg)) - printf(" SAVED type->pwg=\"%s\", ORIG type->pwg=\"%s\"\n", - map2->pwg, map->pwg); - - if (strcmp(map2->ppd, map->ppd)) - printf(" SAVED type->ppd=\"%s\", ORIG type->ppd=\"%s\"\n", - map2->ppd, map->ppd); - - status ++; - break; - } - } - } - - if (!status) - puts("PASS"); - - _ppdCacheDestroy(pc2); - } - - /* - * Test PageSize mapping code... - */ - - status += test_pagesize(pc, ppd, "Letter"); - status += test_pagesize(pc, ppd, "na-letter"); - status += test_pagesize(pc, ppd, "A4"); - status += test_pagesize(pc, ppd, "iso-a4"); + if (num_media == 1) + testEnd(true); - return (status); + return (testsPassed ? 0 : 1); } diff --git a/xcode/libcups.xcodeproj/project.pbxproj b/xcode/libcups.xcodeproj/project.pbxproj index 19a04fc46..8ab966f94 100644 --- a/xcode/libcups.xcodeproj/project.pbxproj +++ b/xcode/libcups.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 2797D73728E5D24E0056D546 /* PBXTargetDependency */, 2794DA8D29DCBDEB00B71A3F /* PBXTargetDependency */, 271284B91CC11FA500E517C7 /* PBXTargetDependency */, + 27C2A7A72A0C59EA00C20AEF /* PBXTargetDependency */, 271284BF1CC11FA500E517C7 /* PBXTargetDependency */, 27D3967927BA8224003D3D8E /* PBXTargetDependency */, 274770E42345347D0089BC31 /* PBXTargetDependency */, @@ -770,6 +771,13 @@ remoteGlobalIDString = 2797D72228E5D2360056D546; remoteInfo = testjson; }; + 27C2A7A62A0C59EA00C20AEF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 72BF96371333042100B1EAD7 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 724FA5F71CC038190092477B; + remoteInfo = testpwg; + }; 27D3966A27BA81D3003D3D8E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 72BF96371333042100B1EAD7 /* Project object */; @@ -2665,7 +2673,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1420; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "Apple Inc."; TargetAttributes = { 270695FD1CADF3E200FFE5FB = { @@ -3284,6 +3292,11 @@ target = 2797D72228E5D2360056D546 /* testjson */; targetProxy = 2797D73628E5D24E0056D546 /* PBXContainerItemProxy */; }; + 27C2A7A72A0C59EA00C20AEF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 724FA5F71CC038190092477B /* testpwg */; + targetProxy = 27C2A7A62A0C59EA00C20AEF /* PBXContainerItemProxy */; + }; 27D3966927BA81D3003D3D8E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 274FF6891333B1C400317ECB /* libcups3_static */; diff --git a/xcode/libcups.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/xcode/libcups.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 6d2f41f0b..60892d3d4 100644 --- a/xcode/libcups.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/xcode/libcups.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -1,6 +1,6 @@ Date: Fri, 12 May 2023 13:17:03 -0400 Subject: [PATCH 14/31] Save additions to support server mode in testcreds. --- cups/testcreds.c | 133 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 129 insertions(+), 4 deletions(-) diff --git a/cups/testcreds.c b/cups/testcreds.c index a71617377..c49ac913f 100644 --- a/cups/testcreds.c +++ b/cups/testcreds.c @@ -45,6 +45,7 @@ #include "cups-private.h" #include "test-internal.h" #include +#include // @@ -580,11 +581,17 @@ test_csr( static int // O - Exit status test_server(const char *host_port) // I - Hostname/port { - char host[256], // Hostname - *hostptr; // Pointer into hostname - int port; // Port number + char host[256], // Hostname + *hostptr; // Pointer into hostname + int port; // Port number + nfds_t i, // Looping var + num_listeners = 0; // Number of listeners + struct pollfd listeners[2]; // Listeners + http_addr_t addr; // Listen address + http_t *http; // Client + // Get the host and port... cupsCopyString(host, host_port, sizeof(host)); if ((hostptr = strrchr(host, ':')) != NULL) { @@ -598,9 +605,127 @@ test_server(const char *host_port) // I - Hostname/port port = 8000 + (int)getuid() % 1000; } + // Setup listeners for IPv4 and IPv6... + memset(&addr, 0, sizeof(addr)); + addr.ipv4.sin_family = AF_INET; + + if ((listeners[num_listeners].fd = httpAddrListen(&addr, port)) > 0) + { + listeners[num_listeners].events = POLLIN | POLLERR; + num_listeners ++; + } + + addr.ipv6.sin6_family = AF_INET6; + + if ((listeners[num_listeners].fd = httpAddrListen(&addr, port)) > 0) + { + listeners[num_listeners].events = POLLIN | POLLERR; + num_listeners ++; + } + + if (num_listeners == 0) + { + fprintf(stderr, "testcreds: Unable to listen on port %d: %s\n", port, cupsLastErrorString()); + return (1); + } + printf("Listening for connections on port %d...\n", port); - return (1); + // Set certificate info... + cupsSetServerCredentials(TEST_CERT_PATH, host, true); + + // Wait for connections... + for (;;) + { + http_state_t state; // HTTP request state + char resource[1024]; // Resource path + + // Look for new connections... + if (poll(listeners, num_listeners, 1000) < 0) + { + if (errno == EINTR || errno == EAGAIN) + continue; + + perror("testcreds: Unable to poll"); + break; + } + + // Try accepting a connection... + for (i = 0, http = NULL; i < num_listeners; i ++) + { + if (listeners[i].revents & POLLIN) + { + if ((http = httpAcceptConnection(listeners[i].fd, true)) != NULL) + break; + + fprintf(stderr, "testcreds: Unable to accept connection: %s\n", cupsLastErrorString()); + } + } + + if (!http) + continue; + + // Negotiate a secure connection... + if (!httpSetEncryption(http, HTTP_ENCRYPTION_ALWAYS)) + { + fprintf(stderr, "testcreds: Unable to encrypt connection: %s\n", cupsLastErrorString()); + httpClose(http); + continue; + } + + // Process a single request and then close it out... + while ((state = httpReadRequest(http, resource, sizeof(resource))) == HTTP_STATE_WAITING) + usleep(1000); + + if (state == HTTP_STATE_ERROR) + { + if (httpError(http) == EPIPE) + fputs("testcreds: Client closed connection.\n", stderr); + else + fprintf(stderr, "testcreds: Bad request line (%s).\n", strerror(httpError(http))); + } + else if (state == HTTP_STATE_UNKNOWN_METHOD) + { + fputs("testcreds: Bad/unknown operation.\n", stderr); + } + else if (state == HTTP_STATE_UNKNOWN_VERSION) + { + fputs("testcreds: Bad HTTP version.\n", stderr); + } + else + { + printf("%s %s\n", httpStateString(state), resource); + + if (state == HTTP_STATE_GET || state == HTTP_STATE_HEAD) + { + httpClearFields(http); + httpSetField(http, HTTP_FIELD_CONTENT_TYPE, "text/plain"); + httpSetField(http, HTTP_FIELD_CONNECTION, "close"); + httpSetLength(http, strlen(resource) + 1); + httpWriteResponse(http, HTTP_STATUS_OK); + + if (state == HTTP_STATE_GET) + { + // Echo back the resource path... + httpWrite(http, resource, strlen(resource)); + httpWrite(http, "\n", 1); + httpFlushWrite(http); + } + } + else + { + httpWriteResponse(http, HTTP_STATUS_BAD_REQUEST); + } + } + + httpClose(http); + } + + // Close listeners and return... + for (i = 0; i < num_listeners; i ++) + httpAddrClose(&addr, listeners[i].fd); + + return (0); } From 4f73e4878587169ca584475c4291d82ce2ccfaa2 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Sat, 13 May 2023 17:37:46 -0400 Subject: [PATCH 15/31] Mirror JSON output fix from CUPS 2.x repo. --- tools/ipptool.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/ipptool.c b/tools/ipptool.c index 51d4acd13..d45a89bd6 100644 --- a/tools/ipptool.c +++ b/tools/ipptool.c @@ -4878,6 +4878,7 @@ print_json_attr( default : /* Out-of-band value */ + cupsFilePrintf(data->outfile, ": null"); break; } } From 8b05605d8522e1aa923267ccb548532a60569338 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 16 May 2023 10:48:31 -0400 Subject: [PATCH 16/31] Save work. --- cups/tls-openssl.c | 59 +++++++++++++++++++++++++ xcode/libcups.xcodeproj/project.pbxproj | 6 +++ 2 files changed, 65 insertions(+) diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index 815e3bdab..74f698eee 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -12,6 +12,9 @@ // #include +#include +#include +#include // @@ -1429,6 +1432,7 @@ static EVP_PKEY * // O - Key pair openssl_create_key(cups_credtype_t type) // I - Type of key { EVP_PKEY *pkey; // Key pair +#if 0 #if defined(EVP_PKEY_EC) EC_KEY *ec = NULL; // EC key #endif // EVP_PKEY_EC @@ -1491,6 +1495,61 @@ openssl_create_key(cups_credtype_t type) // I - Type of key else #endif // EVP_PKEY_EC EVP_PKEY_assign_RSA(pkey, rsa); +#else + EVP_PKEY_CTX *ctx; // Key generation context + int algid; // Algorithm NID + int bits = 0; // Bits + + + switch (type) + { + case CUPS_CREDTYPE_ECDSA_P256_SHA256 : + algid = EVP_PKEY_EC; + break; + + case CUPS_CREDTYPE_ECDSA_P384_SHA256 : + algid = EVP_PKEY_EC; + break; + + case CUPS_CREDTYPE_ECDSA_P521_SHA256 : + algid = EVP_PKEY_EC; + break; + + case CUPS_CREDTYPE_RSA_2048_SHA256 : + algid = EVP_PKEY_RSA; + bits = 2048; + break; + + default : + case CUPS_CREDTYPE_RSA_3072_SHA256 : + algid = EVP_PKEY_RSA; + bits = 3072; + break; + + case CUPS_CREDTYPE_RSA_4096_SHA256 : + algid = EVP_PKEY_RSA; + bits = 4096; + break; + } + + pkey = NULL; + + if ((ctx = EVP_PKEY_CTX_new_id(algid, NULL)) == NULL) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create private key context."), 1); + } + else if (EVP_PKEY_keygen_init(ctx) <= 0) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to initialize private key context."), 1); + } + else if (bits && EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) <= 0) + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to configure private key context."), 1); + else if (EVP_PKEY_keygen(ctx, &pkey) <= 0) + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create private key."), 1); + + EVP_PKEY_free(pkey); +#endif // 0 + return (pkey); } diff --git a/xcode/libcups.xcodeproj/project.pbxproj b/xcode/libcups.xcodeproj/project.pbxproj index 8ab966f94..6d7fa02ed 100644 --- a/xcode/libcups.xcodeproj/project.pbxproj +++ b/xcode/libcups.xcodeproj/project.pbxproj @@ -136,6 +136,9 @@ 2712865D1CC1309000E517C7 /* libcups_static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 72A4332F155844CF002E172D /* libcups_static.a */; }; 2712865E1CC1309000E517C7 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2767FC591926750C000F61D3 /* CoreFoundation.framework */; }; 271286691CC130C700E517C7 /* tlscheck.c in Sources */ = {isa = PBXBuildFile; fileRef = 271286681CC130BD00E517C7 /* tlscheck.c */; }; + 271607952A0EC955002508F6 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D3967A27BB389C003D3D8E /* libz.tbd */; }; + 271607962A0EC96B002508F6 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D3968227BB390D003D3D8E /* libresolv.tbd */; }; + 271607972A0EC971002508F6 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D3967E27BB38E6003D3D8E /* libiconv.tbd */; }; 273B1EA1226B3E4800428143 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2767FC591926750C000F61D3 /* CoreFoundation.framework */; }; 273B1EA2226B3E4800428143 /* libresolv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2767FC5B1926750C000F61D3 /* libresolv.dylib */; }; 273B1EA3226B3E4800428143 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2767FC5C1926750C000F61D3 /* libz.dylib */; }; @@ -1724,7 +1727,10 @@ buildActionMask = 2147483647; files = ( 279B960D28C582F000869E97 /* SystemConfiguration.framework in Frameworks */, + 271607952A0EC955002508F6 /* libz.tbd in Frameworks */, + 271607972A0EC971002508F6 /* libiconv.tbd in Frameworks */, 27CAFC7C282C8C6F00FE02FC /* libcrypto.a in Frameworks */, + 271607962A0EC96B002508F6 /* libresolv.tbd in Frameworks */, 27CAFC7D282C8C6F00FE02FC /* libssl.a in Frameworks */, 724FA5FF1CC038190092477B /* libcups_static.a in Frameworks */, 724FA6001CC038190092477B /* CoreFoundation.framework in Frameworks */, From ca61e3bb5a1f1e7af32a5aa460d71803b40f7d9d Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 16 May 2023 11:22:06 -0400 Subject: [PATCH 17/31] Fix OpenSSL key generation to use modern API that doesn't crash with EC keys. Implement first pass of unit tests with testcreds. Update alt_names to use const char * const * type. --- cups/cups.h | 4 +- cups/testcreds.c | 70 +++++++++++++++++++++++++++++++- cups/tls-openssl.c | 99 +++++++++------------------------------------- 3 files changed, 89 insertions(+), 84 deletions(-) diff --git a/cups/cups.h b/cups/cups.h index c455672b1..940def6e0 100644 --- a/cups/cups.h +++ b/cups/cups.h @@ -318,8 +318,8 @@ extern size_t cupsCopyDest(cups_dest_t *dest, size_t num_dests, cups_dest_t **d extern cups_dinfo_t *cupsCopyDestInfo(http_t *http, cups_dest_t *dest) _CUPS_PUBLIC; extern int cupsCopyDestConflicts(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, size_t num_options, cups_option_t *options, const char *new_option, const char *new_value, size_t *num_conflicts, cups_option_t **conflicts, size_t *num_resolved, cups_option_t **resolved) _CUPS_PUBLIC; extern size_t cupsCopyString(char *dst, const char *src, size_t dstsize) _CUPS_PUBLIC; -extern bool cupsCreateCredentials(const char *path, bool ca_cert, cups_credpurpose_t purpose, cups_credtype_t type, cups_credusage_t usage, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, size_t num_alt_names, const char **alt_names, const char *root_name, time_t expiration_date) _CUPS_PUBLIC; -extern bool cupsCreateCredentialsRequest(const char *path, cups_credpurpose_t purpose, cups_credtype_t type, cups_credusage_t usage, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, size_t num_alt_names, const char **alt_names) _CUPS_PUBLIC; +extern bool cupsCreateCredentials(const char *path, bool ca_cert, cups_credpurpose_t purpose, cups_credtype_t type, cups_credusage_t usage, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, size_t num_alt_names, const char * const *alt_names, const char *root_name, time_t expiration_date) _CUPS_PUBLIC; +extern bool cupsCreateCredentialsRequest(const char *path, cups_credpurpose_t purpose, cups_credtype_t type, cups_credusage_t usage, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, size_t num_alt_names, const char * const *alt_names) _CUPS_PUBLIC; extern ipp_status_t cupsCreateDestJob(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, int *job_id, const char *title, size_t num_options, cups_option_t *options) _CUPS_PUBLIC; extern int cupsDoAuthentication(http_t *http, const char *method, const char *resource) _CUPS_PUBLIC; diff --git a/cups/testcreds.c b/cups/testcreds.c index c49ac913f..f4f2ccc6e 100644 --- a/cups/testcreds.c +++ b/cups/testcreds.c @@ -391,7 +391,75 @@ main(int argc, // I - Number of command-line arguments static int // O - Exit status do_unit_tests(void) { - return (1); + cups_credtype_t type; // Current credential type + char *data; // Cert data + static const char * const alt_names[] = + { // subjectAltName values + "printer.example.com", + "localhost" + }; + static const char * const types[] = + { // Credential types + "default", + "rsa-2048", + "rsa-3072", + "rsa-4096", + "ecdsa-p256", + "ecdsa-p384", + "ecdsa-p521" + }; + + + for (type = CUPS_CREDTYPE_DEFAULT; type <= CUPS_CREDTYPE_ECDSA_P521_SHA256; type ++) + { + testBegin("cupsCreateCredentials(_site_, %s, no alt names, CA)", types[type]); + if (cupsCreateCredentials(TEST_CERT_PATH, true, CUPS_CREDPURPOSE_SERVER_AUTH, type, CUPS_CREDUSAGE_DEFAULT_TLS, "Organization", "Unit", "Locality", "Ontario", "CA", "_site_", 0, NULL, NULL, time(NULL) + 30 * 86400)) + { + testEnd(true); + + testBegin("cupsCopyCredentials(_site_)"); + data = cupsCopyCredentials(TEST_CERT_PATH, "_site_"); + testEnd(data != NULL); + free(data); + + testBegin("cupsCopyCredentialsKey(_site_)"); + data = cupsCopyCredentialsKey(TEST_CERT_PATH, "_site_"); + testEnd(data != NULL); + free(data); + } + else + { + testEndMessage(false, "%s", cupsLastErrorString()); + } + + testBegin("cupsCreateCredentials(printer, %s, alt names, signed by CA cert)", types[type]); + if (cupsCreateCredentials(TEST_CERT_PATH, false, CUPS_CREDPURPOSE_SERVER_AUTH, type, CUPS_CREDUSAGE_DEFAULT_TLS, "Organization", "Unit", "Locality", "Ontario", "CA", "printer", sizeof(alt_names) / sizeof(alt_names[0]), alt_names, "_site_", time(NULL) + 30 * 86400)) + testEnd(true); + else + testEndMessage(false, "%s", cupsLastErrorString()); + + testBegin("cupsCreateCredentialsRequest(printer, %s, alt names)", types[type]); + if (cupsCreateCredentialsRequest(TEST_CERT_PATH, CUPS_CREDPURPOSE_SERVER_AUTH, type, CUPS_CREDUSAGE_DEFAULT_TLS, "Organization", "Unit", "Locality", "Ontario", "CA", "printer", sizeof(alt_names) / sizeof(alt_names[0]), alt_names)) + { + testEnd(true); + + testBegin("cupsCopyCredentialsRequest(printer)"); + data = cupsCopyCredentialsRequest(TEST_CERT_PATH, "printer"); + testEnd(data != NULL); + free(data); + + testBegin("cupsCopyCredentialsKey(printer)"); + data = cupsCopyCredentialsKey(TEST_CERT_PATH, "printer"); + testEnd(data != NULL); + free(data); + } + else + { + testEndMessage(false, "%s", cupsLastErrorString()); + } + } + + return (testsPassed ? 0 : 1); } diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index 74f698eee..0adaf6be6 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -33,7 +33,7 @@ static bool openssl_add_ext(STACK_OF(X509_EXTENSION) *exts, int nid, const char static X509 *openssl_create_credential(http_credential_t *credential); static X509_NAME *openssl_create_name(const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name); static EVP_PKEY *openssl_create_key(cups_credtype_t type); -static bool openssl_create_san(STACK_OF(X509_EXTENSION) *exts, const char *common_name, size_t num_alt_names, const char **alt_names); +static bool openssl_create_san(STACK_OF(X509_EXTENSION) *exts, const char *common_name, size_t num_alt_names, const char * const *alt_names); static time_t openssl_get_date(X509 *cert, int which); //static void openssl_load_crl(void); @@ -93,7 +93,7 @@ cupsCreateCredentials( const char *country, // I - Country or `NULL` for locale-based default const char *common_name, // I - Common name size_t num_alt_names, // I - Number of subject alternate names - const char **alt_names, // I - Subject Alternate Names + const char * const *alt_names, // I - Subject Alternate Names const char *root_name, // I - Root certificate/domain name or `NULL` for site/self-signed time_t expiration_date) // I - Expiration date { @@ -340,7 +340,7 @@ cupsCreateCredentialsRequest( const char *country, // I - Country or `NULL` for locale-based default const char *common_name, // I - Common name size_t num_alt_names, // I - Number of subject alternate names - const char **alt_names) // I - Subject Alternate Names + const char * const *alt_names) // I - Subject Alternate Names { char *result = NULL; // Return value EVP_PKEY *pkey; // Key pair @@ -1429,90 +1429,31 @@ openssl_create_credential( // static EVP_PKEY * // O - Key pair -openssl_create_key(cups_credtype_t type) // I - Type of key +openssl_create_key( + cups_credtype_t type) // I - Type of key { EVP_PKEY *pkey; // Key pair -#if 0 -#if defined(EVP_PKEY_EC) - EC_KEY *ec = NULL; // EC key -#endif // EVP_PKEY_EC - RSA *rsa = NULL; // RSA key pair - - - switch (type) - { -#if defined(EVP_PKEY_EC) - case CUPS_CREDTYPE_ECDSA_P256_SHA256 : - ec = EC_KEY_new_by_curve_name(NID_secp256k1); - break; - - case CUPS_CREDTYPE_ECDSA_P384_SHA256 : - ec = EC_KEY_new_by_curve_name(NID_secp384r1); - break; - - case CUPS_CREDTYPE_ECDSA_P521_SHA256 : - ec = EC_KEY_new_by_curve_name(NID_secp521r1); - break; -#endif // EVP_PKEY_EC && USE_EC - - case CUPS_CREDTYPE_RSA_2048_SHA256 : - rsa = RSA_generate_key(2048, RSA_F4, NULL, NULL); - break; - - default : - case CUPS_CREDTYPE_RSA_3072_SHA256 : - rsa = RSA_generate_key(3072, RSA_F4, NULL, NULL); - break; - - case CUPS_CREDTYPE_RSA_4096_SHA256 : - rsa = RSA_generate_key(4096, RSA_F4, NULL, NULL); - break; - } - - if (!rsa && !ec) - { - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create key pair."), 1); - return (NULL); - } - - if ((pkey = EVP_PKEY_new()) == NULL) - { -#if defined(EVP_PKEY_EC) - if (ec) - EC_KEY_free(ec); -#endif // EVP_PKEY_EC && USE_EC - - if (rsa) - RSA_free(rsa); - - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create private key."), 1); - return (NULL); - } - -#if defined(EVP_PKEY_EC) - if (ec) - EVP_PKEY_assign_EC_KEY(pkey, ec); - else -#endif // EVP_PKEY_EC - EVP_PKEY_assign_RSA(pkey, rsa); -#else EVP_PKEY_CTX *ctx; // Key generation context int algid; // Algorithm NID int bits = 0; // Bits + int curveid = 0; // Curve NID switch (type) { case CUPS_CREDTYPE_ECDSA_P256_SHA256 : - algid = EVP_PKEY_EC; + algid = EVP_PKEY_EC; + curveid = NID_secp256k1; break; case CUPS_CREDTYPE_ECDSA_P384_SHA256 : - algid = EVP_PKEY_EC; + algid = EVP_PKEY_EC; + curveid = NID_secp384r1; break; case CUPS_CREDTYPE_ECDSA_P521_SHA256 : - algid = EVP_PKEY_EC; + algid = EVP_PKEY_EC; + curveid = NID_secp521r1; break; case CUPS_CREDTYPE_RSA_2048_SHA256 : @@ -1535,21 +1476,17 @@ openssl_create_key(cups_credtype_t type) // I - Type of key pkey = NULL; if ((ctx = EVP_PKEY_CTX_new_id(algid, NULL)) == NULL) - { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create private key context."), 1); - } else if (EVP_PKEY_keygen_init(ctx) <= 0) - { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to initialize private key context."), 1); - } else if (bits && EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) <= 0) _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to configure private key context."), 1); + else if (curveid && EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curveid) <= 0) + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to configure private key context."), 1); else if (EVP_PKEY_keygen(ctx, &pkey) <= 0) _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create private key."), 1); - EVP_PKEY_free(pkey); -#endif // 0 - + EVP_PKEY_CTX_free(ctx); return (pkey); } @@ -1599,9 +1536,9 @@ openssl_create_name( static bool // O - `true` on success, `false` otherwise openssl_create_san( STACK_OF(X509_EXTENSION) *exts, // I - Extensions - const char *common_name, // I - Common name - size_t num_alt_names, // I - Number of alternate names - const char **alt_names) // I - List of alternate names + const char *common_name, // I - Common name + size_t num_alt_names, // I - Number of alternate names + const char * const *alt_names) // I - List of alternate names { char temp[2048], // Temporary string *tempptr; // Pointer into temporary string From cf992f5b46f940d8bc3bc2438f04b0309598ab32 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 16 May 2023 13:50:59 -0400 Subject: [PATCH 18/31] Save work on GNU TLS. --- cups/tls-gnutls.c | 170 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 126 insertions(+), 44 deletions(-) diff --git a/cups/tls-gnutls.c b/cups/tls-gnutls.c index ba05da168..e1c9059e5 100644 --- a/cups/tls-gnutls.c +++ b/cups/tls-gnutls.c @@ -18,9 +18,16 @@ static gnutls_x509_crt_t gnutls_create_credential(http_credential_t *credential); static gnutls_x509_privkey_t gnutls_create_key(cups_credtype_t type); +static ssize_t gnutls_http_read(gnutls_transport_ptr_t ptr, void *data, size_t length); +static ssize_t gnutls_http_write(gnutls_transport_ptr_t ptr, const void *data, size_t length); static void gnutls_load_crl(void); -static ssize_t gnutls_read(gnutls_transport_ptr_t ptr, void *data, size_t length); -static ssize_t gnutls_write(gnutls_transport_ptr_t ptr, const void *data, size_t length); + + +// +// Local globals... +// + +static gnutls_x509_crl_t tls_crl = NULL;/* Certificate revocation list */ // @@ -49,7 +56,7 @@ cupsCreateCredentials( const char *country, // I - Country or `NULL` for locale-based default const char *common_name, // I - Common name size_t num_alt_names, // I - Number of subject alternate names - const char **alt_names, // I - Subject Alternate Names + const char * const *alt_names, // I - Subject Alternate Names const char *root_name, // I - Root certificate/domain name or `NULL` for site/self-signed time_t expiration_date) // I - Expiration date { @@ -58,8 +65,7 @@ cupsCreateCredentials( char temp[1024], // Temporary directory name crtfile[1024], // Certificate filename keyfile[1024]; // Private key filename - cups_lang_t *language; // Default language info - const char *langname; // Default language name + unsigned gnutls_usage = 0;// GNU TLS keyUsage bits cups_file_t *fp; // Key/cert file unsigned char buffer[8192]; // Buffer for x509 data size_t bytes; // Number of bytes of data @@ -77,7 +83,7 @@ cupsCreateCredentials( if (!path || !common_name) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); - return (0); + return (false); } http_make_path(crtfile, sizeof(crtfile), path, common_name, "crt"); @@ -98,7 +104,7 @@ cupsCreateCredentials( DEBUG_printf(("1cupsCreateCredentials: Unable to export private key: %s", gnutls_strerror(result))); _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); gnutls_x509_privkey_deinit(key); - return (0); + return (false); } else if ((fp = cupsFileOpen(keyfile, "w")) != NULL) { @@ -111,36 +117,41 @@ cupsCreateCredentials( DEBUG_printf(("1cupsCreateCredentials: Unable to create private key file \"%s\": %s", keyfile, strerror(errno))); _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); gnutls_x509_privkey_deinit(key); - return (0); + return (false); } - // Create the self-signed certificate... - DEBUG_puts("1cupsCreateCredentials: Generating self-signed X.509 certificate."); + // Create the certificate... + DEBUG_puts("1cupsCreateCredentials: Generating X.509 certificate."); - language = cupsLangDefault(); - langname = cupsLangGetName(language); curtime = time(NULL); serial[0] = (unsigned char)(curtime >> 24); serial[1] = (unsigned char)(curtime >> 16); serial[2] = (unsigned char)(curtime >> 8); serial[3] = (unsigned char)(curtime); + if (!organization) + organization = common_name; + if (!org_unit) + org_unit = ""; + if (!locality) + locality = "Unknown"; + if (!state_province) + state_province = "Unknown"; + if (!country) + country = "US"; + gnutls_x509_crt_init(&crt); - if (strlen(langname) == 5) - gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_COUNTRY_NAME, 0, langname + 3, 2); - else - gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_COUNTRY_NAME, 0, "US", 2); + gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_COUNTRY_NAME, 0, "US", 2); gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_COMMON_NAME, 0, common_name, strlen(common_name)); - gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, common_name, strlen(common_name)); - gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, "Unknown", 7); - gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, "Unknown", 7); - gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_LOCALITY_NAME, 0, "Unknown", 7); -// gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_PKCS9_EMAIL, 0, ServerAdmin, strlen(ServerAdmin)); + gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, organization, strlen(organization)); + gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, org_unit, strlen(org_unit)); + gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, state_province, strlen(state_province)); + gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_LOCALITY_NAME, 0, locality, strlen(locality)); gnutls_x509_crt_set_key(crt, key); gnutls_x509_crt_set_serial(crt, serial, sizeof(serial)); gnutls_x509_crt_set_activation_time(crt, curtime); gnutls_x509_crt_set_expiration_time(crt, expiration_date); - gnutls_x509_crt_set_ca_status(crt, 0); + gnutls_x509_crt_set_ca_status(crt, ca_cert ? 1 : 0); gnutls_x509_crt_set_subject_alt_name(crt, GNUTLS_SAN_DNSNAME, common_name, (unsigned)strlen(common_name), GNUTLS_FSAN_SET); if (!strchr(common_name, '.')) { @@ -163,8 +174,38 @@ cupsCreateCredentials( } } } - gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_TLS_WWW_SERVER, 0); - gnutls_x509_crt_set_key_usage(crt, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT); + + if (purpose & CUPS_CREDPURPOSE_SERVER_AUTH) + gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_TLS_WWW_SERVER, 0); + if (purpose & CUPS_CREDPURPOSE_CLIENT_AUTH) + gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_TLS_WWW_CLIENT, 0); + if (purpose & CUPS_CREDPURPOSE_CODE_SIGNING) + gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_CODE_SIGNING, 0); + if (purpose & CUPS_CREDPURPOSE_EMAIL_PROTECTION) + gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_EMAIL_PROTECTION, 0); + if (purpose & CUPS_CREDPURPOSE_OCSP_SIGNING) + gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_OCSP_SIGNING, 0); + + if (usage & CUPS_CREDUSAGE_DIGITAL_SIGNATURE) + gnutls_usage |= GNUTLS_KEY_DIGITAL_SIGNATURE; + if (usage & CUPS_CREDUSAGE_NON_REPUDIATION) + gnutls_usage |= GNUTLS_KEY_NON_REPUDIATION; + if (usage & CUPS_CREDUSAGE_KEY_ENCIPHERMENT) + gnutls_usage |= GNUTLS_KEY_KEY_ENCIPHERMENT; + if (usage & CUPS_CREDUSAGE_DATA_ENCIPHERMENT) + gnutls_usage |= GNUTLS_KEY_DATA_ENCIPHERMENT; + if (usage & CUPS_CREDUSAGE_KEY_AGREEMENT) + gnutls_usage |= GNUTLS_KEY_KEY_AGREEMENT; + if (usage & CUPS_CREDUSAGE_KEY_CERT_SIGN) + gnutls_usage |= GNUTLS_KEY_KEY_CERT_SIGN; + if (usage & CUPS_CREDUSAGE_CRL_SIGN) + gnutls_usage |= GNUTLS_KEY_CRL_SIGN; + if (usage & CUPS_CREDUSAGE_ENCIPHER_ONLY) + gnutls_usage |= GNUTLS_KEY_ENCIPHER_ONLY; + if (usage & CUPS_CREDUSAGE_DECIPHER_ONLY) + gnutls_usage |= GNUTLS_KEY_DECIPHER_ONLY; + + gnutls_x509_crt_set_key_usage(crt, gnutls_usage); gnutls_x509_crt_set_version(crt, 3); bytes = sizeof(buffer); @@ -181,7 +222,7 @@ cupsCreateCredentials( _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); gnutls_x509_crt_deinit(crt); gnutls_x509_privkey_deinit(key); - return (0); + return (false); } else if ((fp = cupsFileOpen(crtfile, "w")) != NULL) { @@ -195,7 +236,7 @@ cupsCreateCredentials( _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); gnutls_x509_crt_deinit(crt); gnutls_x509_privkey_deinit(key); - return (0); + return (false); } // Cleanup... @@ -204,7 +245,48 @@ cupsCreateCredentials( DEBUG_puts("1cupsCreateCredentials: Successfully created credentials."); - return (1); + return (true); +} + + +// +// 'cupsCreateCredentialsRequest()' - Make an X.509 Certificate Signing Request. +// +// This function creates an X.509 certificate signing request (CSR) and +// associated private key. The CSR and key are stored in the directory "path" +// or, if "path" is `NULL`, in a per-user or system-wide (when running as root) +// certificate/key store. +// + +bool // O - `true` on success, `false` on error +cupsCreateCredentialsRequest( + const char *path, // I - Directory path for certificate/key store or `NULL` for default + cups_credpurpose_t purpose, // I - Credential purposes + cups_credtype_t type, // I - Credential type + cups_credusage_t usage, // I - Credential usages + const char *organization, // I - Organization or `NULL` to use common name + const char *org_unit, // I - Organizational unit or `NULL` for none + const char *locality, // I - City/town or `NULL` for "Unknown" + const char *state_province, // I - State/province or `NULL` for "Unknown" + const char *country, // I - Country or `NULL` for locale-based default + const char *common_name, // I - Common name + size_t num_alt_names, // I - Number of subject alternate names + const char * const *alt_names) // I - Subject Alternate Names +{ + (void)path; + (void)purpose; + (void)type; + (void)usage; + (void)organization; + (void)org_unit; + (void)locality; + (void)state_province; + (void)country; + (void)common_name; + (void)num_alt_names; + (void)alt_names; + + return (false); } @@ -213,7 +295,7 @@ cupsCreateCredentials( // an encrypted connection. // -int // O - Status of call (0 = success) +bool // O - `true` on success, `false` on failure httpCopyCredentials( http_t *http, // I - Connection to server cups_array_t **credentials) // O - Array of credentials @@ -228,7 +310,7 @@ httpCopyCredentials( *credentials = NULL; if (!http || !http->tls || !credentials) - return (-1); + return (false); *credentials = cupsArrayNew(NULL, NULL, NULL, 0, NULL, NULL); certs = gnutls_certificate_get_peers(http->tls, &count); @@ -245,7 +327,7 @@ httpCopyCredentials( } } - return (0); + return (true); } @@ -267,13 +349,13 @@ _httpCreateCredentials( // 'httpCredentialsAreValidForName()' - Return whether the credentials are valid for the given name. // -int // O - 1 if valid, 0 otherwise +bool // O - `true` if valid, `false` otherwise httpCredentialsAreValidForName( cups_array_t *credentials, // I - Credentials const char *common_name) // I - Name to check { gnutls_x509_crt_t cert; // Certificate - int result = 0; // Result + bool result = false; // Result cert = gnutls_create_credential((http_credential_t *)cupsArrayGetFirst(credentials)); @@ -303,7 +385,7 @@ httpCredentialsAreValidForName( { if (cserial_size == rserial_size && !memcmp(cserial, rserial, rserial_size)) { - result = 0; + result = false; break; } @@ -738,11 +820,11 @@ gnutls_load_crl(void) // -// 'gnutls_read()' - Read function for the GNU TLS library. +// 'gnutls_http_read()' - Read function for the GNU TLS library. // static ssize_t // O - Number of bytes read or -1 on error -gnutls_read( +gnutls_http_read( gnutls_transport_ptr_t ptr, // I - Connection to server void *data, // I - Buffer size_t length) // I - Number of bytes to read @@ -751,7 +833,7 @@ gnutls_read( ssize_t bytes; // Bytes read - DEBUG_printf(("5gnutls_read(ptr=%p, data=%p, length=%d)", ptr, data, (int)length)); + DEBUG_printf(("5gnutls_http_read(ptr=%p, data=%p, length=%d)", ptr, data, (int)length)); http = (http_t *)ptr; @@ -769,17 +851,17 @@ gnutls_read( } bytes = recv(http->fd, data, length, 0); - DEBUG_printf(("5gnutls_read: bytes=%d", (int)bytes)); + DEBUG_printf(("5gnutls_http_read: bytes=%d", (int)bytes)); return (bytes); } // -// 'gnutls_write()' - Write function for the GNU TLS library. +// 'gnutls_http_write()' - Write function for the GNU TLS library. // static ssize_t // O - Number of bytes written or -1 on error -gnutls_write( +gnutls_http_write( gnutls_transport_ptr_t ptr, // I - Connection to server const void *data, // I - Data buffer size_t length) // I - Number of bytes to write @@ -787,10 +869,10 @@ gnutls_write( ssize_t bytes; // Bytes written - DEBUG_printf(("5gnutls_write(ptr=%p, data=%p, length=%d)", ptr, data, + DEBUG_printf(("5gnutls_http_write(ptr=%p, data=%p, length=%d)", ptr, data, (int)length)); bytes = send(((http_t *)ptr)->fd, data, length, 0); - DEBUG_printf(("5gnutls_write: bytes=%d", (int)bytes)); + DEBUG_printf(("5gnutls_http_write: bytes=%d", (int)bytes)); return (bytes); } @@ -1044,7 +1126,7 @@ _httpTLSStart(http_t *http) // I - Connection to server { DEBUG_printf(("4_httpTLSStart: Auto-create credentials for \"%s\".", cn)); - if (!cupsCreateCredentials(tls_keypath, cn, 0, NULL, time(NULL) + 3650 * 86400)) + if (!cupsCreateCredentials(tls_keypath, false, CUPS_CREDPURPOSE_SERVER_AUTH, CUPS_CREDTYPE_DEFAULT, CUPS_CREDUSAGE_DEFAULT_TLS, NULL, NULL, NULL, NULL, NULL, cn, 0, NULL, NULL, time(NULL) + 3650 * 86400)) { DEBUG_puts("4_httpTLSStart: cupsCreateCredentials failed."); http->error = errno = EINVAL; @@ -1132,11 +1214,11 @@ _httpTLSStart(http_t *http) // I - Connection to server #endif // HAVE_GNUTLS_PRIORITY_SET_DIRECT gnutls_transport_set_ptr(http->tls, (gnutls_transport_ptr_t)http); - gnutls_transport_set_pull_function(http->tls, gnutls_read); + gnutls_transport_set_pull_function(http->tls, gnutls_http_read); #ifdef HAVE_GNUTLS_TRANSPORT_SET_PULL_TIMEOUT_FUNCTION gnutls_transport_set_pull_timeout_function(http->tls, (gnutls_pull_timeout_func)httpWait); #endif // HAVE_GNUTLS_TRANSPORT_SET_PULL_TIMEOUT_FUNCTION - gnutls_transport_set_push_function(http->tls, gnutls_write); + gnutls_transport_set_push_function(http->tls, gnutls_http_write); // Enforce a minimum timeout of 10 seconds for the TLS handshake... old_timeout = http->timeout_value; From 57d3a3d9bb7b79d19462fa240cb1284849b0f80f Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 16 May 2023 17:06:16 -0400 Subject: [PATCH 19/31] Implement root certificate/key support. Ignore generated files. --- .gitignore | 2 ++ cups/tls-gnutls.c | 65 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 61887bec7..9f46b6001 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /configure~ /cups3.pc /Makedefs +/cups/.testssl /cups/fuzzipp /cups/libcups.a /cups/libcups.dylib @@ -23,6 +24,7 @@ /cups/locale/ /cups/rasterbench /cups/test.json +/cups/test.log /cups/test.pwg /cups/test.raster /cups/testarray diff --git a/cups/tls-gnutls.c b/cups/tls-gnutls.c index e1c9059e5..49cbe2a3a 100644 --- a/cups/tls-gnutls.c +++ b/cups/tls-gnutls.c @@ -60,14 +60,18 @@ cupsCreateCredentials( const char *root_name, // I - Root certificate/domain name or `NULL` for site/self-signed time_t expiration_date) // I - Expiration date { - gnutls_x509_crt_t crt; // Self-signed certificate + gnutls_x509_crt_t crt; // New certificate gnutls_x509_privkey_t key; // Encryption private key + gnutls_x509_crt_t root_crt = NULL;// Root certificate + gnutls_x509_privkey_t root_key = NULL;// Root private key char temp[1024], // Temporary directory name crtfile[1024], // Certificate filename - keyfile[1024]; // Private key filename + keyfile[1024], // Private key filename + *root_crtdata, // Root certificate data + *root_keydata; // Root private key data unsigned gnutls_usage = 0;// GNU TLS keyUsage bits cups_file_t *fp; // Key/cert file - unsigned char buffer[8192]; // Buffer for x509 data + unsigned char buffer[65536]; // Buffer for x509 data size_t bytes; // Number of bytes of data unsigned char serial[4]; // Serial number buffer time_t curtime; // Current time @@ -212,7 +216,60 @@ cupsCreateCredentials( if (gnutls_x509_crt_get_key_id(crt, 0, buffer, &bytes) >= 0) gnutls_x509_crt_set_subject_key_id(crt, buffer, bytes); - gnutls_x509_crt_sign(crt, crt, key); + // Try loading a root certificate... + if (!ca_cert) + { + root_crtdata = cupsCopyCredentials(path, root_name ? root_name : "_site_"); + root_keydata = cupsCopyCredentialsKey(path, root_name ? root_name : "_site_"); + + if (root_crtdata && root_keydata) + { + // Load root certificate... + gnutls_datum_t datum; // Datum for cert/key + + datum.data = (unsigned char *)root_crtdata; + datum.size = strlen(root_crtdata); + + gnutls_x509_crt_init(&root_crt); + if (gnutls_x509_crt_import(root_crt, &datum, GNUTLS_X509_FMT_PEM) < 0) + { + // No good, clear it... + gnutls_x509_crt_deinit(root_crt); + root_crt = NULL; + } + else + { + // Load root private key... + datum.data = (unsigned char *)root_keydata; + datum.size = strlen(root_keydata); + + gnutls_x509_privkey_init(&root_key); + if (gnutls_x509_privkey_import(root_key, &datum, GNUTLS_X509_FMT_PEM) < 0) + { + // No food, clear them... + gnutls_x509_privkey_deinit(root_key); + root_key = NULL; + + gnutls_x509_crt_deinit(root_crt); + root_crt = NULL; + } + } + } + + free(root_crtdata); + free(root_keydata); + } + + if (root_crt && root_key) + { + gnutls_x509_crt_sign(crt, root_crt, root_key); + gnutls_x509_crt_deinit(root_crt); + gnutls_x509_privkey_deinit(root_key); + } + else + { + gnutls_x509_crt_sign(crt, crt, key); + } // Save it... bytes = sizeof(buffer); From 41255a85e9ffbc04b7d2272aa364ef7d4d3233d2 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Thu, 18 May 2023 10:13:31 -0400 Subject: [PATCH 20/31] Add GNU TLS CSR generation, clean up CRT generation. --- cups/tls-gnutls.c | 219 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 188 insertions(+), 31 deletions(-) diff --git a/cups/tls-gnutls.c b/cups/tls-gnutls.c index 49cbe2a3a..328fc54df 100644 --- a/cups/tls-gnutls.c +++ b/cups/tls-gnutls.c @@ -60,8 +60,9 @@ cupsCreateCredentials( const char *root_name, // I - Root certificate/domain name or `NULL` for site/self-signed time_t expiration_date) // I - Expiration date { - gnutls_x509_crt_t crt; // New certificate - gnutls_x509_privkey_t key; // Encryption private key + bool ret = false; // Return value + gnutls_x509_crt_t crt = NULL; // New certificate + gnutls_x509_privkey_t key = NULL; // Encryption private key gnutls_x509_crt_t root_crt = NULL;// Root certificate gnutls_x509_privkey_t root_key = NULL;// Root private key char temp[1024], // Temporary directory name @@ -87,7 +88,7 @@ cupsCreateCredentials( if (!path || !common_name) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); - return (false); + goto done; } http_make_path(crtfile, sizeof(crtfile), path, common_name, "crt"); @@ -107,8 +108,7 @@ cupsCreateCredentials( { DEBUG_printf(("1cupsCreateCredentials: Unable to export private key: %s", gnutls_strerror(result))); _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); - gnutls_x509_privkey_deinit(key); - return (false); + goto done; } else if ((fp = cupsFileOpen(keyfile, "w")) != NULL) { @@ -120,8 +120,7 @@ cupsCreateCredentials( { DEBUG_printf(("1cupsCreateCredentials: Unable to create private key file \"%s\": %s", keyfile, strerror(errno))); _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); - gnutls_x509_privkey_deinit(key); - return (false); + goto done; } // Create the certificate... @@ -277,9 +276,7 @@ cupsCreateCredentials( { DEBUG_printf(("1cupsCreateCredentials: Unable to export public key and X.509 certificate: %s", gnutls_strerror(result))); _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); - gnutls_x509_crt_deinit(crt); - gnutls_x509_privkey_deinit(key); - return (false); + goto done; } else if ((fp = cupsFileOpen(crtfile, "w")) != NULL) { @@ -291,18 +288,22 @@ cupsCreateCredentials( { DEBUG_printf(("1cupsCreateCredentials: Unable to create public key and X.509 certificate file \"%s\": %s", crtfile, strerror(errno))); _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); - gnutls_x509_crt_deinit(crt); - gnutls_x509_privkey_deinit(key); - return (false); + goto done; } + DEBUG_puts("1cupsCreateCredentials: Successfully created credentials."); + + ret = true; + // Cleanup... - gnutls_x509_crt_deinit(crt); - gnutls_x509_privkey_deinit(key); + done: - DEBUG_puts("1cupsCreateCredentials: Successfully created credentials."); + if (crt) + gnutls_x509_crt_deinit(crt); + if (key) + gnutls_x509_privkey_deinit(key); - return (true); + return (ret); } @@ -330,20 +331,176 @@ cupsCreateCredentialsRequest( size_t num_alt_names, // I - Number of subject alternate names const char * const *alt_names) // I - Subject Alternate Names { - (void)path; - (void)purpose; - (void)type; - (void)usage; - (void)organization; - (void)org_unit; - (void)locality; - (void)state_province; - (void)country; - (void)common_name; - (void)num_alt_names; - (void)alt_names; - - return (false); + bool ret = false; // Return value + gnutls_x509_crq_t crq = NULL; // Certificate request + gnutls_x509_privkey_t key = NULL; // Private/public key pair + char temp[1024], // Temporary directory name + csrfile[1024], // Certificate signing request filename + keyfile[1024]; // Private key filename + unsigned gnutls_usage = 0;// GNU TLS keyUsage bits + cups_file_t *fp; // Key/cert file + unsigned char buffer[8192]; // Buffer for key/cert data + size_t bytes; // Number of bytes of data + int status; // GNU TLS status + + + DEBUG_printf(("cupsCreateCredentialsRequest(path=\"%s\", purpose=0x%x, type=%d, usage=0x%x, organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", common_name=\"%s\", num_alt_names=%u, alt_names=%p)", path, purpose, type, usage, organization, org_unit, locality, state_province, country, common_name, (unsigned)num_alt_names, alt_names)); + + // Filenames... + if (!path) + path = http_default_path(temp, sizeof(temp)); + + if (!path || !common_name) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); + goto done; + } + + http_make_path(csrfile, sizeof(csrfile), path, common_name, "csr"); + http_make_path(keyfile, sizeof(keyfile), path, common_name, "key"); + + // Create the encryption key... + DEBUG_puts("1cupsCreateCredentialsRequest: Creating key pair."); + + key = gnutls_create_key(type); + + DEBUG_puts("1cupsCreateCredentialsRequest: Key pair created."); + + // Save it... + bytes = sizeof(buffer); + + if ((result = gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) + { + DEBUG_printf(("1cupsCreateCredentialsRequest: Unable to export private key: %s", gnutls_strerror(result))); + goto done; + } + else if ((fp = cupsFileOpen(keyfile, "w")) != NULL) + { + DEBUG_printf(("1cupsCreateCredentialsRequest: Writing private key to \"%s\".", keyfile)); + cupsFileWrite(fp, (char *)buffer, bytes); + cupsFileClose(fp); + } + else + { + DEBUG_printf(("1cupsCreateCredentialsRequest: Unable to create private key file \"%s\": %s", keyfile, strerror(errno))); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); + goto done; + } + + // Create the certificate... + DEBUG_puts("1cupsCreateCredentialsRequest: Generating X.509 certificate request."); + + if (!organization) + organization = common_name; + if (!org_unit) + org_unit = ""; + if (!locality) + locality = "Unknown"; + if (!state_province) + state_province = "Unknown"; + if (!country) + country = "US"; + + gnutls_x509_crq_init(&crq); + gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_X520_COUNTRY_NAME, 0, country, (unsigned)strlen(country)); + gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_X520_COMMON_NAME, 0, hostname, (unsigned)strlen(hostname)); + gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, organization, (unsigned)strlen(organization)); + gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, org_unit, (unsigned)strlen(org_unit)); + gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, state_province, (unsigned)strlen(state_province)); + gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_X520_LOCALITY_NAME, 0, locality, (unsigned)strlen(locality)); + gnutls_x509_crq_set_key(crq, key); + gnutls_x509_crq_set_subject_alt_name(crq, GNUTLS_SAN_DNSNAME, common_name, (unsigned)strlen(common_name), GNUTLS_FSAN_SET); + if (!strchr(common_name, '.')) + { + // Add common_name.local to the list, too... + char localname[256]; // hostname.local + + snprintf(localname, sizeof(localname), "%s.local", common_name); + gnutls_x509_crq_set_subject_alt_name(crq, GNUTLS_SAN_DNSNAME, localname, (unsigned)strlen(localname), GNUTLS_FSAN_APPEND); + } + gnutls_x509_crq_set_subject_alt_name(crq, GNUTLS_SAN_DNSNAME, "localhost", 9, GNUTLS_FSAN_APPEND); + if (num_alt_names > 0) + { + size_t i; // Looping var + + for (i = 0; i < num_alt_names; i ++) + { + if (strcmp(alt_names[i], "localhost")) + { + gnutls_x509_crq_set_subject_alt_name(crq, GNUTLS_SAN_DNSNAME, alt_names[i], (unsigned)strlen(alt_names[i]), GNUTLS_FSAN_APPEND); + } + } + } + + if (purpose & CUPS_CREDPURPOSE_SERVER_AUTH) + gnutls_x509_crq_set_key_purpose_oid(crq, GNUTLS_KP_TLS_WWW_SERVER, 0); + if (purpose & CUPS_CREDPURPOSE_CLIENT_AUTH) + gnutls_x509_crq_set_key_purpose_oid(crq, GNUTLS_KP_TLS_WWW_CLIENT, 0); + if (purpose & CUPS_CREDPURPOSE_CODE_SIGNING) + gnutls_x509_crq_set_key_purpose_oid(crq, GNUTLS_KP_CODE_SIGNING, 0); + if (purpose & CUPS_CREDPURPOSE_EMAIL_PROTECTION) + gnutls_x509_crq_set_key_purpose_oid(crq, GNUTLS_KP_EMAIL_PROTECTION, 0); + if (purpose & CUPS_CREDPURPOSE_OCSP_SIGNING) + gnutls_x509_crq_set_key_purpose_oid(crq, GNUTLS_KP_OCSP_SIGNING, 0); + + if (usage & CUPS_CREDUSAGE_DIGITAL_SIGNATURE) + gnutls_usage |= GNUTLS_KEY_DIGITAL_SIGNATURE; + if (usage & CUPS_CREDUSAGE_NON_REPUDIATION) + gnutls_usage |= GNUTLS_KEY_NON_REPUDIATION; + if (usage & CUPS_CREDUSAGE_KEY_ENCIPHERMENT) + gnutls_usage |= GNUTLS_KEY_KEY_ENCIPHERMENT; + if (usage & CUPS_CREDUSAGE_DATA_ENCIPHERMENT) + gnutls_usage |= GNUTLS_KEY_DATA_ENCIPHERMENT; + if (usage & CUPS_CREDUSAGE_KEY_AGREEMENT) + gnutls_usage |= GNUTLS_KEY_KEY_AGREEMENT; + if (usage & CUPS_CREDUSAGE_KEY_CERT_SIGN) + gnutls_usage |= GNUTLS_KEY_KEY_CERT_SIGN; + if (usage & CUPS_CREDUSAGE_CRL_SIGN) + gnutls_usage |= GNUTLS_KEY_CRL_SIGN; + if (usage & CUPS_CREDUSAGE_ENCIPHER_ONLY) + gnutls_usage |= GNUTLS_KEY_ENCIPHER_ONLY; + if (usage & CUPS_CREDUSAGE_DECIPHER_ONLY) + gnutls_usage |= GNUTLS_KEY_DECIPHER_ONLY; + + gnutls_x509_crq_set_key_usage(crq, gnutls_usage); + gnutls_x509_crq_set_version(crq, 3); + + gnutls_x509_crq_sign2(crq, key, GNUTLS_DIG_SHA256, 0); + + // Save it... + bytes = sizeof(buffer); + if ((result = gnutls_x509_crq_export(crq, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) + { + DEBUG_printf(("1cupsCreateCredentialsRequest: Unable to export public key and X.509 certificate request: %s", gnutls_strerror(result))); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); + goto done; + } + else if ((fp = cupsFileOpen(crqfile, "w")) != NULL) + { + DEBUG_printf(("1cupsCreateCredentialsRequest: Writing public key and X.509 certificate request to \"%s\".", crtfile)); + cupsFileWrite(fp, (char *)buffer, bytes); + cupsFileClose(fp); + } + else + { + DEBUG_printf(("1cupsCreateCredentialsRequest: Unable to create public key and X.509 certificate request file \"%s\": %s", crtfile, strerror(errno))); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); + goto done; + } + + DEBUG_puts("1cupsCreateCredentialsRequest: Successfully created credentials request."); + + ret = true; + + // Cleanup... + done: + + if (crq) + gnutls_x509_crq_deinit(crq); + if (key) + gnutls_x509_privkey_deinit(key); + + return (ret); } From 97e2b7c7b96476598902cc4e1677ec54b4054be2 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Thu, 18 May 2023 10:16:49 -0400 Subject: [PATCH 21/31] Fix typos in new CSR code. --- cups/tls-gnutls.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cups/tls-gnutls.c b/cups/tls-gnutls.c index 328fc54df..1fd158c6f 100644 --- a/cups/tls-gnutls.c +++ b/cups/tls-gnutls.c @@ -341,7 +341,7 @@ cupsCreateCredentialsRequest( cups_file_t *fp; // Key/cert file unsigned char buffer[8192]; // Buffer for key/cert data size_t bytes; // Number of bytes of data - int status; // GNU TLS status + int result; // GNU TLS status DEBUG_printf(("cupsCreateCredentialsRequest(path=\"%s\", purpose=0x%x, type=%d, usage=0x%x, organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", common_name=\"%s\", num_alt_names=%u, alt_names=%p)", path, purpose, type, usage, organization, org_unit, locality, state_province, country, common_name, (unsigned)num_alt_names, alt_names)); @@ -403,7 +403,7 @@ cupsCreateCredentialsRequest( gnutls_x509_crq_init(&crq); gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_X520_COUNTRY_NAME, 0, country, (unsigned)strlen(country)); - gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_X520_COMMON_NAME, 0, hostname, (unsigned)strlen(hostname)); + gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_X520_COMMON_NAME, 0, common_name, (unsigned)strlen(common_name)); gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, organization, (unsigned)strlen(organization)); gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, org_unit, (unsigned)strlen(org_unit)); gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, state_province, (unsigned)strlen(state_province)); @@ -475,15 +475,15 @@ cupsCreateCredentialsRequest( _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); goto done; } - else if ((fp = cupsFileOpen(crqfile, "w")) != NULL) + else if ((fp = cupsFileOpen(csrfile, "w")) != NULL) { - DEBUG_printf(("1cupsCreateCredentialsRequest: Writing public key and X.509 certificate request to \"%s\".", crtfile)); + DEBUG_printf(("1cupsCreateCredentialsRequest: Writing public key and X.509 certificate request to \"%s\".", csrfile)); cupsFileWrite(fp, (char *)buffer, bytes); cupsFileClose(fp); } else { - DEBUG_printf(("1cupsCreateCredentialsRequest: Unable to create public key and X.509 certificate request file \"%s\": %s", crtfile, strerror(errno))); + DEBUG_printf(("1cupsCreateCredentialsRequest: Unable to create public key and X.509 certificate request file \"%s\": %s", csrfile, strerror(errno))); _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); goto done; } From 6227ff60a6f31d8a2811b9b2a0c4f68372c26cc5 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Thu, 25 May 2023 07:10:12 -0400 Subject: [PATCH 22/31] Save work. --- cups/cups.h | 8 +- cups/testcreds.c | 33 +++++-- cups/tls-openssl.c | 210 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 217 insertions(+), 34 deletions(-) diff --git a/cups/cups.h b/cups/cups.h index 940def6e0..304938fc3 100644 --- a/cups/cups.h +++ b/cups/cups.h @@ -140,7 +140,8 @@ enum cups_credpurpose_e //// X.509 credential purposes CUPS_CREDPURPOSE_CODE_SIGNING = 0x04, // codeSigning CUPS_CREDPURPOSE_EMAIL_PROTECTION = 0x08, // emailProtection CUPS_CREDPURPOSE_TIME_STAMPING = 0x10, // timeStamping - CUPS_CREDPURPOSE_OCSP_SIGNING = 0x20 // OCSPSigning + CUPS_CREDPURPOSE_OCSP_SIGNING = 0x20, // OCSPSigning + CUPS_CREDPURPOSE_ALL = 0x3f // All purposes }; typedef unsigned cups_credpurpose_t; //// Combined X.509 credential purposes for @link cupsCreateCredentials@ and @link cupsCreateCredentialsRequest@ @@ -167,7 +168,8 @@ enum cups_credusage_e //// X.509 keyUsage flags CUPS_CREDUSAGE_ENCIPHER_ONLY = 0x080, // encipherOnly CUPS_CREDUSAGE_DECIPHER_ONLY = 0x100, // decipherOnly CUPS_CREDUSAGE_DEFAULT_CA = 0x061, // Defaults for CA certs - CUPS_CREDUSAGE_DEFAULT_TLS = 0x005 // Defaults for TLS certs + CUPS_CREDUSAGE_DEFAULT_TLS = 0x005, // Defaults for TLS certs + CUPS_CREDUSAGE_ALL = 0x1ff // All keyUsage flags }; typedef unsigned cups_credusage_t; //// Combined X.509 keyUsage flags for @link cupsCreateCredentials@ and @link cupsCreateCredentialsRequest@ @@ -398,7 +400,7 @@ extern void cupsSetServerCertCB(cups_server_cert_cb_t cb, void *user_data) _CUP extern bool cupsSetServerCredentials(const char *path, const char *common_name, bool auto_create) _CUPS_PUBLIC; extern void cupsSetUser(const char *user) _CUPS_PUBLIC; extern void cupsSetUserAgent(const char *user_agent) _CUPS_PUBLIC; -extern bool cupsSignCredentialsRequest(const char *path, const char *common_name, const char *request, const char *root_name, time_t expiration_date) _CUPS_PUBLIC; +extern bool cupsSignCredentialsRequest(const char *path, const char *common_name, const char *request, const char *root_name, cups_credpurpose_t allowed_purpose, cups_credusage_t allowed_usage, const char *allowed_domain, time_t expiration_date) _CUPS_PUBLIC; extern http_status_t cupsStartDestDocument(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, int job_id, const char *docname, const char *format, size_t num_options, cups_option_t *options, bool last_document) _CUPS_PUBLIC; extern int cupsTempFd(const char *prefix, const char *suffix, char *filename, size_t len) _CUPS_PUBLIC; extern cups_file_t *cupsTempFile(const char *prefix, const char *suffix, char *filename, size_t len) _CUPS_PUBLIC; diff --git a/cups/testcreds.c b/cups/testcreds.c index f4f2ccc6e..39b292605 100644 --- a/cups/testcreds.c +++ b/cups/testcreds.c @@ -438,20 +438,39 @@ do_unit_tests(void) else testEndMessage(false, "%s", cupsLastErrorString()); - testBegin("cupsCreateCredentialsRequest(printer, %s, alt names)", types[type]); - if (cupsCreateCredentialsRequest(TEST_CERT_PATH, CUPS_CREDPURPOSE_SERVER_AUTH, type, CUPS_CREDUSAGE_DEFAULT_TLS, "Organization", "Unit", "Locality", "Ontario", "CA", "printer", sizeof(alt_names) / sizeof(alt_names[0]), alt_names)) + testBegin("cupsCreateCredentialsRequest(altprinter, %s, alt names)", types[type]); + if (cupsCreateCredentialsRequest(TEST_CERT_PATH, CUPS_CREDPURPOSE_SERVER_AUTH, type, CUPS_CREDUSAGE_DEFAULT_TLS, "Organization", "Unit", "Locality", "Ontario", "CA", "altprinter", sizeof(alt_names) / sizeof(alt_names[0]), alt_names)) { testEnd(true); - testBegin("cupsCopyCredentialsRequest(printer)"); - data = cupsCopyCredentialsRequest(TEST_CERT_PATH, "printer"); + testBegin("cupsCopyCredentialsKey(altprinter)"); + data = cupsCopyCredentialsKey(TEST_CERT_PATH, "altprinter"); testEnd(data != NULL); free(data); - testBegin("cupsCopyCredentialsKey(printer)"); - data = cupsCopyCredentialsKey(TEST_CERT_PATH, "printer"); + testBegin("cupsCopyCredentialsRequest(altprinter)"); + data = cupsCopyCredentialsRequest(TEST_CERT_PATH, "altprinter"); testEnd(data != NULL); - free(data); + + if (data) + { + testBegin("cupsSignCredentialsRequest(altprinter)"); + if (cupsSignCredentialsRequest(TEST_CERT_PATH, "altprinter", data, "_site_", CUPS_CREDPURPOSE_ALL, CUPS_CREDUSAGE_ALL, /*allowed_domain*/NULL, time(NULL) + 30 * 86400)) + { + testEnd(true); + free(data); + + testBegin("cupsCopyCredentialsKey(altprinter)"); + data = cupsCopyCredentialsKey(TEST_CERT_PATH, "altprinter"); + testEnd(data != NULL); + } + else + { + testEndMessage(false, "%s", cupsLastErrorString()); + } + + free(data); + } } else { diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index 0adaf6be6..4c88e6920 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -102,8 +102,11 @@ cupsCreateCredentials( X509 *cert; // Certificate X509 *root_cert = NULL; // Root certificate, if any EVP_PKEY *root_key = NULL; // Root private key, if any - char root_crtfile[1024], // Path to root certificate - root_keyfile[1024]; // Path to root private key + char defpath[1024], // Default path + crtfile[1024], // Certificate filename + keyfile[1024], // Private key filename + root_crtfile[1024], // Root certificate filename + root_keyfile[1024]; // Root private key filename time_t curtime; // Current time X509_NAME *name; // Subject/issuer name ASN1_INTEGER *serial; // Serial number @@ -111,9 +114,7 @@ cupsCreateCredentials( *notAfter; // Expiration date BIO *bio; // Output file char temp[1024], // Temporary string - *tempptr, // Pointer into temporary string - crtfile[1024], // Certificate filename - keyfile[1024]; // Private key filename + *tempptr; // Pointer into temporary string STACK_OF(X509_EXTENSION) *exts; // Extensions X509_EXTENSION *ext; // Current extension unsigned i; // Looping var @@ -125,7 +126,7 @@ cupsCreateCredentials( // Filenames... if (!path) - path = http_default_path(temp, sizeof(temp)); + path = http_default_path(defpath, sizeof(defpath)); if (!path || !common_name) { @@ -133,9 +134,6 @@ cupsCreateCredentials( return (false); } - http_make_path(crtfile, sizeof(crtfile), path, common_name, "crt"); - http_make_path(keyfile, sizeof(keyfile), path, common_name, "key"); - // Create the encryption key... DEBUG_puts("1cupsCreateCredentials: Creating key pair."); @@ -145,7 +143,7 @@ cupsCreateCredentials( DEBUG_puts("1cupsCreateCredentials: Key pair created."); // Create the X.509 certificate... - DEBUG_puts("1cupsCreateCredentials: Generating self-signed X.509 certificate."); + DEBUG_puts("1cupsCreateCredentials: Generating X.509 certificate."); if ((cert = X509_new()) == NULL) { @@ -173,6 +171,10 @@ cupsCreateCredentials( X509_set_pubkey(cert, pkey); + name = openssl_create_name(organization, org_unit, locality, state_province, country, common_name); + + X509_set_subject_name(cert, name); + // Try loading a root certificate... http_make_path(root_crtfile, sizeof(root_crtfile), path, root_name ? root_name : "_site_", "crt"); http_make_path(root_keyfile, sizeof(root_keyfile), path, root_name ? root_name : "_site_", "key"); @@ -198,15 +200,18 @@ cupsCreateCredentials( } } + if (!root_cert || !root_key) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to load X.509 CA certificate and private key."), 1); + goto done; + } } - name = openssl_create_name(organization, org_unit, locality, state_province, country, common_name); - if (root_cert) X509_set_issuer_name(cert, X509_get_subject_name(root_cert)); else X509_set_issuer_name(cert, name); - X509_set_subject_name(cert, name); + X509_NAME_free(name); exts = sk_X509_EXTENSION_new_null(); @@ -267,6 +272,9 @@ cupsCreateCredentials( X509_sign(cert, pkey, EVP_sha256()); // Save them... + http_make_path(crtfile, sizeof(crtfile), path, common_name, "crt"); + http_make_path(keyfile, sizeof(keyfile), path, common_name, "key"); + if ((bio = BIO_new_file(keyfile, "wb")) == NULL) { _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); @@ -487,19 +495,173 @@ cupsCreateCredentialsRequest( bool // O - `true` on success, `false` on failure cupsSignCredentialsRequest( - const char *path, // I - Directory path for certificate/key store or `NULL` for default - const char *common_name, // I - Common name to use - const char *request, // I - PEM-encoded CSR - const char *root_name, // I - Root certificate - time_t expiration_date) // I - Certificate expiration date + const char *path, // I - Directory path for certificate/key store or `NULL` for default + const char *common_name, // I - Common name to use + const char *request, // I - PEM-encoded CSR + const char *root_name, // I - Root certificate + cups_credpurpose_t allowed_purpose, // I - Allowed credential purpose(s) + cups_credusage_t allowed_usage, // I - Allowed credential usage(s) + const char *allowed_domain, // I - Allowed domain name (beyond .local) or `NULL` for just .local + time_t expiration_date) // I - Certificate expiration date { - (void)path; - (void)common_name; - (void)request; - (void)root_name; - (void)expiration_date; + bool result = false; // Return value + X509 *cert = NULL; // Certificate + X509_REQ *crq = NULL; // Certificate request + X509 *root_cert = NULL; // Root certificate, if any + EVP_PKEY *root_key = NULL; // Root private key, if any + char defpath[1024], // Default path + crtfile[1024], // Certificate filename + root_crtfile[1024], // Root certificate filename + root_keyfile[1024]; // Root private key filename + time_t curtime; // Current time + ASN1_INTEGER *serial; // Serial number + ASN1_TIME *notBefore, // Initial date + *notAfter; // Expiration date + BIO *bio; // Output file +#if 0 + char temp[1024], // Temporary string + *tempptr; // Pointer into temporary string + STACK_OF(X509_EXTENSION) *exts; // Extensions + X509_EXTENSION *ext; // Current extension + unsigned i; // Looping var + cups_credpurpose_t purpose_bit; // Current purpose + cups_credusage_t usage_bit; // Current usage +#endif // 0 + + + DEBUG_printf(("cupsSignCredentialsRequest(path=\"%s\", common_name=\"%s\", request=\"%s\", root_name=\"%s\", allowed_purpose=0x%x, allowed_usage=0x%x, allowed_domain=\"%s\", expiration_date=%ld)", path, common_name, request, root_name, allowed_purpose, allowed_usage, allowed_domain, (long)expiration_date)); + + // Filenames... + if (!path) + path = http_default_path(defpath, sizeof(defpath)); + + if (!path || !common_name || !request) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); + return (false); + } + + // Import the X.509 certificate request... + DEBUG_puts("1cupsCreateCredentials: Importing X.509 certificate request."); + if ((bio = BIO_new_mem_buf(request, (int)strlen(request))) != NULL) + { + PEM_read_bio_X509_REQ(bio, &crq, /*cb*/NULL, /*u*/NULL); + BIO_free(bio); + } + + if (!crq) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to import X.509 certificate request."), 1); + return (false); + } + + if (X509_REQ_verify(crq, X509_REQ_get_pubkey(crq)) < 0) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to verify X.509 certificate request."), 1); + goto done; + } + + // Create the X.509 certificate... + DEBUG_puts("1cupsSignCredentialsRequest: Generating X.509 certificate."); - return (false); + if ((cert = X509_new()) == NULL) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create X.509 certificate."), 1); + goto done; + } + + curtime = time(NULL); + + notBefore = ASN1_TIME_new(); + ASN1_TIME_set(notBefore, curtime); + X509_set_notBefore(cert, notBefore); + ASN1_TIME_free(notBefore); + + notAfter = ASN1_TIME_new(); + ASN1_TIME_set(notAfter, expiration_date); + X509_set_notAfter(cert, notAfter); + ASN1_TIME_free(notAfter); + + serial = ASN1_INTEGER_new(); + ASN1_INTEGER_set(serial, (int)curtime); + X509_set_serialNumber(cert, serial); + ASN1_INTEGER_free(serial); + + X509_set_pubkey(cert, X509_REQ_get_pubkey(crq)); + + X509_set_issuer_name(cert, X509_get_subject_name(root_cert)); + X509_set_subject_name(cert, X509_REQ_get_subject_name(crq)); + X509_set_version(cert, 2); // v3 + + // Try loading a root certificate... + http_make_path(root_crtfile, sizeof(root_crtfile), path, root_name ? root_name : "_site_", "crt"); + http_make_path(root_keyfile, sizeof(root_keyfile), path, root_name ? root_name : "_site_", "key"); + + if (!access(root_crtfile, 0) && !access(root_keyfile, 0)) + { + if ((bio = BIO_new_file(root_crtfile, "rb")) != NULL) + { + PEM_read_bio_X509(bio, &root_cert, /*cb*/NULL, /*u*/NULL); + BIO_free(bio); + + if ((bio = BIO_new_file(root_keyfile, "rb")) != NULL) + { + PEM_read_bio_PrivateKey(bio, &root_key, /*cb*/NULL, /*u*/NULL); + BIO_free(bio); + } + + if (!root_key) + { + // Only use root certificate if we have the key... + X509_free(root_cert); + root_cert = NULL; + } + } + } + + if (!root_cert || !root_key) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to load X.509 CA certificate and private key."), 1); + goto done; + } + + X509_sign(cert, root_key, EVP_sha256()); + + // Save the certificate... + http_make_path(crtfile, sizeof(crtfile), path, common_name, "crt"); + + if ((bio = BIO_new_file(crtfile, "wb")) == NULL) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); + goto done; + } + + if (!PEM_write_bio_X509(bio, cert)) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to write X.509 certificate."), 1); + BIO_free(bio); + goto done; + } + + PEM_write_bio_X509(bio, root_cert); + + BIO_free(bio); + result = true; + DEBUG_puts("1cupsSignRequest: Successfully created credentials."); + + // Cleanup... + done: + + if (crq) + X509_REQ_free(crq); + if (cert) + X509_free(cert); + if (root_cert) + X509_free(root_cert); + if (root_key) + EVP_PKEY_free(root_key); + + return (result); } From 79af0a94ff17230aef93e002faa7ace22e92b1aa Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Thu, 25 May 2023 13:14:27 -0400 Subject: [PATCH 23/31] Copy extensions from request to certificate. Add callback for validating subjectAltName. TODO: Still need to validate extensions. --- cups/cups.h | 5 +++- cups/testcreds.c | 2 +- cups/tls-openssl.c | 62 ++++++++++++++++++++++++++++++++-------------- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/cups/cups.h b/cups/cups.h index 304938fc3..ccb035a3e 100644 --- a/cups/cups.h +++ b/cups/cups.h @@ -283,6 +283,9 @@ typedef struct cups_size_s //// Media information top; // Top margin in hundredths of millimeters } cups_size_t; +typedef bool (*cups_cert_sign_cb_t)(const char *subject_alt_name, void *user_data); + // Certificate signing subjectAltName callback + typedef bool (*cups_client_cert_cb_t)(http_t *http, void *tls, cups_array_t *distinguished_names, void *user_data); // Client credentials callback @@ -400,7 +403,7 @@ extern void cupsSetServerCertCB(cups_server_cert_cb_t cb, void *user_data) _CUP extern bool cupsSetServerCredentials(const char *path, const char *common_name, bool auto_create) _CUPS_PUBLIC; extern void cupsSetUser(const char *user) _CUPS_PUBLIC; extern void cupsSetUserAgent(const char *user_agent) _CUPS_PUBLIC; -extern bool cupsSignCredentialsRequest(const char *path, const char *common_name, const char *request, const char *root_name, cups_credpurpose_t allowed_purpose, cups_credusage_t allowed_usage, const char *allowed_domain, time_t expiration_date) _CUPS_PUBLIC; +extern bool cupsSignCredentialsRequest(const char *path, const char *common_name, const char *request, const char *root_name, cups_credpurpose_t allowed_purpose, cups_credusage_t allowed_usage, cups_cert_sign_cb_t cb, void *cb_data, time_t expiration_date) _CUPS_PUBLIC; extern http_status_t cupsStartDestDocument(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, int job_id, const char *docname, const char *format, size_t num_options, cups_option_t *options, bool last_document) _CUPS_PUBLIC; extern int cupsTempFd(const char *prefix, const char *suffix, char *filename, size_t len) _CUPS_PUBLIC; extern cups_file_t *cupsTempFile(const char *prefix, const char *suffix, char *filename, size_t len) _CUPS_PUBLIC; diff --git a/cups/testcreds.c b/cups/testcreds.c index 39b292605..4fc8ab6d5 100644 --- a/cups/testcreds.c +++ b/cups/testcreds.c @@ -455,7 +455,7 @@ do_unit_tests(void) if (data) { testBegin("cupsSignCredentialsRequest(altprinter)"); - if (cupsSignCredentialsRequest(TEST_CERT_PATH, "altprinter", data, "_site_", CUPS_CREDPURPOSE_ALL, CUPS_CREDUSAGE_ALL, /*allowed_domain*/NULL, time(NULL) + 30 * 86400)) + if (cupsSignCredentialsRequest(TEST_CERT_PATH, "altprinter", data, "_site_", CUPS_CREDPURPOSE_ALL, CUPS_CREDUSAGE_ALL, /*cb*/NULL, /*cb_data*/NULL, time(NULL) + 30 * 86400)) { testEnd(true); free(data); diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index 4c88e6920..0a8db4827 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -495,14 +495,15 @@ cupsCreateCredentialsRequest( bool // O - `true` on success, `false` on failure cupsSignCredentialsRequest( - const char *path, // I - Directory path for certificate/key store or `NULL` for default - const char *common_name, // I - Common name to use - const char *request, // I - PEM-encoded CSR - const char *root_name, // I - Root certificate - cups_credpurpose_t allowed_purpose, // I - Allowed credential purpose(s) - cups_credusage_t allowed_usage, // I - Allowed credential usage(s) - const char *allowed_domain, // I - Allowed domain name (beyond .local) or `NULL` for just .local - time_t expiration_date) // I - Certificate expiration date + const char *path, // I - Directory path for certificate/key store or `NULL` for default + const char *common_name, // I - Common name to use + const char *request, // I - PEM-encoded CSR + const char *root_name, // I - Root certificate + cups_credpurpose_t allowed_purpose,// I - Allowed credential purpose(s) + cups_credusage_t allowed_usage, // I - Allowed credential usage(s) + cups_cert_sign_cb_t cb, // I - subjectAltName callback or `NULL` to allow just .local + void *cb_data, // I - Callback data + time_t expiration_date)// I - Certificate expiration date { bool result = false; // Return value X509 *cert = NULL; // Certificate @@ -518,18 +519,17 @@ cupsSignCredentialsRequest( ASN1_TIME *notBefore, // Initial date *notAfter; // Expiration date BIO *bio; // Output file -#if 0 - char temp[1024], // Temporary string - *tempptr; // Pointer into temporary string - STACK_OF(X509_EXTENSION) *exts; // Extensions + char temp[1024]; // Temporary string +// char *tempptr; // Pointer into temporary string + int i, // Looping var + num_exts; // Number of extensions + STACK_OF(X509_EXTENSION) *exts = NULL;// Extensions X509_EXTENSION *ext; // Current extension - unsigned i; // Looping var - cups_credpurpose_t purpose_bit; // Current purpose - cups_credusage_t usage_bit; // Current usage -#endif // 0 +// cups_credpurpose_t purpose_bit; // Current purpose +// cups_credusage_t usage_bit; // Current usage - DEBUG_printf(("cupsSignCredentialsRequest(path=\"%s\", common_name=\"%s\", request=\"%s\", root_name=\"%s\", allowed_purpose=0x%x, allowed_usage=0x%x, allowed_domain=\"%s\", expiration_date=%ld)", path, common_name, request, root_name, allowed_purpose, allowed_usage, allowed_domain, (long)expiration_date)); + DEBUG_printf(("cupsSignCredentialsRequest(path=\"%s\", common_name=\"%s\", request=\"%s\", root_name=\"%s\", allowed_purpose=0x%x, allowed_usage=0x%x, cb=%p, cb_data=%p, expiration_date=%ld)", path, common_name, request, root_name, allowed_purpose, allowed_usage, cb, cb_data, (long)expiration_date)); // Filenames... if (!path) @@ -589,10 +589,33 @@ cupsSignCredentialsRequest( X509_set_pubkey(cert, X509_REQ_get_pubkey(crq)); - X509_set_issuer_name(cert, X509_get_subject_name(root_cert)); X509_set_subject_name(cert, X509_REQ_get_subject_name(crq)); X509_set_version(cert, 2); // v3 + // Copy/verify extensions... + exts = X509_REQ_get_extensions(crq); + num_exts = sk_X509_EXTENSION_num(exts); + + for (i = 0; i < num_exts; i ++) + { + // Get the extension object... + ASN1_OBJECT *obj; // Extension object + + ext = sk_X509_EXTENSION_value(exts, i); + obj = X509_EXTENSION_get_object(ext); + + // TODO: Use callback to verify subjectAltName values + i2t_ASN1_OBJECT(temp, (int)sizeof(temp), obj); + fprintf(stderr, "exts[%d]=\"%s\"\n", i, temp); + + (void)cb; + (void)cb_data; + + // If we get this far, the object is OK and we can add it... + if (!X509_add_ext(cert, ext, -1)) + goto done; + } + // Try loading a root certificate... http_make_path(root_crtfile, sizeof(root_crtfile), path, root_name ? root_name : "_site_", "crt"); http_make_path(root_keyfile, sizeof(root_keyfile), path, root_name ? root_name : "_site_", "key"); @@ -625,6 +648,7 @@ cupsSignCredentialsRequest( goto done; } + X509_set_issuer_name(cert, X509_get_subject_name(root_cert)); X509_sign(cert, root_key, EVP_sha256()); // Save the certificate... @@ -652,6 +676,8 @@ cupsSignCredentialsRequest( // Cleanup... done: + if (exts) + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); if (crq) X509_REQ_free(crq); if (cert) From 8335d5b50169d1d44c9a1bc925000bc75e9927ff Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Fri, 26 May 2023 12:29:52 -0400 Subject: [PATCH 24/31] Save more work. --- cups/tls-openssl.c | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index 0a8db4827..71f5d50d4 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -527,6 +527,9 @@ cupsSignCredentialsRequest( X509_EXTENSION *ext; // Current extension // cups_credpurpose_t purpose_bit; // Current purpose // cups_credusage_t usage_bit; // Current usage + bool saw_usage = false, // Saw NID_key_usage? + saw_ext_usage = false, // Saw NID_ext_key_usage? + saw_san = false; // Saw NID_subject_alt_name? DEBUG_printf(("cupsSignCredentialsRequest(path=\"%s\", common_name=\"%s\", request=\"%s\", root_name=\"%s\", allowed_purpose=0x%x, allowed_usage=0x%x, cb=%p, cb_data=%p, expiration_date=%ld)", path, common_name, request, root_name, allowed_purpose, allowed_usage, cb, cb_data, (long)expiration_date)); @@ -604,18 +607,41 @@ cupsSignCredentialsRequest( ext = sk_X509_EXTENSION_value(exts, i); obj = X509_EXTENSION_get_object(ext); - // TODO: Use callback to verify subjectAltName values - i2t_ASN1_OBJECT(temp, (int)sizeof(temp), obj); - fprintf(stderr, "exts[%d]=\"%s\"\n", i, temp); + OBJ_obj2txt(temp, (int)sizeof(temp), obj, 0); + fprintf(stderr, "NID %d: %s\n", OBJ_obj2nid(obj), temp); - (void)cb; - (void)cb_data; + switch (OBJ_obj2nid(obj)) + { + case NID_ext_key_usage : + saw_ext_usage = true; + break; + + case NID_key_usage : + saw_usage = true; + break; + + case NID_subject_alt_name : + saw_san = true; + // TODO: Use callback to validate subjectAltName values + break; + + case NID_basic_constraints : + // Ignore basicConstraints in request... + continue; + } // If we get this far, the object is OK and we can add it... if (!X509_add_ext(cert, ext, -1)) goto done; } + // Add basic constraints for an "edge" certificate... + if ((ext = X509V3_EXT_conf_nid(/*conf*/NULL, /*ctx*/NULL, NID_basic_constraints, "critical,CA:FALSE,pathlen:0")) == NULL || !X509_add_ext(cert, ext, -1)) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to add extension to X.509 certificate."), 1); + goto done; + } + // Try loading a root certificate... http_make_path(root_crtfile, sizeof(root_crtfile), path, root_name ? root_name : "_site_", "crt"); http_make_path(root_keyfile, sizeof(root_keyfile), path, root_name ? root_name : "_site_", "key"); From 453159f31a427c10ed31795ef5378d00e03a17d7 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Tue, 30 May 2023 20:36:38 -0400 Subject: [PATCH 25/31] Implement the rest of the X.509 request validation code in cupsSignCredentialsRequest. --- cups/cups.h | 4 +- cups/testcreds.c | 48 ++++++++-- cups/tls-openssl.c | 213 ++++++++++++++++++++++++++++++++++++++------- cups/tls.c | 25 ++++++ 4 files changed, 251 insertions(+), 39 deletions(-) diff --git a/cups/cups.h b/cups/cups.h index ccb035a3e..70139b957 100644 --- a/cups/cups.h +++ b/cups/cups.h @@ -283,7 +283,7 @@ typedef struct cups_size_s //// Media information top; // Top margin in hundredths of millimeters } cups_size_t; -typedef bool (*cups_cert_sign_cb_t)(const char *subject_alt_name, void *user_data); +typedef bool (*cups_cert_san_cb_t)(const char *common_name, const char *subject_alt_name, void *user_data); // Certificate signing subjectAltName callback typedef bool (*cups_client_cert_cb_t)(http_t *http, void *tls, cups_array_t *distinguished_names, void *user_data); @@ -403,7 +403,7 @@ extern void cupsSetServerCertCB(cups_server_cert_cb_t cb, void *user_data) _CUP extern bool cupsSetServerCredentials(const char *path, const char *common_name, bool auto_create) _CUPS_PUBLIC; extern void cupsSetUser(const char *user) _CUPS_PUBLIC; extern void cupsSetUserAgent(const char *user_agent) _CUPS_PUBLIC; -extern bool cupsSignCredentialsRequest(const char *path, const char *common_name, const char *request, const char *root_name, cups_credpurpose_t allowed_purpose, cups_credusage_t allowed_usage, cups_cert_sign_cb_t cb, void *cb_data, time_t expiration_date) _CUPS_PUBLIC; +extern bool cupsSignCredentialsRequest(const char *path, const char *common_name, const char *request, const char *root_name, cups_credpurpose_t allowed_purpose, cups_credusage_t allowed_usage, cups_cert_san_cb_t cb, void *cb_data, time_t expiration_date) _CUPS_PUBLIC; extern http_status_t cupsStartDestDocument(http_t *http, cups_dest_t *dest, cups_dinfo_t *info, int job_id, const char *docname, const char *format, size_t num_options, cups_option_t *options, bool last_document) _CUPS_PUBLIC; extern int cupsTempFd(const char *prefix, const char *suffix, char *filename, size_t len) _CUPS_PUBLIC; extern cups_file_t *cupsTempFile(const char *prefix, const char *suffix, char *filename, size_t len) _CUPS_PUBLIC; diff --git a/cups/testcreds.c b/cups/testcreds.c index 4fc8ab6d5..ca40d5321 100644 --- a/cups/testcreds.c +++ b/cups/testcreds.c @@ -412,7 +412,7 @@ do_unit_tests(void) for (type = CUPS_CREDTYPE_DEFAULT; type <= CUPS_CREDTYPE_ECDSA_P521_SHA256; type ++) { - testBegin("cupsCreateCredentials(_site_, %s, no alt names, CA)", types[type]); + testBegin("cupsCreateCredentials(_site_, %s, CA)", types[type]); if (cupsCreateCredentials(TEST_CERT_PATH, true, CUPS_CREDPURPOSE_SERVER_AUTH, type, CUPS_CREDUSAGE_DEFAULT_TLS, "Organization", "Unit", "Locality", "Ontario", "CA", "_site_", 0, NULL, NULL, time(NULL) + 30 * 86400)) { testEnd(true); @@ -432,35 +432,69 @@ do_unit_tests(void) testEndMessage(false, "%s", cupsLastErrorString()); } - testBegin("cupsCreateCredentials(printer, %s, alt names, signed by CA cert)", types[type]); + testBegin("cupsCreateCredentials(printer w/alt names, %s, signed by CA cert)", types[type]); if (cupsCreateCredentials(TEST_CERT_PATH, false, CUPS_CREDPURPOSE_SERVER_AUTH, type, CUPS_CREDUSAGE_DEFAULT_TLS, "Organization", "Unit", "Locality", "Ontario", "CA", "printer", sizeof(alt_names) / sizeof(alt_names[0]), alt_names, "_site_", time(NULL) + 30 * 86400)) testEnd(true); else testEndMessage(false, "%s", cupsLastErrorString()); - testBegin("cupsCreateCredentialsRequest(altprinter, %s, alt names)", types[type]); + testBegin("cupsCreateCredentialsRequest(altprinter w/alt names, %s)", types[type]); if (cupsCreateCredentialsRequest(TEST_CERT_PATH, CUPS_CREDPURPOSE_SERVER_AUTH, type, CUPS_CREDUSAGE_DEFAULT_TLS, "Organization", "Unit", "Locality", "Ontario", "CA", "altprinter", sizeof(alt_names) / sizeof(alt_names[0]), alt_names)) { testEnd(true); - testBegin("cupsCopyCredentialsKey(altprinter)"); + testBegin("cupsCopyCredentialsKey(altprinter w/alt names)"); data = cupsCopyCredentialsKey(TEST_CERT_PATH, "altprinter"); testEnd(data != NULL); free(data); - testBegin("cupsCopyCredentialsRequest(altprinter)"); + testBegin("cupsCopyCredentialsRequest(altprinter w/alt names)"); data = cupsCopyCredentialsRequest(TEST_CERT_PATH, "altprinter"); testEnd(data != NULL); if (data) { - testBegin("cupsSignCredentialsRequest(altprinter)"); + testBegin("cupsSignCredentialsRequest(altprinter w/alt names)"); + if (cupsSignCredentialsRequest(TEST_CERT_PATH, "altprinter", data, "_site_", CUPS_CREDPURPOSE_ALL, CUPS_CREDUSAGE_ALL, /*cb*/NULL, /*cb_data*/NULL, time(NULL) + 30 * 86400)) + { + testEndMessage(false, "Expected a failure"); + } + else + { + testEndMessage(true, "%s", cupsLastErrorString()); + } + + free(data); + } + } + else + { + testEndMessage(false, "%s", cupsLastErrorString()); + } + + testBegin("cupsCreateCredentialsRequest(altprinter w/o alt names, %s)", types[type]); + if (cupsCreateCredentialsRequest(TEST_CERT_PATH, CUPS_CREDPURPOSE_SERVER_AUTH, type, CUPS_CREDUSAGE_DEFAULT_TLS, "Organization", "Unit", "Locality", "Ontario", "CA", "altprinter", 0, NULL)) + { + testEnd(true); + + testBegin("cupsCopyCredentialsKey(altprinter w/o alt names)"); + data = cupsCopyCredentialsKey(TEST_CERT_PATH, "altprinter"); + testEnd(data != NULL); + free(data); + + testBegin("cupsCopyCredentialsRequest(altprinter w/o alt names)"); + data = cupsCopyCredentialsRequest(TEST_CERT_PATH, "altprinter"); + testEnd(data != NULL); + + if (data) + { + testBegin("cupsSignCredentialsRequest(altprinter w/o alt names)"); if (cupsSignCredentialsRequest(TEST_CERT_PATH, "altprinter", data, "_site_", CUPS_CREDPURPOSE_ALL, CUPS_CREDUSAGE_ALL, /*cb*/NULL, /*cb_data*/NULL, time(NULL) + 30 * 86400)) { testEnd(true); free(data); - testBegin("cupsCopyCredentialsKey(altprinter)"); + testBegin("cupsCopyCredentialsKey(altprinter w/o alt names)"); data = cupsCopyCredentialsKey(TEST_CERT_PATH, "altprinter"); testEnd(data != NULL); } diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index 71f5d50d4..74b7947cc 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -33,7 +33,7 @@ static bool openssl_add_ext(STACK_OF(X509_EXTENSION) *exts, int nid, const char static X509 *openssl_create_credential(http_credential_t *credential); static X509_NAME *openssl_create_name(const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name); static EVP_PKEY *openssl_create_key(cups_credtype_t type); -static bool openssl_create_san(STACK_OF(X509_EXTENSION) *exts, const char *common_name, size_t num_alt_names, const char * const *alt_names); +static X509_EXTENSION *openssl_create_san(const char *common_name, size_t num_alt_names, const char * const *alt_names); static time_t openssl_get_date(X509 *cert, int which); //static void openssl_load_crl(void); @@ -224,9 +224,11 @@ cupsCreateCredentials( else { // Add extension with DNS names and free buffer for GENERAL_NAME - if (!openssl_create_san(exts, common_name, num_alt_names, alt_names)) + if ((ext = openssl_create_san(common_name, num_alt_names, alt_names)) == NULL) goto done; + sk_X509_EXTENSION_push(exts, ext); + // Add extensions that are required to make Chrome happy... openssl_add_ext(exts, NID_basic_constraints, "critical,CA:FALSE,pathlen:0"); } @@ -354,6 +356,7 @@ cupsCreateCredentialsRequest( EVP_PKEY *pkey; // Key pair X509_REQ *csr; // Certificate signing request X509_NAME *name; // Subject/issuer name + X509_EXTENSION *ext; // X509 extension BIO *bio; // Output file char temp[1024], // Temporary directory name *tempptr, // Pointer into temporary string @@ -409,9 +412,11 @@ cupsCreateCredentialsRequest( // Add extension with DNS names and free buffer for GENERAL_NAME exts = sk_X509_EXTENSION_new_null(); - if (!openssl_create_san(exts, common_name, num_alt_names, alt_names)) + if ((ext = openssl_create_san(common_name, num_alt_names, alt_names)) == NULL) goto done; + sk_X509_EXTENSION_push(exts, ext); + cupsCopyString(temp, "critical", sizeof(temp)); for (tempptr = temp + strlen(temp), i = 0, usage_bit = CUPS_CREDUSAGE_DIGITAL_SIGNATURE; i < (sizeof(tls_usage_strings) / sizeof(tls_usage_strings[0])); i ++, usage_bit *= 2) { @@ -495,15 +500,15 @@ cupsCreateCredentialsRequest( bool // O - `true` on success, `false` on failure cupsSignCredentialsRequest( - const char *path, // I - Directory path for certificate/key store or `NULL` for default - const char *common_name, // I - Common name to use - const char *request, // I - PEM-encoded CSR - const char *root_name, // I - Root certificate - cups_credpurpose_t allowed_purpose,// I - Allowed credential purpose(s) - cups_credusage_t allowed_usage, // I - Allowed credential usage(s) - cups_cert_sign_cb_t cb, // I - subjectAltName callback or `NULL` to allow just .local - void *cb_data, // I - Callback data - time_t expiration_date)// I - Certificate expiration date + const char *path, // I - Directory path for certificate/key store or `NULL` for default + const char *common_name, // I - Common name to use + const char *request, // I - PEM-encoded CSR + const char *root_name, // I - Root certificate + cups_credpurpose_t allowed_purpose, // I - Allowed credential purpose(s) + cups_credusage_t allowed_usage, // I - Allowed credential usage(s) + cups_cert_san_cb_t cb, // I - subjectAltName callback or `NULL` to allow just .local + void *cb_data, // I - Callback data + time_t expiration_date) // I - Certificate expiration date { bool result = false; // Return value X509 *cert = NULL; // Certificate @@ -520,13 +525,12 @@ cupsSignCredentialsRequest( *notAfter; // Expiration date BIO *bio; // Output file char temp[1024]; // Temporary string -// char *tempptr; // Pointer into temporary string - int i, // Looping var + int i, j, // Looping vars num_exts; // Number of extensions STACK_OF(X509_EXTENSION) *exts = NULL;// Extensions X509_EXTENSION *ext; // Current extension -// cups_credpurpose_t purpose_bit; // Current purpose -// cups_credusage_t usage_bit; // Current usage + cups_credpurpose_t purpose; // Current purpose + cups_credusage_t usage; // Current usage bool saw_usage = false, // Saw NID_key_usage? saw_ext_usage = false, // Saw NID_ext_key_usage? saw_san = false; // Saw NID_subject_alt_name? @@ -544,6 +548,9 @@ cupsSignCredentialsRequest( return (false); } + if (!cb) + cb = http_default_san_cb; + // Import the X.509 certificate request... DEBUG_puts("1cupsCreateCredentials: Importing X.509 certificate request."); if ((bio = BIO_new_mem_buf(request, (int)strlen(request))) != NULL) @@ -602,36 +609,155 @@ cupsSignCredentialsRequest( for (i = 0; i < num_exts; i ++) { // Get the extension object... - ASN1_OBJECT *obj; // Extension object + bool add_ext = false; // Add this extension? + ASN1_OBJECT *obj; // Extension object + ASN1_OCTET_STRING *extdata; // Extension data string + unsigned char *data = NULL; // Extension data bytes + int datalen; // Length of extension data + + ext = sk_X509_EXTENSION_value(exts, i); + obj = X509_EXTENSION_get_object(ext); + extdata = X509_EXTENSION_get_data(ext); + datalen = i2d_ASN1_OCTET_STRING(extdata, &data); + +#ifdef DEBUG + char *tempptr; // Pointer into string - ext = sk_X509_EXTENSION_value(exts, i); - obj = X509_EXTENSION_get_object(ext); + for (j = 0, tempptr = temp; j < datalen; j ++, tempptr += 2) + snprintf(tempptr, sizeof(temp) - (size_t)(tempptr - temp), "%02X", data[j]); - OBJ_obj2txt(temp, (int)sizeof(temp), obj, 0); - fprintf(stderr, "NID %d: %s\n", OBJ_obj2nid(obj), temp); + DEBUG_printf(("1cupsSignCredentialsRequest: EXT%d=%s", OBJ_obj2nid(obj), temp)); +#endif // DEBUG switch (OBJ_obj2nid(obj)) { case NID_ext_key_usage : + add_ext = true; saw_ext_usage = true; + + if (datalen < 12 || data[2] != 0x30 || data[3] != (datalen - 4)) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad keyUsage extension in X.509 certificate request."), 1); + goto done; + } + + for (purpose = 0, j = 4; j < datalen; j += data[j + 1] + 2) + { + if (data[j] != 0x06 || data[j + 1] != 8 || memcmp(data + j + 2, "+\006\001\005\005\007\003", 7)) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad keyUsage extension in X.509 certificate request."), 1); + goto done; + } + + switch (data[j + 9]) + { + case 1 : + purpose |= CUPS_CREDPURPOSE_SERVER_AUTH; + break; + case 2 : + purpose |= CUPS_CREDPURPOSE_CLIENT_AUTH; + break; + case 3 : + purpose |= CUPS_CREDPURPOSE_CODE_SIGNING; + break; + case 4 : + purpose |= CUPS_CREDPURPOSE_EMAIL_PROTECTION; + break; + case 8 : + purpose |= CUPS_CREDPURPOSE_TIME_STAMPING; + break; + case 9 : + purpose |= CUPS_CREDPURPOSE_OCSP_SIGNING; + break; + default : + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad keyUsage extension in X.509 certificate request."), 1); + goto done; + } + } + + DEBUG_printf(("1cupsSignCredentialsRequest: purpose=0x%04x", purpose)); + + if (purpose & ~allowed_purpose) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad keyUsage extension in X.509 certificate request."), 1); + goto done; + } break; case NID_key_usage : + add_ext = true; saw_usage = true; + + if (datalen < 6 || datalen > 7 || data[2] != 0x03 || data[3] != (datalen - 4)) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad extKeyUsage extension in X.509 certificate request."), 1); + goto done; + } + + usage = 0; + if (data[5] & 0x80) + usage |= CUPS_CREDUSAGE_DIGITAL_SIGNATURE; + if (data[5] & 0x40) + usage |= CUPS_CREDUSAGE_NON_REPUDIATION; + if (data[5] & 0x20) + usage |= CUPS_CREDUSAGE_KEY_ENCIPHERMENT; + if (data[5] & 0x10) + usage |= CUPS_CREDUSAGE_DATA_ENCIPHERMENT; + if (data[5] & 0x08) + usage |= CUPS_CREDUSAGE_KEY_AGREEMENT; + if (data[5] & 0x04) + usage |= CUPS_CREDUSAGE_KEY_CERT_SIGN; + if (data[5] & 0x02) + usage |= CUPS_CREDUSAGE_CRL_SIGN; + if (data[5] & 0x01) + usage |= CUPS_CREDUSAGE_ENCIPHER_ONLY; + if (datalen == 7 && (data[6] & 0x80)) + usage |= CUPS_CREDUSAGE_DECIPHER_ONLY; + + DEBUG_printf(("1cupsSignCredentialsRequest: usage=0x%04x", usage)); + + if (usage & ~allowed_usage) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad extKeyUsage extension in X.509 certificate request."), 1); + goto done; + } break; case NID_subject_alt_name : + add_ext = true; saw_san = true; - // TODO: Use callback to validate subjectAltName values - break; - case NID_basic_constraints : - // Ignore basicConstraints in request... - continue; + if (datalen < 4 || data[2] != 0x30 || data[3] != (datalen - 4)) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad subjectAltName extension in X.509 certificate request."), 1); + goto done; + } + + // Parse the SAN values (there should be an easier/standard OpenSSL API to do this!) + for (j = 4, datalen -= 2; j < datalen; j += data[j + 1] + 2) + { + if (data[j] == 0x82 && data[j + 1]) + { + // GENERAL_STRING for DNS + memcpy(temp, data + j + 2, data[j + 1]); + temp[data[j + 1]] = '\0'; + + DEBUG_printf(("1cupsSignCredentialsRequest: SAN %s", temp)); + + if (!(cb)(common_name, temp, cb_data)) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Validation of subjectAltName in X.509 certificate request failed."), 1); + goto done; + } + } + } + break; } + OPENSSL_free(data); + // If we get this far, the object is OK and we can add it... - if (!X509_add_ext(cert, ext, -1)) + if (add_ext && !X509_add_ext(cert, ext, -1)) goto done; } @@ -642,6 +768,34 @@ cupsSignCredentialsRequest( goto done; } + // Add key usage extensions as needed... + if (!saw_usage) + { + if ((ext = X509V3_EXT_conf_nid(/*conf*/NULL, /*ctx*/NULL, NID_key_usage, "critical,digitalSignature,keyEncipherment")) == NULL || !X509_add_ext(cert, ext, -1)) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to add extension to X.509 certificate."), 1); + goto done; + } + } + + if (!saw_ext_usage) + { + if ((ext = X509V3_EXT_conf_nid(/*conf*/NULL, /*ctx*/NULL, NID_ext_key_usage, tls_usage_strings[0])) == NULL || !X509_add_ext(cert, ext, -1)) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to add extension to X.509 certificate."), 1); + goto done; + } + } + + if (!saw_san) + { + if ((ext = openssl_create_san(common_name, /*num_alt_names*/0, /*alt_names*/NULL)) == NULL || !X509_add_ext(cert, ext, -1)) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to add extension to X.509 certificate."), 1); + goto done; + } + } + // Try loading a root certificate... http_make_path(root_crtfile, sizeof(root_crtfile), path, root_name ? root_name : "_site_", "crt"); http_make_path(root_keyfile, sizeof(root_keyfile), path, root_name ? root_name : "_site_", "key"); @@ -1747,9 +1901,8 @@ openssl_create_name( // 'openssl_create_san()' - Create a list of subjectAltName values for a certificate/signing request. // -static bool // O - `true` on success, `false` otherwise +static X509_EXTENSION * // O - Extension openssl_create_san( - STACK_OF(X509_EXTENSION) *exts, // I - Extensions const char *common_name, // I - Common name size_t num_alt_names, // I - Number of alternate names const char * const *alt_names) // I - List of alternate names @@ -1788,7 +1941,7 @@ openssl_create_san( } // Return the stack - return (openssl_add_ext(exts, NID_subject_alt_name, temp)); + return (X509V3_EXT_conf_nid(/*conf*/NULL, /*ctx*/NULL, NID_subject_alt_name, temp)); } diff --git a/cups/tls.c b/cups/tls.c index 1ad53348e..bd84d2a37 100644 --- a/cups/tls.c +++ b/cups/tls.c @@ -47,6 +47,7 @@ static int tls_options = -1,// Options for TLS connections static char *http_copy_file(const char *path, const char *common_name, const char *ext); static const char *http_default_path(char *buffer, size_t bufsize); +static bool http_default_san_cb(const char *common_name, const char *subject_alt_name, void *data); static const char *http_make_path(char *buffer, size_t bufsize, const char *dirname, const char *filename, const char *ext); static bool http_save_file(const char *path, const char *common_name, const char *ext, const char *value); @@ -496,6 +497,30 @@ http_default_path( } +// +// 'http_default_san_cb()' - Validate a subjectAltName value. +// + +static bool // O - `true` if OK, `false` otherwise +http_default_san_cb( + const char *common_name, // I - Common name value + const char *subject_alt_name, // I - subjectAltName value + void *data) // I - Callback data (unused) +{ + size_t common_len; // Common name length + + + (void)data; + + if (!_cups_strcasecmp(subject_alt_name, common_name) || !_cups_strcasecmp(subject_alt_name, "localhost")) + return (true); + + common_len = strlen(common_name); + + return (!_cups_strncasecmp(subject_alt_name, common_name, common_len) && subject_alt_name[common_len] == '.'); +} + + // // 'http_make_path()' - Format a filename for a certificate or key file. // From e20bd45f388369d051270d961cb542c1347da726 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Wed, 31 May 2023 16:29:27 -0400 Subject: [PATCH 26/31] Save work on GNU TLS version of cupsSignCredentialsRequest. --- cups/debug.c | 26 ++-- cups/string.c | 40 +++--- cups/tls-gnutls.c | 317 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 350 insertions(+), 33 deletions(-) diff --git a/cups/debug.c b/cups/debug.c index 005ce8a3f..68466ac67 100644 --- a/cups/debug.c +++ b/cups/debug.c @@ -1,7 +1,7 @@ /* * Debugging functions for CUPS. * - * Copyright © 2022 by OpenPrinting. + * Copyright © 2022-2023 by OpenPrinting. * Copyright © 2008-2018 by Apple Inc. * * Licensed under Apache License v2.0. See the file "LICENSE" for more @@ -148,7 +148,6 @@ _cups_debug_printf(const char *format, /* I - Printf-style format string */ else if (buffer[bytes - 1] != '\n') { buffer[bytes++] = '\n'; - buffer[bytes] = '\0'; } /* @@ -381,7 +380,7 @@ _cups_safe_vsnprintf( if (*format == '%') { - if (bufptr < bufend) + if (bufptr && bufptr < bufend) *bufptr++ = *format; bytes ++; format ++; @@ -493,7 +492,7 @@ _cups_safe_vsnprintf( bytes += (int)strlen(temp); - if (bufptr) + if (bufptr && bufptr < bufend) { cupsCopyString(bufptr, temp, (size_t)(bufend - bufptr)); bufptr += strlen(bufptr); @@ -523,7 +522,7 @@ _cups_safe_vsnprintf( bytes += (int)strlen(temp); - if (bufptr) + if (bufptr && bufptr < bufend) { cupsCopyString(bufptr, temp, (size_t)(bufend - bufptr)); bufptr += strlen(bufptr); @@ -538,7 +537,7 @@ _cups_safe_vsnprintf( bytes += (int)strlen(temp); - if (bufptr) + if (bufptr && bufptr < bufend) { cupsCopyString(bufptr, temp, (size_t)(bufend - bufptr)); bufptr += strlen(bufptr); @@ -548,10 +547,12 @@ _cups_safe_vsnprintf( case 'c' : /* Character or character array */ bytes += width; - if (bufptr) + if (bufptr && bufptr < bufend) { if (width <= 1) + { *bufptr++ = (char)va_arg(ap, int); + } else { if ((bufptr + width) > bufend) @@ -567,6 +568,12 @@ _cups_safe_vsnprintf( if ((s = va_arg(ap, char *)) == NULL) s = "(null)"; + if (!bufptr) + { + bytes += 2 * strlen(s); + break; + } + /* * Copy the C string, replacing control chars and \ with * C character escapes... @@ -640,7 +647,7 @@ _cups_safe_vsnprintf( { bytes ++; - if (bufptr < bufend) + if (bufptr && bufptr < bufend) *bufptr++ = *format; format ++; @@ -651,7 +658,8 @@ _cups_safe_vsnprintf( * Nul-terminate the string and return the number of characters needed. */ - *bufptr = '\0'; + if (bufptr) + *bufptr = '\0'; return (bytes); } diff --git a/cups/string.c b/cups/string.c index c72346544..6e650ea62 100644 --- a/cups/string.c +++ b/cups/string.c @@ -1,7 +1,7 @@ /* * String functions for CUPS. * - * Copyright © 2022 by OpenPrinting. + * Copyright © 2022-2023 by OpenPrinting. * Copyright © 2007-2019 by Apple Inc. * Copyright © 1997-2007 by Easy Software Products. * @@ -141,6 +141,10 @@ cupsConcatString(char *dst, /* O - Destination string */ const char *src, /* I - Source string */ size_t dstsize) /* I - Size of destination string buffer */ { + // Range check input... + if (!dst || !src || dstsize == 0) + return (0); + #ifdef HAVE_STRLCAT return (strlcat(dst, src, dstsize)); @@ -149,10 +153,7 @@ cupsConcatString(char *dst, /* O - Destination string */ size_t dstlen; /* Length of destination string */ - /* - * Figure out how much room is left... - */ - + // Figure out how much room is left... dstlen = strlen(dst); if (dstsize < (dstlen + 1)) @@ -160,16 +161,10 @@ cupsConcatString(char *dst, /* O - Destination string */ dstsize -= dstlen + 1; - /* - * Figure out how much room is needed... - */ - + // Figure out how much room is needed... srclen = strlen(src); - /* - * Copy the appropriate amount... - */ - + // Copy the appropriate amount... if (srclen > dstsize) srclen = dstsize; @@ -177,7 +172,7 @@ cupsConcatString(char *dst, /* O - Destination string */ dst[dstlen + srclen] = '\0'; return (dstlen + srclen); -#endif /* HAVE_STRLCAT */ +#endif // HAVE_STRLCAT } @@ -190,25 +185,22 @@ cupsCopyString(char *dst, /* O - Destination string */ const char *src, /* I - Source string */ size_t dstsize) /* I - Size of destination string buffer */ { + // Range check input... + if (!dst || !src || dstsize == 0) + return (0); + #ifdef HAVE_STRLCPY return (strlcpy(dst, src, dstsize)); #else size_t srclen; /* Length of source string */ - - /* - * Figure out how much room is needed... - */ - + // Figure out how much room is needed... dstsize --; srclen = strlen(src); - /* - * Copy the appropriate amount... - */ - + // Copy the appropriate amount... if (srclen > dstsize) srclen = dstsize; @@ -216,7 +208,7 @@ cupsCopyString(char *dst, /* O - Destination string */ dst[srclen] = '\0'; return (srclen); -#endif /* HAVE_STRLCPY */ +#endif // HAVE_STRLCPY } diff --git a/cups/tls-gnutls.c b/cups/tls-gnutls.c index 1fd158c6f..ae1ccb19f 100644 --- a/cups/tls-gnutls.c +++ b/cups/tls-gnutls.c @@ -504,6 +504,323 @@ cupsCreateCredentialsRequest( } +// +// 'cupsSignCredentialsRequest()' - Sign an X.509 certificate signing request to produce an X.509 certificate chain. +// + +bool // O - `true` on success, `false` on failure +cupsSignCredentialsRequest( + const char *path, // I - Directory path for certificate/key store or `NULL` for default + const char *common_name, // I - Common name to use + const char *request, // I - PEM-encoded CSR + const char *root_name, // I - Root certificate + cups_credpurpose_t allowed_purpose, // I - Allowed credential purpose(s) + cups_credusage_t allowed_usage, // I - Allowed credential usage(s) + cups_cert_san_cb_t cb, // I - subjectAltName callback or `NULL` to allow just .local + void *cb_data, // I - Callback data + time_t expiration_date) // I - Certificate expiration date +{ + bool ret = false; // Return value + int i, // Looping var + err; // GNU TLS error code, if any + gnutls_x509_crq_t crq = NULL; // Certificate request + gnutls_x509_crt_t crt = NULL; // Certificate + gnutls_x509_crt_t root_crt = NULL;// Root certificate + gnutls_x509_privkey_t root_key = NULL;// Root private key + gnutls_datum_t datum; // Datum + char defpath[1024], // Default path + temp[1024], // Temporary string + crtfile[1024], // Certificate filename + *root_crtdata, // Root certificate data + *root_keydata; // Root private key data + size_t tempsize; // Size of temporary string + cups_credpurpose_t purpose; // Credential purpose(s) + unsigned gnutls_usage; // GNU TLS keyUsage bits + cups_credusage_t usage; // Credential usage(s) + cups_file_t *fp; // Key/cert file + unsigned char buffer[65536]; // Buffer for x509 data + size_t bytes; // Number of bytes of data + unsigned char serial[4]; // Serial number buffer + time_t curtime; // Current time + int result; // Result of GNU TLS calls + + + DEBUG_printf(("cupsSignCredentialsRequest(path=\"%s\", common_name=\"%s\", request=\"%s\", root_name=\"%s\", allowed_purpose=0x%x, allowed_usage=0x%x, cb=%p, cb_data=%p, expiration_date=%ld)", path, common_name, request, root_name, allowed_purpose, allowed_usage, cb, cb_data, (long)expiration_date)); + + // Filenames... + if (!path) + path = http_default_path(defpath, sizeof(defpath)); + + if (!path || !common_name || !request) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); + goto done; + } + + if (!cb) + cb = http_default_san_cb; + + // Import the request... + gnutls_x509_crq_init(&crq); + + datum.data = (unsigned char *)request; + datum.size = strlen(request); + + if ((err = gnutls_x509_crq_import(crq, &datum, GNUTLS_X509_FMT_PEM)) < 0) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(err), 0); + goto done; + } + + // Create the certificate... + DEBUG_puts("1cupsSignCredentialsRequest: Generating X.509 certificate."); + + curtime = time(NULL); + serial[0] = (unsigned char)(curtime >> 24); + serial[1] = (unsigned char)(curtime >> 16); + serial[2] = (unsigned char)(curtime >> 8); + serial[3] = (unsigned char)(curtime); + + gnutls_x509_crt_init(&crt); + + tempsize = sizeof(temp) - 1; + if (gnutls_x509_crq_get_dn_by_oid(crq, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, temp, &tempsize) >= 0) + temp[tempsize] = '\0'; + else + cupsCopyString(temp, "US", sizeof(temp)); + gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_COUNTRY_NAME, 0, temp, (unsigned)strlen(temp)); + + gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_COMMON_NAME, 0, common_name, strlen(common_name)); + + tempsize = sizeof(temp) - 1; + if (gnutls_x509_crq_get_dn_by_oid(crq, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, temp, &tempsize) >= 0) + { + temp[tempsize] = '\0'; + gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, temp, (unsigned)strlen(temp)); + } + + tempsize = sizeof(temp) - 1; + if (gnutls_x509_crq_get_dn_by_oid(crq, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, 0, temp, &tempsize) >= 0) + { + temp[tempsize] = '\0'; + gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, 0, temp, (unsigned)strlen(temp)); + } + + tempsize = sizeof(temp) - 1; + if (gnutls_x509_crq_get_dn_by_oid(crq, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, 0, temp, &tempsize) >= 0) + temp[tempsize] = '\0'; + else + cupsCopyString(temp, "Unknown", sizeof(temp)); + gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, 0, temp, strlen(temp)); + + tempsize = sizeof(temp) - 1; + if (gnutls_x509_crq_get_dn_by_oid(crq, GNUTLS_OID_X520_LOCALITY_NAME, 0, 0, temp, &tempsize) >= 0) + temp[tempsize] = '\0'; + else + cupsCopyString(temp, "Unknown", sizeof(temp)); + gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_LOCALITY_NAME, 0, temp, strlen(temp)); + +// gnutls_x509_crt_set_key(crt, key); + gnutls_x509_crt_set_serial(crt, serial, sizeof(serial)); + gnutls_x509_crt_set_activation_time(crt, curtime); + gnutls_x509_crt_set_expiration_time(crt, expiration_date); + gnutls_x509_crt_set_ca_status(crt, 0); + + for (i = 0; i < 100; i ++) + { + unsigned type; // Name type + + tempsize = sizeof(temp) - 1; + if (gnutls_x509_crq_get_subject_alt_name(crq, i, temp, &tempsize, &type, NULL) < 0) + break; + + temp[tempsize] = '\0'; + + DEBUG_printf(("1cupsSignCredentialsRequest: SAN %s", temp)); + + if (type != GNUTLS_SAN_DNSNAME || (cb)(common_name, temp, cb_data)) + { + // Good subjectAltName + gnutls_x509_crt_set_subject_alt_name(crt, type, temp, (unsigned)strlen(temp), i ? GNUTLS_FSAN_APPEND : GNUTLS_FSAN_SET); + } + else + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Validation of subjectAltName in X.509 certificate request failed."), 1); + goto done; + } + } + + for (purpose = 0, i = 0; i < 100; i ++) + { + tempsize = sizeof(temp) - 1; + if (gnutls_x509_crq_get_key_purpose_oid(crq, i, temp, &tempsize, NULL) < 0) + break; + temp[tempsize] = '\0'; + + if (!strcmp(temp, GNUTLS_KP_TLS_WWW_SERVER)) + purpose |= CUPS_CREDPURPOSE_SERVER_AUTH; + if (!strcmp(temp, GNUTLS_KP_TLS_WWW_CLIENT)) + purpose |= CUPS_CREDPURPOSE_CLIENT_AUTH; + if (!strcmp(temp, GNUTLS_KP_CODE_SIGNING)) + purpose |= CUPS_CREDPURPOSE_CODE_SIGNING; + if (!strcmp(temp, GNUTLS_KP_EMAIL_PROTECTION)) + purpose |= CUPS_CREDPURPOSE_EMAIL_PROTECTION; + if (!strcmp(temp, GNUTLS_KP_OCSP_SIGNING)) + purpose |= CUPS_CREDPURPOSE_OCSP_SIGNING; + } + DEBUG_printf(("1cupsSignCredentialsRequest: purpose=0x%04x", purpose)); + + if (purpose & ~allowed_purpose) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad keyUsage extension in X.509 certificate request."), 1); + goto done; + } + + if (purpose == 0 || (purpose & CUPS_CREDPURPOSE_SERVER_AUTH)) + gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_TLS_WWW_SERVER, 0); + if (purpose & CUPS_CREDPURPOSE_CLIENT_AUTH) + gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_TLS_WWW_CLIENT, 0); + if (purpose & CUPS_CREDPURPOSE_CODE_SIGNING) + gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_CODE_SIGNING, 0); + if (purpose & CUPS_CREDPURPOSE_EMAIL_PROTECTION) + gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_EMAIL_PROTECTION, 0); + if (purpose & CUPS_CREDPURPOSE_OCSP_SIGNING) + gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_OCSP_SIGNING, 0); + + if (gnutls_x509_crq_get_key_usage(crq, &gnutls_usage, NULL) < 0) + { + // No keyUsage, use default for TLS... + gnutls_usage = GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT; + } + else + { + // Got keyUsage, convert to CUPS bitfield + usage = 0; + if (gnutls_usage & GNUTLS_KEY_DIGITAL_SIGNATURE) + usage |= CUPS_CREDUSAGE_DIGITAL_SIGNATURE; + if (gnutls_usage & GNUTLS_KEY_NON_REPUDIATION) + usage |= CUPS_CREDUSAGE_NON_REPUDIATION; + if (gnutls_usage & GNUTLS_KEY_KEY_ENCIPHERMENT) + usage |= CUPS_CREDUSAGE_KEY_ENCIPHERMENT; + if (gnutls_usage & GNUTLS_KEY_DATA_ENCIPHERMENT) + usage |= CUPS_CREDUSAGE_DATA_ENCIPHERMENT; + if (gnutls_usage & GNUTLS_KEY_KEY_AGREEMENT) + usage |= CUPS_CREDUSAGE_KEY_AGREEMENT; + if (gnutls_usage & GNUTLS_KEY_KEY_CERT_SIGN) + usage |= CUPS_CREDUSAGE_KEY_CERT_SIGN; + if (gnutls_usage & GNUTLS_KEY_CRL_SIGN) + usage |= CUPS_CREDUSAGE_CRL_SIGN; + if (gnutls_usage & GNUTLS_KEY_ENCIPHER_ONLY) + usage |= CUPS_CREDUSAGE_ENCIPHER_ONLY; + if (gnutls_usage & GNUTLS_KEY_DECIPHER_ONLY) + usage |= CUPS_CREDUSAGE_DECIPHER_ONLY; + + DEBUG_printf(("1cupsSignCredentialsRequest: usage=0x%04x", usage)); + + if (usage & ~allowed_usage) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad extKeyUsage extension in X.509 certificate request."), 1); + goto done; + } + } + gnutls_x509_crt_set_key_usage(crt, gnutls_usage); + + gnutls_x509_crt_set_version(crt, 3); + + bytes = sizeof(buffer); + if (gnutls_x509_crt_get_key_id(crt, 0, buffer, &bytes) >= 0) + gnutls_x509_crt_set_subject_key_id(crt, buffer, bytes); + + // Try loading a root certificate... + root_crtdata = cupsCopyCredentials(path, root_name ? root_name : "_site_"); + root_keydata = cupsCopyCredentialsKey(path, root_name ? root_name : "_site_"); + + if (root_crtdata && root_keydata) + { + // Load root certificate... + datum.data = (unsigned char *)root_crtdata; + datum.size = strlen(root_crtdata); + + gnutls_x509_crt_init(&root_crt); + if (gnutls_x509_crt_import(root_crt, &datum, GNUTLS_X509_FMT_PEM) < 0) + { + // No good, clear it... + gnutls_x509_crt_deinit(root_crt); + root_crt = NULL; + } + else + { + // Load root private key... + datum.data = (unsigned char *)root_keydata; + datum.size = strlen(root_keydata); + + gnutls_x509_privkey_init(&root_key); + if (gnutls_x509_privkey_import(root_key, &datum, GNUTLS_X509_FMT_PEM) < 0) + { + // No food, clear them... + gnutls_x509_privkey_deinit(root_key); + root_key = NULL; + + gnutls_x509_crt_deinit(root_crt); + root_crt = NULL; + } + } + } + + free(root_crtdata); + free(root_keydata); + + if (!root_crt || !root_key) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to load X.509 CA certificate and private key."), 1); + goto done; + } + + gnutls_x509_crt_sign(crt, root_crt, root_key); + + // Save it... + http_make_path(crtfile, sizeof(crtfile), path, common_name, "crt"); + + bytes = sizeof(buffer); + if ((result = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) + { + DEBUG_printf(("1cupsSignCredentialsRequest: Unable to export public key and X.509 certificate: %s", gnutls_strerror(result))); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); + goto done; + } + else if ((fp = cupsFileOpen(crtfile, "w")) != NULL) + { + DEBUG_printf(("1cupsSignCredentialsRequest: Writing public key and X.509 certificate to \"%s\".", crtfile)); + cupsFileWrite(fp, (char *)buffer, bytes); + cupsFileClose(fp); + } + else + { + DEBUG_printf(("1cupsSignCredentialsRequest: Unable to create public key and X.509 certificate file \"%s\": %s", crtfile, strerror(errno))); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); + goto done; + } + + DEBUG_puts("1cupsSignCredentialsRequest: Successfully created credentials."); + + ret = true; + + // Cleanup... + done: + + if (crq) + gnutls_x509_crq_deinit(crq); + if (crt) + gnutls_x509_crt_deinit(crt); + if (root_crt) + gnutls_x509_crt_deinit(root_crt); + if (root_key) + gnutls_x509_privkey_deinit(root_key); + + return (ret); +} + + // // 'httpCopyCredentials()' - Copy the credentials associated with the peer in // an encrypted connection. From 20cc44b7411d5b0e3e64cba881638a5161a45e65 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Wed, 31 May 2023 19:58:37 -0400 Subject: [PATCH 27/31] Finalize GNU TLS version of cupsSignCredentialsRequest. --- cups/tls-gnutls.c | 53 +++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/cups/tls-gnutls.c b/cups/tls-gnutls.c index ae1ccb19f..c72a48e9f 100644 --- a/cups/tls-gnutls.c +++ b/cups/tls-gnutls.c @@ -76,7 +76,7 @@ cupsCreateCredentials( size_t bytes; // Number of bytes of data unsigned char serial[4]; // Serial number buffer time_t curtime; // Current time - int result; // Result of GNU TLS calls + int err; // Result of GNU TLS calls DEBUG_printf(("cupsCreateCredentials(path=\"%s\", ca_cert=%s, purpose=0x%x, type=%d, usage=0x%x, organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", common_name=\"%s\", num_alt_names=%u, alt_names=%p, root_name=\"%s\", expiration_date=%ld)", path, ca_cert ? "true" : "false", purpose, type, usage, organization, org_unit, locality, state_province, country, common_name, (unsigned)num_alt_names, alt_names, root_name, (long)expiration_date)); @@ -104,10 +104,10 @@ cupsCreateCredentials( // Save it... bytes = sizeof(buffer); - if ((result = gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) + if ((err = gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) { - DEBUG_printf(("1cupsCreateCredentials: Unable to export private key: %s", gnutls_strerror(result))); - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); + DEBUG_printf(("1cupsCreateCredentials: Unable to export private key: %s", gnutls_strerror(err))); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(err), 0); goto done; } else if ((fp = cupsFileOpen(keyfile, "w")) != NULL) @@ -272,10 +272,10 @@ cupsCreateCredentials( // Save it... bytes = sizeof(buffer); - if ((result = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) + if ((err = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) { - DEBUG_printf(("1cupsCreateCredentials: Unable to export public key and X.509 certificate: %s", gnutls_strerror(result))); - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); + DEBUG_printf(("1cupsCreateCredentials: Unable to export public key and X.509 certificate: %s", gnutls_strerror(err))); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(err), 0); goto done; } else if ((fp = cupsFileOpen(crtfile, "w")) != NULL) @@ -341,7 +341,7 @@ cupsCreateCredentialsRequest( cups_file_t *fp; // Key/cert file unsigned char buffer[8192]; // Buffer for key/cert data size_t bytes; // Number of bytes of data - int result; // GNU TLS status + int err; // GNU TLS status DEBUG_printf(("cupsCreateCredentialsRequest(path=\"%s\", purpose=0x%x, type=%d, usage=0x%x, organization=\"%s\", org_unit=\"%s\", locality=\"%s\", state_province=\"%s\", country=\"%s\", common_name=\"%s\", num_alt_names=%u, alt_names=%p)", path, purpose, type, usage, organization, org_unit, locality, state_province, country, common_name, (unsigned)num_alt_names, alt_names)); @@ -369,9 +369,9 @@ cupsCreateCredentialsRequest( // Save it... bytes = sizeof(buffer); - if ((result = gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) + if ((err = gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) { - DEBUG_printf(("1cupsCreateCredentialsRequest: Unable to export private key: %s", gnutls_strerror(result))); + DEBUG_printf(("1cupsCreateCredentialsRequest: Unable to export private key: %s", gnutls_strerror(err))); goto done; } else if ((fp = cupsFileOpen(keyfile, "w")) != NULL) @@ -469,10 +469,10 @@ cupsCreateCredentialsRequest( // Save it... bytes = sizeof(buffer); - if ((result = gnutls_x509_crq_export(crq, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) + if ((err = gnutls_x509_crq_export(crq, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) { - DEBUG_printf(("1cupsCreateCredentialsRequest: Unable to export public key and X.509 certificate request: %s", gnutls_strerror(result))); - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); + DEBUG_printf(("1cupsCreateCredentialsRequest: Unable to export public key and X.509 certificate request: %s", gnutls_strerror(err))); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(err), 0); goto done; } else if ((fp = cupsFileOpen(csrfile, "w")) != NULL) @@ -542,7 +542,6 @@ cupsSignCredentialsRequest( size_t bytes; // Number of bytes of data unsigned char serial[4]; // Serial number buffer time_t curtime; // Current time - int result; // Result of GNU TLS calls DEBUG_printf(("cupsSignCredentialsRequest(path=\"%s\", common_name=\"%s\", request=\"%s\", root_name=\"%s\", allowed_purpose=0x%x, allowed_usage=0x%x, cb=%p, cb_data=%p, expiration_date=%ld)", path, common_name, request, root_name, allowed_purpose, allowed_usage, cb, cb_data, (long)expiration_date)); @@ -582,7 +581,9 @@ cupsSignCredentialsRequest( serial[3] = (unsigned char)(curtime); gnutls_x509_crt_init(&crt); + gnutls_x509_crt_set_crq(crt, crq); +#if 0 tempsize = sizeof(temp) - 1; if (gnutls_x509_crq_get_dn_by_oid(crq, GNUTLS_OID_X520_COUNTRY_NAME, 0, 0, temp, &tempsize) >= 0) temp[tempsize] = '\0'; @@ -619,8 +620,8 @@ cupsSignCredentialsRequest( else cupsCopyString(temp, "Unknown", sizeof(temp)); gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_LOCALITY_NAME, 0, temp, strlen(temp)); +#endif // 0 -// gnutls_x509_crt_set_key(crt, key); gnutls_x509_crt_set_serial(crt, serial, sizeof(serial)); gnutls_x509_crt_set_activation_time(crt, curtime); gnutls_x509_crt_set_expiration_time(crt, expiration_date); @@ -641,7 +642,7 @@ cupsSignCredentialsRequest( if (type != GNUTLS_SAN_DNSNAME || (cb)(common_name, temp, cb_data)) { // Good subjectAltName - gnutls_x509_crt_set_subject_alt_name(crt, type, temp, (unsigned)strlen(temp), i ? GNUTLS_FSAN_APPEND : GNUTLS_FSAN_SET); +// gnutls_x509_crt_set_subject_alt_name(crt, type, temp, (unsigned)strlen(temp), i ? GNUTLS_FSAN_APPEND : GNUTLS_FSAN_SET); } else { @@ -676,6 +677,7 @@ cupsSignCredentialsRequest( goto done; } +#if 0 if (purpose == 0 || (purpose & CUPS_CREDPURPOSE_SERVER_AUTH)) gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_TLS_WWW_SERVER, 0); if (purpose & CUPS_CREDPURPOSE_CLIENT_AUTH) @@ -686,6 +688,7 @@ cupsSignCredentialsRequest( gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_EMAIL_PROTECTION, 0); if (purpose & CUPS_CREDPURPOSE_OCSP_SIGNING) gnutls_x509_crt_set_key_purpose_oid(crt, GNUTLS_KP_OCSP_SIGNING, 0); +#endif // 0 if (gnutls_x509_crq_get_key_usage(crq, &gnutls_usage, NULL) < 0) { @@ -723,7 +726,7 @@ cupsSignCredentialsRequest( goto done; } } - gnutls_x509_crt_set_key_usage(crt, gnutls_usage); +// gnutls_x509_crt_set_key_usage(crt, gnutls_usage); gnutls_x509_crt_set_version(crt, 3); @@ -782,10 +785,10 @@ cupsSignCredentialsRequest( http_make_path(crtfile, sizeof(crtfile), path, common_name, "crt"); bytes = sizeof(buffer); - if ((result = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) + if ((err = gnutls_x509_crt_export(crt, GNUTLS_X509_FMT_PEM, buffer, &bytes)) < 0) { - DEBUG_printf(("1cupsSignCredentialsRequest: Unable to export public key and X.509 certificate: %s", gnutls_strerror(result))); - _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(result), 0); + DEBUG_printf(("1cupsSignCredentialsRequest: Unable to export public key and X.509 certificate: %s", gnutls_strerror(err))); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, gnutls_strerror(err), 0); goto done; } else if ((fp = cupsFileOpen(crtfile, "w")) != NULL) @@ -1186,7 +1189,7 @@ static gnutls_x509_crt_t // O - Certificate gnutls_create_credential( http_credential_t *credential) // I - Credential { - int result; // Result from GNU TLS + int err; // Result from GNU TLS gnutls_x509_crt_t cert; // Certificate gnutls_datum_t datum; // Data record @@ -1196,18 +1199,18 @@ gnutls_create_credential( if (!credential) return (NULL); - if ((result = gnutls_x509_crt_init(&cert)) < 0) + if ((err = gnutls_x509_crt_init(&cert)) < 0) { - DEBUG_printf(("4gnutls_create_credential: init error: %s", gnutls_strerror(result))); + DEBUG_printf(("4gnutls_create_credential: init error: %s", gnutls_strerror(err))); return (NULL); } datum.data = credential->data; datum.size = credential->datalen; - if ((result = gnutls_x509_crt_import(cert, &datum, GNUTLS_X509_FMT_DER)) < 0) + if ((err = gnutls_x509_crt_import(cert, &datum, GNUTLS_X509_FMT_DER)) < 0) { - DEBUG_printf(("4gnutls_create_credential: import error: %s", gnutls_strerror(result))); + DEBUG_printf(("4gnutls_create_credential: import error: %s", gnutls_strerror(err))); gnutls_x509_crt_deinit(cert); return (NULL); From 5ea07175b1e3e72a2d449e53f031d3add717a336 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Wed, 31 May 2023 20:20:13 -0400 Subject: [PATCH 28/31] Add testcreds to test suite. --- cups/Makefile | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cups/Makefile b/cups/Makefile index 9d44aa5d3..d54962834 100644 --- a/cups/Makefile +++ b/cups/Makefile @@ -188,28 +188,43 @@ test: $(UNITTARGETS) # echo Running CUPS API tests...; \ # ./testcups 2>>test.log; \ # fi + echo "" + echo Running credentials API tests... + ./testcreds 2>>test.log + echo "" echo Running file API tests... ./testfile 2>>test.log + echo "" echo Running form API tests... ./testform 2>>test.log +# echo "" # echo Running cupsGetDests API tests... # ./testgetdests 2>>test.log + echo "" echo Running hash API tests... ./testhash 2>>test.log + echo "" echo Running HTTP API tests... ./testhttp 2>>test.log + echo "" echo Running IPP API tests... ./testipp 2>>test.log + echo "" echo Running internationalization API tests... ./testi18n 2>>test.log + echo "" echo Running JSON API tests... ./testjson 2>>test.log + echo "" echo Running JWT API tests... ./testjwt 2>>test.log + echo "" echo Running option API tests... ./testoptions 2>>test.log + echo "" echo Running PWG API tests... ./testpwg 2>>test.log + echo "" echo Running raster API tests... ./testraster 2>>test.log ./testtestpage 2>>test.log @@ -217,6 +232,7 @@ test: $(UNITTARGETS) fuzz: $(UNITTARGETS) echo Fuzzing IPP API... ./fuzzipp 2>fuzz.log + echo "" echo Fuzzing JSON API... AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=yes AFL_NO_UI=yes afl-fuzz -n -i json-afl-in -o json-afl-out -V 120 -- ./testjson 2>>fuzz.log From 91951443fa420282b3305964a59fefbb9b20d9df Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Wed, 31 May 2023 20:20:24 -0400 Subject: [PATCH 29/31] Update to use C99 comments. --- cups/debug.c | 306 +++++++++++++++-------------------- cups/string.c | 429 ++++++++++++++++++++++---------------------------- 2 files changed, 319 insertions(+), 416 deletions(-) diff --git a/cups/debug.c b/cups/debug.c index 68466ac67..4b18b367b 100644 --- a/cups/debug.c +++ b/cups/debug.c @@ -1,16 +1,16 @@ -/* - * Debugging functions for CUPS. - * - * Copyright © 2022-2023 by OpenPrinting. - * Copyright © 2008-2018 by Apple Inc. - * - * Licensed under Apache License v2.0. See the file "LICENSE" for more - * information. - */ - -/* - * Include necessary headers... - */ +// +// Debugging functions for CUPS. +// +// Copyright © 2022-2023 by OpenPrinting. +// Copyright © 2008-2018 by Apple Inc. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +// +// Include necessary headers... +// #include "cups-private.h" #include "debug-internal.h" @@ -20,11 +20,11 @@ # include # include # define getpid (int)GetCurrentProcessId -int /* O - 0 on success, -1 on failure */ -_cups_gettimeofday(struct timeval *tv, /* I - Timeval struct */ - void *tz) /* I - Timezone */ +int // O - 0 on success, -1 on failure +_cups_gettimeofday(struct timeval *tv, // I - Timeval struct + void *tz) // I - Timezone { - struct _timeb timebuffer; /* Time buffer struct */ + struct _timeb timebuffer; // Time buffer struct _ftime(&timebuffer); tv->tv_sec = (long)timebuffer.time; tv->tv_usec = timebuffer.millitm * 1000; @@ -33,79 +33,72 @@ _cups_gettimeofday(struct timeval *tv, /* I - Timeval struct */ #else # include # include -#endif /* _WIN32 */ +#endif // _WIN32 #include #include #ifdef DEBUG -/* - * Globals... - */ +// +// Globals... +// int _cups_debug_fd = -1; - /* Debug log file descriptor */ + // Debug log file descriptor int _cups_debug_level = 1; - /* Log level (0 to 9) */ + // Log level (0 to 9) -/* - * Local globals... - */ +// +// Local globals... +// static regex_t *debug_filter = NULL; - /* Filter expression for messages */ -static int debug_init = 0; /* Did we initialize debugging? */ + // Filter expression for messages +static int debug_init = 0; // Did we initialize debugging? static cups_mutex_t debug_init_mutex = CUPS_MUTEX_INITIALIZER, - /* Mutex to control initialization */ + // Mutex to control initialization debug_log_mutex = CUPS_MUTEX_INITIALIZER; - /* Mutex to serialize log entries */ + // Mutex to serialize log entries -/* - * 'debug_thread_id()' - Return an integer representing the current thread. - */ +// +// 'debug_thread_id()' - Return an integer representing the current thread. +// -static int /* O - Local thread ID */ +static int // O - Local thread ID debug_thread_id(void) { - _cups_globals_t *cg = _cupsGlobals(); /* Global data */ + _cups_globals_t *cg = _cupsGlobals(); // Global data return (cg->thread_id); } -/* - * '_cups_debug_printf()' - Write a formatted line to the log. - */ +// +// '_cups_debug_printf()' - Write a formatted line to the log. +// void -_cups_debug_printf(const char *format, /* I - Printf-style format string */ - ...) /* I - Additional arguments as needed */ +_cups_debug_printf(const char *format, // I - Printf-style format string + ...) // I - Additional arguments as needed { - va_list ap; /* Pointer to arguments */ - struct timeval curtime; /* Current time */ - char buffer[2048]; /* Output buffer */ - ssize_t bytes; /* Number of bytes in buffer */ - int level; /* Log level in message */ + va_list ap; // Pointer to arguments + struct timeval curtime; // Current time + char buffer[2048]; // Output buffer + ssize_t bytes; // Number of bytes in buffer + int level; // Log level in message - /* - * See if we need to do any logging... - */ - + // See if we need to do any logging... if (!debug_init) - _cups_debug_set(getenv("CUPS_DEBUG_LOG"), getenv("CUPS_DEBUG_LEVEL"), - getenv("CUPS_DEBUG_FILTER"), 0); + _cups_debug_set(getenv("CUPS_DEBUG_LOG"), getenv("CUPS_DEBUG_LEVEL"), getenv("CUPS_DEBUG_FILTER"), 0); if (_cups_debug_fd < 0) return; - /* - * Filter as needed... - */ - + // Filter as needed... if (isdigit(format[0])) level = *format++ - '0'; else @@ -116,7 +109,7 @@ _cups_debug_printf(const char *format, /* I - Printf-style format string */ if (debug_filter) { - int result; /* Filter result */ + int result; // Filter result cupsMutexLock(&debug_init_mutex); result = regexec(debug_filter, format, 0, NULL, 0); @@ -126,15 +119,9 @@ _cups_debug_printf(const char *format, /* I - Printf-style format string */ return; } - /* - * Format the message... - */ - + // Format the message... gettimeofday(&curtime, NULL); - snprintf(buffer, sizeof(buffer), "T%03d %02d:%02d:%02d.%03d ", - debug_thread_id(), (int)((curtime.tv_sec / 3600) % 24), - (int)((curtime.tv_sec / 60) % 60), - (int)(curtime.tv_sec % 60), (int)(curtime.tv_usec / 1000)); + snprintf(buffer, sizeof(buffer), "T%03d %02d:%02d:%02d.%03d ", debug_thread_id(), (int)((curtime.tv_sec / 3600) % 24), (int)((curtime.tv_sec / 60) % 60), (int)(curtime.tv_sec % 60), (int)(curtime.tv_usec / 1000)); va_start(ap, format); bytes = _cups_safe_vsnprintf(buffer + 19, sizeof(buffer) - 20, format, ap) + 19; @@ -150,44 +137,34 @@ _cups_debug_printf(const char *format, /* I - Printf-style format string */ buffer[bytes++] = '\n'; } - /* - * Write it out... - */ - + // Write it out... cupsMutexLock(&debug_log_mutex); write(_cups_debug_fd, buffer, (size_t)bytes); cupsMutexUnlock(&debug_log_mutex); } -/* - * '_cups_debug_puts()' - Write a single line to the log. - */ +// +// '_cups_debug_puts()' - Write a single line to the log. +// void -_cups_debug_puts(const char *s) /* I - String to output */ +_cups_debug_puts(const char *s) // I - String to output { - struct timeval curtime; /* Current time */ - char buffer[2048]; /* Output buffer */ - ssize_t bytes; /* Number of bytes in buffer */ - int level; /* Log level in message */ + struct timeval curtime; // Current time + char buffer[2048]; // Output buffer + ssize_t bytes; // Number of bytes in buffer + int level; // Log level in message - /* - * See if we need to do any logging... - */ - + // See if we need to do any logging... if (!debug_init) - _cups_debug_set(getenv("CUPS_DEBUG_LOG"), getenv("CUPS_DEBUG_LEVEL"), - getenv("CUPS_DEBUG_FILTER"), 0); + _cups_debug_set(getenv("CUPS_DEBUG_LOG"), getenv("CUPS_DEBUG_LEVEL"), getenv("CUPS_DEBUG_FILTER"), 0); if (_cups_debug_fd < 0) return; - /* - * Filter as needed... - */ - + // Filter as needed... if (isdigit(s[0])) level = *s++ - '0'; else @@ -198,7 +175,7 @@ _cups_debug_puts(const char *s) /* I - String to output */ if (debug_filter) { - int result; /* Filter result */ + int result; // Filter result cupsMutexLock(&debug_init_mutex); result = regexec(debug_filter, s, 0, NULL, 0); @@ -208,16 +185,9 @@ _cups_debug_puts(const char *s) /* I - String to output */ return; } - /* - * Format the message... - */ - + // Format the message... gettimeofday(&curtime, NULL); - bytes = snprintf(buffer, sizeof(buffer), "T%03d %02d:%02d:%02d.%03d %s", - debug_thread_id(), (int)((curtime.tv_sec / 3600) % 24), - (int)((curtime.tv_sec / 60) % 60), - (int)(curtime.tv_sec % 60), (int)(curtime.tv_usec / 1000), - s); + bytes = snprintf(buffer, sizeof(buffer), "T%03d %02d:%02d:%02d.%03d %s", debug_thread_id(), (int)((curtime.tv_sec / 3600) % 24), (int)((curtime.tv_sec / 60) % 60), (int)(curtime.tv_sec % 60), (int)(curtime.tv_usec / 1000), s); if ((size_t)bytes >= (sizeof(buffer) - 1)) { @@ -227,37 +197,30 @@ _cups_debug_puts(const char *s) /* I - String to output */ else if (buffer[bytes - 1] != '\n') { buffer[bytes++] = '\n'; - buffer[bytes] = '\0'; } - /* - * Write it out... - */ - + // Write it out... cupsMutexLock(&debug_log_mutex); write(_cups_debug_fd, buffer, (size_t)bytes); cupsMutexUnlock(&debug_log_mutex); } -/* - * '_cups_debug_set()' - Enable or disable debug logging. - */ +// +// '_cups_debug_set()' - Enable or disable debug logging. +// void -_cups_debug_set(const char *logfile, /* I - Log file or NULL */ - const char *level, /* I - Log level or NULL */ - const char *filter, /* I - Filter string or NULL */ - int force) /* I - Force initialization */ +_cups_debug_set(const char *logfile, // I - Log file or NULL + const char *level, // I - Log level or NULL + const char *filter, // I - Filter string or NULL + int force) // I - Force initialization { cupsMutexLock(&debug_init_mutex); if (!debug_init || force) { - /* - * Restore debug settings to defaults... - */ - + // Restore debug settings to defaults... if (_cups_debug_fd != -1) { close(_cups_debug_fd); @@ -272,17 +235,18 @@ _cups_debug_set(const char *logfile, /* I - Log file or NULL */ _cups_debug_level = 1; - /* - * Open logs, set log levels, etc. - */ - + // Open logs, set log levels, etc. if (!logfile) + { _cups_debug_fd = -1; + } else if (!strcmp(logfile, "-")) + { _cups_debug_fd = 2; + } else { - char buffer[1024]; /* Filename buffer */ + char buffer[1024]; // Filename buffer snprintf(buffer, sizeof(buffer), logfile, getpid()); @@ -298,12 +262,12 @@ _cups_debug_set(const char *logfile, /* I - Log file or NULL */ if (filter) { if ((debug_filter = (regex_t *)calloc(1, sizeof(regex_t))) == NULL) - fputs("Unable to allocate memory for CUPS_DEBUG_FILTER - results not " - "filtered!\n", stderr); + { + fputs("Unable to allocate memory for CUPS_DEBUG_FILTER - results not filtered.\n", stderr); + } else if (regcomp(debug_filter, filter, REG_EXTENDED)) { - fputs("Bad regular expression in CUPS_DEBUG_FILTER - results not " - "filtered!\n", stderr); + fputs("Bad regular expression in CUPS_DEBUG_FILTER - results not filtered.\n", stderr); free(debug_filter); debug_filter = NULL; } @@ -317,56 +281,53 @@ _cups_debug_set(const char *logfile, /* I - Log file or NULL */ #else -/* - * '_cups_debug_set()' - Enable or disable debug logging. - */ +// +// '_cups_debug_set()' - Enable or disable debug logging. +// void -_cups_debug_set(const char *logfile, /* I - Log file or NULL */ - const char *level, /* I - Log level or NULL */ - const char *filter, /* I - Filter string or NULL */ - int force) /* I - Force initialization */ +_cups_debug_set(const char *logfile, // I - Log file or NULL + const char *level, // I - Log level or NULL + const char *filter, // I - Filter string or NULL + int force) // I - Force initialization { (void)logfile; (void)level; (void)filter; (void)force; } -#endif /* DEBUG */ +#endif // DEBUG -/* - * '_cups_safe_vsnprintf()' - Format a string into a fixed size buffer, - * quoting special characters. - */ +// +// '_cups_safe_vsnprintf()' - Format a string into a fixed size buffer, +// quoting special characters. +// -ssize_t /* O - Number of bytes formatted */ +ssize_t // O - Number of bytes formatted _cups_safe_vsnprintf( - char *buffer, /* O - Output buffer */ - size_t bufsize, /* O - Size of output buffer */ - const char *format, /* I - printf-style format string */ - va_list ap) /* I - Pointer to additional arguments */ + char *buffer, // O - Output buffer + size_t bufsize, // O - Size of output buffer + const char *format, // I - printf-style format string + va_list ap) // I - Pointer to additional arguments { - char *bufptr, /* Pointer to position in buffer */ - *bufend, /* Pointer to end of buffer */ - size, /* Size character (h, l, L) */ - type; /* Format type character */ - int width, /* Width of field */ - prec; /* Number of characters of precision */ - char tformat[100], /* Temporary format string for snprintf() */ - *tptr, /* Pointer into temporary format */ - temp[1024]; /* Buffer for formatted numbers */ - char *s; /* Pointer to string */ - ssize_t bytes; /* Total number of bytes needed */ + char *bufptr, // Pointer to position in buffer + *bufend, // Pointer to end of buffer + size, // Size character (h, l, L) + type; // Format type character + int width, // Width of field + prec; // Number of characters of precision + char tformat[100], // Temporary format string for snprintf() + *tptr, // Pointer into temporary format + temp[1024]; // Buffer for formatted numbers + char *s; // Pointer to string + ssize_t bytes; // Total number of bytes needed if (!buffer || bufsize < 2 || !format) return (-1); - /* - * Loop through the format string, formatting as needed... - */ - + // Loop through the format string, formatting as needed... bufptr = buffer; bufend = buffer + bufsize - 1; bytes = 0; @@ -387,14 +348,13 @@ _cups_safe_vsnprintf( continue; } else if (strchr(" -+#\'", *format)) + { *tptr++ = *format++; + } if (*format == '*') { - /* - * Get width from argument... - */ - + // Get width from argument... format ++; width = va_arg(ap, int); @@ -423,10 +383,7 @@ _cups_safe_vsnprintf( if (*format == '*') { - /* - * Get precision from argument... - */ - + // Get precision from argument... format ++; prec = va_arg(ap, int); @@ -467,7 +424,9 @@ _cups_safe_vsnprintf( size = *format++; } else + { size = 0; + } if (!*format) break; @@ -480,7 +439,7 @@ _cups_safe_vsnprintf( switch (type) { - case 'E' : /* Floating point formats */ + case 'E' : // Floating point formats case 'G' : case 'e' : case 'f' : @@ -499,7 +458,7 @@ _cups_safe_vsnprintf( } break; - case 'B' : /* Integer formats */ + case 'B' : // Integer formats case 'X' : case 'b' : case 'd' : @@ -514,7 +473,7 @@ _cups_safe_vsnprintf( if (size == 'L') snprintf(temp, sizeof(temp), tformat, va_arg(ap, long long)); else -# endif /* HAVE_LONG_LONG */ +# endif // HAVE_LONG_LONG if (size == 'l') snprintf(temp, sizeof(temp), tformat, va_arg(ap, long)); else @@ -529,7 +488,7 @@ _cups_safe_vsnprintf( } break; - case 'p' : /* Pointer value */ + case 'p' : // Pointer value if ((size_t)(width + 2) > sizeof(temp)) break; @@ -544,7 +503,7 @@ _cups_safe_vsnprintf( } break; - case 'c' : /* Character or character array */ + case 'c' : // Character or character array bytes += width; if (bufptr && bufptr < bufend) @@ -564,7 +523,7 @@ _cups_safe_vsnprintf( } break; - case 's' : /* String */ + case 's' : // String if ((s = va_arg(ap, char *)) == NULL) s = "(null)"; @@ -574,11 +533,7 @@ _cups_safe_vsnprintf( break; } - /* - * Copy the C string, replacing control chars and \ with - * C character escapes... - */ - + // Copy the C string, replacing control chars and \ with C character escapes... for (bufend --; *s && bufptr < bufend; s ++) { if (*s == '\n') @@ -638,7 +593,7 @@ _cups_safe_vsnprintf( bufend ++; break; - case 'n' : /* Output number of chars so far */ + case 'n' : // Output number of chars so far *(va_arg(ap, int *)) = (int)bytes; break; } @@ -654,10 +609,7 @@ _cups_safe_vsnprintf( } } - /* - * Nul-terminate the string and return the number of characters needed. - */ - + // Nul-terminate the string and return the number of characters needed. if (bufptr) *bufptr = '\0'; diff --git a/cups/string.c b/cups/string.c index 6e650ea62..02c9b2c4b 100644 --- a/cups/string.c +++ b/cups/string.c @@ -1,17 +1,17 @@ -/* - * String functions for CUPS. - * - * Copyright © 2022-2023 by OpenPrinting. - * Copyright © 2007-2019 by Apple Inc. - * Copyright © 1997-2007 by Easy Software Products. - * - * Licensed under Apache License v2.0. See the file "LICENSE" for more - * information. - */ - -/* - * Include necessary headers... - */ +// +// String functions for CUPS. +// +// Copyright © 2022-2023 by OpenPrinting. +// Copyright © 2007-2019 by Apple Inc. +// Copyright © 1997-2007 by Easy Software Products. +// +// Licensed under Apache License v2.0. See the file "LICENSE" for more +// information. +// + +// +// Include necessary headers... +// #define _CUPS_STRING_C_ #include "cups-private.h" @@ -20,46 +20,40 @@ #include -/* - * Local globals... - */ +// +// Local globals... +// static cups_mutex_t sp_mutex = CUPS_MUTEX_INITIALIZER; - /* Mutex to control access to pool */ + // Mutex to control access to pool static cups_array_t *stringpool = NULL; - /* Global string pool */ + // Global string pool -/* - * Local functions... - */ +// +// Local functions... +// static int compare_sp_items(_cups_sp_item_t *a, _cups_sp_item_t *b); -/* - * '_cupsStrAlloc()' - Allocate/reference a string. - */ +// +// '_cupsStrAlloc()' - Allocate/reference a string. +// -char * /* O - String pointer */ -_cupsStrAlloc(const char *s) /* I - String */ +char * // O - String pointer +_cupsStrAlloc(const char *s) // I - String { - size_t slen; /* Length of string */ - _cups_sp_item_t *item, /* String pool item */ - *key; /* Search key */ + size_t slen; // Length of string + _cups_sp_item_t *item, // String pool item + *key; // Search key - /* - * Range check input... - */ - + // Range check input... if (!s) return (NULL); - /* - * Get the string pool... - */ - + // Get the string pool... cupsMutexLock(&sp_mutex); if (!stringpool) @@ -72,18 +66,12 @@ _cupsStrAlloc(const char *s) /* I - String */ return (NULL); } - /* - * See if the string is already in the pool... - */ - + // See if the string is already in the pool... key = (_cups_sp_item_t *)(s - offsetof(_cups_sp_item_t, str)); if ((item = (_cups_sp_item_t *)cupsArrayFind(stringpool, key)) != NULL) { - /* - * Found it, return the cached string... - */ - + // Found it, return the cached string... item->ref_count ++; #ifdef DEBUG_GUARDS @@ -91,17 +79,14 @@ _cupsStrAlloc(const char *s) /* I - String */ if (item->guard != _CUPS_STR_GUARD) abort(); -#endif /* DEBUG_GUARDS */ +#endif // DEBUG_GUARDS cupsMutexUnlock(&sp_mutex); return (item->str); } - /* - * Not found, so allocate a new one... - */ - + // Not found, so allocate a new one... slen = strlen(s); item = (_cups_sp_item_t *)calloc(1, sizeof(_cups_sp_item_t) + slen); if (!item) @@ -118,12 +103,9 @@ _cupsStrAlloc(const char *s) /* I - String */ item->guard = _CUPS_STR_GUARD; DEBUG_printf(("5_cupsStrAlloc: Created string %p(%s) for \"%s\", guard=%08x, ref_count=%d", item, item->str, s, item->guard, item->ref_count)); -#endif /* DEBUG_GUARDS */ - - /* - * Add the string to the pool and return it... - */ +#endif // DEBUG_GUARDS + // Add the string to the pool and return it... cupsArrayAdd(stringpool, item); cupsMutexUnlock(&sp_mutex); @@ -132,14 +114,14 @@ _cupsStrAlloc(const char *s) /* I - String */ } -/* - * 'cupsConcatString()' - Safely concatenate two strings. - */ +// +// 'cupsConcatString()' - Safely concatenate two strings. +// -size_t /* O - Length of string */ -cupsConcatString(char *dst, /* O - Destination string */ - const char *src, /* I - Source string */ - size_t dstsize) /* I - Size of destination string buffer */ +size_t // O - Length of string +cupsConcatString(char *dst, // O - Destination string + const char *src, // I - Source string + size_t dstsize) // I - Size of destination string buffer { // Range check input... if (!dst || !src || dstsize == 0) @@ -149,15 +131,15 @@ cupsConcatString(char *dst, /* O - Destination string */ return (strlcat(dst, src, dstsize)); #else - size_t srclen; /* Length of source string */ - size_t dstlen; /* Length of destination string */ + size_t srclen; // Length of source string + size_t dstlen; // Length of destination string // Figure out how much room is left... dstlen = strlen(dst); if (dstsize < (dstlen + 1)) - return (dstlen); /* No room, return immediately... */ + return (dstlen); // No room, return immediately... dstsize -= dstlen + 1; @@ -176,14 +158,14 @@ cupsConcatString(char *dst, /* O - Destination string */ } -/* - * 'cupsCopyString()' - Safely copy two strings. - */ +// +// 'cupsCopyString()' - Safely copy two strings. +// -size_t /* O - Length of string */ -cupsCopyString(char *dst, /* O - Destination string */ - const char *src, /* I - Source string */ - size_t dstsize) /* I - Size of destination string buffer */ +size_t // O - Length of string +cupsCopyString(char *dst, // O - Destination string + const char *src, // I - Source string + size_t dstsize) // I - Size of destination string buffer { // Range check input... if (!dst || !src || dstsize == 0) @@ -193,7 +175,7 @@ cupsCopyString(char *dst, /* O - Destination string */ return (strlcpy(dst, src, dstsize)); #else - size_t srclen; /* Length of source string */ + size_t srclen; // Length of source string // Figure out how much room is needed... dstsize --; @@ -212,14 +194,14 @@ cupsCopyString(char *dst, /* O - Destination string */ } -/* - * '_cupsStrFlush()' - Flush the string pool. - */ +// +// '_cupsStrFlush()' - Flush the string pool. +// void _cupsStrFlush(void) { - _cups_sp_item_t *item; /* Current item */ + _cups_sp_item_t *item; // Current item DEBUG_printf(("4_cupsStrFlush: %u strings in array", (unsigned)cupsArrayGetCount(stringpool))); @@ -236,38 +218,31 @@ _cupsStrFlush(void) } -/* - * '_cupsStrFormatd()' - Format a floating-point number. - */ +// +// '_cupsStrFormatd()' - Format a floating-point number. +// -char * /* O - Pointer to end of string */ -_cupsStrFormatd(char *buf, /* I - String */ - char *bufend, /* I - End of string buffer */ - double number, /* I - Number to format */ - struct lconv *loc) /* I - Locale data */ +char * // O - Pointer to end of string +_cupsStrFormatd(char *buf, // I - String + char *bufend, // I - End of string buffer + double number, // I - Number to format + struct lconv *loc) // I - Locale data { - char *bufptr, /* Pointer into buffer */ - temp[1024], /* Temporary string */ - *tempdec, /* Pointer to decimal point */ - *tempptr; /* Pointer into temporary string */ - const char *dec; /* Decimal point */ - int declen; /* Length of decimal point */ - + char *bufptr, // Pointer into buffer + temp[1024], // Temporary string + *tempdec, // Pointer to decimal point + *tempptr; // Pointer into temporary string + const char *dec; // Decimal point + int declen; // Length of decimal point - /* - * Format the number using the "%.12f" format and then eliminate - * unnecessary trailing 0's. - */ + // Format the number using the "%.12f" format and then eliminate unnecessary trailing 0's. snprintf(temp, sizeof(temp), "%.12f", number); - for (tempptr = temp + strlen(temp) - 1; - tempptr > temp && *tempptr == '0'; - *tempptr-- = '\0'); - - /* - * Next, find the decimal point... - */ + tempptr = temp + strlen(temp) - 1; + while (tempptr > temp && *tempptr == '0') + *tempptr-- = '\0'; + // Next, find the decimal point... if (loc && loc->decimal_point) { dec = loc->decimal_point; @@ -284,15 +259,15 @@ _cupsStrFormatd(char *buf, /* I - String */ else tempdec = strstr(temp, dec); - /* - * Copy everything up to the decimal point... - */ - + // Copy everything up to the decimal point... if (tempdec) { - for (tempptr = temp, bufptr = buf; - tempptr < tempdec && bufptr < bufend; - *bufptr++ = *tempptr++); + tempptr = temp; + bufptr = buf; + while (tempptr < tempdec && bufptr < bufend) + { + *bufptr++ = *tempptr++; + } tempptr += declen; @@ -316,39 +291,33 @@ _cupsStrFormatd(char *buf, /* I - String */ } -/* - * '_cupsStrFree()' - Free/dereference a string. - */ +// +// '_cupsStrFree()' - Free/dereference a string. +// void -_cupsStrFree(const char *s) /* I - String to free */ +_cupsStrFree(const char *s) // I - String to free { - _cups_sp_item_t *item, /* String pool item */ - *key; /* Search key */ + _cups_sp_item_t *item, // String pool item + *key; // Search key - /* - * Range check input... - */ - + // Range check input... if (!s) return; - /* - * Check the string pool... - * - * We don't need to lock the mutex yet, as we only want to know if - * the stringpool is initialized. The rest of the code will still - * work if it is initialized before we lock... - */ + // + // Check the string pool... + // + // We don't need to lock the mutex yet, as we only want to know if + // the stringpool is initialized. The rest of the code will still + // work if it is initialized before we lock... + // if (!stringpool) return; - /* - * See if the string is already in the pool... - */ - + // See if the string is already in the pool... cupsMutexLock(&sp_mutex); key = (_cups_sp_item_t *)(s - offsetof(_cups_sp_item_t, str)); @@ -356,26 +325,20 @@ _cupsStrFree(const char *s) /* I - String to free */ if ((item = (_cups_sp_item_t *)cupsArrayFind(stringpool, key)) != NULL && item == key) { - /* - * Found it, dereference... - */ - + // Found it, dereference... #ifdef DEBUG_GUARDS if (key->guard != _CUPS_STR_GUARD) { DEBUG_printf(("5_cupsStrFree: Freeing string %p(%s), guard=%08x, ref_count=%d", key, key->str, key->guard, key->ref_count)); abort(); } -#endif /* DEBUG_GUARDS */ +#endif // DEBUG_GUARDS item->ref_count --; if (!item->ref_count) { - /* - * Remove and free... - */ - + // Remove and free... cupsArrayRemove(stringpool, item); free(item); @@ -386,18 +349,18 @@ _cupsStrFree(const char *s) /* I - String to free */ } -/* - * '_cupsStrRetain()' - Increment the reference count of a string. - * - * Note: This function does not verify that the passed pointer is in the - * string pool, so any calls to it MUST know they are passing in a - * good pointer. - */ +// +// '_cupsStrRetain()' - Increment the reference count of a string. +// +// Note: This function does not verify that the passed pointer is in the +// string pool, so any calls to it MUST know they are passing in a +// good pointer. +// -char * /* O - Pointer to string */ -_cupsStrRetain(const char *s) /* I - String to retain */ +char * // O - Pointer to string +_cupsStrRetain(const char *s) // I - String to retain { - _cups_sp_item_t *item; /* Pointer to string pool item */ + _cups_sp_item_t *item; // Pointer to string pool item if (s) @@ -411,7 +374,7 @@ _cupsStrRetain(const char *s) /* I - String to retain */ "ref_count=%d", item, s, item->guard, item->ref_count)); abort(); } -#endif /* DEBUG_GUARDS */ +#endif // DEBUG_GUARDS cupsMutexLock(&sp_mutex); @@ -424,47 +387,41 @@ _cupsStrRetain(const char *s) /* I - String to retain */ } -/* - * '_cupsStrScand()' - Scan a string for a floating-point number. - * - * This function handles the locale-specific BS so that a decimal - * point is always the period (".")... - */ +// +// '_cupsStrScand()' - Scan a string for a floating-point number. +// +// This function handles the locale-specific BS so that a decimal +// point is always the period (".")... +// -double /* O - Number */ -_cupsStrScand(const char *buf, /* I - Pointer to number */ - char **bufptr, /* O - New pointer or NULL on error */ - struct lconv *loc) /* I - Locale data */ +double // O - Number +_cupsStrScand(const char *buf, // I - Pointer to number + char **bufptr, // O - New pointer or NULL on error + struct lconv *loc) // I - Locale data { - char temp[1024], /* Temporary buffer */ - *tempptr; /* Pointer into temporary buffer */ - + char temp[1024], // Temporary buffer + *tempptr; // Pointer into temporary buffer - /* - * Range check input... - */ + // Range check input... if (!buf) return (0.0); - /* - * Skip leading whitespace... - */ - + // Skip leading whitespace... while (_cups_isspace(*buf)) buf ++; - /* - * Copy leading sign, numbers, period, and then numbers... - */ - + // Copy leading sign, numbers, period, and then numbers... tempptr = temp; if (*buf == '-' || *buf == '+') *tempptr++ = *buf++; while (isdigit(*buf & 255)) + { if (tempptr < (temp + sizeof(temp) - 1)) + { *tempptr++ = *buf++; + } else { if (bufptr) @@ -472,13 +429,11 @@ _cupsStrScand(const char *buf, /* I - Pointer to number */ return (0.0); } + } if (*buf == '.') { - /* - * Read fractional portion of number... - */ - + // Read fractional portion of number... buf ++; if (loc && loc->decimal_point) @@ -487,7 +442,9 @@ _cupsStrScand(const char *buf, /* I - Pointer to number */ tempptr += strlen(tempptr); } else if (tempptr < (temp + sizeof(temp) - 1)) + { *tempptr++ = '.'; + } else { if (bufptr) @@ -497,8 +454,11 @@ _cupsStrScand(const char *buf, /* I - Pointer to number */ } while (isdigit(*buf & 255)) + { if (tempptr < (temp + sizeof(temp) - 1)) + { *tempptr++ = *buf++; + } else { if (bufptr) @@ -506,16 +466,16 @@ _cupsStrScand(const char *buf, /* I - Pointer to number */ return (0.0); } + } } if (*buf == 'e' || *buf == 'E') { - /* - * Read exponent... - */ - + // Read exponent... if (tempptr < (temp + sizeof(temp) - 1)) + { *tempptr++ = *buf++; + } else { if (bufptr) @@ -527,7 +487,9 @@ _cupsStrScand(const char *buf, /* I - Pointer to number */ if (*buf == '+' || *buf == '-') { if (tempptr < (temp + sizeof(temp) - 1)) + { *tempptr++ = *buf++; + } else { if (bufptr) @@ -538,8 +500,11 @@ _cupsStrScand(const char *buf, /* I - Pointer to number */ } while (isdigit(*buf & 255)) + { if (tempptr < (temp + sizeof(temp) - 1)) + { *tempptr++ = *buf++; + } else { if (bufptr) @@ -547,12 +512,10 @@ _cupsStrScand(const char *buf, /* I - Pointer to number */ return (0.0); } + } } - /* - * Nul-terminate the temporary string and return the value... - */ - + // Nul-terminate the temporary string and return the value... if (bufptr) *bufptr = (char *)buf; @@ -562,36 +525,27 @@ _cupsStrScand(const char *buf, /* I - Pointer to number */ } -/* - * '_cupsStrStatistics()' - Return allocation statistics for string pool. - */ +// +// '_cupsStrStatistics()' - Return allocation statistics for string pool. +// -size_t /* O - Number of strings */ -_cupsStrStatistics(size_t *alloc_bytes, /* O - Allocated bytes */ - size_t *total_bytes) /* O - Total string bytes */ +size_t // O - Number of strings +_cupsStrStatistics(size_t *alloc_bytes, // O - Allocated bytes + size_t *total_bytes) // O - Total string bytes { - size_t count, /* Number of strings */ - abytes, /* Allocated string bytes */ - tbytes, /* Total string bytes */ - len; /* Length of string */ - _cups_sp_item_t *item; /* Current item */ - + size_t count, // Number of strings + abytes, // Allocated string bytes + tbytes, // Total string bytes + len; // Length of string + _cups_sp_item_t *item; // Current item - /* - * Loop through strings in pool, counting everything up... - */ + // Loop through strings in pool, counting everything up... cupsMutexLock(&sp_mutex); - for (count = 0, abytes = 0, tbytes = 0, - item = (_cups_sp_item_t *)cupsArrayGetFirst(stringpool); - item; - item = (_cups_sp_item_t *)cupsArrayGetNext(stringpool)) + for (count = 0, abytes = 0, tbytes = 0, item = (_cups_sp_item_t *)cupsArrayGetFirst(stringpool); item; item = (_cups_sp_item_t *)cupsArrayGetNext(stringpool)) { - /* - * Count allocated memory, using a 64-bit aligned buffer as a basis. - */ - + // Count allocated memory, using a 64-bit aligned buffer as a basis. count += item->ref_count; len = (strlen(item->str) + 8) & (size_t)~7; abytes += sizeof(_cups_sp_item_t) + len; @@ -600,10 +554,7 @@ _cupsStrStatistics(size_t *alloc_bytes, /* O - Allocated bytes */ cupsMutexUnlock(&sp_mutex); - /* - * Return values... - */ - + // Return values... if (alloc_bytes) *alloc_bytes = abytes; @@ -614,13 +565,13 @@ _cupsStrStatistics(size_t *alloc_bytes, /* O - Allocated bytes */ } -/* - * '_cups_strcpy()' - Copy a string allowing for overlapping strings. - */ +// +// '_cups_strcpy()' - Copy a string allowing for overlapping strings. +// void -_cups_strcpy(char *dst, /* I - Destination string */ - const char *src) /* I - Source string */ +_cups_strcpy(char *dst, // I - Destination string + const char *src) // I - Source string { while (*src) *dst++ = *src++; @@ -629,13 +580,13 @@ _cups_strcpy(char *dst, /* I - Destination string */ } -/* - * '_cups_strcasecmp()' - Do a case-insensitive comparison. - */ +// +// '_cups_strcasecmp()' - Do a case-insensitive comparison. +// -int /* O - Result of comparison (-1, 0, or 1) */ -_cups_strcasecmp(const char *s, /* I - First string */ - const char *t) /* I - Second string */ +int // O - Result of comparison (-1, 0, or 1) +_cups_strcasecmp(const char *s, // I - First string + const char *t) // I - Second string { while (*s != '\0' && *t != '\0') { @@ -656,14 +607,14 @@ _cups_strcasecmp(const char *s, /* I - First string */ return (-1); } -/* - * '_cups_strncasecmp()' - Do a case-insensitive comparison on up to N chars. - */ +// +// '_cups_strncasecmp()' - Do a case-insensitive comparison on up to N chars. +// -int /* O - Result of comparison (-1, 0, or 1) */ -_cups_strncasecmp(const char *s, /* I - First string */ - const char *t, /* I - Second string */ - size_t n) /* I - Maximum number of characters to compare */ +int // O - Result of comparison (-1, 0, or 1) +_cups_strncasecmp(const char *s, // I - First string + const char *t, // I - Second string + size_t n) // I - Maximum number of characters to compare { while (*s != '\0' && *t != '\0' && n > 0) { @@ -688,13 +639,13 @@ _cups_strncasecmp(const char *s, /* I - First string */ } -/* - * 'compare_sp_items()' - Compare two string pool items... - */ +// +// 'compare_sp_items()' - Compare two string pool items... +// -static int /* O - Result of comparison */ -compare_sp_items(_cups_sp_item_t *a, /* I - First item */ - _cups_sp_item_t *b) /* I - Second item */ +static int // O - Result of comparison +compare_sp_items(_cups_sp_item_t *a, // I - First item + _cups_sp_item_t *b) // I - Second item { return (strcmp(a->str, b->str)); } From 5d17c1e7f6414ec1fbcffc76c12757028d51fd24 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Thu, 1 Jun 2023 12:41:22 -0400 Subject: [PATCH 30/31] Documentation. --- cups/Makefile | 2 +- cups/cups.h | 30 +- cups/testcreds.c | 87 +++- cups/tls-gnutls.c | 147 +++++++ cups/tls-openssl.c | 147 +++++++ doc/cupspm.epub | Bin 229529 -> 236415 bytes doc/cupspm.html | 993 +++++++++++++++++++++++++++++++++++++++++++-- 7 files changed, 1349 insertions(+), 57 deletions(-) diff --git a/cups/Makefile b/cups/Makefile index d54962834..5d260992b 100644 --- a/cups/Makefile +++ b/cups/Makefile @@ -648,7 +648,7 @@ doc: echo "Generating CUPS Programming Manual..." $(RM) cupspm.xml codedoc --section "Programming" --body cupspm.md cupspm.xml \ - $(LIBOBJS:.o=.c) $(HEADERS) --coverimage cupspm.png \ + $(LIBOBJS:.o=.c) tls-openssl.c $(HEADERS) --coverimage cupspm.png \ --epub ../doc/cupspm.epub codedoc --section "Programming" --body cupspm.md \ cupspm.xml > ../doc/cupspm.html diff --git a/cups/cups.h b/cups/cups.h index 70139b957..3fca693c8 100644 --- a/cups/cups.h +++ b/cups/cups.h @@ -133,7 +133,7 @@ extern "C" { // Types and structures... // -enum cups_credpurpose_e //// X.509 credential purposes +enum cups_credpurpose_e // X.509 credential purposes { CUPS_CREDPURPOSE_SERVER_AUTH = 0x01, // serverAuth CUPS_CREDPURPOSE_CLIENT_AUTH = 0x02, // clientAuth @@ -143,9 +143,9 @@ enum cups_credpurpose_e //// X.509 credential purposes CUPS_CREDPURPOSE_OCSP_SIGNING = 0x20, // OCSPSigning CUPS_CREDPURPOSE_ALL = 0x3f // All purposes }; -typedef unsigned cups_credpurpose_t; //// Combined X.509 credential purposes for @link cupsCreateCredentials@ and @link cupsCreateCredentialsRequest@ +typedef unsigned cups_credpurpose_t; // Combined X.509 credential purposes for @link cupsCreateCredentials@ and @link cupsCreateCredentialsRequest@ -typedef enum cups_credtype_e //// X.509 credential types for @link cupsCreateCredentials@ and @link cupsCreateCredentialsRequest@ +typedef enum cups_credtype_e // X.509 credential types for @link cupsCreateCredentials@ and @link cupsCreateCredentialsRequest@ { CUPS_CREDTYPE_DEFAULT, // Default type CUPS_CREDTYPE_RSA_2048_SHA256, // RSA with 2048-bit keys and SHA-256 hash @@ -156,7 +156,7 @@ typedef enum cups_credtype_e //// X.509 credential types for @link cupsCreateCr CUPS_CREDTYPE_ECDSA_P521_SHA256 // ECDSA using the P-521 curve with SHA-256 hash } cups_credtype_t; -enum cups_credusage_e //// X.509 keyUsage flags +enum cups_credusage_e // X.509 keyUsage flags { CUPS_CREDUSAGE_DIGITAL_SIGNATURE = 0x001, // digitalSignature CUPS_CREDUSAGE_NON_REPUDIATION = 0x002, // nonRepudiation/contentCommitment @@ -171,9 +171,9 @@ enum cups_credusage_e //// X.509 keyUsage flags CUPS_CREDUSAGE_DEFAULT_TLS = 0x005, // Defaults for TLS certs CUPS_CREDUSAGE_ALL = 0x1ff // All keyUsage flags }; -typedef unsigned cups_credusage_t; //// Combined X.509 keyUsage flags for @link cupsCreateCredentials@ and @link cupsCreateCredentialsRequest@ +typedef unsigned cups_credusage_t; // Combined X.509 keyUsage flags for @link cupsCreateCredentials@ and @link cupsCreateCredentialsRequest@ -enum cups_dest_flags_e //// Flags for @link cupsConnectDest@ and @link cupsEnumDests@ +enum cups_dest_flags_e // Flags for @link cupsConnectDest@ and @link cupsEnumDests@ { CUPS_DEST_FLAGS_NONE = 0x00, // No flags are set CUPS_DEST_FLAGS_UNCONNECTED = 0x01, // There is no connection @@ -185,9 +185,9 @@ enum cups_dest_flags_e //// Flags for @link cupsConnectDest@ and @link cupsEnu CUPS_DEST_FLAGS_CANCELED = 0x40, // Operation was canceled CUPS_DEST_FLAGS_DEVICE = 0x80 // For @link cupsConnectDest@: Connect to device }; -typedef unsigned cups_dest_flags_t; //// Combined flags for @link cupsConnectDest@ and @link cupsEnumDests@ +typedef unsigned cups_dest_flags_t; // Combined flags for @link cupsConnectDest@ and @link cupsEnumDests@ -enum cups_media_flags_e //// Flags for @link cupsGetDestMediaByName@ and @link cupsGetDestMediaBySize@ +enum cups_media_flags_e // Flags for @link cupsGetDestMediaByName@ and @link cupsGetDestMediaBySize@ { CUPS_MEDIA_FLAGS_DEFAULT = 0x00, // Find the closest size supported by the printer CUPS_MEDIA_FLAGS_BORDERLESS = 0x01, // Find a borderless size @@ -195,9 +195,9 @@ enum cups_media_flags_e //// Flags for @link cupsGetDestMediaByName@ and @link CUPS_MEDIA_FLAGS_EXACT = 0x04, // Find an exact match for the size CUPS_MEDIA_FLAGS_READY = 0x08 // If the printer supports media sensing, find the size amongst the "ready" media. }; -typedef unsigned cups_media_flags_t; //// Combined flags for @link cupsGetDestMediaByName@ and @link cupsGetDestMediaBySize@ +typedef unsigned cups_media_flags_t; // Combined flags for @link cupsGetDestMediaByName@ and @link cupsGetDestMediaBySize@ -enum cups_ptype_e //// Printer type/capability flags +enum cups_ptype_e // Printer type/capability flags { CUPS_PRINTER_LOCAL = 0x0000, // Local printer or class CUPS_PRINTER_CLASS = 0x0001, // Printer class @@ -227,22 +227,22 @@ enum cups_ptype_e //// Printer type/capability flags CUPS_PRINTER_MFP = 0x4000000, // Printer with scanning capabilities CUPS_PRINTER_OPTIONS = 0x6fffc // ~(CLASS | REMOTE | IMPLICIT | DEFAULT | FAX | REJECTING | DELETE | NOT_SHARED | AUTHENTICATED | COMMANDS | DISCOVERED) @private@ }; -typedef unsigned cups_ptype_t; //// Combined printer type/capability flags +typedef unsigned cups_ptype_t; // Combined printer type/capability flags -typedef enum cups_whichjobs_e //// Which jobs for @link cupsGetJobs@ +typedef enum cups_whichjobs_e // Which jobs for @link cupsGetJobs@ { CUPS_WHICHJOBS_ALL = -1, // All jobs CUPS_WHICHJOBS_ACTIVE, // Pending/held/processing jobs CUPS_WHICHJOBS_COMPLETED // Completed/canceled/aborted jobs } cups_whichjobs_t; -typedef struct cups_option_s //// Printer Options +typedef struct cups_option_s // Printer Options { char *name; // Name of option char *value; // Value of option } cups_option_t; -typedef struct cups_dest_s //// Destination +typedef struct cups_dest_s // Destination { char *name, // Printer or class name *instance; // Local instance name or NULL @@ -269,7 +269,7 @@ typedef struct cups_job_s // Job time_t processing_time; // Time the job was processed } cups_job_t; -typedef struct cups_size_s //// Media information +typedef struct cups_size_s // Media information { char media[128], // Media name to use color[128], // Media color (blank for any/auto) diff --git a/cups/testcreds.c b/cups/testcreds.c index ca40d5321..b7c868876 100644 --- a/cups/testcreds.c +++ b/cups/testcreds.c @@ -25,6 +25,7 @@ // -C COUNTRY Set country. // -L LOCALITY Set locality name. // -O ORGANIZATION Set organization name. +// -R CSR-FILENAME Specify certificate signing request filename. // -S STATE Set state. // -U ORGANIZATIONAL-UNIT Set organizational unit name. // -a SUBJECT-ALT-NAME Add a subjectAltName. @@ -60,7 +61,7 @@ // static int do_unit_tests(void); -static int test_ca(const char *common_name, const char *root_name, int days); +static int test_ca(const char *common_name, const char *csrfile, const char *root_name, int days); static int test_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 test_client(const char *uri); static int test_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); @@ -81,6 +82,7 @@ main(int argc, // I - Number of command-line arguments const char *subcommand = NULL, // Sub-command *arg = NULL, // Argument for sub-command *opt, // Current option character + *csrfile = NULL, // Certificste signing request filename *root_name = NULL, // Name of root certificate *organization = NULL, // Organization *org_unit = NULL, // Organizational unit @@ -146,6 +148,16 @@ main(int argc, // I - Number of command-line arguments organization = argv[i]; break; + case 'R' : // -R CSR-FILENAME + i ++; + if (i >= argc) + { + fputs("testcreds: Missing CSR filename after '-R'.\n", stderr); + return (usage(stderr)); + } + csrfile = argv[i]; + break; + case 'S' : // -S STATE i ++; if (i >= argc) @@ -350,7 +362,7 @@ main(int argc, // I - Number of command-line arguments // Run the corresponding sub-command... if (!strcmp(subcommand, "ca")) { - return (test_ca(arg, root_name, days)); + return (test_ca(arg, csrfile, root_name, days)); } else if (!strcmp(subcommand, "cacert")) { @@ -522,14 +534,77 @@ do_unit_tests(void) static int // O - Exit status test_ca(const char *common_name, // I - Common name + const char *csrfile, // I - CSR filename, if any const char *root_name, // I - Root certificate name int days) // I - Number of days { - (void)common_name; - (void)root_name; - (void)days; + char *request, // Certificate request + *cert; // Certificate + + + if (csrfile) + { + int csrfd = open(csrfile, O_RDONLY); + // File descriptor + struct stat csrinfo; // File information - return (1); + if (csrfd < 0) + { + fprintf(stderr, "testcreds: Unable to access '%s': %s\n", csrfile, strerror(errno)); + return (1); + } + + if (fstat(csrfd, &csrinfo)) + { + fprintf(stderr, "testcreds: Unable to stat '%s': %s\n", csrfile, strerror(errno)); + close(csrfd); + return (1); + } + + if ((request = malloc((size_t)csrinfo.st_size + 1)) == NULL) + { + fprintf(stderr, "testcreds: Unable to allocate memory for '%s': %s\n", csrfile, strerror(errno)); + close(csrfd); + return (1); + } + + if (read(csrfd, request, (size_t)csrinfo.st_size) < (ssize_t)csrinfo.st_size) + { + fprintf(stderr, "testcreds: Unable to read '%s'.\n", csrfile); + close(csrfd); + return (1); + } + + close(csrfd); + request[csrinfo.st_size] = '\0'; + } + else if ((request = cupsCopyCredentialsRequest(TEST_CERT_PATH, common_name)) == NULL) + { + fprintf(stderr, "testcreds: No request for '%s'.\n", common_name); + return (1); + } + + if (!cupsSignCredentialsRequest(TEST_CERT_PATH, common_name, request, root_name, CUPS_CREDPURPOSE_ALL, CUPS_CREDUSAGE_ALL, /*cb*/NULL, /*cb_data*/NULL, time(NULL) + days * 86400)) + { + fprintf(stderr, "testcreds: Unable to create certificate (%s)\n", cupsLastErrorString()); + free(request); + return (1); + } + + free(request); + + if ((cert = cupsCopyCredentials(TEST_CERT_PATH, common_name)) != NULL) + { + puts(cert); + free(cert); + } + else + { + fprintf(stderr, "testcreds: Unable to get generated certificate for '%s'.\n", common_name); + return (1); + } + + return (0); } diff --git a/cups/tls-gnutls.c b/cups/tls-gnutls.c index c72a48e9f..1a27f0885 100644 --- a/cups/tls-gnutls.c +++ b/cups/tls-gnutls.c @@ -41,6 +41,57 @@ static gnutls_x509_crl_t tls_crl = NULL;/* Certificate revocation list */ // "root_name" is `NULL` and there is no site-wide default root certificate, a // self-signed certificate is generated instead. // +// The "ca_cert" argument specifies whether a CA certificate should be created. +// +// The "purpose" argument specifies the purpose(s) used for the credentials as a +// bitwise OR of the following constants: +// +// - `CUPS_CREDPURPOSE_SERVER_AUTH` for validating TLS servers, +// - `CUPS_CREDPURPOSE_CLIENT_AUTH` for validating TLS clients, +// - `CUPS_CREDPURPOSE_CODE_SIGNING` for validating compiled code, +// - `CUPS_CREDPURPOSE_EMAIL_PROTECTION` for validating email messages, +// - `CUPS_CREDPURPOSE_TIME_STAMPING` for signing timestamps to objects, and/or +// - `CUPS_CREDPURPOSE_OCSP_SIGNING` for Online Certificate Status Protocol +// message signing. +// +// The "type" argument specifies the type of credentials using one of the +// following constants: +// +// - `CUPS_CREDTYPE_DEFAULT`: default type (RSA-3072 or P-384), +// - `CUPS_CREDTYPE_RSA_2048_SHA256`: RSA with 2048-bit keys and SHA-256 hash, +// - `CUPS_CREDTYPE_RSA_3072_SHA256`: RSA with 3072-bit keys and SHA-256 hash, +// - `CUPS_CREDTYPE_RSA_4096_SHA256`: RSA with 4096-bit keys and SHA-256 hash, +// - `CUPS_CREDTYPE_ECDSA_P256_SHA256`: ECDSA using the P-256 curve with SHA-256 hash, +// - `CUPS_CREDTYPE_ECDSA_P384_SHA256`: ECDSA using the P-384 curve with SHA-256 hash, or +// - `CUPS_CREDTYPE_ECDSA_P521_SHA256`: ECDSA using the P-521 curve with SHA-256 hash. +// +// The "usage" argument specifies the usage(s) for the credentials as a bitwise +// OR of the following constants: +// +// - `CUPS_CREDUSAGE_DIGITAL_SIGNATURE`: digital signatures, +// - `CUPS_CREDUSAGE_NON_REPUDIATION`: non-repudiation/content commitment, +// - `CUPS_CREDUSAGE_KEY_ENCIPHERMENT`: key encipherment, +// - `CUPS_CREDUSAGE_DATA_ENCIPHERMENT`: data encipherment, +// - `CUPS_CREDUSAGE_KEY_AGREEMENT`: key agreement, +// - `CUPS_CREDUSAGE_KEY_CERT_SIGN`: key certicate signing, +// - `CUPS_CREDUSAGE_CRL_SIGN`: certificate revocation list signing, +// - `CUPS_CREDUSAGE_ENCIPHER_ONLY`: encipherment only, +// - `CUPS_CREDUSAGE_DECIPHER_ONLY`: decipherment only, +// - `CUPS_CREDUSAGE_DEFAULT_CA`: defaults for CA certificates, +// - `CUPS_CREDUSAGE_DEFAULT_TLS`: defaults for TLS certificates, and/or +// - `CUPS_CREDUSAGE_ALL`: all usages. +// +// The "organization", "org_unit", "locality", "state_province", and "country" +// arguments specify information about the identity and geolocation of the +// issuer. +// +// The "common_name" argument specifies the common name and the "num_alt_names" +// and "alt_names" arguments specify a list of DNS hostnames for the +// certificate. +// +// The "expiration_date" argument specifies the expiration date and time as a +// Unix `time_t` value in seconds. +// bool // O - `true` on success, `false` on error cupsCreateCredentials( @@ -315,6 +366,52 @@ cupsCreateCredentials( // or, if "path" is `NULL`, in a per-user or system-wide (when running as root) // certificate/key store. // +// The "purpose" argument specifies the purpose(s) used for the credentials as a +// bitwise OR of the following constants: +// +// - `CUPS_CREDPURPOSE_SERVER_AUTH` for validating TLS servers, +// - `CUPS_CREDPURPOSE_CLIENT_AUTH` for validating TLS clients, +// - `CUPS_CREDPURPOSE_CODE_SIGNING` for validating compiled code, +// - `CUPS_CREDPURPOSE_EMAIL_PROTECTION` for validating email messages, +// - `CUPS_CREDPURPOSE_TIME_STAMPING` for signing timestamps to objects, and/or +// - `CUPS_CREDPURPOSE_OCSP_SIGNING` for Online Certificate Status Protocol +// message signing. +// +// The "type" argument specifies the type of credentials using one of the +// following constants: +// +// - `CUPS_CREDTYPE_DEFAULT`: default type (RSA-3072 or P-384), +// - `CUPS_CREDTYPE_RSA_2048_SHA256`: RSA with 2048-bit keys and SHA-256 hash, +// - `CUPS_CREDTYPE_RSA_3072_SHA256`: RSA with 3072-bit keys and SHA-256 hash, +// - `CUPS_CREDTYPE_RSA_4096_SHA256`: RSA with 4096-bit keys and SHA-256 hash, +// - `CUPS_CREDTYPE_ECDSA_P256_SHA256`: ECDSA using the P-256 curve with SHA-256 hash, +// - `CUPS_CREDTYPE_ECDSA_P384_SHA256`: ECDSA using the P-384 curve with SHA-256 hash, or +// - `CUPS_CREDTYPE_ECDSA_P521_SHA256`: ECDSA using the P-521 curve with SHA-256 hash. +// +// The "usage" argument specifies the usage(s) for the credentials as a bitwise +// OR of the following constants: +// +// - `CUPS_CREDUSAGE_DIGITAL_SIGNATURE`: digital signatures, +// - `CUPS_CREDUSAGE_NON_REPUDIATION`: non-repudiation/content commitment, +// - `CUPS_CREDUSAGE_KEY_ENCIPHERMENT`: key encipherment, +// - `CUPS_CREDUSAGE_DATA_ENCIPHERMENT`: data encipherment, +// - `CUPS_CREDUSAGE_KEY_AGREEMENT`: key agreement, +// - `CUPS_CREDUSAGE_KEY_CERT_SIGN`: key certicate signing, +// - `CUPS_CREDUSAGE_CRL_SIGN`: certificate revocation list signing, +// - `CUPS_CREDUSAGE_ENCIPHER_ONLY`: encipherment only, +// - `CUPS_CREDUSAGE_DECIPHER_ONLY`: decipherment only, +// - `CUPS_CREDUSAGE_DEFAULT_CA`: defaults for CA certificates, +// - `CUPS_CREDUSAGE_DEFAULT_TLS`: defaults for TLS certificates, and/or +// - `CUPS_CREDUSAGE_ALL`: all usages. +// +// The "organization", "org_unit", "locality", "state_province", and "country" +// arguments specify information about the identity and geolocation of the +// issuer. +// +// The "common_name" argument specifies the common name and the "num_alt_names" +// and "alt_names" arguments specify a list of DNS hostnames for the +// certificate. +// bool // O - `true` on success, `false` on error cupsCreateCredentialsRequest( @@ -507,6 +604,56 @@ cupsCreateCredentialsRequest( // // 'cupsSignCredentialsRequest()' - Sign an X.509 certificate signing request to produce an X.509 certificate chain. // +// This function creates an X.509 certificate from a signing request. The +// certificate is stored in the directory "path" or, if "path" is `NULL`, in a +// per-user or system-wide (when running as root) certificate/key store. The +// generated certificate is signed by the named root certificate or, if +// "root_name" is `NULL`, a site-wide default root certificate. When +// "root_name" is `NULL` and there is no site-wide default root certificate, a +// self-signed certificate is generated instead. +// +// The "allowed_purpose" argument specifies the allowed purpose(s) used for the +// credentials as a bitwise OR of the following constants: +// +// - `CUPS_CREDPURPOSE_SERVER_AUTH` for validating TLS servers, +// - `CUPS_CREDPURPOSE_CLIENT_AUTH` for validating TLS clients, +// - `CUPS_CREDPURPOSE_CODE_SIGNING` for validating compiled code, +// - `CUPS_CREDPURPOSE_EMAIL_PROTECTION` for validating email messages, +// - `CUPS_CREDPURPOSE_TIME_STAMPING` for signing timestamps to objects, and/or +// - `CUPS_CREDPURPOSE_OCSP_SIGNING` for Online Certificate Status Protocol +// message signing. +// +// The "allowed_usage" argument specifies the allowed usage(s) for the +// credentials as a bitwise OR of the following constants: +// +// - `CUPS_CREDUSAGE_DIGITAL_SIGNATURE`: digital signatures, +// - `CUPS_CREDUSAGE_NON_REPUDIATION`: non-repudiation/content commitment, +// - `CUPS_CREDUSAGE_KEY_ENCIPHERMENT`: key encipherment, +// - `CUPS_CREDUSAGE_DATA_ENCIPHERMENT`: data encipherment, +// - `CUPS_CREDUSAGE_KEY_AGREEMENT`: key agreement, +// - `CUPS_CREDUSAGE_KEY_CERT_SIGN`: key certicate signing, +// - `CUPS_CREDUSAGE_CRL_SIGN`: certificate revocation list signing, +// - `CUPS_CREDUSAGE_ENCIPHER_ONLY`: encipherment only, +// - `CUPS_CREDUSAGE_DECIPHER_ONLY`: decipherment only, +// - `CUPS_CREDUSAGE_DEFAULT_CA`: defaults for CA certificates, +// - `CUPS_CREDUSAGE_DEFAULT_TLS`: defaults for TLS certificates, and/or +// - `CUPS_CREDUSAGE_ALL`: all usages. +// +// The "cb" and "cb_data" arguments specify a function and its data that are +// used to validate any subjectAltName values in the signing request: +// +// ``` +// bool san_cb(const char *common_name, const char *alt_name, void *cb_data) { +// ... return true if OK and false if not ... +// } +// ``` +// +// If `NULL`, a default validation function is used that allows "localhost" and +// variations of the common name. +// +// The "expiration_date" argument specifies the expiration date and time as a +// Unix `time_t` value in seconds. +// bool // O - `true` on success, `false` on failure cupsSignCredentialsRequest( diff --git a/cups/tls-openssl.c b/cups/tls-openssl.c index 74b7947cc..b071b33e1 100644 --- a/cups/tls-openssl.c +++ b/cups/tls-openssl.c @@ -78,6 +78,57 @@ static const char * const tls_usage_strings[] = // "root_name" is `NULL` and there is no site-wide default root certificate, a // self-signed certificate is generated instead. // +// The "ca_cert" argument specifies whether a CA certificate should be created. +// +// The "purpose" argument specifies the purpose(s) used for the credentials as a +// bitwise OR of the following constants: +// +// - `CUPS_CREDPURPOSE_SERVER_AUTH` for validating TLS servers, +// - `CUPS_CREDPURPOSE_CLIENT_AUTH` for validating TLS clients, +// - `CUPS_CREDPURPOSE_CODE_SIGNING` for validating compiled code, +// - `CUPS_CREDPURPOSE_EMAIL_PROTECTION` for validating email messages, +// - `CUPS_CREDPURPOSE_TIME_STAMPING` for signing timestamps to objects, and/or +// - `CUPS_CREDPURPOSE_OCSP_SIGNING` for Online Certificate Status Protocol +// message signing. +// +// The "type" argument specifies the type of credentials using one of the +// following constants: +// +// - `CUPS_CREDTYPE_DEFAULT`: default type (RSA-3072 or P-384), +// - `CUPS_CREDTYPE_RSA_2048_SHA256`: RSA with 2048-bit keys and SHA-256 hash, +// - `CUPS_CREDTYPE_RSA_3072_SHA256`: RSA with 3072-bit keys and SHA-256 hash, +// - `CUPS_CREDTYPE_RSA_4096_SHA256`: RSA with 4096-bit keys and SHA-256 hash, +// - `CUPS_CREDTYPE_ECDSA_P256_SHA256`: ECDSA using the P-256 curve with SHA-256 hash, +// - `CUPS_CREDTYPE_ECDSA_P384_SHA256`: ECDSA using the P-384 curve with SHA-256 hash, or +// - `CUPS_CREDTYPE_ECDSA_P521_SHA256`: ECDSA using the P-521 curve with SHA-256 hash. +// +// The "usage" argument specifies the usage(s) for the credentials as a bitwise +// OR of the following constants: +// +// - `CUPS_CREDUSAGE_DIGITAL_SIGNATURE`: digital signatures, +// - `CUPS_CREDUSAGE_NON_REPUDIATION`: non-repudiation/content commitment, +// - `CUPS_CREDUSAGE_KEY_ENCIPHERMENT`: key encipherment, +// - `CUPS_CREDUSAGE_DATA_ENCIPHERMENT`: data encipherment, +// - `CUPS_CREDUSAGE_KEY_AGREEMENT`: key agreement, +// - `CUPS_CREDUSAGE_KEY_CERT_SIGN`: key certicate signing, +// - `CUPS_CREDUSAGE_CRL_SIGN`: certificate revocation list signing, +// - `CUPS_CREDUSAGE_ENCIPHER_ONLY`: encipherment only, +// - `CUPS_CREDUSAGE_DECIPHER_ONLY`: decipherment only, +// - `CUPS_CREDUSAGE_DEFAULT_CA`: defaults for CA certificates, +// - `CUPS_CREDUSAGE_DEFAULT_TLS`: defaults for TLS certificates, and/or +// - `CUPS_CREDUSAGE_ALL`: all usages. +// +// The "organization", "org_unit", "locality", "state_province", and "country" +// arguments specify information about the identity and geolocation of the +// issuer. +// +// The "common_name" argument specifies the common name and the "num_alt_names" +// and "alt_names" arguments specify a list of DNS hostnames for the +// certificate. +// +// The "expiration_date" argument specifies the expiration date and time as a +// Unix `time_t` value in seconds. +// bool // O - `true` on success, `false` on failure cupsCreateCredentials( @@ -336,6 +387,52 @@ cupsCreateCredentials( // or, if "path" is `NULL`, in a per-user or system-wide (when running as root) // certificate/key store. // +// The "purpose" argument specifies the purpose(s) used for the credentials as a +// bitwise OR of the following constants: +// +// - `CUPS_CREDPURPOSE_SERVER_AUTH` for validating TLS servers, +// - `CUPS_CREDPURPOSE_CLIENT_AUTH` for validating TLS clients, +// - `CUPS_CREDPURPOSE_CODE_SIGNING` for validating compiled code, +// - `CUPS_CREDPURPOSE_EMAIL_PROTECTION` for validating email messages, +// - `CUPS_CREDPURPOSE_TIME_STAMPING` for signing timestamps to objects, and/or +// - `CUPS_CREDPURPOSE_OCSP_SIGNING` for Online Certificate Status Protocol +// message signing. +// +// The "type" argument specifies the type of credentials using one of the +// following constants: +// +// - `CUPS_CREDTYPE_DEFAULT`: default type (RSA-3072 or P-384), +// - `CUPS_CREDTYPE_RSA_2048_SHA256`: RSA with 2048-bit keys and SHA-256 hash, +// - `CUPS_CREDTYPE_RSA_3072_SHA256`: RSA with 3072-bit keys and SHA-256 hash, +// - `CUPS_CREDTYPE_RSA_4096_SHA256`: RSA with 4096-bit keys and SHA-256 hash, +// - `CUPS_CREDTYPE_ECDSA_P256_SHA256`: ECDSA using the P-256 curve with SHA-256 hash, +// - `CUPS_CREDTYPE_ECDSA_P384_SHA256`: ECDSA using the P-384 curve with SHA-256 hash, or +// - `CUPS_CREDTYPE_ECDSA_P521_SHA256`: ECDSA using the P-521 curve with SHA-256 hash. +// +// The "usage" argument specifies the usage(s) for the credentials as a bitwise +// OR of the following constants: +// +// - `CUPS_CREDUSAGE_DIGITAL_SIGNATURE`: digital signatures, +// - `CUPS_CREDUSAGE_NON_REPUDIATION`: non-repudiation/content commitment, +// - `CUPS_CREDUSAGE_KEY_ENCIPHERMENT`: key encipherment, +// - `CUPS_CREDUSAGE_DATA_ENCIPHERMENT`: data encipherment, +// - `CUPS_CREDUSAGE_KEY_AGREEMENT`: key agreement, +// - `CUPS_CREDUSAGE_KEY_CERT_SIGN`: key certicate signing, +// - `CUPS_CREDUSAGE_CRL_SIGN`: certificate revocation list signing, +// - `CUPS_CREDUSAGE_ENCIPHER_ONLY`: encipherment only, +// - `CUPS_CREDUSAGE_DECIPHER_ONLY`: decipherment only, +// - `CUPS_CREDUSAGE_DEFAULT_CA`: defaults for CA certificates, +// - `CUPS_CREDUSAGE_DEFAULT_TLS`: defaults for TLS certificates, and/or +// - `CUPS_CREDUSAGE_ALL`: all usages. +// +// The "organization", "org_unit", "locality", "state_province", and "country" +// arguments specify information about the identity and geolocation of the +// issuer. +// +// The "common_name" argument specifies the common name and the "num_alt_names" +// and "alt_names" arguments specify a list of DNS hostnames for the +// certificate. +// bool // O - `true` on success, `false` on error cupsCreateCredentialsRequest( @@ -497,6 +594,56 @@ cupsCreateCredentialsRequest( // // 'cupsSignCredentialsRequest()' - Sign an X.509 certificate signing request to produce an X.509 certificate chain. // +// This function creates an X.509 certificate from a signing request. The +// certificate is stored in the directory "path" or, if "path" is `NULL`, in a +// per-user or system-wide (when running as root) certificate/key store. The +// generated certificate is signed by the named root certificate or, if +// "root_name" is `NULL`, a site-wide default root certificate. When +// "root_name" is `NULL` and there is no site-wide default root certificate, a +// self-signed certificate is generated instead. +// +// The "allowed_purpose" argument specifies the allowed purpose(s) used for the +// credentials as a bitwise OR of the following constants: +// +// - `CUPS_CREDPURPOSE_SERVER_AUTH` for validating TLS servers, +// - `CUPS_CREDPURPOSE_CLIENT_AUTH` for validating TLS clients, +// - `CUPS_CREDPURPOSE_CODE_SIGNING` for validating compiled code, +// - `CUPS_CREDPURPOSE_EMAIL_PROTECTION` for validating email messages, +// - `CUPS_CREDPURPOSE_TIME_STAMPING` for signing timestamps to objects, and/or +// - `CUPS_CREDPURPOSE_OCSP_SIGNING` for Online Certificate Status Protocol +// message signing. +// +// The "allowed_usage" argument specifies the allowed usage(s) for the +// credentials as a bitwise OR of the following constants: +// +// - `CUPS_CREDUSAGE_DIGITAL_SIGNATURE`: digital signatures, +// - `CUPS_CREDUSAGE_NON_REPUDIATION`: non-repudiation/content commitment, +// - `CUPS_CREDUSAGE_KEY_ENCIPHERMENT`: key encipherment, +// - `CUPS_CREDUSAGE_DATA_ENCIPHERMENT`: data encipherment, +// - `CUPS_CREDUSAGE_KEY_AGREEMENT`: key agreement, +// - `CUPS_CREDUSAGE_KEY_CERT_SIGN`: key certicate signing, +// - `CUPS_CREDUSAGE_CRL_SIGN`: certificate revocation list signing, +// - `CUPS_CREDUSAGE_ENCIPHER_ONLY`: encipherment only, +// - `CUPS_CREDUSAGE_DECIPHER_ONLY`: decipherment only, +// - `CUPS_CREDUSAGE_DEFAULT_CA`: defaults for CA certificates, +// - `CUPS_CREDUSAGE_DEFAULT_TLS`: defaults for TLS certificates, and/or +// - `CUPS_CREDUSAGE_ALL`: all usages. +// +// The "cb" and "cb_data" arguments specify a function and its data that are +// used to validate any subjectAltName values in the signing request: +// +// ``` +// bool san_cb(const char *common_name, const char *alt_name, void *cb_data) { +// ... return true if OK and false if not ... +// } +// ``` +// +// If `NULL`, a default validation function is used that allows "localhost" and +// variations of the common name. +// +// The "expiration_date" argument specifies the expiration date and time as a +// Unix `time_t` value in seconds. +// bool // O - `true` on success, `false` on failure cupsSignCredentialsRequest( diff --git a/doc/cupspm.epub b/doc/cupspm.epub index 78381005bfb2453fb8f093fd1511da1a942ef911..f1fd127557216b32c10c38faedd927a6e5ebe0d6 100644 GIT binary patch delta 89535 zcmV)9K*hhAfe!z+4h>LC0|XQR00000*kr+x4L1YWWWkX~Bm>xF!I5s<1K4E2lT863 z57=bER)9@rJkvn|05~lPlYIdif5??E`kzmM@vNCh4k`K8oy6VtcW8-nGm%A&E`6Cy zmOhXuN{B�YJ;jtUS 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%0DVgY`ql*K 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 N=QX6*HxN3q^*=Runy3H( delta 82504 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
    #?gm&O5BBX#G-56H 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 b-&X%G{rNvbsB+Gl9xContents
  • cupsCondInit
  • cupsCondWait
  • cupsConnectDest
  • +
  • cupsCopyCredentials
  • +
  • cupsCopyCredentialsKey
  • +
  • cupsCopyCredentialsRequest
  • cupsCopyDest
  • cupsCopyDestConflicts
  • cupsCopyDestInfo
  • cupsCopyString
  • +
  • cupsCreateCredentials
  • +
  • cupsCreateCredentialsRequest
  • cupsCreateDestJob
  • cupsDNSSDAssembleFullName
  • cupsDNSSDBrowseDelete
  • @@ -414,9 +419,12 @@

    Contents

  • cupsGetServer
  • cupsGetUser
  • cupsGetUserAgent
  • +
  • cupsHMACData
  • cupsHashData
  • cupsHashString
  • cupsJSONDelete
  • +
  • cupsJSONExportFile
  • +
  • cupsJSONExportString
  • cupsJSONFind
  • cupsJSONGetChild
  • cupsJSONGetCount
  • @@ -426,14 +434,30 @@

    Contents

  • cupsJSONGetSibling
  • cupsJSONGetString
  • cupsJSONGetType
  • -
  • cupsJSONLoadFile
  • -
  • cupsJSONLoadString
  • +
  • cupsJSONImportFile
  • +
  • cupsJSONImportString
  • +
  • cupsJSONImportURL
  • cupsJSONNew
  • cupsJSONNewKey
  • cupsJSONNewNumber
  • cupsJSONNewString
  • -
  • cupsJSONSaveFile
  • -
  • cupsJSONSaveString
  • +
  • cupsJWTDelete
  • +
  • cupsJWTExportString
  • +
  • cupsJWTGetAlgorithm
  • +
  • cupsJWTGetClaimNumber
  • +
  • cupsJWTGetClaimString
  • +
  • cupsJWTGetClaimType
  • +
  • cupsJWTGetClaimValue
  • +
  • cupsJWTGetClaims
  • +
  • cupsJWTHasValidSignature
  • +
  • cupsJWTImportString
  • +
  • cupsJWTMakePrivateKey
  • +
  • cupsJWTMakePublicKey
  • +
  • cupsJWTNew
  • +
  • cupsJWTSetClaimNumber
  • +
  • cupsJWTSetClaimString
  • +
  • cupsJWTSetClaimValue
  • +
  • cupsJWTSign
  • cupsLangAddStrings
  • cupsLangDefault
  • cupsLangFind
  • @@ -487,8 +511,10 @@

    Contents

  • cupsSetPasswordCB
  • cupsSetServer
  • cupsSetServerCertCB
  • +
  • cupsSetServerCredentials
  • cupsSetUser
  • cupsSetUserAgent
  • +
  • cupsSignCredentialsRequest
  • cupsStartDestDocument
  • cupsTempFd
  • cupsTempFile
  • @@ -526,7 +552,12 @@

    Contents

  • httpClose
  • httpCompareCredentials
  • httpConnect
  • +
  • httpCopyCredentials
  • httpCreateCredentials
  • +
  • httpCredentialsAreValidForName
  • +
  • httpCredentialsGetExpiration
  • +
  • httpCredentialsGetTrust
  • +
  • httpCredentialsString
  • httpDecode64
  • httpEncode64
  • httpError
  • @@ -560,12 +591,14 @@

    Contents

  • httpInitialize
  • httpIsChunked
  • httpIsEncrypted
  • +
  • httpLoadCredentials
  • httpPeek
  • httpRead
  • httpReadRequest
  • httpReconnect
  • httpResolveHostname
  • httpResolveURI
  • +
  • httpSaveCredentials
  • httpSeparateURI
  • httpSetAuthString
  • httpSetBlocking
  • @@ -718,12 +751,17 @@

    Contents

  • cups_array_cb_t
  • cups_array_t
  • cups_bool_t
  • +
  • cups_cert_san_cb_t
  • cups_client_cert_cb_t
  • cups_cond_t
  • +
  • cups_credpurpose_t
  • +
  • cups_credtype_t
  • +
  • cups_credusage_t
  • cups_cspace_t
  • cups_cut_t
  • cups_dentry_t
  • cups_dest_cb_t
  • +
  • cups_dest_flags_t
  • cups_dest_t
  • cups_dinfo_t
  • cups_dir_t
  • @@ -745,7 +783,10 @@

    Contents

  • cups_jog_t
  • cups_json_t
  • cups_jtype_t
  • +
  • cups_jwa_t
  • +
  • cups_jwt_t
  • cups_lang_t
  • +
  • cups_media_flags_t
  • cups_mediapos_t
  • cups_mutex_t
  • cups_oauth_cb_t
  • @@ -813,14 +854,20 @@

    Contents

  • Enumerations
    • cups_adv_e
    • cups_bool_e
    • +
    • cups_credpurpose_e
    • +
    • cups_credtype_e
    • +
    • cups_credusage_e
    • cups_cspace_e
    • cups_cut_e
    • +
    • cups_dest_flags_e
    • cups_dnssd_flags_e
    • cups_dnssd_rrtype_e
    • cups_edge_e
    • cups_encoding_e
    • cups_jog_e
    • cups_jtype_e
    • +
    • cups_jwa_e
    • +
    • cups_media_flags_e
    • cups_mediapos_e
    • cups_order_e
    • cups_orient_e
    • @@ -2470,6 +2517,45 @@

      Discussion

      The caller can pass CUPS_DEST_FLAGS_DEVICE for the "flags" argument to connect directly to the device associated with the destination. Otherwise, the connection is made to the CUPS scheduler associated with the destination.

      +

      cupsCopyCredentials

      +

      +

      +char *cupsCopyCredentials(const char *path, const char *common_name);

      +

      Parameters

      + + + + + +
      pathDirectory path for certificate/key store or NULL for default
      common_nameCommon name
      +

      Return Value

      +

      Copy the X.509 certificate chain to a string.

      +

      cupsCopyCredentialsKey

      +

      +

      +char *cupsCopyCredentialsKey(const char *path, const char *common_name);

      +

      Parameters

      + + + + + +
      pathDirectory path for certificate/key store or NULL for default
      common_nameCommon name
      +

      Return Value

      +

      Copy the private key to a string.

      +

      cupsCopyCredentialsRequest

      +

      +

      +char *cupsCopyCredentialsRequest(const char *path, const char *common_name);

      +

      Parameters

      + + + + + +
      pathDirectory path for certificate/key store or NULL for default
      common_nameCommon name
      +

      Return Value

      +

      Copy the X.509 certificate signing request to a string.

      cupsCopyDest

      Copy a destination.

      @@ -2568,6 +2654,238 @@

      Parameters

      Return Value

      Length of string

      +

      cupsCreateCredentials

      +

      Make an X.509 certificate and private key pair.

      +

      +bool cupsCreateCredentials(const char *path, bool ca_cert, cups_credpurpose_t purpose, cups_credtype_t type, cups_credusage_t usage, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, size_t num_alt_names, const char *const *alt_names, const char *root_name, time_t expiration_date);

      +

      Parameters

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      pathDirectory path for certificate/key store or NULL for default
      ca_certtrue to create a CA certificate, false for a client/server certificate
      purposeCredential purposes
      typeCredential type
      usageCredential usages
      organizationOrganization or NULL to use common name
      org_unitOrganizational unit or NULL for none
      localityCity/town or NULL for "Unknown"
      state_provinceState/province or NULL for "Unknown"
      countryCountry or NULL for locale-based default
      common_nameCommon name
      num_alt_namesNumber of subject alternate names
      alt_namesSubject Alternate Names
      root_nameRoot certificate/domain name or NULL for site/self-signed
      expiration_dateExpiration date
      +

      Return Value

      +

      true on success, false on failure

      +

      Discussion

      +

      This function creates an X.509 certificate and private key pair. The +certificate and key are stored in the directory "path" or, if "path" is +NULL, in a per-user or system-wide (when running as root) certificate/key +store. The generated certificate is signed by the named root certificate or, +if "root_name" is NULL, a site-wide default root certificate. When +"root_name" is NULL and there is no site-wide default root certificate, a +self-signed certificate is generated instead.
      +
      +The "ca_cert" argument specifies whether a CA certificate should be created.
      +
      +The "purpose" argument specifies the purpose(s) used for the credentials as a +bitwise OR of the following constants: + +

        +
      • CUPS_CREDPURPOSE_SERVER_AUTH for validating TLS servers, +
      • +
      • CUPS_CREDPURPOSE_CLIENT_AUTH for validating TLS clients, +
      • +
      • CUPS_CREDPURPOSE_CODE_SIGNING for validating compiled code, +
      • +
      • CUPS_CREDPURPOSE_EMAIL_PROTECTION for validating email messages, +
      • +
      • CUPS_CREDPURPOSE_TIME_STAMPING for signing timestamps to objects, and/or +
      • +
      • CUPS_CREDPURPOSE_OCSP_SIGNING for Online Certificate Status Protocol + message signing.
      • +
      +

      The "type" argument specifies the type of credentials using one of the +following constants: + +

        +
      • CUPS_CREDTYPE_DEFAULT: default type (RSA-3072 or P-384), +
      • +
      • CUPS_CREDTYPE_RSA_2048_SHA256: RSA with 2048-bit keys and SHA-256 hash, +
      • +
      • CUPS_CREDTYPE_RSA_3072_SHA256: RSA with 3072-bit keys and SHA-256 hash, +
      • +
      • CUPS_CREDTYPE_RSA_4096_SHA256: RSA with 4096-bit keys and SHA-256 hash, +
      • +
      • CUPS_CREDTYPE_ECDSA_P256_SHA256: ECDSA using the P-256 curve with SHA-256 hash, +
      • +
      • CUPS_CREDTYPE_ECDSA_P384_SHA256: ECDSA using the P-384 curve with SHA-256 hash, or +
      • +
      • CUPS_CREDTYPE_ECDSA_P521_SHA256: ECDSA using the P-521 curve with SHA-256 hash.
      • +
      +

      The "usage" argument specifies the usage(s) for the credentials as a bitwise +OR of the following constants: + +

        +
      • CUPS_CREDUSAGE_DIGITAL_SIGNATURE: digital signatures, +
      • +
      • CUPS_CREDUSAGE_NON_REPUDIATION: non-repudiation/content commitment, +
      • +
      • CUPS_CREDUSAGE_KEY_ENCIPHERMENT: key encipherment, +
      • +
      • CUPS_CREDUSAGE_DATA_ENCIPHERMENT: data encipherment, +
      • +
      • CUPS_CREDUSAGE_KEY_AGREEMENT: key agreement, +
      • +
      • CUPS_CREDUSAGE_KEY_CERT_SIGN: key certicate signing, +
      • +
      • CUPS_CREDUSAGE_CRL_SIGN: certificate revocation list signing, +
      • +
      • CUPS_CREDUSAGE_ENCIPHER_ONLY: encipherment only, +
      • +
      • CUPS_CREDUSAGE_DECIPHER_ONLY: decipherment only, +
      • +
      • CUPS_CREDUSAGE_DEFAULT_CA: defaults for CA certificates, +
      • +
      • CUPS_CREDUSAGE_DEFAULT_TLS: defaults for TLS certificates, and/or +
      • +
      • CUPS_CREDUSAGE_ALL: all usages.
      • +
      +

      The "organization", "org_unit", "locality", "state_province", and "country" +arguments specify information about the identity and geolocation of the +issuer.
      +
      +The "common_name" argument specifies the common name and the "num_alt_names" +and "alt_names" arguments specify a list of DNS hostnames for the +certificate.
      +
      +The "expiration_date" argument specifies the expiration date and time as a +Unix time_t value in seconds.

      +

      cupsCreateCredentialsRequest

      +

      Make an X.509 Certificate Signing Request.

      +

      +bool cupsCreateCredentialsRequest(const char *path, cups_credpurpose_t purpose, cups_credtype_t type, cups_credusage_t usage, const char *organization, const char *org_unit, const char *locality, const char *state_province, const char *country, const char *common_name, size_t num_alt_names, const char *const *alt_names);

      +

      Parameters

      + + + + + + + + + + + + + + + + + + + + + + + + + +
      pathDirectory path for certificate/key store or NULL for default
      purposeCredential purposes
      typeCredential type
      usageCredential usages
      organizationOrganization or NULL to use common name
      org_unitOrganizational unit or NULL for none
      localityCity/town or NULL for "Unknown"
      state_provinceState/province or NULL for "Unknown"
      countryCountry or NULL for locale-based default
      common_nameCommon name
      num_alt_namesNumber of subject alternate names
      alt_namesSubject Alternate Names
      +

      Return Value

      +

      true on success, false on error

      +

      Discussion

      +

      This function creates an X.509 certificate signing request (CSR) and +associated private key. The CSR and key are stored in the directory "path" +or, if "path" is NULL, in a per-user or system-wide (when running as root) +certificate/key store.
      +
      +The "purpose" argument specifies the purpose(s) used for the credentials as a +bitwise OR of the following constants: + +

        +
      • CUPS_CREDPURPOSE_SERVER_AUTH for validating TLS servers, +
      • +
      • CUPS_CREDPURPOSE_CLIENT_AUTH for validating TLS clients, +
      • +
      • CUPS_CREDPURPOSE_CODE_SIGNING for validating compiled code, +
      • +
      • CUPS_CREDPURPOSE_EMAIL_PROTECTION for validating email messages, +
      • +
      • CUPS_CREDPURPOSE_TIME_STAMPING for signing timestamps to objects, and/or +
      • +
      • CUPS_CREDPURPOSE_OCSP_SIGNING for Online Certificate Status Protocol + message signing.
      • +
      +

      The "type" argument specifies the type of credentials using one of the +following constants: + +

        +
      • CUPS_CREDTYPE_DEFAULT: default type (RSA-3072 or P-384), +
      • +
      • CUPS_CREDTYPE_RSA_2048_SHA256: RSA with 2048-bit keys and SHA-256 hash, +
      • +
      • CUPS_CREDTYPE_RSA_3072_SHA256: RSA with 3072-bit keys and SHA-256 hash, +
      • +
      • CUPS_CREDTYPE_RSA_4096_SHA256: RSA with 4096-bit keys and SHA-256 hash, +
      • +
      • CUPS_CREDTYPE_ECDSA_P256_SHA256: ECDSA using the P-256 curve with SHA-256 hash, +
      • +
      • CUPS_CREDTYPE_ECDSA_P384_SHA256: ECDSA using the P-384 curve with SHA-256 hash, or +
      • +
      • CUPS_CREDTYPE_ECDSA_P521_SHA256: ECDSA using the P-521 curve with SHA-256 hash.
      • +
      +

      The "usage" argument specifies the usage(s) for the credentials as a bitwise +OR of the following constants: + +

        +
      • CUPS_CREDUSAGE_DIGITAL_SIGNATURE: digital signatures, +
      • +
      • CUPS_CREDUSAGE_NON_REPUDIATION: non-repudiation/content commitment, +
      • +
      • CUPS_CREDUSAGE_KEY_ENCIPHERMENT: key encipherment, +
      • +
      • CUPS_CREDUSAGE_DATA_ENCIPHERMENT: data encipherment, +
      • +
      • CUPS_CREDUSAGE_KEY_AGREEMENT: key agreement, +
      • +
      • CUPS_CREDUSAGE_KEY_CERT_SIGN: key certicate signing, +
      • +
      • CUPS_CREDUSAGE_CRL_SIGN: certificate revocation list signing, +
      • +
      • CUPS_CREDUSAGE_ENCIPHER_ONLY: encipherment only, +
      • +
      • CUPS_CREDUSAGE_DECIPHER_ONLY: decipherment only, +
      • +
      • CUPS_CREDUSAGE_DEFAULT_CA: defaults for CA certificates, +
      • +
      • CUPS_CREDUSAGE_DEFAULT_TLS: defaults for TLS certificates, and/or +
      • +
      • CUPS_CREDUSAGE_ALL: all usages.
      • +
      +

      The "organization", "org_unit", "locality", "state_province", and "country" +arguments specify information about the identity and geolocation of the +issuer.
      +
      +The "common_name" argument specifies the common name and the "num_alt_names" +and "alt_names" arguments specify a list of DNS hostnames for the +certificate.

      cupsCreateDestJob

      Create a job on a destination.

      @@ -3757,9 +4075,11 @@

      Discussion

      cupsFormEncode

      Encode options as URL-encoded form data.

      -char *cupsFormEncode(size_t num_vars, cups_option_t *vars);

      +char *cupsFormEncode(const char *url, size_t num_vars, cups_option_t *vars);

      Parameters

      + + @@ -3768,8 +4088,8 @@

      Parameters

      Return Value

      URL-encoded form data

      Discussion

      -

      This function encodes a CUPS options array as URL-encoded form data, -returning an allocated string.
      +

      This function encodes a CUPS options array as URL-encoded form data with an +optional URL prefix, returning an allocated string.

      Use free to return the memory used for the string.

      cupsFreeDestInfo

      @@ -4282,6 +4602,39 @@

      cupsGetUserAgent

      const char *cupsGetUserAgent(void);

      Return Value

      User-Agent string

      +

      cupsHMACData

      +

      Perform a HMAC function on the given data.

      +

      +ssize_t cupsHMACData(const char *algorithm, const unsigned char *key, size_t keylen, const void *data, size_t datalen, unsigned char *hmac, size_t hmacsize);

      +

      Parameters

      +
      urlURL or NULL for none
      num_vars Number of variables
      vars
      + + + + + + + + + + + + + + +
      algorithmHash algorithm
      keyKey
      keylenLength of key
      dataData to hash
      datalenLength of data to hash
      hmacHMAC buffer
      hmacsizeSize of HMAC buffer
      +

      Return Value

      +

      The length of the HMAC or -1 on error

      +

      Discussion

      +

      This function performs a HMAC function on the given data with the given key. +The "algorithm" argument can be any of the registered, non-deprecated IPP +hash algorithms for the "job-password-encryption" attribute, including +"sha" for SHA-1, "sha2-256" for SHA2-256, etc.
      +
      +The "hmac" argument points to a buffer of "hmacsize" bytes and should be at +least 64 bytes in length for all of the supported algorithms.
      +
      +The returned HMAC is binary data.

      cupsHashData

      Perform a hash function on the given data.

      @@ -4302,9 +4655,10 @@

      Parameters

      Return Value

      Size of hash or -1 on error

      Discussion

      -

      The "algorithm" argument can be any of the registered, non-deprecated IPP -hash algorithms for the "job-password-encryption" attribute, including -"sha" for SHA-1, "sha-256" for SHA2-256, etc.
      +

      This function performs a hash function on the given data. The "algorithm" +argument can be any of the registered, non-deprecated IPP hash algorithms for +the "job-password-encryption" attribute, including "sha" for SHA-1, +"sha2-256" for SHA2-256, etc.

      The "hash" argument points to a buffer of "hashsize" bytes and should be at least 64 bytes in length for all of the supported algorithms.
      @@ -4338,6 +4692,33 @@

      Parameters

      json JSON node +

      cupsJSONExportFile

      +

      Save a JSON node tree to a file.

      +

      +bool cupsJSONExportFile(cups_json_t *json, const char *filename);

      +

      Parameters

      + + + + + +
      jsonJSON root node
      filenameJSON filename
      +

      Return Value

      +

      true on success, false on failure

      +

      cupsJSONExportString

      +

      Save a JSON node tree to a string.

      +

      +char *cupsJSONExportString(cups_json_t *json);

      +

      Parameters

      + + + +
      jsonJSON root node
      +

      Return Value

      +

      JSON string or NULL on error

      +

      Discussion

      +

      This function saves a JSON node tree to an allocated string. The resulting +string must be freed using the free function.

      cupsJSONFind

      Find the value(s) associated with a given key.

      @@ -4450,10 +4831,10 @@

      Parameters

      Return Value

      JSON node type

      -

      cupsJSONLoadFile

      +

      cupsJSONImportFile

      Load a JSON object file.

      -cups_json_t *cupsJSONLoadFile(const char *filename);

      +cups_json_t *cupsJSONImportFile(const char *filename);

      Parameters

      @@ -4461,10 +4842,10 @@

      Parameters

      filename

      Return Value

      Root JSON object node

      -

      cupsJSONLoadString

      +

      cupsJSONImportString

      Load a JSON object from a string.

      -cups_json_t *cupsJSONLoadString(const char *s);

      +cups_json_t *cupsJSONImportString(const char *s);

      Parameters

      @@ -4472,6 +4853,32 @@

      Parameters

      s

      Return Value

      Root JSON object node

      +

      cupsJSONImportURL

      +

      Load a JSON object from a URL.

      +

      +cups_json_t *cupsJSONImportURL(const char *url, time_t *last_modified);

      +

      Parameters

      + + + + + +
      urlURL
      last_modifiedLast modified date/time or NULL
      +

      Return Value

      +

      Root JSON object node

      +

      Discussion

      +

      This function loads a JSON object from a URL. The "url" can be a "http:" or +"https:" URL. The "last_modified" argument provides a pointer to a time_t +variable with the last modified date and time from a previous load. If +NULL or the variable has a value of 0, the JSON is loaded unconditionally +from the URL.
      +
      +On success, a pointer to the root JSON object node is returned and the +"last_modified" variable, if not NULL, is updated to the Last-Modified +date and time returned by the server. Otherwise, NULL is returned with +the cupsLastError value set to IPP_STATUS_OK_EVENTS_COMPLETE if +the JSON data has not been updated since the "last_modified" date and time +or a suitable IPP_STATUS_ERROR_ value if an error occurred.

      cupsJSONNew

      Create a new JSON node.

      @@ -4532,33 +4939,219 @@

      Parameters

      Return Value

      JSON node

      -

      cupsJSONSaveFile

      -

      Save a JSON node tree to a file.

      +

      cupsJWTDelete

      +

      Free the memory used for a JSON Web Token.

      -bool cupsJSONSaveFile(cups_json_t *json, const char *filename);

      +void cupsJWTDelete(cups_jwt_t *jwt);

      Parameters

      - - - - + + +
      jsonJSON root node
      filenameJSON filename
      jwtJWT object
      +

      cupsJWTExportString

      +

      Export a JWT with the JWS Compact Serialization format.

      +

      +char *cupsJWTExportString(cups_jwt_t *jwt);

      +

      Parameters

      + + +
      jwtJWT object

      Return Value

      -

      true on success, false on failure

      -

      cupsJSONSaveString

      -

      Save a JSON node tree to a string.

      +

      JWT/JWS Compact Serialization string

      +

      cupsJWTGetAlgorithm

      +

      Get the signature algorithm used by a JSON Web Token.

      -char *cupsJSONSaveString(cups_json_t *json);

      +cups_jwa_t cupsJWTGetAlgorithm(cups_jwt_t *jwt);

      Parameters

      - - + +
      jsonJSON root node
      jwtJWT object

      Return Value

      -

      JSON string or NULL on error

      +

      Signature algorithm

      +

      cupsJWTGetClaimNumber

      +

      Get the number value of a claim.

      +

      +double cupsJWTGetClaimNumber(cups_jwt_t *jwt, const char *claim);

      +

      Parameters

      + + + + + +
      jwtJWT object
      claimClaim name
      +

      Return Value

      +

      Number value

      +

      cupsJWTGetClaimString

      +

      Get the string value of a claim.

      +

      +const char *cupsJWTGetClaimString(cups_jwt_t *jwt, const char *claim);

      +

      Parameters

      + + + + + +
      jwtJWT object
      claimClaim name
      +

      Return Value

      +

      String value

      +

      cupsJWTGetClaimType

      +

      Get the value type of a claim.

      +

      +cups_jtype_t cupsJWTGetClaimType(cups_jwt_t *jwt, const char *claim);

      +

      Parameters

      + + + + + +
      jwtJWT object
      claimClaim name
      +

      Return Value

      +

      JSON value type

      +

      cupsJWTGetClaimValue

      +

      Get the value node of a claim.

      +

      +cups_json_t *cupsJWTGetClaimValue(cups_jwt_t *jwt, const char *claim);

      +

      Parameters

      + + + + + +
      jwtJWT object
      claimClaim name
      +

      Return Value

      +

      JSON value node

      +

      cupsJWTGetClaims

      +

      Get the JWT claims as a JSON object.

      +

      +cups_json_t *cupsJWTGetClaims(cups_jwt_t *jwt);

      +

      Parameters

      + + + +
      jwtJWT object
      +

      Return Value

      +

      JSON object

      +

      cupsJWTHasValidSignature

      +

      Determine whether the JWT has a valid signature.

      +

      +bool cupsJWTHasValidSignature(cups_jwt_t *jwt, cups_json_t *jwk);

      +

      Parameters

      + + + + + +
      jwtJWT object
      jwkJWK key set
      +

      Return Value

      +

      true if value, false otherwise

      +

      cupsJWTImportString

      +

      Import a JSON Web Token or JSON Web Signature.

      +

      +cups_jwt_t *cupsJWTImportString(const char *token);

      +

      Parameters

      + + + +
      tokenJWS Compact Serialization string
      +

      Return Value

      +

      JWT object

      +

      cupsJWTMakePrivateKey

      +

      Make a JSON Web Key for encryption and signing.

      +

      +cups_json_t *cupsJWTMakePrivateKey(cups_jwa_t alg);

      +

      Parameters

      + + + +
      algSigning/encryption algorithm
      +

      Return Value

      +

      Private JSON Web Key or NULL on error

      Discussion

      -

      This function saves a JSON node tree to an allocated string. The resulting -string must be freed using the free function.

      +

      This function makes a JSON Web Key (JWK) for the specified JWS/JWE algorithm +for use when signing or encrypting JSON Web Tokens. The resulting JWK +must not be provided to clients - instead, call cupsJWTMakePublicKey +to produce a public key subset suitable for verification and decryption.

      +

      cupsJWTMakePublicKey

      +

      Make a JSON Web Key for decryption and verification.

      +

      +cups_json_t *cupsJWTMakePublicKey(cups_json_t *jwk);

      +

      Parameters

      + + + +
      jwkPrivate JSON Web Key
      +

      Return Value

      +

      Public JSON Web Key or NULL on error

      +

      Discussion

      +

      This function makes a public JSON Web Key (JWK) from the specified private +JWK suitable for use when decrypting or verifying a JWE/JWS message.

      +

      cupsJWTNew

      +

      Create a new, empty JSON Web Token.

      +

      +cups_jwt_t *cupsJWTNew(const char *type);

      +

      Parameters

      + + + +
      typeJWT type or NULL for default ("JWT")
      +

      Return Value

      +

      JWT object

      +

      cupsJWTSetClaimNumber

      +

      Set a claim number.

      +

      +void cupsJWTSetClaimNumber(cups_jwt_t *jwt, const char *claim, double value);

      +

      Parameters

      + + + + + + + +
      jwtJWT object
      claimClaim name
      valueNumber value
      +

      cupsJWTSetClaimString

      +

      Set a claim string.

      +

      +void cupsJWTSetClaimString(cups_jwt_t *jwt, const char *claim, const char *value);

      +

      Parameters

      + + + + + + + +
      jwtJWT object
      claimClaim name
      valueString value
      +

      cupsJWTSetClaimValue

      +

      Set a claim value.

      +

      +void cupsJWTSetClaimValue(cups_jwt_t *jwt, const char *claim, cups_json_t *value);

      +

      Parameters

      + + + + + + + +
      jwtJWT object
      claimClaim name
      valueJSON value node
      +

      cupsJWTSign

      +

      Sign a JSON Web Token, creating a JSON Web Signature.

      +

      +bool cupsJWTSign(cups_jwt_t *jwt, cups_jwa_t alg, cups_json_t *jwk);

      +

      Parameters

      + + + + + + + +
      jwtJWT object
      algSigning algorithm
      jwkJWK key set
      +

      Return Value

      +

      true on success, false on error

      cupsLangAddStrings

      Add strings for the specified language.

      @@ -5413,6 +6006,24 @@

      Discussion

      Note: The current credentials callback is tracked separately for each thread in a program. Multi-threaded programs that override the callback need to do so in each thread for the same callback to be used.

      +

      cupsSetServerCredentials

      +

      Set the default server credentials.

      +

      +bool cupsSetServerCredentials(const char *path, const char *common_name, bool auto_create);

      +

      Parameters

      + + + + + + + +
      pathDirectory path for certificate/key store or NULL for default
      common_nameDefault common name for server
      auto_createtrue = automatically create self-signed certificates
      +

      Return Value

      +

      true on success, false on failure

      +

      Discussion

      +

      Note: The server credentials are used by all threads in the running process. +This function is threadsafe.

      cupsSetUser

      Set the default user name.

      @@ -5440,6 +6051,101 @@

      Parameters

      Discussion

      Setting the string to NULL forces the default value containing the CUPS version, IPP version, and operating system version and architecture.

      +

      cupsSignCredentialsRequest

      +

      Sign an X.509 certificate signing request to produce an X.509 certificate chain.

      +

      +bool cupsSignCredentialsRequest(const char *path, const char *common_name, const char *request, const char *root_name, cups_credpurpose_t allowed_purpose, cups_credusage_t allowed_usage, cups_cert_san_cb_t cb, void *cb_data, time_t expiration_date);

      +

      Parameters

      + + + + + + + + + + + + + + + + + + + +
      pathDirectory path for certificate/key store or NULL for default
      common_nameCommon name to use
      requestPEM-encoded CSR
      root_nameRoot certificate
      allowed_purposeAllowed credential purpose(s)
      allowed_usageAllowed credential usage(s)
      cbsubjectAltName callback or NULL to allow just .local
      cb_dataCallback data
      expiration_dateCertificate expiration date
      +

      Return Value

      +

      true on success, false on failure

      +

      Discussion

      +

      This function creates an X.509 certificate from a signing request. The +certificate is stored in the directory "path" or, if "path" is NULL, in a +per-user or system-wide (when running as root) certificate/key store. The +generated certificate is signed by the named root certificate or, if +"root_name" is NULL, a site-wide default root certificate. When +"root_name" is NULL and there is no site-wide default root certificate, a +self-signed certificate is generated instead.
      +
      +The "allowed_purpose" argument specifies the allowed purpose(s) used for the +credentials as a bitwise OR of the following constants: + +

        +
      • CUPS_CREDPURPOSE_SERVER_AUTH for validating TLS servers, +
      • +
      • CUPS_CREDPURPOSE_CLIENT_AUTH for validating TLS clients, +
      • +
      • CUPS_CREDPURPOSE_CODE_SIGNING for validating compiled code, +
      • +
      • CUPS_CREDPURPOSE_EMAIL_PROTECTION for validating email messages, +
      • +
      • CUPS_CREDPURPOSE_TIME_STAMPING for signing timestamps to objects, and/or +
      • +
      • CUPS_CREDPURPOSE_OCSP_SIGNING for Online Certificate Status Protocol + message signing.
      • +
      +

      The "allowed_usage" argument specifies the allowed usage(s) for the +credentials as a bitwise OR of the following constants: + +

        +
      • CUPS_CREDUSAGE_DIGITAL_SIGNATURE: digital signatures, +
      • +
      • CUPS_CREDUSAGE_NON_REPUDIATION: non-repudiation/content commitment, +
      • +
      • CUPS_CREDUSAGE_KEY_ENCIPHERMENT: key encipherment, +
      • +
      • CUPS_CREDUSAGE_DATA_ENCIPHERMENT: data encipherment, +
      • +
      • CUPS_CREDUSAGE_KEY_AGREEMENT: key agreement, +
      • +
      • CUPS_CREDUSAGE_KEY_CERT_SIGN: key certicate signing, +
      • +
      • CUPS_CREDUSAGE_CRL_SIGN: certificate revocation list signing, +
      • +
      • CUPS_CREDUSAGE_ENCIPHER_ONLY: encipherment only, +
      • +
      • CUPS_CREDUSAGE_DECIPHER_ONLY: decipherment only, +
      • +
      • CUPS_CREDUSAGE_DEFAULT_CA: defaults for CA certificates, +
      • +
      • CUPS_CREDUSAGE_DEFAULT_TLS: defaults for TLS certificates, and/or +
      • +
      • CUPS_CREDUSAGE_ALL: all usages.
      • +
      +

      The "cb" and "cb_data" arguments specify a function and its data that are +used to validate any subjectAltName values in the signing request: + +

      +bool san_cb(const char *common_name, const char *alt_name, void *cb_data) {
      +  ... return true if OK and false if not ...
      +}
      +
      + +If NULL, a default validation function is used that allows "localhost" and +variations of the common name.
      +
      +The "expiration_date" argument specifies the expiration date and time as a +Unix time_t value in seconds.

      cupsStartDestDocument

      Start a new document.

      @@ -6023,6 +6729,20 @@

      Parameters

      Return Value

      New HTTP connection

      +

      httpCopyCredentials

      +

      Copy the credentials associated with the peer in + an encrypted connection.

      +

      +bool httpCopyCredentials(http_t *http, cups_array_t **credentials);

      +

      Parameters

      + + + + + +
      httpConnection to server
      credentialsArray of credentials
      +

      Return Value

      +

      Status of call (true = success)

      httpCreateCredentials

      Create a new array of HTTP credentials.

      @@ -6039,6 +6759,59 @@

      Return Value

      Discussion

      This function creates a new array of HTTP credentials for use with the httpAddCredentials and httpSetCredentials functions.

      +

      httpCredentialsAreValidForName

      +

      Return whether the credentials are valid + for the given name.

      +

      +bool httpCredentialsAreValidForName(cups_array_t *credentials, const char *common_name);

      +

      Parameters

      + + + + + +
      credentialsCredentials
      common_nameName to check
      +

      Return Value

      +

      true if valid, false otherwise

      +

      httpCredentialsGetExpiration

      +

      Return the expiration date of the credentials.

      +

      +time_t httpCredentialsGetExpiration(cups_array_t *credentials);

      +

      Parameters

      + + + +
      credentialsCredentials
      +

      Return Value

      +

      Expiration date of credentials

      +

      httpCredentialsGetTrust

      +

      Return the trust of credentials.

      +

      +http_trust_t httpCredentialsGetTrust(cups_array_t *credentials, const char *common_name);

      +

      Parameters

      + + + + + +
      credentialsCredentials
      common_nameCommon name for trust lookup
      +

      Return Value

      +

      Level of trust

      +

      httpCredentialsString

      +

      Return a string representing the credentials.

      +

      +size_t httpCredentialsString(cups_array_t *credentials, char *buffer, size_t bufsize);

      +

      Parameters

      + + + + + + + +
      credentialsCredentials
      bufferBuffer
      bufsizeSize of buffer
      +

      Return Value

      +

      Total size of credentials string

      httpDecode64

      Base64-decode a string.

      @@ -6493,6 +7266,21 @@

      Return Value

      true if encrypted, false if not

      Discussion

      This function returns true if the connection is currently encrypted.

      +

      httpLoadCredentials

      +

      Load X.509 credentials from a keychain file.

      +

      +bool httpLoadCredentials(const char *path, cups_array_t **credentials, const char *common_name);

      +

      Parameters

      + + + + + + + +
      pathKeychain/PKCS#12 path
      credentialsCredentials
      common_nameCommon name for credentials
      +

      Return Value

      +

      true on success, false on error

      httpPeek

      Peek at data from a HTTP connection.

      @@ -6613,6 +7401,21 @@

      Discussion

      bool value that is true to continue and false to stop. If no callback is specified ("cb" is NULL), then this function will block up to 90 seconds to resolve the specified URI.

      +

      httpSaveCredentials

      +

      Save X.509 credentials to a keychain file.

      +

      +bool httpSaveCredentials(const char *path, cups_array_t *credentials, const char *common_name);

      +

      Parameters

      + + + + + + + +
      pathKeychain/PKCS#12 path
      credentialsCredentials
      common_nameCommon name for credentials
      +

      Return Value

      +

      true on success, false on error

      httpSeparateURI

      Separate a Universal Resource Identifier into its components.

      @@ -9304,6 +10107,11 @@

      cups_bool_t

      typedef enum cups_bool_e cups_bool_t;

      +

      cups_cert_san_cb_t

      +

      Certificate signing subjectAltName callback

      +

      +typedef bool(*)(const char *common_name, const char *subject_alt_name, void *user_data)cups_cert_san_cb_t; +

      cups_client_cert_cb_t

      Client credentials callback

      @@ -9314,6 +10122,21 @@

      cups_cond_t

      typedef pthread_cond_t cups_cond_t;

      +

      cups_credpurpose_t

      +

      Combined X.509 credential purposes for cupsCreateCredentials and cupsCreateCredentialsRequest

      +

      +typedef unsigned cups_credpurpose_t; +

      +

      cups_credtype_t

      +

      X.509 credential types for cupsCreateCredentials and cupsCreateCredentialsRequest

      +

      +typedef enum cups_credtype_e cups_credtype_t; +

      +

      cups_credusage_t

      +

      Combined X.509 keyUsage flags for cupsCreateCredentials and cupsCreateCredentialsRequest

      +

      +typedef unsigned cups_credusage_t; +

      cups_cspace_t

      cupsColorSpace attribute values

      @@ -9332,7 +10155,12 @@

      cups_dentry_t

      cups_dest_cb_t

      Destination enumeration callback

      -typedef bool(*)(void *user_data, unsigned flags, cups_dest_t *dest)cups_dest_cb_t; +typedef bool(*)(void *user_data, cups_dest_flags_t flags, cups_dest_t *dest)cups_dest_cb_t; +

      +

      cups_dest_flags_t

      +

      Combined flags for cupsConnectDest and cupsEnumDests

      +

      +typedef unsigned cups_dest_flags_t;

      cups_dest_t

      Destination

      @@ -9439,11 +10267,26 @@

      cups_jtype_t

      typedef enum cups_jtype_e cups_jtype_t;

      +

      cups_jwa_t

      +

      JSON Web Algorithms

      +

      +typedef enum cups_jwa_e cups_jwa_t; +

      +

      cups_jwt_t

      +

      JSON Web Token

      +

      +typedef struct _cups_jwt_s cups_jwt_t; +

      cups_lang_t

      Language Cache

      typedef struct _cups_lang_s cups_lang_t;

      +

      cups_media_flags_t

      +

      Combined flags for cupsGetDestMediaByName and cupsGetDestMediaBySize

      +

      +typedef unsigned cups_media_flags_t; +

      cups_mediapos_t

      MediaPosition values

      @@ -9485,7 +10328,7 @@

      cups_password_cb_t

      typedef const char *(*)(const char *prompt, http_t *http, const char *method, const char *resource, void *user_data)cups_password_cb_t;

      cups_ptype_t

      -

      Printer type/capability bits

      +

      Combined printer type/capability flags

      typedef unsigned cups_ptype_t;

      @@ -9510,7 +10353,7 @@

      cups_server_cert_cb_t

      typedef bool(*)(http_t *http, void *tls, cups_array_t *certs, void *user_data)cups_server_cert_cb_t;

      cups_size_t

      -

      // Media Size

      +

      Media information

      typedef struct cups_size_s cups_size_t;

      @@ -9953,7 +10796,7 @@

      Members

      Width of page image in pixels

      cups_size_s

      -

      // Media Size

      +

      Media information

      struct cups_size_s {
          int width, length, bottom, left, right, top;
          char media[128], color[128], source[128], type[128];
      @@ -10009,6 +10852,47 @@

      Constants

      CUPS_FALSE Logical false CUPS_TRUE Logical true +

      cups_credpurpose_e

      +

      X.509 credential purposes

      +

      Constants

      + + + + + + + + +
      CUPS_CREDPURPOSE_ALL All purposes
      CUPS_CREDPURPOSE_CLIENT_AUTH clientAuth
      CUPS_CREDPURPOSE_CODE_SIGNING codeSigning
      CUPS_CREDPURPOSE_EMAIL_PROTECTION emailProtection
      CUPS_CREDPURPOSE_OCSP_SIGNING OCSPSigning
      CUPS_CREDPURPOSE_SERVER_AUTH serverAuth
      CUPS_CREDPURPOSE_TIME_STAMPING timeStamping
      +

      cups_credtype_e

      +

      X.509 credential types for cupsCreateCredentials and cupsCreateCredentialsRequest

      +

      Constants

      + + + + + + + + +
      CUPS_CREDTYPE_DEFAULT Default type
      CUPS_CREDTYPE_ECDSA_P256_SHA256 ECDSA using the P-256 curve with SHA-256 hash
      CUPS_CREDTYPE_ECDSA_P384_SHA256 ECDSA using the P-384 curve with SHA-256 hash
      CUPS_CREDTYPE_ECDSA_P521_SHA256 ECDSA using the P-521 curve with SHA-256 hash
      CUPS_CREDTYPE_RSA_2048_SHA256 RSA with 2048-bit keys and SHA-256 hash
      CUPS_CREDTYPE_RSA_3072_SHA256 RSA with 3072-bit keys and SHA-256 hash
      CUPS_CREDTYPE_RSA_4096_SHA256 RSA with 4096-bit keys and SHA-256 hash
      +

      cups_credusage_e

      +

      X.509 keyUsage flags

      +

      Constants

      + + + + + + + + + + + + + +
      CUPS_CREDUSAGE_ALL All keyUsage flags
      CUPS_CREDUSAGE_CRL_SIGN cRLSign
      CUPS_CREDUSAGE_DATA_ENCIPHERMENT dataEncipherment
      CUPS_CREDUSAGE_DECIPHER_ONLY decipherOnly
      CUPS_CREDUSAGE_DEFAULT_CA Defaults for CA certs
      CUPS_CREDUSAGE_DEFAULT_TLS Defaults for TLS certs
      CUPS_CREDUSAGE_DIGITAL_SIGNATURE digitalSignature
      CUPS_CREDUSAGE_ENCIPHER_ONLY encipherOnly
      CUPS_CREDUSAGE_KEY_AGREEMENT keyAgreement
      CUPS_CREDUSAGE_KEY_CERT_SIGN keyCertSign
      CUPS_CREDUSAGE_KEY_ENCIPHERMENT keyEncipherment
      CUPS_CREDUSAGE_NON_REPUDIATION nonRepudiation/contentCommitment

      cups_cspace_e

      cupsColorSpace attribute values

      Constants

      @@ -10075,6 +10959,20 @@

      Constants

      CUPS_CUT_PAGE Cut the roll after this page CUPS_CUT_SET Cut the roll after this set +

      cups_dest_flags_e

      +

      Flags for cupsConnectDest and cupsEnumDests

      +

      Constants

      + + + + + + + + + + +
      CUPS_DEST_FLAGS_CANCELED Operation was canceled
      CUPS_DEST_FLAGS_CONNECTING A connection is being established
      CUPS_DEST_FLAGS_DEVICE For cupsConnectDest: Connect to device
      CUPS_DEST_FLAGS_ERROR An error occurred
      CUPS_DEST_FLAGS_MORE There are more destinations
      CUPS_DEST_FLAGS_NONE No flags are set
      CUPS_DEST_FLAGS_REMOVED The destination has gone away
      CUPS_DEST_FLAGS_RESOLVING The destination address is being resolved
      CUPS_DEST_FLAGS_UNCONNECTED There is no connection

      cups_dnssd_flags_e

      DNS-SD callback flag values

      Constants

      @@ -10188,6 +11086,31 @@

      Constants

      CUPS_JTYPE_STRING String value CUPS_JTYPE_TRUE Boolean true value +

      cups_jwa_e

      +

      JSON Web Algorithms

      +

      Constants

      + + + + + + + + + + + +
      CUPS_JWA_ES256 ECDSA using P-256 and SHA-256
      CUPS_JWA_ES384 ECDSA using P-384 and SHA-384
      CUPS_JWA_ES512 ECDSA using P-521 and SHA-512
      CUPS_JWA_HS256 HMAC using SHA-256
      CUPS_JWA_HS384 HMAC using SHA-384
      CUPS_JWA_HS512 HMAC using SHA-512
      CUPS_JWA_NONE No algorithm
      CUPS_JWA_RS256 RSASSA-PKCS1-v1_5 using SHA-256
      CUPS_JWA_RS384 RSASSA-PKCS1-v1_5 using SHA-384
      CUPS_JWA_RS512 RSASSA-PKCS1-v1_5 using SHA-512
      +

      cups_media_flags_e

      +

      Flags for cupsGetDestMediaByName and cupsGetDestMediaBySize

      +

      Constants

      + + + + + + +
      CUPS_MEDIA_FLAGS_BORDERLESS Find a borderless size
      CUPS_MEDIA_FLAGS_DEFAULT Find the closest size supported by the printer
      CUPS_MEDIA_FLAGS_DUPLEX Find a size compatible with 2-sided printing
      CUPS_MEDIA_FLAGS_EXACT Find an exact match for the size
      CUPS_MEDIA_FLAGS_READY If the printer supports media sensing, find the size amongst the "ready" media.

      cups_mediapos_e

      MediaPosition values

      Constants

      @@ -10261,7 +11184,7 @@

      Constants

      CUPS_ORIENT_90 Rotate the page counter-clockwise

      cups_ptype_e

      -

      Printer type/capability bit constants

      +

      Printer type/capability flags

      Constants

      From 513839fc53840ff6f62548f0b3c255eec84e6f3e Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Thu, 1 Jun 2023 14:19:40 -0400 Subject: [PATCH 31/31] Fix usage output. --- cups/testcreds.c | 1 + 1 file changed, 1 insertion(+) diff --git a/cups/testcreds.c b/cups/testcreds.c index b7c868876..0e826987b 100644 --- a/cups/testcreds.c +++ b/cups/testcreds.c @@ -983,6 +983,7 @@ usage(FILE *fp) // I - Output file (stdout or stderr) fputs(" -C COUNTRY Set country.\n", fp); fputs(" -L LOCALITY Set locality name.\n", fp); fputs(" -O ORGANIZATION Set organization name.\n", fp); + fputs(" -R CSR-FILENAME Specify certificate signing request file.\n", fp); fputs(" -S STATE Set state.\n", fp); fputs(" -U ORGANIZATIONAL-UNIT Set organizational unit name.\n", fp); fputs(" -a SUBJECT-ALT-NAME Add a subjectAltName.\n", fp);
      CUPS_PRINTER_AUTHENTICATED Printer requires authentication