Skip to content

Commit 7404a8e

Browse files
committed
Fix JWT signature validation/tracking - needed to support "kid" for RSA and
ECDSA algorithms, needed to set the "kid" value when importing. Show claims and JOSE header values, support JWKS and validation in testjwt code.
1 parent 2c506ed commit 7404a8e

3 files changed

Lines changed: 159 additions & 55 deletions

File tree

cups/jwt.c

Lines changed: 94 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ static const char * const cups_jwa_algorithms[CUPS_JWA_MAX] =
8181
// Local functions...
8282
//
8383

84+
static cups_json_t *find_key(cups_json_t *jwk, cups_jwa_t sigalg, const char *kid);
8485
#ifdef HAVE_OPENSSL
8586
static BIGNUM *make_bignum(cups_json_t *jwk, const char *key);
8687
static void make_bnstring(const BIGNUM *bn, char *buffer, size_t bufsize);
@@ -392,7 +393,7 @@ cupsJWTHasValidSignature(
392393
if (!jwt || !jwt->signature || !jwk)
393394
return (false);
394395

395-
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]);
396+
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]);
396397

397398
switch (jwt->sigalg)
398399
{
@@ -416,6 +417,7 @@ cupsJWTHasValidSignature(
416417
// Get the message hash...
417418
text = make_string(jwt, false);
418419
text_len = strlen(text);
420+
jwk = find_key(jwk, jwt->sigalg, jwt->sigkid);
419421

420422
#ifdef HAVE_OPENSSL
421423
hash_len = cupsHashData(cups_jwa_algorithms[jwt->sigalg], text, text_len, hash, sizeof(hash));
@@ -451,6 +453,7 @@ cupsJWTHasValidSignature(
451453
// Get the message hash...
452454
text = make_string(jwt, false);
453455
text_len = strlen(text);
456+
jwk = find_key(jwk, jwt->sigalg, jwt->sigkid);
454457

455458
#ifdef HAVE_OPENSSL
456459
hash_len = cupsHashData(cups_jwa_algorithms[jwt->sigalg], text, text_len, hash, sizeof(hash));
@@ -523,7 +526,8 @@ cupsJWTImportString(
523526
cups_jwt_t *jwt; // JWT object
524527
size_t datalen; // Size of data
525528
char data[65536]; // Data
526-
const char *alg; // Signature algorithm, if any
529+
const char *kid, // Key identifier
530+
*alg; // Signature algorithm, if any
527531

528532

529533
// Allocate a JWT...
@@ -577,8 +581,6 @@ cupsJWTImportString(
577581
// Import JSON...
578582
cups_json_t *json, // JSON data
579583
*json_value, // BASE64URL-encoded string value node
580-
*header, // Unprotected header
581-
*kid, // Key ID node
582584
*signatures, // Signatures array
583585
*signature; // Signature element to load
584586
const char *value, // C string value
@@ -664,26 +666,39 @@ cupsJWTImportString(
664666
memcpy(jwt->signature, data, datalen);
665667
jwt->sigsize = datalen;
666668
}
667-
668-
if ((header = cupsJSONFind(signature, "header")) != NULL && (kid = cupsJSONFind(header, "kid")) != NULL && (value = cupsJSONGetString(kid)) != NULL)
669-
jwt->sigkid = strdup(value);
670669
}
671670

671+
#ifdef DEBUG
672+
if (jwt->sigsize >= 8)
673+
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]);
674+
else if (jwt->sigsize > 0)
675+
DEBUG_printf("1cupsJWTImportString: signature[%u]=<...>", (unsigned)jwt->sigsize);
676+
#endif // DEBUG
677+
672678
// Check the algorithm used in the protected header...
673679
if ((alg = cupsJSONGetString(cupsJSONFind(jwt->jose, "alg"))) != NULL)
674680
{
675681
cups_jwa_t sigalg; // Signing algorithm
676682

683+
DEBUG_printf("1cupsJWTImportString: alg=\"%s\"", alg);
684+
677685
for (sigalg = CUPS_JWA_NONE; sigalg < CUPS_JWA_MAX; sigalg ++)
678686
{
679687
if (!strcmp(alg, cups_jwa_strings[sigalg]))
680688
{
681689
jwt->sigalg = sigalg;
690+
DEBUG_printf("1cupsJWTImportString: sigalg=%d", sigalg);
682691
break;
683692
}
684693
}
685694
}
686695

696+
if ((kid = cupsJSONGetString(cupsJSONFind(jwt->jose, "kid"))) != NULL)
697+
{
698+
DEBUG_printf("1cupsJWTImportString: kid=\"%s\"", kid);
699+
jwt->sigkid = strdup(kid);
700+
}
701+
687702
// Can't have signature with none or no signature for !none...
688703
if ((jwt->sigalg == CUPS_JWA_NONE) != (jwt->sigsize == 0))
689704
goto import_error;
@@ -1193,6 +1208,67 @@ cupsJWTSign(cups_jwt_t *jwt, // I - JWT object
11931208
}
11941209

11951210

1211+
//
1212+
// 'find_key()' - Find the key by name or algorithm.
1213+
//
1214+
1215+
static cups_json_t * // O - Key data
1216+
find_key(cups_json_t *jwk, // I - Key set
1217+
cups_jwa_t alg, // I - Signature algorithm
1218+
const char *kid) // I - Signature key ID
1219+
{
1220+
cups_json_t *keys; // Array of keys
1221+
1222+
1223+
if ((keys = cupsJSONFind(jwk, "keys")) != NULL)
1224+
{
1225+
// Full key set, find the key we need to use...
1226+
size_t i, // Looping var
1227+
count; // Number of keys
1228+
cups_json_t *current; // Current key
1229+
const char *curkid, // Current key ID
1230+
*curkty; // Current key type
1231+
1232+
count = cupsJSONGetCount(keys);
1233+
1234+
if (kid)
1235+
{
1236+
// Find the matching key ID
1237+
for (i = 0; i < count; i ++)
1238+
{
1239+
current = cupsJSONGetChild(keys, i);
1240+
curkid = cupsJSONGetString(cupsJSONFind(current, "kid"));
1241+
1242+
if (curkid && !strcmp(curkid, kid))
1243+
{
1244+
DEBUG_printf("4make_signature: Found matching key \"%s\" at %p.", curkid, (void *)current);
1245+
jwk = current;
1246+
break;
1247+
}
1248+
}
1249+
}
1250+
else
1251+
{
1252+
// Find a key that can be used for the specified algorithm
1253+
for (i = 0; i < count; i ++)
1254+
{
1255+
current = cupsJSONGetChild(keys, i);
1256+
curkty = cupsJSONGetString(cupsJSONFind(current, "kty"));
1257+
1258+
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))
1259+
{
1260+
DEBUG_printf("4make_signature: Found compatible key \"%s\" at %p.", cupsJSONGetString(cupsJSONFind(current, "kid")), (void *)current);
1261+
jwk = current;
1262+
break;
1263+
}
1264+
}
1265+
}
1266+
}
1267+
1268+
return (jwk);
1269+
}
1270+
1271+
11961272
#ifdef HAVE_OPENSSL
11971273
//
11981274
// 'make_bignum()' - Make a BIGNUM for the specified key.
@@ -1623,7 +1699,6 @@ make_signature(cups_jwt_t *jwt, // I - JWT
16231699
const char **sigkid) // IO - Key ID string, if any
16241700
{
16251701
bool ret = false; // Return value
1626-
cups_json_t *keys; // Array of keys
16271702
char *text; // JWS Signing Input
16281703
size_t text_len; // Length of signing input
16291704
#ifdef HAVE_OPENSSL
@@ -1638,52 +1713,12 @@ make_signature(cups_jwt_t *jwt, // I - JWT
16381713
#endif // HAVE_OPENSSL
16391714

16401715

1716+
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);
1717+
16411718
// Get text to sign...
16421719
text = make_string(jwt, false);
16431720
text_len = strlen(text);
1644-
1645-
if ((keys = cupsJSONFind(jwk, "keys")) != NULL)
1646-
{
1647-
// Full key set, find the key we need to use...
1648-
size_t i, // Looping var
1649-
count; // Number of keys
1650-
cups_json_t *current; // Current key
1651-
const char *curkid, // Current key ID
1652-
*curkty; // Current key type
1653-
1654-
count = cupsJSONGetCount(keys);
1655-
1656-
if (*sigkid)
1657-
{
1658-
// Find the matching key ID
1659-
for (i = 0; i < count; i ++)
1660-
{
1661-
current = cupsJSONGetChild(keys, i);
1662-
curkid = cupsJSONGetString(cupsJSONFind(current, "kid"));
1663-
1664-
if (curkid && !strcmp(curkid, *sigkid))
1665-
{
1666-
jwk = current;
1667-
break;
1668-
}
1669-
}
1670-
}
1671-
else
1672-
{
1673-
// Find a key that can be used for the specified algorithm
1674-
for (i = 0; i < count; i ++)
1675-
{
1676-
current = cupsJSONGetChild(keys, i);
1677-
curkty = cupsJSONGetString(cupsJSONFind(current, "kty"));
1678-
1679-
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))
1680-
{
1681-
jwk = current;
1682-
break;
1683-
}
1684-
}
1685-
}
1686-
}
1721+
jwk = find_key(jwk, alg, *sigkid);
16871722

