From a95796d6a535b0a5936b1274326f7670f581685b Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Sat, 8 Apr 2023 21:01:40 -0400 Subject: [PATCH] Add initial GNU TLS support code (Issue #50) --- cups/jwt.c | 485 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 471 insertions(+), 14 deletions(-) diff --git a/cups/jwt.c b/cups/jwt.c index 021a81264..fa4030a47 100644 --- a/cups/jwt.c +++ b/cups/jwt.c @@ -11,6 +11,7 @@ #include "jwt.h" #include "json-private.h" #ifdef HAVE_OPENSSL +# include # include # include #else @@ -79,8 +80,12 @@ static const char * const cups_jwa_algorithms[CUPS_JWA_MAX] = #ifdef HAVE_OPENSSL static BIGNUM *make_bignum(cups_json_t *jwk, const char *key); +static EC_KEY *make_ec_key(cups_json_t *jwk, bool verify); static RSA *make_rsa(cups_json_t *jwk); #else // HAVE_GNUTLS +static gnutls_datum_t *make_datum(cups_json_t *jwk, const char *key); +static gnutls_privkey_t *make_private_key(cups_json_t *jwk); +static gnutls_pubkey_t *make_public_key(cups_json_t *jwk); #endif // HAVE_OPENSSL static bool make_signature(cups_jwt_t *jwt, cups_jwa_t alg, cups_json_t *jwk, unsigned char *signature, size_t *sigsize); static char *make_string(cups_jwt_t *jwt, bool with_signature); @@ -229,8 +234,16 @@ cupsJWTHasValidSignature( unsigned char hash[128]; // Hash ssize_t hash_len; // Length of hash #ifdef HAVE_OPENSSL - RSA *rsa; // Public key + RSA *rsa; // RSA public key + EC_KEY *ec; // ECDSA public key + static int nids[] = { NID_sha256, NID_sha384, NID_sha512 }; + // Hash NIDs #else // HAVE_GNUTLS + gnutls_pubkey_t *key; // Public key + gnutls_datum_t text_datum, // Text datum + sig_datum; // Signature datum + static gnutls_sign_algorithm_t algs[] = { GNUTLS_DIG_SHA256, GNUTLS_DIG_SHA384, GNUTLS_DIG_SHA512 }; + // Hash algorithms #endif // HAVE_OPENSSL @@ -238,6 +251,8 @@ cupsJWTHasValidSignature( if (!jwt || !jwt->signature || !jwk) return (false); + fprintf(stderr, "orig sig(%u) = %02X%02X%02X%02X...%02X%02X%02X%02X\n", (unsigned)jwt->sigsize, jwt->signature[0], jwt->signature[1], jwt->signature[2], jwt->signature[3], jwt->signature[jwt->sigsize - 4], jwt->signature[jwt->sigsize - 3], jwt->signature[jwt->sigsize - 2], jwt->signature[jwt->sigsize - 1]); + DEBUG_printf(("1cupsJWTHasValidSignature: orig sig(%u) = %02X%02X%02X%02X...%02X%02X%02X%02X", (unsigned)jwt->sigsize, jwt->signature[0], jwt->signature[1], jwt->signature[2], jwt->signature[3], jwt->signature[jwt->sigsize - 4], jwt->signature[jwt->sigsize - 3], jwt->signature[jwt->sigsize - 2], jwt->signature[jwt->sigsize - 1])); switch (jwt->sigalg) { case CUPS_JWA_HS256 : @@ -247,7 +262,6 @@ cupsJWTHasValidSignature( if (!make_signature(jwt, jwt->sigalg, jwk, signature, &sigsize)) break; - DEBUG_printf(("1cupsJWTHasValidSignature: orig sig(%u) = %02X%02X%02X%02X...%02X%02X%02X%02X", (unsigned)jwt->sigsize, jwt->signature[0], jwt->signature[1], jwt->signature[2], jwt->signature[3], jwt->signature[jwt->sigsize - 4], jwt->signature[jwt->sigsize - 3], jwt->signature[jwt->sigsize - 2], jwt->signature[jwt->sigsize - 1])); DEBUG_printf(("1cupsJWTHasValidSignature: calc sig(%u) = %02X%02X%02X%02X...%02X%02X%02X%02X", (unsigned)sigsize, signature[0], signature[1], signature[2], signature[3], signature[sigsize - 4], signature[sigsize - 3], signature[sigsize - 2], signature[sigsize - 1])); // Compare and return the result... @@ -257,22 +271,67 @@ cupsJWTHasValidSignature( case CUPS_JWA_RS256 : case CUPS_JWA_RS384 : case CUPS_JWA_RS512 : + // Get the message hash... text = make_string(jwt, false); text_len = strlen(text); - hash_len = cupsHashData(cups_jwa_algorithms[jwt->sigalg], text, text_len, hash, sizeof(hash)); #ifdef HAVE_OPENSSL + hash_len = cupsHashData(cups_jwa_algorithms[jwt->sigalg], text, text_len, hash, sizeof(hash)); + if ((rsa = make_rsa(jwk)) != NULL) { - static int nids[] = { NID_sha256, NID_sha384, NID_sha512 }; - // Hash NIDs - ret = RSA_verify(nids[jwt->sigalg - CUPS_JWA_RS256], hash, hash_len, jwt->signature, jwt->sigsize, rsa) == 1; RSA_free(rsa); } + #else // HAVE_GNUTLS + if ((key = make_public_key(jwk)) != NULL) + { + text_datum.data = text; + text_datum.size = (unsigned)text_len; + sig_datum.data = jwt->signature; + sig_datum.size = (unsigned)jwt->sigsize; + + ret = !gnutls_pubkey_verify_data2(key, algs[jwt->sigalg - CUPS_JWA_RS256], 0, &text_datum, &sig_datum); + } #endif // HAVE_OPENSSL + + // Free memory + free(text); + break; + + case CUPS_JWA_ES256 : + case CUPS_JWA_ES384 : + case CUPS_JWA_ES512 : + // Get the message hash... + text = make_string(jwt, false); + text_len = strlen(text); + +#ifdef HAVE_OPENSSL + hash_len = cupsHashData(cups_jwa_algorithms[jwt->sigalg], text, text_len, hash, sizeof(hash)); + + if ((ec = make_ec_key(jwk, true)) != NULL) + { + ret = ECDSA_verify(0, hash, hash_len, jwt->signature, jwt->sigsize, ec) == 1; + + EC_KEY_free(ec); + } + +#else // HAVE_GNUTLS + if ((key = make_public_key(jwk)) != NULL) + { + text_datum.data = text; + text_datum.size = (unsigned)text_len; + sig_datum.data = jwt->signature; + sig_datum.size = (unsigned)jwt->sigsize; + + ret = !gnutls_pubkey_verify_data2(key, algs[jwt->sigalg - CUPS_JWA_ES256], 0, &text_datum, &sig_datum); + } +#endif // HAVE_OPENSSL + + // Free memory + free(text); break; default : @@ -540,21 +599,91 @@ make_bignum(cups_json_t *jwk, // I - JSON web key { const char *value, // Key value *value_end; // End of value - unsigned char value_bytes[512]; // Decoded value + unsigned char value_bytes[1024]; // Decoded value size_t value_len; // Length of value + // See if we have the value... if ((value = cupsJSONGetString(cupsJSONFind(jwk, key))) == NULL) return (NULL); + // Decode and validate... value_len = sizeof(value_bytes); if (!httpDecode64((char *)value_bytes, &value_len, value, &value_end) || (value_end && *value_end)) return (NULL); + // Convert to a BIGNUM... return (BN_bin2bn(value_bytes, value_len, NULL)); } +// +// 'make_ec_key()' - Make an ECDSA signing/verification object. +// + +static EC_KEY * // O - EC object or `NULL` on error +make_ec_key(cups_json_t *jwk, // I - JSON web key + bool verify) // I - `true` for verification only, `false` for signing/verification +{ + EC_KEY *ec = NULL; // EC object + EC_GROUP *group; // Group parameters + EC_POINT *point; // Public key point + const char *crv; // EC curve ("P-256", "P-384", or "P-521") + BIGNUM *x, // X coordinate + *y, // Y coordinate + *d; // Private key + + + crv = cupsJSONGetString(cupsJSONFind(jwk, "crv")); + x = make_bignum(jwk, "x"); + y = make_bignum(jwk, "y"); + d = verify ? NULL : make_bignum(jwk, "d"); + + if (!crv || ((!x || !y) && !d)) + goto ec_done; + + if (!strcmp(crv, "P-256")) + ec = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + else if (!strcmp(crv, "P-384")) + ec = EC_KEY_new_by_curve_name(NID_secp384r1); + else if (!strcmp(crv, "P-521")) + ec = EC_KEY_new_by_curve_name(NID_secp521r1); + else + goto ec_done; + + group = (EC_GROUP *)EC_KEY_get0_group(ec); + point = EC_POINT_new(group); + + if (d) + { + // Set private key... + EC_KEY_set_private_key(ec, d); + + // Create a new public key + EC_POINT_mul(group, point, d, NULL, NULL, NULL); + } + else + { + // Create a public key using the supplied coordinates... + EC_POINT_set_affine_coordinates_GFp(group, point, x, y, NULL); + } + + // Set public key... + EC_KEY_set_public_key(ec, point); + + ec_done: + + if (!ec) + { + BN_free(x); + BN_free(y); + BN_free(d); + } + + return (ec); +} + + // // 'make_rsa()' - Create an RSA signing/verification object. // @@ -583,7 +712,7 @@ make_rsa(cups_json_t *jwk) // I - JSON web key qi = make_bignum(jwk, "qi"); if (!n || !e) - return (NULL); + goto rsa_done; rsa = RSA_new(); RSA_set0_key(rsa, n, e, d); @@ -592,12 +721,272 @@ make_rsa(cups_json_t *jwk) // I - JSON web key if (dp && dq && qi) RSA_set0_crt_params(rsa, dp, dq, qi); + rsa_done: + + if (!rsa) + { + BN_free(n); + BN_free(e); + BN_free(d); + BN_free(p); + BN_free(q); + BN_free(dp); + BN_free(dq); + BN_free(qi); + } + return (rsa); } - #else // HAVE_GNUTLS +// +// 'make_datum()' - Make a datum value for a parameter. +// + +static gnutls_datum_t * // O - Datum or `NULL` +make_datum(cups_json_t *jwk, // I - JSON web key + const char *key) // I - Object key +{ + const char *value, // Key value + *value_end; // End of value + unsigned char value_bytes[1024]; + // Decoded value + size_t value_len; // Length of value + gnutls_datum_t *datum; // GNU TLS datum + + + // See if we have the value... + if ((value = cupsJSONGetString(cupsJSONFind(jwk, key))) == NULL) + return (NULL); + + // Decode and validate... + value_len = sizeof(value_bytes); + if (!httpDecode64((char *)value_bytes, &value_len, value, &value_end) || (value_end && *value_end)) + return (NULL); + + // Convert to a datum... + if ((datum = (gnutls_datum_t *)calloc(1, sizeof(gnutls_datum_t) + value_len)) != NULL) + { + // Set pointer and length, and copy value bytes... + datum->data = (unsigned char *)(datum + 1); + datum->size = (unsigned)value_len; + + memcpy(datum + 1, value_bytes, value_len); + } + + return (datum); +} + + +// +// 'make_private_key()' - Make a private key for EC or RSA signing. +// + +static gnutls_privkey_t * // O - Private key or `NULL` +make_private_key(cups_json_t *jwk) // I - JSON web key +{ + const char *kty; // Key type + gnutls_privkey_t *key = NULL; // Private key + + + if ((kty = cupsJSONGetString(cupsJSONFind(jwk, "kty"))) == NULL) + { + // No type so we can't load it... + return (NULL); + } + else if (!strcmp(kty, "RSA")) + { + // Get RSA parameters... + gnutls_datum_t *n, // Public key modulus + *e, // Public key exponent + *d, // Private key exponent + *p, // Private key first prime factor + *q, // Private key second prime factor + *dp, // First factor exponent + *dq, // Second factor exponent + *qi; // First CRT coefficient + + n = make_datum(jwk, "n"); + e = make_datum(jwk, "e"); + d = make_datum(jwk, "d"); + p = make_datum(jwk, "p"); + q = make_datum(jwk, "q"); + dp = make_datum(jwk, "dp"); + dq = make_datum(jwk, "dq"); + qi = make_datum(jwk, "qi"); + + if (n && e && d && p && q && (key = (gnutls_privkey_t *)calloc(1, sizeof(gnutls_privkey_t))) != NULL) + { + // Import RSA private key... + if (gnutls_privkey_init(key)) + { + free(key); + key = NULL; + } + else if (gnutls_privkey_import_rsa_raw(key, n, e, d, p, q, qi, dp, dq)) + { + free(key); + key = NULL; + } + } + + // Free memory... + free(n); + free(e); + free(d); + free(p); + free(q); + free(dp); + free(dq); + free(qi); + } + else if (!strcmp(kty, "EC")) + { + // Get EC parameters... + const char *crv; // EC curve ("P-256", "P-384", or "P-521") + gnutls_ecc_curve_t curve; // Curve constant + gnutls_datum *x, // X coordinate + *y, // Y coordinate + *d; // Private key + + crv = cupsJSONGetString(cupsJSONFind(jwk, "crv")); + + if (!crv) + return (NULL); + else if (!strcmp(crv, "P-256")) + curve = GNUTLS_ECC_CURVE_SECP256R1; + else if (!strcmp(crv, "P-384")) + curve = GNUTLS_ECC_CURVE_SECP384R1; + else if (!strcmp(crv, "P-521")) + curve = GNUTLS_ECC_CURVE_SECP521R1; + else + return (NULL); + + x = make_datum(jwk, "x"); + y = make_datum(jwk, "y"); + d = make_datum(jwk, "d"); + + if (x && y && d && (key = (gnutls_privkey_t *)calloc(1, sizeof(gnutls_privkey_t))) != NULL) + { + // Import EC private key... + if (gnutls_privkey_init(key)) + { + free(key); + key = NULL; + } + else if (gnutls_privkey_import_ecc_raw(key, curve, x, y, d)) + { + free(key); + key = NULL; + } + } + + // Free memory... + free(x); + free(y); + free(d); + } + + // Return whatever key we got... + return (key); +} + + +// +// 'make_public_key()' - Make a public key for EC or RSA verification. +// + +static gnutls_pubkey_t * // O - Public key or `NULL` +make_public_key(cups_json_t *jwk) // I - JSON web key +{ + const char *kty; // Key type + gnutls_pubkey_t *key = NULL; // Private key + + + if ((kty = cupsJSONGetString(cupsJSONFind(jwk, "kty"))) == NULL) + { + // No type so we can't load it... + return (NULL); + } + else if (!strcmp(kty, "RSA")) + { + // Get RSA parameters... + gnutls_datum_t *n, // Public key modulus + *e; // Public key exponent + + + n = make_bignum(jwk, "n"); + e = make_bignum(jwk, "e"); + + if (n && e && (key = (gnutls_pubkey_t *)calloc(1, sizeof(gnutls_pubkey_t))) != NULL) + { + // Import RSA private key... + if (gnutls_pubkey_init(key)) + { + free(key); + key = NULL; + } + else if (gnutls_pubkey_import_rsa_raw(key, n, e)) + { + free(key); + key = NULL; + } + } + + // Free memory and return... + free(n); + free(e); + } + else if (!strcmp(kty, "EC")) + { + // Get EC parameters... + const char *crv; // EC curve ("P-256", "P-384", or "P-521") + gnutls_ecc_curve_t curve; // Curve constant + gnutls_datum *x, // X coordinate + *y, // Y coordinate + *d; // Private key + + crv = cupsJSONGetString(cupsJSONFind(jwk, "crv")); + + if (!crv) + return (NULL); + else if (!strcmp(crv, "P-256")) + curve = GNUTLS_ECC_CURVE_SECP256R1; + else if (!strcmp(crv, "P-384")) + curve = GNUTLS_ECC_CURVE_SECP384R1; + else if (!strcmp(crv, "P-521")) + curve = GNUTLS_ECC_CURVE_SECP521R1; + else + return (NULL); + + x = make_datum(jwk, "x"); + y = make_datum(jwk, "y"); + d = make_datum(jwk, "d"); + + if (x && y && d && (key = (gnutls_privkey_t *)calloc(1, sizeof(gnutls_privkey_t))) != NULL) + { + // Import EC private key... + if (gnutls_privkey_init(key)) + { + free(key); + key = NULL; + } + else if (gnutls_privkey_import_ecc_raw(key, curve, x, y, d)) + { + free(key); + key = NULL; + } + } + + // Free memory... + free(x); + free(y); + free(d); + } + + return (key); +} #endif // HAVE_OPENSSL @@ -612,9 +1001,19 @@ make_signature(cups_jwt_t *jwt, // I - JWT unsigned char *signature,// I - Signature buffer size_t *sigsize) // IO - Signature size { - bool ret = false; // Return value - char *text; // JWS Signing Input - size_t text_len; // Length of signing input + bool ret = false; // Return value + char *text; // JWS Signing Input + size_t text_len; // Length of signing input +#ifdef HAVE_OPENSSL + static int nids[] = { NID_sha256, NID_sha384, NID_sha512 }; + // Hash NIDs +#else // HAVE_GNUTLS + gnutls_privkey_t *key; // Private key + gnutls_datum_t text_datum, // Text datum + sig_datum; // Signature datum + static gnutls_sign_algorithm_t algs[] = { GNUTLS_DIG_SHA256, GNUTLS_DIG_SHA384, GNUTLS_DIG_SHA512 }; + // Hash algorithms +#endif // HAVE_OPENSSL // Initialize default signature... @@ -654,8 +1053,6 @@ make_signature(cups_jwt_t *jwt, // I - JWT // Length of signature #ifdef HAVE_OPENSSL RSA *rsa; // RSA public/private key - static int nids[] = { NID_sha256, NID_sha384, NID_sha512 }; - // Hash NIDs if ((rsa = make_rsa(jwk)) != NULL) { @@ -669,6 +1066,66 @@ make_signature(cups_jwt_t *jwt, // I - JWT RSA_free(rsa); } #else // HAVE_GNUTLS + if ((key = make_private_key(jwk)) != NULL) + { + text_datum.data = text; + text_datum.size = (unsigned)text_len; + sig_datum.data = NULL; + sig_datum.size = 0; + + if (!gnutls_privkey_sign_data(key, algs[alg - CUPS_JWA_RS256], 0, &hash_datum, &sig_datum) && sig_datum.size <= *sigsize) + { + memcpy(signature, sig_datum.data, sig_datum.size); + *sigsize = sig_datum.size; + ret = true; + } + + gnutls_free(sig_datum.data); + gnutls_privkey_deinit(key); + free(key); + } +#endif // HAVE_OPENSSL + } + else if (alg >= CUPS_JWA_ES256 && alg <= CUPS_JWA_ES512) + { + // ECDSA P-256 SHA-256/384/512 + unsigned char hash[128]; // SHA-256/384/512 hash + ssize_t hash_len; // Length of hash + unsigned siglen = (unsigned)*sigsize; + // Length of signature +#ifdef HAVE_OPENSSL + EC_KEY *ec; // EC public/private key + + if ((ec = make_ec_key(jwk, false)) != NULL) + { + hash_len = cupsHashData(cups_jwa_algorithms[alg], text, text_len, hash, sizeof(hash)); + if (ECDSA_sign(0, hash, hash_len, signature, &siglen, ec) == 1) + { + *sigsize = siglen; + ret = true; + } + + EC_KEY_free(ec); + } +#else // HAVE_GNUTLS + if ((key = make_private_key(jwk)) != NULL) + { + text_datum.data = text; + text_datum.size = (unsigned)text_len; + sig_datum.data = NULL; + sig_datum.size = 0; + + if (!gnutls_privkey_sign_data(key, algs[alg - CUPS_JWA_ES256], 0, &hash_datum, &sig_datum) && sig_datum.size <= *sigsize) + { + memcpy(signature, sig_datum.data, sig_datum.size); + *sigsize = sig_datum.size; + ret = true; + } + + gnutls_free(sig_datum.data); + gnutls_privkey_deinit(key); + free(key); + } #endif // HAVE_OPENSSL }