diff --git a/cups/jwt.c b/cups/jwt.c index 44f2ca423..339d0c662 100644 --- a/cups/jwt.c +++ b/cups/jwt.c @@ -81,6 +81,7 @@ static const char * const cups_jwa_algorithms[CUPS_JWA_MAX] = // Local functions... // +static cups_json_t *find_key(cups_json_t *jwk, cups_jwa_t sigalg, const char *kid); #ifdef HAVE_OPENSSL static BIGNUM *make_bignum(cups_json_t *jwk, const char *key); static void make_bnstring(const BIGNUM *bn, char *buffer, size_t bufsize); @@ -392,7 +393,7 @@ cupsJWTHasValidSignature( if (!jwt || !jwt->signature || !jwk) return (false); - 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: sigalg=%d, orig sig[%u]=<%02X%02X%02X%02X...%02X%02X%02X%02X>", jwt->sigalg, (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) { @@ -416,6 +417,7 @@ cupsJWTHasValidSignature( // Get the message hash... text = make_string(jwt, false); text_len = strlen(text); + jwk = find_key(jwk, jwt->sigalg, jwt->sigkid); #ifdef HAVE_OPENSSL hash_len = cupsHashData(cups_jwa_algorithms[jwt->sigalg], text, text_len, hash, sizeof(hash)); @@ -451,6 +453,7 @@ cupsJWTHasValidSignature( // Get the message hash... text = make_string(jwt, false); text_len = strlen(text); + jwk = find_key(jwk, jwt->sigalg, jwt->sigkid); #ifdef HAVE_OPENSSL hash_len = cupsHashData(cups_jwa_algorithms[jwt->sigalg], text, text_len, hash, sizeof(hash)); @@ -523,7 +526,8 @@ cupsJWTImportString( cups_jwt_t *jwt; // JWT object size_t datalen; // Size of data char data[65536]; // Data - const char *alg; // Signature algorithm, if any + const char *kid, // Key identifier + *alg; // Signature algorithm, if any // Allocate a JWT... @@ -577,8 +581,6 @@ cupsJWTImportString( // Import JSON... cups_json_t *json, // JSON data *json_value, // BASE64URL-encoded string value node - *header, // Unprotected header - *kid, // Key ID node *signatures, // Signatures array *signature; // Signature element to load const char *value, // C string value @@ -664,26 +666,39 @@ cupsJWTImportString( memcpy(jwt->signature, data, datalen); jwt->sigsize = datalen; } - - if ((header = cupsJSONFind(signature, "header")) != NULL && (kid = cupsJSONFind(header, "kid")) != NULL && (value = cupsJSONGetString(kid)) != NULL) - jwt->sigkid = strdup(value); } +#ifdef DEBUG + if (jwt->sigsize >= 8) + DEBUG_printf("1cupsJWTImportString: signature[%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]); + else if (jwt->sigsize > 0) + DEBUG_printf("1cupsJWTImportString: signature[%u]=<...>", (unsigned)jwt->sigsize); +#endif // DEBUG + // Check the algorithm used in the protected header... if ((alg = cupsJSONGetString(cupsJSONFind(jwt->jose, "alg"))) != NULL) { cups_jwa_t sigalg; // Signing algorithm + DEBUG_printf("1cupsJWTImportString: alg=\"%s\"", alg); + for (sigalg = CUPS_JWA_NONE; sigalg < CUPS_JWA_MAX; sigalg ++) { if (!strcmp(alg, cups_jwa_strings[sigalg])) { jwt->sigalg = sigalg; + DEBUG_printf("1cupsJWTImportString: sigalg=%d", sigalg); break; } } } + if ((kid = cupsJSONGetString(cupsJSONFind(jwt->jose, "kid"))) != NULL) + { + DEBUG_printf("1cupsJWTImportString: kid=\"%s\"", kid); + jwt->sigkid = strdup(kid); + } + // Can't have signature with none or no signature for !none... if ((jwt->sigalg == CUPS_JWA_NONE) != (jwt->sigsize == 0)) goto import_error; @@ -1193,6 +1208,67 @@ cupsJWTSign(cups_jwt_t *jwt, // I - JWT object } +// +// 'find_key()' - Find the key by name or algorithm. +// + +static cups_json_t * // O - Key data +find_key(cups_json_t *jwk, // I - Key set + cups_jwa_t alg, // I - Signature algorithm + const char *kid) // I - Signature key ID +{ + cups_json_t *keys; // Array of keys + + + if ((keys = cupsJSONFind(jwk, "keys")) != NULL) + { + // Full key set, find the key we need to use... + size_t i, // Looping var + count; // Number of keys + cups_json_t *current; // Current key + const char *curkid, // Current key ID + *curkty; // Current key type + + count = cupsJSONGetCount(keys); + + if (kid) + { + // Find the matching key ID + for (i = 0; i < count; i ++) + { + current = cupsJSONGetChild(keys, i); + curkid = cupsJSONGetString(cupsJSONFind(current, "kid")); + + if (curkid && !strcmp(curkid, kid)) + { + DEBUG_printf("4make_signature: Found matching key \"%s\" at %p.", curkid, (void *)current); + jwk = current; + break; + } + } + } + else + { + // Find a key that can be used for the specified algorithm + for (i = 0; i < count; i ++) + { + current = cupsJSONGetChild(keys, i); + curkty = cupsJSONGetString(cupsJSONFind(current, "kty")); + + if (((!curkty || !strcmp(curkty, "ocy")) && alg >= CUPS_JWA_HS256 && alg <= CUPS_JWA_HS512) || (curkty && !strcmp(curkty, "RSA") && alg >= CUPS_JWA_RS256 && alg <= CUPS_JWA_RS512) || (curkty && !strcmp(curkty, "EC") && alg >= CUPS_JWA_ES256 && alg <= CUPS_JWA_ES512)) + { + DEBUG_printf("4make_signature: Found compatible key \"%s\" at %p.", cupsJSONGetString(cupsJSONFind(current, "kid")), (void *)current); + jwk = current; + break; + } + } + } + } + + return (jwk); +} + + #ifdef HAVE_OPENSSL // // 'make_bignum()' - Make a BIGNUM for the specified key. @@ -1623,7 +1699,6 @@ make_signature(cups_jwt_t *jwt, // I - JWT const char **sigkid) // IO - Key ID string, if any { bool ret = false; // Return value - cups_json_t *keys; // Array of keys char *text; // JWS Signing Input size_t text_len; // Length of signing input #ifdef HAVE_OPENSSL @@ -1638,52 +1713,12 @@ make_signature(cups_jwt_t *jwt, // I - JWT #endif // HAVE_OPENSSL + DEBUG_printf("3make_signature(jwt=%p, alg=%d, jwk=%p, signature=%p, sigsize=%p(%u), sigkid=%p(%s))", (void *)jwt, alg, (void *)jwk, (void *)signature, (void *)sigsize, (unsigned)*sigsize, (void *)sigkid, *sigkid); + // Get text to sign... text = make_string(jwt, false); text_len = strlen(text); - - if ((keys = cupsJSONFind(jwk, "keys")) != NULL) - { - // Full key set, find the key we need to use... - size_t i, // Looping var - count; // Number of keys - cups_json_t *current; // Current key - const char *curkid, // Current key ID - *curkty; // Current key type - - count = cupsJSONGetCount(keys); - - if (*sigkid) - { - // Find the matching key ID - for (i = 0; i < count; i ++) - { - current = cupsJSONGetChild(keys, i); - curkid = cupsJSONGetString(cupsJSONFind(current, "kid")); - - if (curkid && !strcmp(curkid, *sigkid)) - { - jwk = current; - break; - } - } - } - else - { - // Find a key that can be used for the specified algorithm - for (i = 0; i < count; i ++) - { - current = cupsJSONGetChild(keys, i); - curkty = cupsJSONGetString(cupsJSONFind(current, "kty")); - - if (((!curkty || !strcmp(curkty, "ocy")) && alg >= CUPS_JWA_HS256 && alg <= CUPS_JWA_HS512) || (curkty && !strcmp(curkty, "RSA") && alg >= CUPS_JWA_RS256 && alg <= CUPS_JWA_RS512) || (curkty && !strcmp(curkty, "EC") && alg >= CUPS_JWA_ES256 && alg <= CUPS_JWA_ES512)) - { - jwk = current; - break; - } - } - } - } + jwk = find_key(jwk, alg, *sigkid); if (alg >= CUPS_JWA_HS256 && alg <= CUPS_JWA_HS512) { @@ -1693,6 +1728,8 @@ make_signature(cups_jwt_t *jwt, // I - JWT size_t key_len; // Length of key ssize_t hmac_len; // Length of HMAC + DEBUG_puts("4make_signature: HMAC signature"); + // Get key... memset(key, 0, sizeof(key)); k = cupsJSONGetString(cupsJSONFind(jwk, "k")); @@ -1709,6 +1746,8 @@ make_signature(cups_jwt_t *jwt, // I - JWT else if (alg >= CUPS_JWA_RS256 && alg <= CUPS_JWA_RS512) { // RSASSA-PKCS1-v1_5 SHA-256/384/512 + DEBUG_puts("4make_signature: RSA signature"); + #ifdef HAVE_OPENSSL unsigned char hash[128]; // SHA-256/384/512 hash ssize_t hash_len; // Length of hash @@ -1750,6 +1789,8 @@ make_signature(cups_jwt_t *jwt, // I - JWT else if (alg >= CUPS_JWA_ES256 && alg <= CUPS_JWA_ES512) { // ECDSA P-256 SHA-256/384/512 + DEBUG_puts("4make_signature: ECDSA signature"); + static unsigned sig_sizes[3] = // Sizes of signatures { 64, 96, 132 }; #ifdef HAVE_OPENSSL @@ -1827,6 +1868,8 @@ make_signature(cups_jwt_t *jwt, // I - JWT done: + DEBUG_printf("4make_signature: Returning %s.", ret ? "true" : "false"); + free(text); if (ret) diff --git a/cups/testjwt.c b/cups/testjwt.c index 175296e35..be3fc0972 100644 --- a/cups/testjwt.c +++ b/cups/testjwt.c @@ -1,7 +1,7 @@ // // JWT API unit tests for CUPS. // -// Copyright © 2023 by OpenPrinting. +// Copyright © 2023-2024 by OpenPrinting. // // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. @@ -293,11 +293,72 @@ main(int argc, // I - Number of command-line arguments else { // Try loading JWT string on the command-line... + cups_json_t *jwks = NULL; // JWT Key Set, if any + for (i = 1; i < argc; i ++) { - if ((jwt = cupsJWTImportString(argv[i], CUPS_JWS_FORMAT_COMPACT)) != NULL) + if (!access(argv[i], R_OK)) + { + if ((jwks = cupsJSONImportFile(argv[i])) == NULL) + { + fprintf(stderr, "%s: %s\n", argv[i], cupsGetErrorString()); + return (1); + } + } + else if ((jwt = cupsJWTImportString(argv[i], CUPS_JWS_FORMAT_COMPACT)) != NULL) { -// printf("%s: OK, %u key/value pairs in root object.\n", argv[i], (unsigned)(cupsJSONGetCount(json) / 2)); + cups_json_t *claims = cupsJWTGetClaims(jwt); + // All claims + cups_json_t *headers = cupsJWTGetHeaders(jwt); + // All JOSE headers + char *temp; // Temporary string + const char *aud = cupsJWTGetClaimString(jwt, CUPS_JWT_AUD); + // Audience + const char *iss = cupsJWTGetClaimString(jwt, CUPS_JWT_ISS); + // Issuer + const char *jti = cupsJWTGetClaimString(jwt, CUPS_JWT_JTI); + // JWT ID + const char *name = cupsJWTGetClaimString(jwt, CUPS_JWT_NAME); + // Display name + const char *sub = cupsJWTGetClaimString(jwt, CUPS_JWT_SUB); + // Subject (username/ID) + double iat = cupsJWTGetClaimNumber(jwt, CUPS_JWT_IAT); + // Issue time + double exp = cupsJWTGetClaimNumber(jwt, CUPS_JWT_EXP); + // Expiration time + double nbf = cupsJWTGetClaimNumber(jwt, CUPS_JWT_NBF); + // Not before time + char date[256]; // Date + + if (iss) + printf("Issuer: %s\n", iss); + if (name) + printf("Display Name: %s\n", name); + if (sub) + printf("Subject: %s\n", sub); + if (aud) + printf("Audience: %s\n", aud); + if (jti) + printf("JWT ID: %s\n", jti); + if (iat > 0.0) + printf("Issued On: %s\n", httpGetDateString((time_t)iat, date, sizeof(date))); + if (exp > 0.0) + printf("Expires On: %s\n", httpGetDateString((time_t)exp, date, sizeof(date))); + if (nbf > 0.0) + printf("Not Before: %s\n", httpGetDateString((time_t)nbf, date, sizeof(date))); + printf("Valid: %s\n", jwks ? (cupsJWTHasValidSignature(jwt, jwks) ? "yes" : "no") : "unknown"); + + if ((temp = cupsJSONExportString(headers)) != NULL) + { + printf("\njose=%s\n", temp); + free(temp); + } + + if ((temp = cupsJSONExportString(claims)) != NULL) + { + printf("\nclaims=%s\n", temp); + free(temp); + } cupsJWTDelete(jwt); } diff --git a/pdfio b/pdfio index 206f75403..986c5f043 160000 --- a/pdfio +++ b/pdfio @@ -1 +1 @@ -Subproject commit 206f75403a085c6bf2033f8e2c0eae2c81ac4e82 +Subproject commit 986c5f0438ea71f549244b841c3775d94659849a