16881723
if (alg >= CUPS_JWA_HS256 && alg <= CUPS_JWA_HS512)
16891724
{
@@ -1693,6 +1728,8 @@ make_signature(cups_jwt_t *jwt, // I - JWT
16931728
size_t key_len; // Length of key
16941729
ssize_t hmac_len; // Length of HMAC
16951730

1731+
DEBUG_puts("4make_signature: HMAC signature");
1732+
16961733
// Get key...
16971734
memset(key, 0, sizeof(key));
16981735
k = cupsJSONGetString(cupsJSONFind(jwk, "k"));
@@ -1709,6 +1746,8 @@ make_signature(cups_jwt_t *jwt, // I - JWT
17091746
else if (alg >= CUPS_JWA_RS256 && alg <= CUPS_JWA_RS512)
17101747
{
17111748
// RSASSA-PKCS1-v1_5 SHA-256/384/512
1749+
DEBUG_puts("4make_signature: RSA signature");
1750+
17121751
#ifdef HAVE_OPENSSL
17131752
unsigned char hash[128]; // SHA-256/384/512 hash
17141753
ssize_t hash_len; // Length of hash
@@ -1750,6 +1789,8 @@ make_signature(cups_jwt_t *jwt, // I - JWT
17501789
else if (alg >= CUPS_JWA_ES256 && alg <= CUPS_JWA_ES512)
17511790
{
17521791
// ECDSA P-256 SHA-256/384/512
1792+
DEBUG_puts("4make_signature: ECDSA signature");
1793+
17531794
static unsigned sig_sizes[3] = // Sizes of signatures
17541795
{ 64, 96, 132 };
17551796
#ifdef HAVE_OPENSSL
@@ -1827,6 +1868,8 @@ make_signature(cups_jwt_t *jwt, // I - JWT
18271868

18281869
done:
18291870

1871+
DEBUG_printf("4make_signature: Returning %s.", ret ? "true" : "false");
1872+
18301873
free(text);
18311874

18321875
if (ret)

cups/testjwt.c

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// JWT API unit tests for CUPS.
33
//
4-
// Copyright © 2023 by OpenPrinting.
4+
// Copyright © 2023-2024 by OpenPrinting.
55
//
66
// Licensed under Apache License v2.0. See the file "LICENSE" for more
77
// information.
@@ -293,11 +293,72 @@ main(int argc, // I - Number of command-line arguments
293293
else
294294
{
295295
// Try loading JWT string on the command-line...
296+
cups_json_t *jwks = NULL; // JWT Key Set, if any
297+
296298
for (i = 1; i < argc; i ++)
297299
{
298-
if ((jwt = cupsJWTImportString(argv[i], CUPS_JWS_FORMAT_COMPACT)) != NULL)
300+
if (!access(argv[i], R_OK))
301+
{
302+
if ((jwks = cupsJSONImportFile(argv[i])) == NULL)
303+
{
304+
fprintf(stderr, "%s: %s\n", argv[i], cupsGetErrorString());
305+
return (1);
306+
}
307+
}
308+
else if ((jwt = cupsJWTImportString(argv[i], CUPS_JWS_FORMAT_COMPACT)) != NULL)
299309
{
300-
// printf("%s: OK, %u key/value pairs in root object.\n", argv[i], (unsigned)(cupsJSONGetCount(json) / 2));
310+
cups_json_t *claims = cupsJWTGetClaims(jwt);
311+
// All claims
312+
cups_json_t *headers = cupsJWTGetHeaders(jwt);
313+
// All JOSE headers
314+
char *temp; // Temporary string
315+
const char *aud = cupsJWTGetClaimString(jwt, CUPS_JWT_AUD);
316+
// Audience
317+
const char *iss = cupsJWTGetClaimString(jwt, CUPS_JWT_ISS);
318+
// Issuer
319+
const char *jti = cupsJWTGetClaimString(jwt, CUPS_JWT_JTI);
320+
// JWT ID
321+
const char *name = cupsJWTGetClaimString(jwt, CUPS_JWT_NAME);
322+
// Display name
323+
const char *sub = cupsJWTGetClaimString(jwt, CUPS_JWT_SUB);
324+
// Subject (username/ID)
325+
double iat = cupsJWTGetClaimNumber(jwt, CUPS_JWT_IAT);
326+
// Issue time
327+
double exp = cupsJWTGetClaimNumber(jwt, CUPS_JWT_EXP);
328+
// Expiration time
329+
double nbf = cupsJWTGetClaimNumber(jwt, CUPS_JWT_NBF);
330+
// Not before time
331+
char date[256]; // Date
332+
333+
if (iss)
334+
printf("Issuer: %s\n", iss);
335+
if (name)
336+
printf("Display Name: %s\n", name);
337+
if (sub)
338+
printf("Subject: %s\n", sub);
339+
if (aud)
340+
printf("Audience: %s\n", aud);
341+
if (jti)
342+
printf("JWT ID: %s\n", jti);
343+
if (iat > 0.0)
344+
printf("Issued On: %s\n", httpGetDateString((time_t)iat, date, sizeof(date)));
345+
if (exp > 0.0)
346+
printf("Expires On: %s\n", httpGetDateString((time_t)exp, date, sizeof(date)));
347+
if (nbf > 0.0)
348+
printf("Not Before: %s\n", httpGetDateString((time_t)nbf, date, sizeof(date)));
349+
printf("Valid: %s\n", jwks ? (cupsJWTHasValidSignature(jwt, jwks) ? "yes" : "no") : "unknown");
350+
351+
if ((temp = cupsJSONExportString(headers)) != NULL)
352+
{
353+
printf("\njose=%s\n", temp);
354+
free(temp);
355+
}
356+
357+
if ((temp = cupsJSONExportString(claims)) != NULL)
358+
{
359+
printf("\nclaims=%s\n", temp);
360+
free(temp);
361+
}
301362

302363
cupsJWTDelete(jwt);
303364
}

0 commit comments

Comments
 (0)