diff --git a/CHANGES.md b/CHANGES.md index 60b0f955..c5b0c1e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -29,3 +29,5 @@ Changes in v1.4.0 - Updated default logging to be less chatty (Issue #270) - Updated the Wi-Fi configuration page to support hidden networks. - Updated the Wi-Fi configuration page reload time to 30 seconds. +- Updated TLS certificate generation to support more types of certificates and + to use modern OpenSSL/GNU TLS APIs. diff --git a/pappl/system-webif.c b/pappl/system-webif.c index f285b426..a52252a5 100644 --- a/pappl/system-webif.c +++ b/pappl/system-webif.c @@ -40,6 +40,16 @@ // Local types... // +typedef enum _pappl_credtype_e // X.509 credential types +{ + _PAPPL_CREDTYPE_RSA_2048_SHA256, // RSA with 2048-bit keys and SHA-256 hash + _PAPPL_CREDTYPE_RSA_3072_SHA256, // RSA with 3072-bit keys and SHA-256 hash + _PAPPL_CREDTYPE_RSA_4096_SHA256, // RSA with 4096-bit keys and SHA-256 hash + _PAPPL_CREDTYPE_ECDSA_P256_SHA256, // ECDSA using the P-256 curve with SHA-256 hash + _PAPPL_CREDTYPE_ECDSA_P384_SHA256, // ECDSA using the P-384 curve with SHA-256 hash + _PAPPL_CREDTYPE_ECDSA_P521_SHA256 // ECDSA using the P-521 curve with SHA-256 hash +} _pappl_credtype_t; + typedef struct _pappl_system_dev_s // System device callback data { pappl_client_t *client; // Client connection @@ -61,6 +71,12 @@ typedef struct _pappl_redirect_s // System redirection data // static size_t get_networks(size_t max_networks, pappl_network_t *networks); +#ifdef HAVE_OPENSSL +static bool openssl_add_ext(STACK_OF(X509_EXTENSION) *exts, int nid, const char *value); +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, const char *email); +static EVP_PKEY *openssl_create_key(pappl_system_t *system, _pappl_credtype_t type); +static X509_EXTENSION *openssl_create_san(const char *hostname, const char *localname); +#endif // HAVE_OPENSSL static bool system_device_cb(const char *device_info, const char *device_uri, const char *device_id, void *data); static void system_footer(pappl_client_t *client); static void system_header(pappl_client_t *client, const char *title); @@ -1773,13 +1789,13 @@ _papplSystemWebTLSNew( " \n", papplClientGetLocString(client, _PAPPL_LOC("This form creates a certificate signing request ('CSR') that you can send to a Certificate Authority ('CA') to obtain a trusted TLS certificate. The private key is saved separately for use with the certificate you get from the CA."))); papplClientHTMLPrintf(client, - " \n" + " \n" " \n" " \n" " \n" " \n" " \n" - " ", papplClientGetLocString(client, _PAPPL_LOC("Level")), papplClientGetLocString(client, _PAPPL_LOC("Good (2048-bit RSA)")), papplClientGetLocString(client, _PAPPL_LOC("Better (3072-bit RSA)")), papplClientGetLocString(client, _PAPPL_LOC("Better (4096-bit RSA)")), papplClientGetLocString(client, _PAPPL_LOC("Better (256-bit ECC)")), papplClientGetLocString(client, _PAPPL_LOC("Best (384-bit ECC)")), papplClientGetLocString(client, _PAPPL_LOC("Best (521-bit ECC)")), papplClientGetLocString(client, _PAPPL_LOC("EMail (contact)")), system->contact.email, papplClientGetLocString(client, _PAPPL_LOC("Organization")), system->organization ? system->organization : "", papplClientGetLocString(client, _PAPPL_LOC("Organization/business name")), papplClientGetLocString(client, _PAPPL_LOC("Organization Unit")), system->org_unit ? system->org_unit : "", papplClientGetLocString(client, _PAPPL_LOC("Unit, department, etc.")), papplClientGetLocString(client, _PAPPL_LOC("City/Locality")), papplClientGetLocString(client, _PAPPL_LOC("City/town name")), papplClientGetLocString(client, _PAPPL_LOC("Use My Position")), papplClientGetLocString(client, _PAPPL_LOC("State/Province")), papplClientGetLocString(client, _PAPPL_LOC("State/province name")), papplClientGetLocString(client, _PAPPL_LOC("Country or Region")), papplClientGetLocString(client, _PAPPL_LOC("Choose"))); for (i = 0; i < (int)(sizeof(countries) / sizeof(countries[0])); i ++) papplClientHTMLPrintf(client, "", countries[i][0], papplClientGetLocString(client, countries[i][1])); @@ -2100,6 +2116,162 @@ get_networks( #endif // _WIN32 } +#ifdef HAVE_OPENSSL +// +// '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 + + + // Create and add the extension... + if ((ext = X509V3_EXT_conf_nid(/*conf*/NULL, /*ctx*/NULL, nid, value)) == NULL) + return (false); + + sk_X509_EXTENSION_push(exts, ext); + + return (true); +} + + +// +// 'openssl_create_key()' - Create a suitable key pair for a certificate/signing request. +// + +static EVP_PKEY * // O - Key pair +openssl_create_key( + pappl_system_t *system, // I - System + _pappl_credtype_t type) // I - Type of key +{ + EVP_PKEY *pkey; // Key pair + EVP_PKEY_CTX *ctx; // Key generation context + int algid; // Algorithm NID + int bits = 0; // Bits + int curveid = 0; // Curve NID + + + switch (type) + { + case _PAPPL_CREDTYPE_ECDSA_P256_SHA256 : + algid = EVP_PKEY_EC; + curveid = NID_secp256k1; + break; + + case _PAPPL_CREDTYPE_ECDSA_P384_SHA256 : + algid = EVP_PKEY_EC; + curveid = NID_secp384r1; + break; + + case _PAPPL_CREDTYPE_ECDSA_P521_SHA256 : + algid = EVP_PKEY_EC; + curveid = NID_secp521r1; + break; + + case _PAPPL_CREDTYPE_RSA_2048_SHA256 : + algid = EVP_PKEY_RSA; + bits = 2048; + break; + + default : + case _PAPPL_CREDTYPE_RSA_3072_SHA256 : + algid = EVP_PKEY_RSA; + bits = 3072; + break; + + case _PAPPL_CREDTYPE_RSA_4096_SHA256 : + algid = EVP_PKEY_RSA; + bits = 4096; + break; + } + + pkey = NULL; + + if ((ctx = EVP_PKEY_CTX_new_id(algid, NULL)) == NULL) + papplLog(system, PAPPL_LOGLEVEL_ERROR, "Unable to create private key context."); + else if (EVP_PKEY_keygen_init(ctx) <= 0) + papplLog(system, PAPPL_LOGLEVEL_ERROR, "Unable to initialize private key context."); + else if (bits && EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) <= 0) + papplLog(system, PAPPL_LOGLEVEL_ERROR, "Unable to configure private key context."); + else if (curveid && EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curveid) <= 0) + papplLog(system, PAPPL_LOGLEVEL_ERROR, "Unable to configure private key context."); + else if (EVP_PKEY_keygen(ctx, &pkey) <= 0) + papplLog(system, PAPPL_LOGLEVEL_ERROR, "Unable to create private key."); + + EVP_PKEY_CTX_free(ctx); + + return (pkey); +} + + +// +// 'openssl_create_name()' - Create an X.509 name value for a certificate/signing request. +// + +static X509_NAME * // O - X.509 name value +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" + 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 + const char *email) // I - Email address or `NULL` for none +{ + 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); + if (email) + X509_NAME_add_entry_by_txt(name, "emailAddress", MBSTRING_ASC, (unsigned char *)email, -1, -1, 0); + + return (name); +} + + +// +// 'openssl_create_san()' - Create a list of subjectAltName values for a certificate/signing request. +// + +static X509_EXTENSION * // O - Extension +openssl_create_san( + const char *hostname, // I - Hostname + const char *localname) // I - Local hostname +{ + char temp[2048]; // Temporary string + + + // Add the hostname and (if set) local name... + if (localname && *localname) + snprintf(temp, sizeof(temp), "DNS:%s,DNS:%s", hostname, localname); + else + snprintf(temp, sizeof(temp), "DNS:%s", hostname); + + // Return the stack + return (X509V3_EXT_conf_nid(/*conf*/NULL, /*ctx*/NULL, NID_subject_alt_name, temp)); +} +#endif // HAVE_OPENSSL + // // 'system_device_cb()' - Device callback for the "add printer" chooser. @@ -2450,26 +2622,25 @@ tls_make_certificate( *state, // State/province *country; // Country int duration; // Duration in years - int num_alt_names = 1; // Alternate names - char alt_names[4][256]; // Subject alternate names char hostname[256], // Hostname + localname[256], // Hostname.local *domain, // Domain name basedir[256], // CUPS directory ssldir[256], // CUPS "ssl" directory crtfile[1024], // Certificate file keyfile[1024]; // Private key file + _pappl_credtype_t credtype; // Type of credentials + time_t curtime; // Current time # ifdef HAVE_OPENSSL bool result = false; // Result of operations EVP_PKEY *pkey; // Private key - BIGNUM *rsaexp; // Public exponent for RSA keys - RSA *rsa = NULL; // RSA key pair - EC_KEY *ecdsa = NULL; // ECDSA key pair X509 *cert; // Certificate - char dns_name[1024]; // DNS: prefixed hostname - X509_EXTENSION *san_ext; // Extension for subjectAltName - ASN1_OCTET_STRING *san_asn1; // ASN1 string - time_t curtime; // Current time + ASN1_INTEGER *serial; // Serial number + ASN1_TIME *notBefore, // Initial date + *notAfter; // Expiration date X509_NAME *name; // Subject/issuer name + STACK_OF(X509_EXTENSION) *exts; // Extensions + X509_EXTENSION *ext; // Current extension BIO *bio; // Output file # else // HAVE_GNUTLS gnutls_x509_crt_t crt; // Self-signed certificate @@ -2477,7 +2648,7 @@ tls_make_certificate( cups_file_t *fp; // Key/cert file unsigned char buffer[8192]; // Buffer for key/cert data size_t bytes; // Number of bytes of data - unsigned char serial[4]; // Serial number buffer + unsigned char serial[8]; // Serial number buffer int status; // GNU TLS status # endif // HAVE_OPENSSL @@ -2499,6 +2670,35 @@ tls_make_certificate( papplLogClient(client, PAPPL_LOGLEVEL_ERROR, "Missing 'level' form field."); return (false); } + else if (!strcmp(level, "rsa-2048")) + { + credtype = _PAPPL_CREDTYPE_RSA_2048_SHA256; + } + else if (!strcmp(level, "rsa-3072")) + { + credtype = _PAPPL_CREDTYPE_RSA_3072_SHA256; + } + else if (!strcmp(level, "rsa-4096")) + { + credtype = _PAPPL_CREDTYPE_RSA_4096_SHA256; + } + else if (!strcmp(level, "ecdsa-p256")) + { + credtype = _PAPPL_CREDTYPE_ECDSA_P256_SHA256; + } + else if (!strcmp(level, "ecdsa-p384")) + { + credtype = _PAPPL_CREDTYPE_ECDSA_P384_SHA256; + } + else if (!strcmp(level, "ecdsa-p521")) + { + credtype = _PAPPL_CREDTYPE_ECDSA_P521_SHA256; + } + else + { + papplLogClient(client, PAPPL_LOGLEVEL_ERROR, "Unknown 'level' form field '%s'.", level); + return (false); + } if ((email = cupsGetOption("email", num_form, form)) == NULL) { @@ -2536,24 +2736,27 @@ tls_make_certificate( return (false); } - // Get all of the names this system is known by... + curtime = time(NULL); + + // Get the name this system is known by... papplSystemGetHostName(system, hostname, sizeof(hostname)); - if ((domain = strchr(hostname, '.')) != NULL) - { - // If the domain name is not hostname.local or hostname.lan, make that the - // second Subject Alternate Name... - if (strcmp(domain, ".local") && strcmp(domain, ".lan")) - papplCopyString(alt_names[num_alt_names ++], hostname, sizeof(alt_names[0])); - *domain = '\0'; + if (strstr(hostname, ".local")) + { + // Hostname is of the form "HOSTNAME.local"... + localname[0] = '\0'; } + else + { + // Hostname is not of the form "HOSTNAME.local"... + char *localptr; // Pointer into localname - // then add hostname as the first alternate name... - papplCopyString(alt_names[0], hostname, sizeof(alt_names[0])); - - // and finish up with hostname.lan and hostname.local as the final alternates... - snprintf(alt_names[num_alt_names ++], sizeof(alt_names[0]), "%s.lan", hostname); - snprintf(alt_names[num_alt_names ++], sizeof(alt_names[0]), "%s.local", hostname); + papplCopyString(localname, hostname, sizeof(localname)); + if ((localptr = strchr(localname, '.')) != NULL) + papplCopyString(localptr, ".local", sizeof(localname) - (size_t)(localptr - localname)); + else + papplCopyString(localname + strlen(localname), ".local", sizeof(localname) - strlen(localname)); + } // Store the certificate and private key in the CUPS "ssl" directory... home = getuid() ? getenv("HOME") : NULL; @@ -2584,47 +2787,12 @@ tls_make_certificate( # ifdef HAVE_OPENSSL // Create the paired encryption keys... - if ((pkey = EVP_PKEY_new()) == NULL) + if ((pkey = openssl_create_key(system, credtype)) == NULL) { papplLogClient(client, PAPPL_LOGLEVEL_ERROR, "Unable to create private key."); return (false); } - if (!strcmp(level, "rsa-2048")) - { - // 2048-bit RSA key... - rsaexp = BN_new(); - BN_set_word(rsaexp, RSA_F4); - rsa = RSA_new(); - RSA_generate_key_ex(rsa, 2048, rsaexp, NULL); - BN_free(rsaexp); - } - else if (!strcmp(level, "rsa-4096")) - { - // 4096-bit RSA key... - rsaexp = BN_new(); - BN_set_word(rsaexp, RSA_F4); - rsa = RSA_new(); - RSA_generate_key_ex(rsa, 4096, rsaexp, NULL); - BN_free(rsaexp); - } - else - { - // 384-bit ECDSA key... - ecdsa = EC_KEY_new_by_curve_name(NID_secp384r1); - } - - if (!rsa && !ecdsa) - { - papplLogClient(client, PAPPL_LOGLEVEL_ERROR, "Unable to create RSA/ECDSA key pair."); - return (false); - } - - if (rsa) - EVP_PKEY_assign_RSA(pkey, rsa); - else - EVP_PKEY_assign_EC_KEY(pkey, ecdsa); - // Create the self-signed certificate... if ((cert = X509_new()) == NULL) { @@ -2633,44 +2801,57 @@ tls_make_certificate( return (false); } - 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, curtime + duration * 365 * 86400); + X509_set_notAfter(cert, notAfter); + ASN1_TIME_free(notAfter); + + serial = ASN1_INTEGER_new(); + ASN1_INTEGER_set(serial, (long)curtime); + X509_set_serialNumber(cert, serial); + ASN1_INTEGER_free(serial); - ASN1_TIME_set(X509_get_notBefore(cert), curtime); - ASN1_TIME_set(X509_get_notAfter(cert), curtime * duration * 365 * 86400); - ASN1_INTEGER_set(X509_get_serialNumber(cert), (int)curtime); X509_set_pubkey(cert, pkey); - name = X509_get_subject_name(cert); - X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)country, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)hostname, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "emailAddress", MBSTRING_ASC, (unsigned char *)email, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, (unsigned char *)city, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)organization, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, (unsigned char *)org_unit, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, (unsigned char *)state, -1, -1, 0); + name = openssl_create_name(organization, org_unit, city, state, country, hostname, email); + X509_set_subject_name(cert, name); X509_set_issuer_name(cert, name); - for (i = 0; i < num_alt_names; i ++) - { - // The subjectAltName value for DNS names starts with a DNS: prefix... - snprintf(dns_name, sizeof(dns_name), "DNS: %s", alt_names[i]); + X509_NAME_free(name); - if ((san_asn1 = ASN1_OCTET_STRING_new()) == NULL) - break; + exts = sk_X509_EXTENSION_new_null(); + + // Add extension with DNS names and free buffer for GENERAL_NAME + if ((ext = openssl_create_san(hostname, localname)) == NULL) + goto done; + + sk_X509_EXTENSION_push(exts, ext); + + // Add other extensions required for TLS... + openssl_add_ext(exts, NID_basic_constraints, "critical,CA:FALSE,pathlen:0"); + openssl_add_ext(exts, NID_key_usage, "critical,digitalSignature,keyEncipherment"); + openssl_add_ext(exts, NID_ext_key_usage, "1.3.6.1.5.5.7.3.1"); // serverAuth OID + openssl_add_ext(exts, NID_subject_key_identifier, "hash"); + openssl_add_ext(exts, NID_authority_key_identifier, "keyid,issuer"); - ASN1_OCTET_STRING_set(san_asn1, (unsigned char *)dns_name, (int)strlen(dns_name)); - if (!X509_EXTENSION_create_by_NID(&san_ext, NID_subject_alt_name, 0, san_asn1)) + while ((ext = sk_X509_EXTENSION_pop(exts)) != NULL) + { + if (!X509_add_ext(cert, ext, -1)) { - ASN1_OCTET_STRING_free(san_asn1); - break; + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); + goto done; } - - X509_add_ext(cert, san_ext, -1); - X509_EXTENSION_free(san_ext); - ASN1_OCTET_STRING_free(san_asn1); } + X509_set_version(cert, 2); // v3 + + // Sign with our private key... X509_sign(cert, pkey, EVP_sha256()); // Save them... @@ -2720,12 +2901,27 @@ tls_make_certificate( // Create the paired encryption keys... gnutls_x509_privkey_init(&key); - if (!strcmp(level, "rsa-2048")) - gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 2048, 0); - else if (!strcmp(level, "rsa-4096")) - gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 4096, 0); - else - gnutls_x509_privkey_generate(key, GNUTLS_PK_ECDSA, 384, 0); + switch (credtype) + { + case _PAPPL_CREDTYPE_RSA_2048 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 2048, 0); + break; + case _PAPPL_CREDTYPE_RSA_3072 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 3072, 0); + break; + case _PAPPL_CREDTYPE_RSA_4096 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 4096, 0); + break; + case _PAPPL_CREDTYPE_ECDSA_P256 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_ECDSA, 256, 0); + break; + case _PAPPL_CREDTYPE_ECDSA_P384 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_ECDSA, 384, 0); + break; + case _PAPPL_CREDTYPE_ECDSA_P521 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_ECDSA, 521, 0); + break; + } // Save the private key... bytes = sizeof(buffer); @@ -2749,11 +2945,14 @@ tls_make_certificate( } // Create the self-signed certificate... - i = (int)(time(NULL) / 60); - serial[0] = (unsigned char)(i >> 24); - serial[1] = (unsigned char)(i >> 16); - serial[2] = (unsigned char)(i >> 8); - serial[3] = (unsigned char)i; + serial[0] = (unsigned char)(curtime >> 56); + serial[1] = (unsigned char)(curtime >> 48); + serial[2] = (unsigned char)(curtime >> 40); + serial[3] = (unsigned char)(curtime >> 32); + serial[4] = (unsigned char)(curtime >> 24); + serial[5] = (unsigned char)(curtime >> 16); + serial[6] = (unsigned char)(curtime >> 8); + serial[7] = (unsigned char)(curtime); gnutls_x509_crt_init(&crt); gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_X520_COUNTRY_NAME, 0, country, (unsigned)strlen(country)); @@ -2765,12 +2964,12 @@ tls_make_certificate( gnutls_x509_crt_set_dn_by_oid(crt, GNUTLS_OID_PKCS9_EMAIL, 0, email, (unsigned)strlen(email)); gnutls_x509_crt_set_key(crt, key); gnutls_x509_crt_set_serial(crt, serial, sizeof(serial)); - gnutls_x509_crt_set_activation_time(crt, time(NULL)); - gnutls_x509_crt_set_expiration_time(crt, time(NULL) + duration * 365 * 86400); + gnutls_x509_crt_set_activation_time(crt, curtime); + gnutls_x509_crt_set_expiration_time(crt, curtime + duration * 365 * 86400); gnutls_x509_crt_set_ca_status(crt, 0); - gnutls_x509_crt_set_subject_alt_name(crt, GNUTLS_SAN_DNSNAME, alt_names[0], (unsigned)strlen(alt_names[0]), GNUTLS_FSAN_SET); - for (i = 1; i < num_alt_names; i ++) - gnutls_x509_crt_set_subject_alt_name(crt, GNUTLS_SAN_DNSNAME, alt_names[i], (unsigned)strlen(alt_names[i]), GNUTLS_FSAN_APPEND); + gnutls_x509_crt_set_subject_alt_name(crt, GNUTLS_SAN_DNSNAME, hostname, (unsigned)strlen(hostname), GNUTLS_FSAN_SET); + if (localname[0]) + gnutls_x509_crt_set_subject_alt_name(crt, GNUTLS_SAN_DNSNAME, localname, (unsigned)strlen(localname), GNUTLS_FSAN_APPEND); 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); gnutls_x509_crt_set_version(crt, 3); @@ -2814,15 +3013,15 @@ tls_make_certificate( # define symlink(src,dst) CreateSymbolicLinkA(dst,src,SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) # endif // _WIN32 - for (i = 1; i < num_alt_names; i ++) + if (localname[0]) { char altfile[1024]; // Alternate cert/key filename - snprintf(altfile, sizeof(altfile), "%s/%s.key", ssldir, alt_names[i]); + snprintf(altfile, sizeof(altfile), "%s/%s.key", ssldir, localname); unlink(altfile); symlink(keyfile, altfile); - snprintf(altfile, sizeof(altfile), "%s/%s.crt", ssldir, alt_names[i]); + snprintf(altfile, sizeof(altfile), "%s/%s.crt", ssldir, localname); unlink(altfile); symlink(crtfile, altfile); } @@ -2853,20 +3052,17 @@ tls_make_certsignreq( *state, // State/province *country; // Country char hostname[256], // Hostname + localname[256], // Hostname.local crqfile[1024], // Certificate request file keyfile[1024]; // Private key file + _pappl_credtype_t credtype; // Type of credentials # ifdef HAVE_OPENSSL bool result = false; // Result of operations EVP_PKEY *pkey; // Private key - BIGNUM *rsaexp; // Public exponent for RSA keys - RSA *rsa = NULL; // RSA key pair - EC_KEY *ecdsa = NULL; // ECDSA key pair X509_REQ *crq; // Certificate request - char dns_name[1024]; // DNS: prefixed hostname - STACK_OF(X509_EXTENSION) *san_exts; // Extensions - X509_EXTENSION *san_ext; // Extension for subjectAltName - ASN1_OCTET_STRING *san_asn1; // ASN1 string X509_NAME *name; // Subject/issuer name + STACK_OF(X509_EXTENSION) *exts; // Extensions + X509_EXTENSION *ext; // Current extension BIO *bio; // Output file # else gnutls_x509_crq_t crq; // Certificate request @@ -2886,6 +3082,35 @@ tls_make_certsignreq( papplLogClient(client, PAPPL_LOGLEVEL_ERROR, "Missing 'level' form field."); return (false); } + else if (!strcmp(level, "rsa-2048")) + { + credtype = _PAPPL_CREDTYPE_RSA_2048_SHA256; + } + else if (!strcmp(level, "rsa-3072")) + { + credtype = _PAPPL_CREDTYPE_RSA_3072_SHA256; + } + else if (!strcmp(level, "rsa-4096")) + { + credtype = _PAPPL_CREDTYPE_RSA_4096_SHA256; + } + else if (!strcmp(level, "ecdsa-p256")) + { + credtype = _PAPPL_CREDTYPE_ECDSA_P256_SHA256; + } + else if (!strcmp(level, "ecdsa-p384")) + { + credtype = _PAPPL_CREDTYPE_ECDSA_P384_SHA256; + } + else if (!strcmp(level, "ecdsa-p521")) + { + credtype = _PAPPL_CREDTYPE_ECDSA_P521_SHA256; + } + else + { + papplLogClient(client, PAPPL_LOGLEVEL_ERROR, "Unknown 'level' form field '%s'.", level); + return (false); + } if ((email = cupsGetOption("email", num_form, form)) == NULL) { @@ -2923,54 +3148,39 @@ tls_make_certsignreq( return (false); } - // Store the certificate request and private key in the spool directory... - snprintf(keyfile, sizeof(keyfile), "%s/%s.key", system->directory, papplSystemGetHostName(system, hostname, sizeof(hostname))); - snprintf(crqfile, sizeof(crqfile), "%s/%s.csr", system->directory, hostname); - snprintf(crqpath, crqsize, "/%s.csr", hostname); - -# ifdef HAVE_OPENSSL - // Create the paired encryption keys... - if ((pkey = EVP_PKEY_new()) == NULL) - { - papplLogClient(client, PAPPL_LOGLEVEL_ERROR, "Unable to create private key."); - return (false); - } + // Get the name this system is known by... + papplSystemGetHostName(system, hostname, sizeof(hostname)); - if (!strcmp(level, "rsa-2048")) - { - // 2048-bit RSA key... - rsaexp = BN_new(); - BN_set_word(rsaexp, RSA_F4); - rsa = RSA_new(); - RSA_generate_key_ex(rsa, 2048, rsaexp, NULL); - BN_free(rsaexp); - } - else if (!strcmp(level, "rsa-4096")) + if (strstr(hostname, ".local")) { - // 4096-bit RSA key... - rsaexp = BN_new(); - BN_set_word(rsaexp, RSA_F4); - rsa = RSA_new(); - RSA_generate_key_ex(rsa, 4096, rsaexp, NULL); - BN_free(rsaexp); + // Hostname is of the form "HOSTNAME.local"... + localname[0] = '\0'; } else { - // 384-bit ECDSA key... - ecdsa = EC_KEY_new_by_curve_name(NID_secp384r1); + // Hostname is not of the form "HOSTNAME.local"... + char *localptr; // Pointer into localname + + papplCopyString(localname, hostname, sizeof(localname)); + if ((localptr = strchr(localname, '.')) != NULL) + papplCopyString(localptr, ".local", sizeof(localname) - (size_t)(localptr - localname)); + else + papplCopyString(localname + strlen(localname), ".local", sizeof(localname) - strlen(localname)); } - if (!rsa && !ecdsa) + // Store the certificate request and private key in the spool directory... + snprintf(keyfile, sizeof(keyfile), "%s/%s.key", system->directory, hostname); + snprintf(crqfile, sizeof(crqfile), "%s/%s.csr", system->directory, hostname); + snprintf(crqpath, crqsize, "/%s.csr", hostname); + +# ifdef HAVE_OPENSSL + // Create the paired encryption keys... + if ((pkey = openssl_create_key(system, credtype)) == NULL) { - papplLogClient(client, PAPPL_LOGLEVEL_ERROR, "Unable to create RSA/ECDSA key pair."); + papplLogClient(client, PAPPL_LOGLEVEL_ERROR, "Unable to create private key."); return (false); } - if (rsa) - EVP_PKEY_assign_RSA(pkey, rsa); - else - EVP_PKEY_assign_EC_KEY(pkey, ecdsa); - // Create the certificate request... if ((crq = X509_REQ_new()) == NULL) { @@ -2981,38 +3191,23 @@ tls_make_certsignreq( X509_REQ_set_pubkey(crq, pkey); - name = X509_REQ_get_subject_name(crq); - X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)country, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)hostname, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "emailAddress", MBSTRING_ASC, (unsigned char *)email, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, (unsigned char *)city, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)organization, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, (unsigned char *)org_unit, -1, -1, 0); - X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, (unsigned char *)state, -1, -1, 0); + name = openssl_create_name(organization, org_unit, city, state, country, hostname, email); - // The subjectAltName value for DNS names starts with a DNS: prefix... - snprintf(dns_name, sizeof(dns_name), "DNS: %s", hostname); + X509_REQ_set_subject_name(crq, name); + X509_NAME_free(name); - if ((san_asn1 = ASN1_OCTET_STRING_new()) == NULL) - goto done; + // Add extension with DNS names and free buffer for GENERAL_NAME + exts = sk_X509_EXTENSION_new_null(); - ASN1_OCTET_STRING_set(san_asn1, (unsigned char *)dns_name, (int)strlen(dns_name)); - if (!X509_EXTENSION_create_by_NID(&san_ext, NID_subject_alt_name, 0, san_asn1)) - { - ASN1_OCTET_STRING_free(san_asn1); + if ((ext = openssl_create_san(hostname, localname)) == NULL) goto done; - } - if ((san_exts = sk_X509_EXTENSION_new_null()) != NULL) - { - sk_X509_EXTENSION_push(san_exts, san_ext); - X509_REQ_add_extensions(crq, san_exts); - sk_X509_EXTENSION_free(san_exts); - } + sk_X509_EXTENSION_push(exts, ext); - X509_EXTENSION_free(san_ext); - ASN1_OCTET_STRING_free(san_asn1); + openssl_add_ext(exts, NID_key_usage, "critical,digitalSignature,keyEncipherment"); + openssl_add_ext(exts, NID_ext_key_usage, "1.3.6.1.5.5.7.3.1"); // serverAuth OID + X509_REQ_add_extensions(crq, exts); X509_REQ_sign(crq, pkey, EVP_sha256()); // Save them... @@ -3061,12 +3256,27 @@ tls_make_certsignreq( // Create the paired encryption keys... gnutls_x509_privkey_init(&key); - if (!strcmp(level, "rsa-2048")) - gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 2048, 0); - else if (!strcmp(level, "rsa-4096")) - gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 4096, 0); - else - gnutls_x509_privkey_generate(key, GNUTLS_PK_ECDSA, 384, 0); + switch (credtype) + { + case _PAPPL_CREDTYPE_RSA_2048 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 2048, 0); + break; + case _PAPPL_CREDTYPE_RSA_3072 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 3072, 0); + break; + case _PAPPL_CREDTYPE_RSA_4096 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_RSA, 4096, 0); + break; + case _PAPPL_CREDTYPE_ECDSA_P256 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_ECDSA, 256, 0); + break; + case _PAPPL_CREDTYPE_ECDSA_P384 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_ECDSA, 384, 0); + break; + case _PAPPL_CREDTYPE_ECDSA_P521 : + gnutls_x509_privkey_generate(key, GNUTLS_PK_ECDSA, 521, 0); + break; + } // Save the private key... bytes = sizeof(buffer); @@ -3100,6 +3310,8 @@ tls_make_certsignreq( gnutls_x509_crq_set_dn_by_oid(crq, GNUTLS_OID_PKCS9_EMAIL, 0, email, (unsigned)strlen(email)); gnutls_x509_crq_set_key(crq, key); gnutls_x509_crq_set_subject_alt_name(crq, GNUTLS_SAN_DNSNAME, hostname, (unsigned)strlen(hostname), GNUTLS_FSAN_SET); + if (localname[0]) + gnutls_x509_crt_set_subject_alt_name(crt, GNUTLS_SAN_DNSNAME, localname, (unsigned)strlen(localname), GNUTLS_FSAN_APPEND); gnutls_x509_crq_set_key_purpose_oid(crq, GNUTLS_KP_TLS_WWW_SERVER, 0); gnutls_x509_crq_set_key_usage(crq, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT); gnutls_x509_crq_set_version(crq, 3);