// Local functions...
//
+static cups_json_t *copy_x5c(cups_json_t *x5c);
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 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, const char **sigkid);
+static bool make_signature(cups_jwt_t *jwt, cups_jwa_t alg, cups_json_t *jwk, unsigned char *signature, size_t *sigsize, const char **sigkid, cups_json_t **sigx5c);
static char *make_string(cups_jwt_t *jwt, bool with_signature);
case CUPS_JWA_HS512 :
// Calculate signature with keys...
sigkid = jwt->sigkid;
- if (!make_signature(jwt, jwt->sigalg, jwk, signature, &sigsize, &sigkid))
+ if (!make_signature(jwt, jwt->sigalg, jwk, signature, &sigsize, &sigkid, NULL))
break;
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]);
break;
}
+ DEBUG_printf("1cupsJWTHasValidSignature: Returning %s.", ret ? "true" : "false");
+
return (ret);
}
}
+//
+// 'cupsJWTLoadCredentials()' - Load X.509 credentials and private key into a JSON Web Key for signing.
+//
+
+cups_json_t * // O - JSON Web Key of `NULL` on error
+cupsJWTLoadCredentials(
+ const char *path, // I - Directory path for certificate/key store or `NULL` for default
+ const char *common_name) // I - Common name
+{
+ cups_json_t *jwk = NULL, // JSON Web Key
+ *x5c = NULL; // JSON array of certificates
+ char *key, // PEM-encoded private key
+ *creds, // PEM-encoded certificate chain
+ *current, // Current line
+ *next, // Next line
+ *b64 = NULL, // Base64 certificate value
+ *b64ptr; // Pointer into Base64 value
+ size_t len; // Length of credentials string
+ bool isrsa; // Is it an RSA key?
+ char n[1024], // Public key modulus
+ e[1024], // Public key exponent
+ d[1024], // Private key exponent
+ p[1024], // Private key first prime factor
+ q[1024], // Private key second prime factor
+ dp[1024], // First factor exponent
+ dq[1024], // Second factor exponent
+ qi[1024], // First CRT coefficient
+ x[1024], // X coordinate
+ y[1024]; // Y coordinate
+ const char *crv; // Curve value
+
+
+
+ // Load certificates and private key
+ creds = cupsCopyCredentials(path, common_name);
+ key = cupsCopyCredentialsKey(path, common_name);
+
+ if (!creds || !key)
+ goto done;
+
+ // Convert certificate chain to an array of Base64-encoded certificates
+ len = strlen(creds);
+
+ if ((b64 = malloc(len + 1)) == NULL)
+ goto done;
+
+ x5c = cupsJSONNew(/*parent*/NULL, /*after*/NULL, CUPS_JTYPE_ARRAY);
+
+ for (current = strstr(creds, "-----BEGIN CERTIFICATE-----\n"); current; current = strstr(current, "-----BEGIN CERTIFICATE-----\n"))
+ {
+ // Extract this certificate...
+ current += 28;
+ b64ptr = b64;
+
+ while (*current && strncmp(current, "-----", 5))
+ {
+ if ((next = strchr(current, '\n')) == NULL)
+ break;
+
+ len = (size_t)(next - current);
+ memcpy(b64ptr, current, len);
+ b64ptr += len;
+ current = next + 1;
+ }
+
+ // Add the certificate to the array...
+ *b64ptr = '\0';
+ cupsJSONNewString(x5c, /*after*/NULL, b64);
+ }
+
+#ifdef HAVE_OPENSSL
+ EVP_PKEY *pkey = NULL; // Private key
+ BIO *bio = BIO_new_mem_buf(key, -1);
+ // String buffer
+ const RSA *rsa; // RSA key
+ const EC_KEY *ec; // EC key
+
+ if (!bio)
+ goto done;
+
+ PEM_read_bio_PrivateKey(bio, &pkey, /*cb*/NULL, /*u*/NULL);
+ BIO_free(bio);
+
+ if (!pkey)
+ goto done;
+
+ rsa = EVP_PKEY_get0_RSA(pkey);
+ ec = EVP_PKEY_get0_EC_KEY(pkey);
+ isrsa = rsa != NULL;
+
+ if (isrsa)
+ {
+ // Copy RSA key
+ make_bnstring(RSA_get0_n(rsa), n, sizeof(n));
+ make_bnstring(RSA_get0_e(rsa), e, sizeof(e));
+ make_bnstring(RSA_get0_d(rsa), d, sizeof(d));
+ make_bnstring(RSA_get0_p(rsa), p, sizeof(p));
+ make_bnstring(RSA_get0_q(rsa), q, sizeof(q));
+ make_bnstring(RSA_get0_dmp1(rsa), dp, sizeof(dp));
+ make_bnstring(RSA_get0_dmq1(rsa), dq, sizeof(dq));
+ make_bnstring(RSA_get0_iqmp(rsa), qi, sizeof(qi));
+ }
+ else if (ec)
+ {
+ // Copy EC key
+ const EC_GROUP *group; // Group
+ const EC_POINT *pubkey; // Public key portion
+ BIGNUM *bx, *by; // Public key coordinates
+
+ make_bnstring(EC_KEY_get0_private_key(ec), d, sizeof(d));
+
+ group = EC_KEY_get0_group(ec);
+ pubkey = EC_KEY_get0_public_key(ec);
+
+ bx = BN_new();
+ by = BN_new();
+ EC_POINT_get_affine_coordinates(group, pubkey, bx, by, NULL);
+ make_bnstring(bx, x, sizeof(x));
+ make_bnstring(by, y, sizeof(y));
+ BN_free(bx);
+ BN_free(by);
+
+ DEBUG_printf("1cupsJWTLoadCredentials: EC_GROUP_get_curve_name(group)=%d", EC_GROUP_get_curve_name(group));
+
+ switch (EC_GROUP_get_curve_name(group))
+ {
+ case NID_secp256k1 :
+ crv = "P-256";
+ break;
+ case NID_secp384r1 :
+ crv = "P-384";
+ break;
+ case NID_secp521r1 :
+ crv = "P-521";
+ break;
+ default :
+ crv = "UNK";
+ break;
+ }
+ }
+ else
+ {
+ EVP_PKEY_free(pkey);
+ goto done;
+ }
+
+ EVP_PKEY_free(pkey);
+
+#else // HAVE_GNUTLS
+ gnutls_datum_t dat_key; // Private key data
+ gnutls_privkey_t pkey; // Private key
+ unsigned bits; // Private key size in bits
+
+
+ if (gnutls_privkey_init(&pkey))
+ goto done;
+
+ dat_key.data = (unsigned char *)key;
+ dat_key.size = strlen(key);
+
+ if (gnutls_privkey_import_x509_raw(pkey, &dat_key, GNUTLS_X509_FMT_PEM, /*password*/NULL, /*flags*/0))
+ goto done;
+
+ isrsa = gnutls_privkey_get_pk_algorithm(pkey, &bits) == GNUTLS_PK_RSA;
+
+ if (isrsa)
+ {
+ // Copy RSA key
+ gnutls_datum_t dat_n, dat_e, dat_d, dat_p, dat_q, dat_dp, dat_dq, dat_qi;
+ // RSA parameters
+
+ gnutls_privkey_export_rsa_raw(pkey, &dat_n, &dat_e, &dat_d, &dat_p, &dat_q, &dat_qi, &dat_dp, &dat_dq);
+ make_datstring(&dat_n, n, sizeof(n));
+ make_datstring(&dat_e, e, sizeof(e));
+ make_datstring(&dat_d, d, sizeof(d));
+ make_datstring(&dat_p, p, sizeof(p));
+ make_datstring(&dat_q, q, sizeof(q));
+ make_datstring(&dat_qi, qi, sizeof(qi));
+ make_datstring(&dat_dp, dp, sizeof(dp));
+ make_datstring(&dat_dq, dq, sizeof(dq));
+ }
+ else
+ {
+ // Copy ECC key
+ gnutls_ecc_curve_t curve; // Curve
+ gnutls_datum_t dat_x, dat_y, dat_d;
+ // ECDSA parameters
+
+ gnutls_privkey_export_ecc_raw(pkey, &curve, &dat_x, &dat_y, &dat_d);
+ make_datstring(&dat_x, x, sizeof(x));
+ make_datstring(&dat_y, y, sizeof(y));
+ make_datstring(&dat_d, d, sizeof(d));
+
+ switch (curve)
+ {
+ case GNUTLS_ECC_CURVE_SECP256R1 :
+ crv = "P-256";
+ break;
+ case GNUTLS_ECC_CURVE_SECP384R1 :
+ crv = "P-384";
+ break;
+ case GNUTLS_ECC_CURVE_SECP521R1 :
+ crv = "P-521";
+ break;
+ default :
+ crv = "UNK";
+ break;
+ }
+ }
+
+ gnutls_privkey_deinit(pkey);
+#endif // HAVE_OPENSSL
+
+ // Create JWK
+ jwk = cupsJSONNew(/*parent*/NULL, /*after*/NULL, CUPS_JTYPE_OBJECT);
+
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "kty"), isrsa ? "RSA" : "EC");
+ cupsJSONAdd(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "x5c"), x5c);
+
+ if (isrsa)
+ {
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "n"), n);
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "e"), e);
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "d"), d);
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "p"), p);
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "q"), q);
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "dp"), dp);
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "dq"), dq);
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "qi"), qi);
+ }
+ else
+ {
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "crv"), crv);
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "x"), x);
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "y"), y);
+ cupsJSONNewString(jwk, cupsJSONNewKey(jwk, /*after*/NULL, "d"), d);
+ }
+
+ done:
+
+ free(creds);
+ free(key);
+ free(b64);
+
+ if (!jwk)
+ cupsJSONDelete(x5c);
+
+ return (jwk);
+}
+
+
//
// 'cupsJWTMakePrivateKey()' - Make a JSON Web Key for encryption and signing.
//
BIGNUM *bx, *by; // Public key coordinates
if (alg == CUPS_JWA_ES256)
- ec = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+ ec = EC_KEY_new_by_curve_name(NID_secp256k1);
else if (alg == CUPS_JWA_ES384)
ec = EC_KEY_new_by_curve_name(NID_secp384r1);
else
size_t sigsize = _CUPS_JWT_MAX_SIGNATURE;
// Size of signature
const char *sigkid = NULL; // Key ID, if any
+ cups_json_t *sigx5c = NULL; // X.509 certificate chain, if any
// Range check input...
}
// Remove existing JOSE string, if any...
- free(jwt->jose_string);
+ DEBUG_printf("1cupsJWTSign: jose=%p, jose_string=\"%s\"", jwt->jose, jwt->jose_string);
+
_cupsJSONDelete(jwt->jose, "alg");
- cupsJSONNewString(jwt->jose, cupsJSONNewKey(jwt->jose, NULL, "alg"), cups_jwa_strings[alg]);
+ _cupsJSONDelete(jwt->jose, "x5c");
+
+ cupsJSONNewString(jwt->jose, cupsJSONNewKey(jwt->jose, /*after*/NULL, "alg"), cups_jwa_strings[alg]);
+ free(jwt->jose_string);
jwt->jose_string = cupsJSONExportString(jwt->jose);
// Clear existing signature...
jwt->sigalg = CUPS_JWA_NONE;
// Create new signature...
- if (!make_signature(jwt, alg, jwk, signature, &sigsize, &sigkid))
+ if (!make_signature(jwt, alg, jwk, signature, &sigsize, &sigkid, &sigx5c))
{
DEBUG_puts("2cupsJWTSign: Unable to create signature.");
return (false);
}
+ // Update the JOSE header...
+ if (sigx5c)
+ {
+ cupsJSONAdd(jwt->jose, cupsJSONNewKey(jwt->jose, /*after*/NULL, "x5c"), copy_x5c(sigx5c));
+
+ free(jwt->jose_string);
+ jwt->jose_string = cupsJSONExportString(jwt->jose);
+ make_signature(jwt, alg, jwk, signature, &sigsize, &sigkid, NULL);
+ }
+
+ DEBUG_printf("1cupsJWTSign: jose_string=\"%s\"", jwt->jose_string);
+
+ // Save the key ID and signature values...
if (sigkid)
jwt->sigkid = strdup(sigkid);
}
+//
+// 'copy_x5c()' - Copy the "x5c" (X.509 certificate chain) array.
+//
+
+static cups_json_t * // O - JSON array
+copy_x5c(cups_json_t *x5c) // I - X.509 certificate chain
+{
+ size_t i, // Looping var
+ count; // Count
+ cups_json_t *new_x5c; // New JSON array
+
+
+ if ((new_x5c = cupsJSONNew(/*parent*/NULL, /*after*/NULL, CUPS_JTYPE_ARRAY)) != NULL)
+ {
+ for (i = 0, count = cupsJSONGetCount(x5c); i < count; i ++)
+ cupsJSONNewString(new_x5c, /*after*/NULL, cupsJSONGetString(cupsJSONGetChild(x5c, i)));
+ }
+
+ return (new_x5c);
+}
+
+
//
// 'find_key()' - Find the key by name or algorithm.
//
y = make_bignum(jwk, "y");
d = verify ? NULL : make_bignum(jwk, "d");
+ DEBUG_printf("4make_ec_key: crv=\"%s\", x=%p, y=%p, d=%p", crv, x, y, 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);
+ ec = EC_KEY_new_by_curve_name(NID_secp256k1);
else if (!strcmp(crv, "P-384"))
ec = EC_KEY_new_by_curve_name(NID_secp384r1);
else if (!strcmp(crv, "P-521"))
rsa_done:
+ DEBUG_printf("4make_rsa: n=%p, e=%p, d=%p, p=%p, q=%p, dp=%p, dq=%p, qi=%p, rsa=%p", n, e, d, p, q, dp, dq, qi, rsa);
+
if (!rsa)
{
BN_free(n);
cups_json_t *jwk, // I - JSON Web Key Set
unsigned char *signature,// I - Signature buffer
size_t *sigsize, // IO - Signature size
- const char **sigkid) // IO - Key ID string, if any
+ const char **sigkid, // IO - Key ID string, if any
+ cups_json_t **sigx5c) // O - X.509 certificate chain, if any
{
bool ret = false; // Return value
char *text; // JWS Signing Input
#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);
+ DEBUG_printf("3make_signature(jwt=%p, alg=%d, jwk=%p, signature=%p, sigsize=%p(%u), sigkid=%p(%s), sigx5c=%p)", (void *)jwt, alg, (void *)jwk, (void *)signature, (void *)sigsize, (unsigned)*sigsize, (void *)sigkid, *sigkid, (void *)sigx5c);
// Get text to sign...
text = make_string(jwt, false);
free(text);
if (ret)
+ {
*sigkid = cupsJSONGetString(cupsJSONFind(jwk, "kid"));
+ if (sigx5c)
+ *sigx5c = cupsJSONFind(jwk, "x5c");
+ }
else
+ {
*sigsize = 0;
+ }
return (ret);
}
if (!jwt->claims_string)
jwt->claims_string = cupsJSONExportString(jwt->claims);
+ DEBUG_printf("4make_string: jose_string=\"%s\", claims_string=\"%s\"", jwt->jose_string, jwt->claims_string);
+
if (!jwt->jose_string || !jwt->claims_string)
return (NULL);
int i; // Looping var
cups_jwt_t *jwt; // JSON Web Token object
cups_json_t *jwk; // JSON Web Key Set
+ char *s; // Temporary string
if (argc == 1)
"W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U\""
"}"
},
- {
+ // P-256 example uses wrong curve...
+ /*{
"eyJhbGciOiJFUzI1NiJ9."
"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt"
"cGxlLmNvbS9pc19yb290Ijp0cnVlfQ."
"\"y\":\"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0\","
"\"d\":\"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI\""
"}"
- }
+ }*/
};
testBegin("cupsJWTNew(NULL)");
cupsJWTDelete(jwt);
}
+ testBegin("cupsCreateCredentials(xxx-rsa-2048)");
+ if (cupsCreateCredentials(/*path*/NULL, /*ca_cert*/false, CUPS_CREDPURPOSE_CLIENT_AUTH | CUPS_CREDPURPOSE_SERVER_AUTH, CUPS_CREDTYPE_RSA_2048_SHA256, CUPS_CREDUSAGE_DEFAULT_TLS, /*organization*/NULL, /*org_unit*/NULL, /*locality*/NULL, /*state_province*/NULL, /*country*/NULL, "xxx-rsa-2048", /*email*/NULL, /*num_alt_names*/0, /*alt_names*/NULL, /*root_name*/NULL, /*expiration_date*/time(NULL) + 86400))
+ {
+ testEnd(true);
+
+ testBegin("cupsJWTLoadCredentials(xxx-rsa-2048)");
+ if ((jwk = cupsJWTLoadCredentials(/*path*/NULL, "xxx-rsa-2048")) != NULL)
+ {
+ testEnd(true);
+ s = cupsJSONExportString(jwk);
+ testMessage("jwk=\"%s\"", s);
+ free(s);
+
+ testBegin("cupsJWTNew(NULL)");
+ jwt = cupsJWTNew(NULL);
+ testEnd(jwt != NULL);
+
+ testBegin("cupsJWTSetClaimNumber(CUPS_JWT_IAT)");
+ cupsJWTSetClaimNumber(jwt, CUPS_JWT_IAT, (double)time(NULL) + 86400.0);
+ testEnd(cupsJWTGetClaimNumber(jwt, CUPS_JWT_IAT) > 0.0);
+
+ testBegin("cupsJWTSetClaimString(CUPS_JWT_SUB)");
+ cupsJWTSetClaimString(jwt, CUPS_JWT_SUB, "joe.user");
+ testEnd(cupsJWTGetClaimString(jwt, CUPS_JWT_SUB) != NULL);
+
+ testBegin("cupsJWTSign(RS256)");
+ testEnd(cupsJWTSign(jwt, CUPS_JWA_RS256, jwk));
+
+ testBegin("cupsJWTHasValidSignature(RS256)");
+ testEnd(cupsJWTHasValidSignature(jwt, jwk));
+
+ cupsJWTDelete(jwt);
+ cupsJSONDelete(jwk);
+ }
+ else
+ {
+ testEndMessage(false, "%s", cupsGetErrorString());
+ }
+ }
+ else
+ {
+ testEndMessage(false, "%s", cupsGetErrorString());
+ }
+
+ testBegin("cupsCreateCredentials(xxx-ecdsa-p256)");
+ if (cupsCreateCredentials(/*path*/NULL, /*ca_cert*/false, CUPS_CREDPURPOSE_CLIENT_AUTH | CUPS_CREDPURPOSE_SERVER_AUTH, CUPS_CREDTYPE_ECDSA_P256_SHA256, CUPS_CREDUSAGE_DEFAULT_TLS, /*organization*/NULL, /*org_unit*/NULL, /*locality*/NULL, /*state_province*/NULL, /*country*/NULL, "xxx-ecdsa-p256", /*email*/NULL, /*num_alt_names*/0, /*alt_names*/NULL, /*root_name*/NULL, /*expiration_date*/time(NULL) + 86400))
+ {
+ testEnd(true);
+
+ testBegin("cupsJWTLoadCredentials(xxx-ecdsa-p256)");
+ if ((jwk = cupsJWTLoadCredentials(/*path*/NULL, "xxx-ecdsa-p256")) != NULL)
+ {
+ testEnd(true);
+ s = cupsJSONExportString(jwk);
+ testMessage("jwk=\"%s\"", s);
+ free(s);
+
+ testBegin("cupsJWTNew(NULL)");
+ jwt = cupsJWTNew(NULL);
+ testEnd(jwt != NULL);
+
+ testBegin("cupsJWTSetClaimNumber(CUPS_JWT_IAT)");
+ cupsJWTSetClaimNumber(jwt, CUPS_JWT_IAT, (double)time(NULL) + 86400.0);
+ testEnd(cupsJWTGetClaimNumber(jwt, CUPS_JWT_IAT) > 0.0);
+
+ testBegin("cupsJWTSetClaimString(CUPS_JWT_SUB)");
+ cupsJWTSetClaimString(jwt, CUPS_JWT_SUB, "joe.user");
+ testEnd(cupsJWTGetClaimString(jwt, CUPS_JWT_SUB) != NULL);
+
+ testBegin("cupsJWTSign(ES256)");
+ testEnd(cupsJWTSign(jwt, CUPS_JWA_ES256, jwk));
+
+ testBegin("cupsJWTHasValidSignature(ES256)");
+ testEnd(cupsJWTHasValidSignature(jwt, jwk));
+
+ cupsJWTDelete(jwt);
+ cupsJSONDelete(jwk);
+ }
+ else
+ {
+ testEndMessage(false, "%s", cupsGetErrorString());
+ }
+ }
+ else
+ {
+ testEndMessage(false, "%s", cupsGetErrorString());
+ }
+
if (!testsPassed)
return (1);
}