]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
Merge JWT fixes from libcups v3.
authorMichael R Sweet <msweet@msweet.org>
Mon, 9 Sep 2024 20:36:49 +0000 (16:36 -0400)
committerMichael R Sweet <msweet@msweet.org>
Mon, 9 Sep 2024 20:36:49 +0000 (16:36 -0400)
cups/jwt.c
cups/testjwt.c

index fb706c1f55d39cba0abf974d8938819d7a0aeb94..0369acbc937b563ca5d0536f13958e4b09fb44de 100644 (file)
@@ -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);
@@ -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)
   {
@@ -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));
@@ -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));
@@ -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...
@@ -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
@@ -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;
@@ -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;
@@ -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.
@@ -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
@@ -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)
   {
@@ -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"));
@@ -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
@@ -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
@@ -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);
     }
@@ -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)
index 832ee5f9319022035dabf49aef2406a40c018953..1be97b8f88957f3eb4f3a6021faf035607d9b4fd 100644 (file)
@@ -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);
       }