hx509: Add JOSE functionality
This commit is contained in:
@@ -27,6 +27,7 @@ dist_libhx509_la_SOURCES = \
|
||||
file.c \
|
||||
hx509.h \
|
||||
hx_locl.h \
|
||||
jose.c \
|
||||
sel.c \
|
||||
sel.h \
|
||||
sel-gram.y \
|
||||
@@ -124,6 +125,7 @@ $(hxtool_OBJECTS): hxtool-commands.h $(nodist_include_HEADERS)
|
||||
hxtool_LDADD = \
|
||||
libhx509template.la \
|
||||
$(top_builddir)/lib/asn1/libasn1.la \
|
||||
$(top_builddir)/lib/base/libheimbase.la \
|
||||
$(LIB_openssl_crypto) \
|
||||
$(LIB_roken) \
|
||||
$(top_builddir)/lib/sl/libsl.la
|
||||
|
||||
@@ -63,7 +63,8 @@ libhx509_la_OBJS = \
|
||||
$(OBJ)\print.obj \
|
||||
$(OBJ)\softp11.obj \
|
||||
$(OBJ)\req.obj \
|
||||
$(OBJ)\revoke.obj
|
||||
$(OBJ)\revoke.obj \
|
||||
$(OBJ)\jose.obj
|
||||
|
||||
$(LIBHX509): $(libhx509_la_OBJS)
|
||||
$(LIBCON)
|
||||
@@ -100,7 +101,8 @@ dist_libhx509_la_SOURCES = \
|
||||
$(SRCDIR)\softp11.c \
|
||||
$(SRCDIR)\ref\pkcs11.h \
|
||||
$(SRCDIR)\req.c \
|
||||
$(SRCDIR)\revoke.c
|
||||
$(SRCDIR)\revoke.c \
|
||||
$(SRCDIR)\jose.c
|
||||
|
||||
{}.c{$(OBJ)}.obj::
|
||||
$(C2OBJ_P) -DBUILD_HX509_LIB -DASN1_LIB
|
||||
@@ -127,7 +129,7 @@ $(OBJ)\hxtool-commands.c $(OBJ)\hxtool-commands.h: hxtool-commands.in $(SLC)
|
||||
cd $(SRCDIR)
|
||||
|
||||
$(BINDIR)\hxtool.exe: $(OBJ)\tool\hxtool.obj $(OBJ)\tool\hxtool-commands.obj $(LIBHEIMDAL) $(OBJ)\hxtool-version.res
|
||||
$(EXECONLINK) $(LIBHEIMDAL) $(LIBROKEN) $(LIBSL) $(LIBVERS) $(LIBCOMERR) $(LIB_openssl_crypto)
|
||||
$(EXECONLINK) $(LIBHEIMDAL) $(LIBHEIMBASE) $(LIBROKEN) $(LIBSL) $(LIBVERS) $(LIBCOMERR) $(LIB_openssl_crypto)
|
||||
$(EXEPREP)
|
||||
|
||||
$(OBJ)\hx509-protos.h:
|
||||
|
||||
@@ -1078,6 +1078,128 @@ command = {
|
||||
argument = "certificate-store"
|
||||
help = "Assert certificate content"
|
||||
}
|
||||
command = {
|
||||
name = "jwt-sign"
|
||||
option = {
|
||||
long = "algorithm"
|
||||
short = "a"
|
||||
type = "string"
|
||||
argument = "algorithm"
|
||||
help = "signature algorithm (RS256, ES256, EdDSA, etc.)"
|
||||
default = "RS256"
|
||||
}
|
||||
option = {
|
||||
long = "private-key"
|
||||
short = "k"
|
||||
type = "string"
|
||||
argument = "keystore"
|
||||
help = "private key (FILE:path, PKCS12:path, PKCS11:uri, or plain path)"
|
||||
}
|
||||
option = {
|
||||
long = "pass"
|
||||
short = "P"
|
||||
type = "strings"
|
||||
argument = "password-spec"
|
||||
help = "password for keystore (same format as other hxtool commands)"
|
||||
}
|
||||
option = {
|
||||
long = "issuer"
|
||||
short = "i"
|
||||
type = "string"
|
||||
argument = "issuer"
|
||||
help = "issuer claim (iss)"
|
||||
}
|
||||
option = {
|
||||
long = "subject"
|
||||
short = "s"
|
||||
type = "string"
|
||||
argument = "subject"
|
||||
help = "subject claim (sub)"
|
||||
}
|
||||
option = {
|
||||
long = "audience"
|
||||
short = "A"
|
||||
type = "string"
|
||||
argument = "audience"
|
||||
help = "audience claim (aud)"
|
||||
}
|
||||
option = {
|
||||
long = "lifetime"
|
||||
short = "l"
|
||||
type = "integer"
|
||||
argument = "seconds"
|
||||
help = "token lifetime in seconds"
|
||||
default = "3600"
|
||||
}
|
||||
option = {
|
||||
long = "output"
|
||||
short = "o"
|
||||
type = "string"
|
||||
argument = "file"
|
||||
help = "output file (default: stdout)"
|
||||
}
|
||||
min_args = "0"
|
||||
max_args = "0"
|
||||
help = "Create a signed JWT"
|
||||
function = "jwt_sign"
|
||||
}
|
||||
command = {
|
||||
name = "jwt-verify"
|
||||
option = {
|
||||
long = "public-key"
|
||||
short = "k"
|
||||
type = "strings"
|
||||
argument = "file"
|
||||
help = "public key file(s) (PEM format)"
|
||||
}
|
||||
option = {
|
||||
long = "jwk"
|
||||
short = "J"
|
||||
type = "string"
|
||||
argument = "jwk-or-file"
|
||||
help = "JWK/JWKS JSON string or file path"
|
||||
}
|
||||
option = {
|
||||
long = "audience"
|
||||
short = "A"
|
||||
type = "string"
|
||||
argument = "audience"
|
||||
help = "required audience"
|
||||
}
|
||||
option = {
|
||||
long = "token"
|
||||
short = "t"
|
||||
type = "string"
|
||||
argument = "token"
|
||||
help = "JWT token (or read from stdin)"
|
||||
}
|
||||
min_args = "0"
|
||||
max_args = "0"
|
||||
help = "Verify a JWT and print claims"
|
||||
function = "jwt_verify"
|
||||
}
|
||||
command = {
|
||||
name = "pem-to-jwk"
|
||||
option = {
|
||||
long = "input"
|
||||
short = "i"
|
||||
type = "string"
|
||||
argument = "file"
|
||||
help = "PEM key file"
|
||||
}
|
||||
option = {
|
||||
long = "output"
|
||||
short = "o"
|
||||
type = "string"
|
||||
argument = "file"
|
||||
help = "output file (default: stdout)"
|
||||
}
|
||||
min_args = "0"
|
||||
max_args = "1"
|
||||
argument = "[pem-file]"
|
||||
help = "Convert PEM key to JWK format"
|
||||
function = "pem_to_jwk"
|
||||
}
|
||||
command = {
|
||||
name = "help"
|
||||
name = "?"
|
||||
|
||||
@@ -3267,6 +3267,251 @@ acert(struct acert_options *opt, int argc, char **argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* JWT / JWS / JWK commands
|
||||
*/
|
||||
|
||||
static char *
|
||||
read_file_to_string(const char *filename)
|
||||
{
|
||||
FILE *f;
|
||||
long size;
|
||||
char *data;
|
||||
|
||||
f = fopen(filename, "r");
|
||||
if (f == NULL)
|
||||
return NULL;
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
data = malloc(size + 1);
|
||||
if (data == NULL) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fread(data, 1, size, f) != (size_t)size) {
|
||||
free(data);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
data[size] = '\0';
|
||||
fclose(f);
|
||||
return data;
|
||||
}
|
||||
|
||||
int
|
||||
jwt_sign(struct jwt_sign_options *opt, int argc, char **argv)
|
||||
{
|
||||
hx509_lock lock = NULL;
|
||||
hx509_certs certs = NULL;
|
||||
hx509_private_key *keys = NULL;
|
||||
hx509_private_key signer = NULL;
|
||||
char *store_name = NULL;
|
||||
char *token = NULL;
|
||||
FILE *out = stdout;
|
||||
int ret;
|
||||
|
||||
if (opt->private_key_string == NULL)
|
||||
errx(1, "--private-key is required");
|
||||
|
||||
/* Set up lock for password-protected keystores */
|
||||
hx509_lock_init(context, &lock);
|
||||
lock_strings(lock, &opt->pass_strings);
|
||||
|
||||
/* Normalize store name (auto-detect FILE: for plain paths) */
|
||||
store_name = fix_store_name(context, opt->private_key_string, "FILE");
|
||||
|
||||
/* Load keystore */
|
||||
ret = hx509_certs_init(context, store_name, 0, lock, &certs);
|
||||
if (ret)
|
||||
hx509_err(context, 1, ret, "Failed to open keystore: %s", store_name);
|
||||
|
||||
/* Extract private keys */
|
||||
ret = _hx509_certs_keys_get(context, certs, &keys);
|
||||
if (ret)
|
||||
hx509_err(context, 1, ret, "Failed to get keys from keystore: %s", store_name);
|
||||
|
||||
if (keys[0] == NULL)
|
||||
errx(1, "No private key found in keystore: %s", store_name);
|
||||
|
||||
signer = _hx509_private_key_ref(keys[0]);
|
||||
|
||||
/* Sign JWT */
|
||||
ret = hx509_jwt_sign_key(context,
|
||||
opt->algorithm_string,
|
||||
signer,
|
||||
opt->issuer_string,
|
||||
opt->subject_string,
|
||||
opt->audience_string,
|
||||
opt->lifetime_integer,
|
||||
NULL, /* extra_claims */
|
||||
&token);
|
||||
|
||||
_hx509_certs_keys_free(context, keys);
|
||||
hx509_private_key_free(&signer);
|
||||
hx509_certs_free(&certs);
|
||||
hx509_lock_free(lock);
|
||||
free(store_name);
|
||||
|
||||
if (ret)
|
||||
hx509_err(context, 1, ret, "Failed to sign JWT");
|
||||
|
||||
if (opt->output_string) {
|
||||
out = fopen(opt->output_string, "w");
|
||||
if (out == NULL)
|
||||
err(1, "Could not open %s for writing", opt->output_string);
|
||||
}
|
||||
|
||||
fprintf(out, "%s\n", token);
|
||||
free(token);
|
||||
|
||||
if (opt->output_string)
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
jwt_verify(struct jwt_verify_options *opt, int argc, char **argv)
|
||||
{
|
||||
char **pem_keys = NULL;
|
||||
char *token = NULL;
|
||||
char *jwk_json = NULL;
|
||||
heim_dict_t claims = NULL;
|
||||
heim_string_t claims_json = NULL;
|
||||
size_t num_keys = 0;
|
||||
int ret;
|
||||
size_t i;
|
||||
|
||||
if (opt->public_key_strings.num_strings == 0 && opt->jwk_string == NULL)
|
||||
errx(1, "--public-key or --jwk is required");
|
||||
|
||||
if (opt->public_key_strings.num_strings > 0 && opt->jwk_string != NULL)
|
||||
errx(1, "--public-key and --jwk are mutually exclusive");
|
||||
|
||||
/* Get token */
|
||||
if (opt->token_string) {
|
||||
token = strdup(opt->token_string);
|
||||
} else {
|
||||
size_t sz = 0;
|
||||
|
||||
if (getline(&token, &sz, stdin) < 0)
|
||||
errx(1, "Could not read token from stdin");
|
||||
/* Remove trailing newline */
|
||||
token[strcspn(token, "\r\n")] = '\0';
|
||||
}
|
||||
|
||||
if (token == NULL)
|
||||
err(1, "Out of memory");
|
||||
|
||||
if (opt->jwk_string) {
|
||||
/* JWK/JWKS mode */
|
||||
const char *jwk_arg = opt->jwk_string;
|
||||
|
||||
/* If it starts with { or [, it's inline JSON; otherwise read from file */
|
||||
if (jwk_arg[0] == '{' || jwk_arg[0] == '[') {
|
||||
jwk_json = strdup(jwk_arg);
|
||||
} else {
|
||||
jwk_json = read_file_to_string(jwk_arg);
|
||||
if (jwk_json == NULL)
|
||||
err(1, "Could not read JWK from %s", jwk_arg);
|
||||
}
|
||||
|
||||
ret = hx509_jwt_verify_jwk(context,
|
||||
token,
|
||||
jwk_json,
|
||||
opt->audience_string,
|
||||
0, /* use current time */
|
||||
&claims);
|
||||
free(jwk_json);
|
||||
} else {
|
||||
/* PEM public key mode */
|
||||
num_keys = opt->public_key_strings.num_strings;
|
||||
pem_keys = calloc(num_keys, sizeof(char *));
|
||||
if (pem_keys == NULL)
|
||||
err(1, "Out of memory");
|
||||
|
||||
for (i = 0; i < num_keys; i++) {
|
||||
pem_keys[i] = read_file_to_string(opt->public_key_strings.strings[i]);
|
||||
if (pem_keys[i] == NULL)
|
||||
err(1, "Could not read public key from %s",
|
||||
opt->public_key_strings.strings[i]);
|
||||
}
|
||||
|
||||
ret = hx509_jwt_verify(context,
|
||||
token,
|
||||
(const char **)pem_keys,
|
||||
num_keys,
|
||||
opt->audience_string,
|
||||
0, /* use current time */
|
||||
&claims);
|
||||
|
||||
for (i = 0; i < num_keys; i++)
|
||||
free(pem_keys[i]);
|
||||
free(pem_keys);
|
||||
}
|
||||
|
||||
free(token);
|
||||
|
||||
if (ret)
|
||||
hx509_err(context, 1, ret, "JWT verification failed");
|
||||
|
||||
/* Print claims */
|
||||
claims_json = heim_json_copy_serialize(claims, HEIM_JSON_F_INDENT2, NULL);
|
||||
if (claims_json)
|
||||
printf("%s\n", heim_string_get_utf8(claims_json));
|
||||
|
||||
heim_release(claims_json);
|
||||
heim_release(claims);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
pem_to_jwk(struct pem_to_jwk_options *opt, int argc, char **argv)
|
||||
{
|
||||
char *pem_key = NULL;
|
||||
char *jwk_json = NULL;
|
||||
const char *input_file;
|
||||
FILE *out = stdout;
|
||||
int ret;
|
||||
|
||||
/* Get input file from option or argument */
|
||||
if (opt->input_string)
|
||||
input_file = opt->input_string;
|
||||
else if (argc > 0)
|
||||
input_file = argv[0];
|
||||
else
|
||||
errx(1, "PEM file required (use --input or provide as argument)");
|
||||
|
||||
pem_key = read_file_to_string(input_file);
|
||||
if (pem_key == NULL)
|
||||
err(1, "Could not read PEM key from %s", input_file);
|
||||
|
||||
ret = hx509_pem_to_jwk_json(context, pem_key, &jwk_json);
|
||||
free(pem_key);
|
||||
|
||||
if (ret)
|
||||
hx509_err(context, 1, ret, "Failed to convert PEM to JWK");
|
||||
|
||||
if (opt->output_string) {
|
||||
out = fopen(opt->output_string, "w");
|
||||
if (out == NULL)
|
||||
err(1, "Could not open %s for writing", opt->output_string);
|
||||
}
|
||||
|
||||
fprintf(out, "%s\n", jwk_json);
|
||||
free(jwk_json);
|
||||
|
||||
if (opt->output_string)
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
||||
2078
lib/hx509/jose.c
Normal file
2078
lib/hx509/jose.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -305,5 +305,18 @@ EXPORTS
|
||||
hx509_xfree
|
||||
initialize_hx_error_table_r
|
||||
|
||||
; JWT/JWS/JWK functions
|
||||
hx509_jwk_to_json
|
||||
hx509_jws_sign
|
||||
hx509_jws_sign_key
|
||||
hx509_jws_verify
|
||||
hx509_jws_verify_jwk
|
||||
hx509_jwt_sign
|
||||
hx509_jwt_sign_key
|
||||
hx509_jwt_verify
|
||||
hx509_jwt_verify_jwk
|
||||
hx509_pem_to_jwk
|
||||
hx509_pem_to_jwk_json
|
||||
|
||||
; pkcs11 symbols
|
||||
C_GetFunctionList
|
||||
|
||||
@@ -317,3 +317,22 @@ HEIMDAL_X509_1.3 {
|
||||
hx509_ca_tbs_set_signature_algorithm;
|
||||
};
|
||||
|
||||
HEIMDAL_X509_1.4 {
|
||||
global:
|
||||
hx509_jwk_to_json;
|
||||
hx509_jws_sign;
|
||||
hx509_jws_verify;
|
||||
hx509_jwt_sign;
|
||||
hx509_jwt_verify;
|
||||
hx509_pem_to_jwk;
|
||||
hx509_pem_to_jwk_json;
|
||||
};
|
||||
|
||||
HEIMDAL_X509_1.5 {
|
||||
global:
|
||||
hx509_jws_sign_key;
|
||||
hx509_jws_verify_jwk;
|
||||
hx509_jwt_sign_key;
|
||||
hx509_jwt_verify_jwk;
|
||||
} HEIMDAL_X509_1.4;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user