mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
libekmfweb: Allow to check if a JSON Web Token (JWT) is expired
When login in with EKMF Web, a bearer token is retrieved from EKMF Web which is then used on subsequent requests to authenticate with EKMF Web. Such a bearer token is valid for several minutes, thus no re-login is required during that time. The bearer token contains a JSON Web Token (JWT, see RFC7519). Allow to check such a token if it is still valid, or already expired. That way a client application can check the token before issuing the next request, and re-login if needed. Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
committed by
Jan Höppner
parent
cbf7f02d69
commit
d8089e69fa
@@ -45,6 +45,9 @@ struct ekmf_config {
|
||||
/** Maximum number of redirects to follow. Zero means that redirects are
|
||||
* not followed. -1 means to infinitely follow redirects. */
|
||||
long max_redirs;
|
||||
/** File name of the login token (JSON Web Token) used for the last
|
||||
* login. */
|
||||
const char *login_token;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -88,4 +91,25 @@ int ekmf_get_server_cert_chain(const struct ekmf_config *config,
|
||||
*/
|
||||
int ekmf_print_certificates(const char *cert_pem, bool verbose);
|
||||
|
||||
/**
|
||||
* Checks if the login token stored in the file denoted by field login_token
|
||||
* of the config structure is valid or not. The file (if existent) contains a
|
||||
* JSON Web Token (JWT, see RFC7519). It is valid if the current date and time
|
||||
* is before its expiration time ("exp" claim), and after or equal its
|
||||
* not-before time ("nbf" claim).
|
||||
* Note: The signature (if any) of the JWT is not checked, nor any other JWT
|
||||
* fields.
|
||||
*
|
||||
* @param config the configuration structure
|
||||
* @param valid On return: true if the token is valid, false if not
|
||||
* @param login_token On return: If not NULL: the login token, if the
|
||||
* token is still valid. The returned string must
|
||||
* be freed by the caller when no longer needed.
|
||||
* @param verbose if true, verbose messages are printed
|
||||
*
|
||||
* @returns a negative errno in case of an error, 0 if success.
|
||||
*/
|
||||
int ekmf_check_login_token(const struct ekmf_config *config, bool *valid,
|
||||
char **login_token, bool verbose);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <err.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
@@ -987,6 +988,149 @@ int ekmf_print_certificates(const char *cert_pem, bool verbose)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the login token stored in the file denoted by field login_token
|
||||
* of the config structure is valid or not. The file (if existent) contains a
|
||||
* JSON Web Token (JWT, see RFC7519). It is valid if the current date and time
|
||||
* is before its expiration time ("exp" claim), and after or equal its
|
||||
* not-before time ("nbf" claim).
|
||||
* Note: The signature (if any) of the JWT is not checked, nor any other JWT
|
||||
* fields.
|
||||
*
|
||||
* @param config the configuration structure
|
||||
* @param valid On return: true if the token is valid, false if not
|
||||
* @param login_token On return: If not NULL: the login token, if the
|
||||
* token is still valid. The returned string must
|
||||
* be freed by the caller when no longer needed.
|
||||
* @param verbose if true, verbose messages are printed
|
||||
*
|
||||
* @returns a negative errno in case of an error, 0 if success.
|
||||
*/
|
||||
int ekmf_check_login_token(const struct ekmf_config *config, bool *valid,
|
||||
char **login_token, bool verbose)
|
||||
{
|
||||
json_object *jwt_payload = NULL;
|
||||
json_object *exp_claim = NULL;
|
||||
json_object *nbf_claim = NULL;
|
||||
char *token = NULL;
|
||||
size_t count, size;
|
||||
int64_t exp, nbf;
|
||||
FILE *fp = NULL;
|
||||
struct stat sb;
|
||||
int rc = 0;
|
||||
time_t now;
|
||||
|
||||
if (config == NULL || valid == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
if (config->login_token == NULL) {
|
||||
*valid = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (login_token != NULL)
|
||||
*login_token = NULL;
|
||||
|
||||
pr_verbose(verbose, "Reading login token from file : '%s'",
|
||||
config->login_token);
|
||||
|
||||
if (stat(config->login_token, &sb)) {
|
||||
rc = -errno;
|
||||
pr_verbose(verbose, "stat on file %s failed: '%s'",
|
||||
config->login_token, strerror(-rc));
|
||||
return rc;
|
||||
}
|
||||
size = sb.st_size;
|
||||
if (size == 0) {
|
||||
pr_verbose(verbose, "File %s is empty", config->login_token);
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
token = (char *)malloc(size + 1);
|
||||
if (token == NULL) {
|
||||
pr_verbose(verbose, "Failed to allocate a buffer");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
fp = fopen(config->login_token, "r");
|
||||
if (fp == NULL) {
|
||||
rc = -errno;
|
||||
pr_verbose(verbose, "Failed to open file %s: '%s'",
|
||||
config->login_token, strerror(-rc));
|
||||
goto out;
|
||||
}
|
||||
|
||||
count = fread(token, 1, size, fp);
|
||||
if (count != size) {
|
||||
pr_verbose(verbose, "Failed to read the token");
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
token[size] = '\0';
|
||||
if (token[size - 1] == '\n')
|
||||
token[size - 1] = '\0';
|
||||
|
||||
fclose(fp);
|
||||
fp = NULL;
|
||||
|
||||
time(&now);
|
||||
*valid = true;
|
||||
|
||||
rc = parse_json_web_token(token, NULL, &jwt_payload, NULL, NULL);
|
||||
if (rc != 0) {
|
||||
pr_verbose(verbose, "parse_json_web_token failed");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (json_object_object_get_ex(jwt_payload, "exp", &exp_claim) &&
|
||||
json_object_is_type(exp_claim, json_type_int)) {
|
||||
exp = json_object_get_int64(exp_claim);
|
||||
if (exp == 0) {
|
||||
pr_verbose(verbose,
|
||||
"failed to get value from exp claim");
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (now > exp) {
|
||||
pr_verbose(verbose, "JWT is expired");
|
||||
*valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (json_object_object_get_ex(jwt_payload, "nbf", &nbf_claim) &&
|
||||
json_object_is_type(nbf_claim, json_type_int)) {
|
||||
nbf = json_object_get_int64(nbf_claim);
|
||||
if (nbf == 0) {
|
||||
pr_verbose(verbose,
|
||||
"failed to get value from nbf claim");
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (now <= nbf) {
|
||||
pr_verbose(verbose, "JWT is not yet valid");
|
||||
*valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (login_token != NULL && *valid) {
|
||||
*login_token = token;
|
||||
token = NULL;
|
||||
}
|
||||
|
||||
out:
|
||||
if (jwt_payload != NULL)
|
||||
json_object_put(jwt_payload);
|
||||
if (token != NULL)
|
||||
free(token);
|
||||
if (fp != NULL)
|
||||
fclose(fp);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Library constructor
|
||||
*/
|
||||
|
||||
@@ -2,5 +2,6 @@ LIBEKMFWEB_1.0 {
|
||||
global:
|
||||
ekmf_get_server_cert_chain;
|
||||
ekmf_print_certificates;
|
||||
ekmf_check_login_token;
|
||||
local: *;
|
||||
};
|
||||
|
||||
@@ -9,12 +9,355 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
|
||||
#include "utilities.h"
|
||||
|
||||
/**
|
||||
* Decodes a Base64URL encoded string. Base64URL is like Base64, but using a
|
||||
* URL and Filename Safe Alphabet, not using characters like '+', '/', or '='.
|
||||
*
|
||||
* The function converts the Base64URL input into standard Base64 data and then
|
||||
* decodes it using OpenSSL EVP_DecodeBlock.
|
||||
*
|
||||
* @param output a buffer to store the output. If NULL, then the
|
||||
* required size in bytes is returned in outlen and
|
||||
* no decoding is performed.
|
||||
* @param outlen on entry: Size of the output buffer in bytes.
|
||||
* on exit: Size of the decoded data in bytes.
|
||||
* @param input the Base64URL encoded data to decode
|
||||
* @param inlen the size of the input data in bytes
|
||||
*
|
||||
* @returns zero for success, a negative errno in case of an error:
|
||||
* -EINVAL: a function parameter is invalid
|
||||
* -ERANGE: the output buffer size is too small. outlen contains the
|
||||
* required size on return.
|
||||
* -ENOMEM: failed to allocate memory
|
||||
* -EIO: OpenSSL failed to decode the data
|
||||
*/
|
||||
int decode_base64url(unsigned char *output, size_t *outlen,
|
||||
const char *input, size_t inlen)
|
||||
{
|
||||
size_t raw_outlen, padded_outlen, padded_inlen;
|
||||
char *padded_input = NULL;
|
||||
char *padded_output;
|
||||
int len, rc = 0;
|
||||
size_t i;
|
||||
|
||||
if (input == NULL || outlen == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
/* Base64URL might optionally include padding chars ('='), remove it */
|
||||
for (; inlen > 0 && input[inlen - 1] == '='; inlen--)
|
||||
;
|
||||
|
||||
/* Might need to pad with standard base64 padding character '=' */
|
||||
padded_inlen = inlen;
|
||||
if ((inlen % 4) > 0)
|
||||
padded_inlen += 4 - (inlen % 4);
|
||||
|
||||
/* Also the output will be padded while decoding */
|
||||
padded_outlen = (padded_inlen / 4) * 3;
|
||||
raw_outlen = (inlen / 4) * 3 + ((inlen % 4) >= 2 ? (inlen % 4) - 1 : 0);
|
||||
|
||||
if (output == NULL) /* size query */
|
||||
goto out;
|
||||
if (*outlen < raw_outlen) {
|
||||
rc = -ERANGE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
padded_input = (char *)malloc(padded_inlen + padded_outlen);
|
||||
if (padded_input == NULL)
|
||||
return -ENOMEM;
|
||||
padded_output = padded_input + padded_inlen;
|
||||
|
||||
memcpy(padded_input, input, inlen);
|
||||
memset(padded_input + inlen, '=', padded_inlen - inlen);
|
||||
|
||||
/* Replace '-' by '+', and '_' by '/' */
|
||||
for (i = 0; i < inlen; i++) {
|
||||
if (padded_input[i] == '-')
|
||||
padded_input[i] = '+';
|
||||
else if (padded_input[i] == '_')
|
||||
padded_input[i] = '/';
|
||||
}
|
||||
|
||||
len = EVP_DecodeBlock((unsigned char *)padded_output,
|
||||
(unsigned char *)padded_input, padded_inlen);
|
||||
if (len != (int)padded_inlen * 3 / 4) {
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
memcpy(output, padded_output, raw_outlen);
|
||||
|
||||
out:
|
||||
*outlen = raw_outlen;
|
||||
|
||||
if (padded_input != NULL)
|
||||
free(padded_input);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a data using Base64URL. Base64URL is like Base64, but using a
|
||||
* URL and Filename Safe Alphabet, not using characters like '+', '/', or '='.
|
||||
*
|
||||
* The function encodes the data into standard Base64 data using OpenSSL
|
||||
* EVP_EncodeBlock and then converts it into Base64URL data
|
||||
*
|
||||
* @param output a buffer to store the output. If NULL, then the
|
||||
* required size in bytes is returned in outlen and
|
||||
* no encoding is performed.
|
||||
* The NUL character is added to the output at the end
|
||||
* of the encoded data.
|
||||
* @param outlen on entry: Size of the output buffer in bytes.
|
||||
* on exit: Size of the encoded data in bytes,
|
||||
* including the NUL character.
|
||||
* @param input the data to Base64URL-encode
|
||||
* @param inlen the size of the input data in bytes
|
||||
*
|
||||
* @returns zero for success, a negative errno in case of an error:
|
||||
* -EINVAL: a function parameter is invalid
|
||||
* -ERANGE: the output buffer size is too small. outlen contains the
|
||||
* required size on return.
|
||||
* -ENOMEM: failed to allocate memory
|
||||
* -EIO: OpenSSL failed to decode the data
|
||||
*/
|
||||
int encode_base64url(char *output, size_t *outlen,
|
||||
const unsigned char *input, size_t inlen)
|
||||
{
|
||||
size_t padded_outlen, raw_outlen, ofs = 0;
|
||||
char *padded_output = NULL;
|
||||
int len, rc = 0;
|
||||
|
||||
if (input == NULL || outlen == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
padded_outlen = (inlen / 3) * 4;
|
||||
if (inlen % 3 > 0)
|
||||
padded_outlen += 4;
|
||||
|
||||
raw_outlen = (inlen / 3) * 4 + ((inlen % 3) > 0 ? (inlen % 3) + 1 : 0);
|
||||
|
||||
if (output == NULL)
|
||||
goto out;
|
||||
|
||||
if (*outlen < raw_outlen + 1) {
|
||||
rc = -ERANGE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
padded_output = (char *)malloc(padded_outlen + 1);
|
||||
if (padded_output == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
len = EVP_EncodeBlock((unsigned char *)padded_output,
|
||||
(unsigned char *)input, inlen);
|
||||
if (len != (int)padded_outlen) {
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Replace '+' by '-', and '/' by '_', and stop at first '=' */
|
||||
for (ofs = 0; ofs < padded_outlen; ofs++) {
|
||||
if (padded_output[ofs] == '+')
|
||||
padded_output[ofs] = '-';
|
||||
else if (padded_output[ofs] == '/')
|
||||
padded_output[ofs] = '_';
|
||||
else if (padded_output[ofs] == '=')
|
||||
break;
|
||||
}
|
||||
if (ofs != raw_outlen) {
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
memcpy(output, padded_output, raw_outlen);
|
||||
output[raw_outlen] = '\0';
|
||||
|
||||
out:
|
||||
*outlen = raw_outlen + 1;
|
||||
|
||||
if (padded_output != NULL)
|
||||
free(padded_output);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a JSON Web Token (JWT) and extracts the header and payload parts as
|
||||
* parsed JSON object. The returned JSON objects must be freed by the caller
|
||||
* using json_object_put() when no longer needed. The returned signature must
|
||||
* also be freed by the caller when no longer needed.
|
||||
*
|
||||
* @param token the JSON Web Tolken to parse
|
||||
* @param header_obj if not NULL, the parsed JWT header as JSON Object
|
||||
* @param payload_obj if not NULL, the parsed JWT payload as JSON Object
|
||||
* @param signature if not NULL, a pointer to the decoded signature
|
||||
* @param signature_len if not NULL, the size of the signature in bytes
|
||||
*
|
||||
* @returns zero for success, a negative errno in case of an error:
|
||||
* -EINVAL: a function parameter is invalid
|
||||
* -EBADMSG: If the token could not be parsed into parts
|
||||
* -ENOMEM: failed to allocate memory
|
||||
* -EIO: Base64 parsing error
|
||||
*/
|
||||
int parse_json_web_token(const char *token, json_object **header_obj,
|
||||
json_object **payload_obj, unsigned char **signature,
|
||||
size_t *signature_len)
|
||||
{
|
||||
json_object *hdr = NULL, *pld = NULL, *b64_obj = NULL;
|
||||
char *ch, *header, *payload, *json = NULL;
|
||||
size_t header_len, payload_len, json_len;
|
||||
bool b64 = true;
|
||||
int rc = 0;
|
||||
|
||||
if (token == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
if (header_obj != NULL)
|
||||
*header_obj = NULL;
|
||||
if (payload_obj != NULL)
|
||||
*payload_obj = NULL;
|
||||
if (signature != NULL)
|
||||
*signature = NULL;
|
||||
if (signature_len != NULL)
|
||||
*signature_len = 0;
|
||||
|
||||
/*
|
||||
* A JSON Web Token consists of several parts, each part Base64URL
|
||||
* encoded, and separated by a colon '.' from each other.
|
||||
* The first part is the JOSE (JSON Object Signing and Encryption)
|
||||
* Header. The second part is the JWS Payload containing the JWS claims,
|
||||
* and the following parts (if any) are used for JWS Signature, or JWE
|
||||
* Encryption (not considered here).
|
||||
*/
|
||||
header = (char *)token;
|
||||
|
||||
ch = strchr(token, '.');
|
||||
if (ch == NULL) {
|
||||
rc = -EBADMSG;
|
||||
goto out;
|
||||
}
|
||||
header_len = ch - token;
|
||||
|
||||
payload = ++ch;
|
||||
|
||||
ch = strchr(ch, '.');
|
||||
if (ch == NULL) {
|
||||
rc = -EBADMSG;
|
||||
goto out;
|
||||
}
|
||||
|
||||
payload_len = ch - payload;
|
||||
ch++;
|
||||
|
||||
rc = decode_base64url(NULL, &json_len, header, header_len);
|
||||
if (rc != 0)
|
||||
goto out;
|
||||
|
||||
json = malloc(json_len + 1);
|
||||
if (json == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = decode_base64url((unsigned char *)json, &json_len,
|
||||
header, header_len);
|
||||
if (rc != 0)
|
||||
goto out;
|
||||
|
||||
json[json_len] = '\0';
|
||||
|
||||
hdr = json_tokener_parse(json);
|
||||
if (hdr == NULL) {
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (json_object_object_get_ex(hdr, "b64", &b64_obj) &&
|
||||
json_object_is_type(b64_obj, json_type_boolean))
|
||||
b64 = json_object_get_boolean(b64_obj);
|
||||
|
||||
free(json);
|
||||
json = NULL;
|
||||
|
||||
if (payload_obj != NULL) {
|
||||
if (b64) {
|
||||
rc = decode_base64url(NULL, &json_len, payload,
|
||||
payload_len);
|
||||
if (rc != 0)
|
||||
goto out;
|
||||
|
||||
json = malloc(json_len + 1);
|
||||
if (json == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = decode_base64url((unsigned char *)json, &json_len,
|
||||
payload, payload_len);
|
||||
if (rc != 0)
|
||||
goto out;
|
||||
|
||||
json[json_len] = '\0';
|
||||
} else {
|
||||
json = strndup(payload, payload_len);
|
||||
}
|
||||
|
||||
pld = json_tokener_parse(json);
|
||||
if (pld == NULL) {
|
||||
rc = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
free(json);
|
||||
json = NULL;
|
||||
}
|
||||
|
||||
if (signature != NULL && signature_len != NULL) {
|
||||
rc = decode_base64url(NULL, signature_len, ch, strlen(ch));
|
||||
if (rc != 0)
|
||||
goto out;
|
||||
|
||||
*signature = malloc(*signature_len);
|
||||
if (*signature == NULL) {
|
||||
rc = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = decode_base64url(*signature, signature_len, ch,
|
||||
strlen(ch));
|
||||
if (rc != 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
if (header_obj != NULL && rc == 0)
|
||||
*header_obj = hdr;
|
||||
else
|
||||
json_object_put(hdr);
|
||||
if (payload_obj != NULL && rc == 0)
|
||||
*payload_obj = pld;
|
||||
else
|
||||
json_object_put(pld);
|
||||
if (signature != NULL && rc != 0) {
|
||||
free(signature);
|
||||
signature = NULL;
|
||||
*signature_len = 0;
|
||||
}
|
||||
if (json != NULL)
|
||||
free(json);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a X.509 certificate from the specified PEM file.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,18 @@
|
||||
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <json-c/json.h>
|
||||
|
||||
int decode_base64url(unsigned char *output, size_t *outlen,
|
||||
const char *input, size_t inlen);
|
||||
|
||||
int encode_base64url(char *output, size_t *outlen,
|
||||
const unsigned char *input, size_t inlen);
|
||||
|
||||
int parse_json_web_token(const char *token, json_object **header_obj,
|
||||
json_object **payload_obj, unsigned char **signature,
|
||||
size_t *signature_len);
|
||||
|
||||
int read_x509_certificate(const char *pem_filename, X509 **cert);
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user