Skip to content

Commit

Permalink
Merge JWT fixes from libcups v3.
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelrsweet committed Sep 9, 2024
1 parent dfb947e commit aa32743
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 54 deletions.
156 changes: 104 additions & 52 deletions cups/jwt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -420,7 +421,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)
{
Expand All @@ -444,6 +445,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));
Expand Down Expand Up @@ -479,6 +481,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));
Expand Down Expand Up @@ -553,7 +556,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...
Expand Down Expand Up @@ -607,8 +611,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
Expand Down Expand Up @@ -694,26 +696,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;
Expand Down Expand Up @@ -1221,13 +1236,19 @@ cupsJWTSign(cups_jwt_t *jwt, // I - JWT object

// Create new signature...
if (!make_signature(jwt, alg, jwk, signature, &sigsize, &sigkid))
{
DEBUG_puts("2cupsJWTSign: Unable to create signature.");
return (false);
}

if (sigkid)
jwt->sigkid = strdup(sigkid);

if ((jwt->signature = malloc(sigsize)) == NULL)
{
DEBUG_printf("2cupsJWTSign: Unable to allocate %d bytes for signature.", (int)sigsize);
return (false);
}

memcpy(jwt->signature, signature, sigsize);
jwt->sigalg = alg;
Expand All @@ -1237,6 +1258,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.
Expand Down Expand Up @@ -1667,7 +1749,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
Expand All @@ -1682,52 +1763,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)
{
Expand All @@ -1737,6 +1778,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"));
Expand All @@ -1753,6 +1796,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
Expand Down Expand Up @@ -1794,6 +1839,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
Expand Down Expand Up @@ -1859,7 +1906,10 @@ make_signature(cups_jwt_t *jwt, // I - JWT
gnutls_free(r.data);
gnutls_free(s.data);
}

else
{
DEBUG_printf("4make_signature: EC signing failed, sig_datum=%d bytes.", (int)sig_datum.size);
}
gnutls_free(sig_datum.data);
gnutls_privkey_deinit(key);
}
Expand All @@ -1868,6 +1918,8 @@ make_signature(cups_jwt_t *jwt, // I - JWT

done:

DEBUG_printf("4make_signature: Returning %s.", ret ? "true" : "false");

free(text);

if (ret)
Expand Down
65 changes: 63 additions & 2 deletions cups/testjwt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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", httpGetDateString2((time_t)iat, date, sizeof(date)));
if (exp > 0.0)
printf("Expires On: %s\n", httpGetDateString2((time_t)exp, date, sizeof(date)));
if (nbf > 0.0)
printf("Not Before: %s\n", httpGetDateString2((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);
}
Expand Down

0 comments on commit aa32743

Please sign in to comment.