Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/alg.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ static const struct {
{ "encr", JOSE_HOOK_ALG_KIND_ENCR },
{ "comp", JOSE_HOOK_ALG_KIND_COMP },
{ "exch", JOSE_HOOK_ALG_KIND_EXCH },
{ "kem", JOSE_HOOK_ALG_KIND_KEM },
{}
};

Expand Down
130 changes: 130 additions & 0 deletions cmd/jwk/decap.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
/*
* Copyright 2026 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "jwk.h"
#include <string.h>
#include <unistd.h>

#define SUMMARY "Performs KEM decapsulation using a private key"

typedef struct {
FILE *output;
json_t *keys;
const char *ct;
} jcmd_opt_t;

static const char *prefix =
"jose jwk decap -i JWK -c CT [-o JWK]\n\n" SUMMARY;

static const jcmd_doc_t doc_input[] = {
{ .arg = "JSON", .doc="Parse private JWK from JSON" },
{ .arg = "FILE", .doc="Read private JWK from FILE" },
{ .arg = "-", .doc="Read private JWK from standard input" },
{}
};

static const jcmd_doc_t doc_output[] = {
{ .arg = "FILE", .doc="Write shared secret JWK to FILE" },
{ .arg = "-", .doc="Write shared secret JWK to standard output" },
{}
};

static const jcmd_doc_t doc_ct[] = {
{ .arg = "B64U", .doc="Base64url-encoded ciphertext" },
{}
};

static bool
jcmd_opt_set_ct(const jcmd_cfg_t *cfg, void *vopt, const char *arg)
{
const char **ct = vopt;
*ct = arg;
return *ct != NULL;
}

static const jcmd_cfg_t cfgs[] = {
{
.opt = { "input", required_argument, .val = 'i' },
.off = offsetof(jcmd_opt_t, keys),
.set = jcmd_opt_set_jwks,
.doc = doc_input,
},
{
.opt = { "output", required_argument, .val = 'o' },
.off = offsetof(jcmd_opt_t, output),
.set = jcmd_opt_set_ofile,
.doc = doc_output,
.def = "-",
},
{
.opt = { "ciphertext", required_argument, .val = 'c' },
.off = offsetof(jcmd_opt_t, ct),
.set = jcmd_opt_set_ct,
.doc = doc_ct,
},
{}
};

static void
jcmd_opt_cleanup(jcmd_opt_t *opt)
{
jcmd_file_cleanup(&opt->output);
json_decrefp(&opt->keys);
}

static int
jcmd_jwk_decap(int argc, char *argv[])
{
jcmd_opt_auto_t opt = {};
json_auto_t *ct_json = NULL;
json_auto_t *result = NULL;

if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
return EXIT_FAILURE;

if (json_array_size(opt.keys) != 1) {
fprintf(stderr, "Private JWK must be specified exactly once!\n");
return EXIT_FAILURE;
}

if (!opt.ct) {
fprintf(stderr, "Ciphertext (-c) is required!\n");
return EXIT_FAILURE;
}

ct_json = json_string(opt.ct);
if (!ct_json)
return EXIT_FAILURE;

result = jose_jwk_kem_dec(NULL, json_array_get(opt.keys, 0), ct_json);
if (!result) {
fprintf(stderr, "Error performing decapsulation!\n");
return EXIT_FAILURE;
}

if (json_dumpf(result, opt.output, JSON_COMPACT | JSON_SORT_KEYS) < 0) {
fprintf(stderr, "Error writing result!\n");
return EXIT_FAILURE;
}

if (isatty(fileno(opt.output)))
fprintf(opt.output, "\n");

return EXIT_SUCCESS;
}

JCMD_REGISTER(SUMMARY, jcmd_jwk_decap, "jwk", "decap")
100 changes: 100 additions & 0 deletions cmd/jwk/encap.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */
/*
* Copyright 2026 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "jwk.h"
#include <string.h>
#include <unistd.h>

#define SUMMARY "Performs KEM encapsulation using a public key"

typedef struct {
FILE *output;
json_t *keys;
} jcmd_opt_t;

static const char *prefix =
"jose jwk encap -i JWK [-o JSON]\n\n" SUMMARY;

static const jcmd_doc_t doc_input[] = {
{ .arg = "JSON", .doc="Parse public JWK from JSON" },
{ .arg = "FILE", .doc="Read public JWK from FILE" },
{ .arg = "-", .doc="Read public JWK from standard input" },
{}
};

static const jcmd_doc_t doc_output[] = {
{ .arg = "FILE", .doc="Write result to FILE" },
{ .arg = "-", .doc="Write result to standard output" },
{}
};

static const jcmd_cfg_t cfgs[] = {
{
.opt = { "input", required_argument, .val = 'i' },
.off = offsetof(jcmd_opt_t, keys),
.set = jcmd_opt_set_jwks,
.doc = doc_input,
},
{
.opt = { "output", required_argument, .val = 'o' },
.off = offsetof(jcmd_opt_t, output),
.set = jcmd_opt_set_ofile,
.doc = doc_output,
.def = "-",
},
{}
};

static void
jcmd_opt_cleanup(jcmd_opt_t *opt)
{
jcmd_file_cleanup(&opt->output);
json_decrefp(&opt->keys);
}

static int
jcmd_jwk_encap(int argc, char *argv[])
{
jcmd_opt_auto_t opt = {};
json_auto_t *result = NULL;

if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix))
return EXIT_FAILURE;

if (json_array_size(opt.keys) != 1) {
fprintf(stderr, "Public JWK must be specified exactly once!\n");
return EXIT_FAILURE;
}

result = jose_jwk_kem_enc(NULL, json_array_get(opt.keys, 0));
if (!result) {
fprintf(stderr, "Error performing encapsulation!\n");
return EXIT_FAILURE;
}

if (json_dumpf(result, opt.output, JSON_COMPACT | JSON_SORT_KEYS) < 0) {
fprintf(stderr, "Error writing result!\n");
return EXIT_FAILURE;
}

if (isatty(fileno(opt.output)))
fprintf(opt.output, "\n");

return EXIT_SUCCESS;
}

JCMD_REGISTER(SUMMARY, jcmd_jwk_encap, "jwk", "encap")
2 changes: 2 additions & 0 deletions cmd/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ executable(meson.project_name(),
'b64/enc.c',
'jwk/jwk.h',
'jwk/eql.c',
'jwk/encap.c',
'jwk/decap.c',
'jwk/exc.c',
'jwk/gen.c',
'jwk/pub.c',
Expand Down
30 changes: 30 additions & 0 deletions include/jose/jwk.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,34 @@ jose_jwk_thp_buf(jose_cfg_t *cfg, const json_t *jwk,
json_t *
jose_jwk_exc(jose_cfg_t *cfg, const json_t *lcl, const json_t *rem);

/**
* Performs KEM encapsulation using a public JWK.
*
* Returns a JSON object containing the shared secret as an oct JWK
* ("ss") and the ciphertext as a base64url string ("ct").
*
* The returned object contains secret material. Callers handling
* sensitive key material should cleanse the "k" value of the "ss"
* object before releasing it.
*
* \param cfg The configuration context (optional).
* \param pub The public JWK to encapsulate against.
* \return On success, a JSON object. Otherwise, NULL.
*/
json_t *
jose_jwk_kem_enc(jose_cfg_t *cfg, const json_t *pub);

/**
* Performs KEM decapsulation using a private JWK and ciphertext.
*
* Returns the shared secret as an oct JWK.
*
* \param cfg The configuration context (optional).
* \param prv The private JWK to decapsulate with.
* \param ct The ciphertext as a base64url JSON string.
* \return On success, a JSON object (oct JWK). Otherwise, NULL.
*/
json_t *
jose_jwk_kem_dec(jose_cfg_t *cfg, const json_t *prv, const json_t *ct);

/** @} */
19 changes: 18 additions & 1 deletion lib/hooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ typedef enum {
JOSE_HOOK_ALG_KIND_ENCR,
JOSE_HOOK_ALG_KIND_COMP,
JOSE_HOOK_ALG_KIND_EXCH,
JOSE_HOOK_ALG_KIND_LAST = JOSE_HOOK_ALG_KIND_EXCH
JOSE_HOOK_ALG_KIND_KEM,
JOSE_HOOK_ALG_KIND_LAST = JOSE_HOOK_ALG_KIND_KEM
} jose_hook_alg_kind_t;

typedef struct jose_hook_jwk jose_hook_jwk_t;
Expand Down Expand Up @@ -169,6 +170,22 @@ struct jose_hook_alg {
(*exc)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
const json_t *prv, const json_t *pub);
} exch;

struct {
const char *prm;

const char *
(*sug)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
const json_t *jwk);

json_t *
(*enc)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
const json_t *pub);

json_t *
(*dec)(const jose_hook_alg_t *alg, jose_cfg_t *cfg,
const json_t *prv, const json_t *ct);
} kem;
};
};

Expand Down
3 changes: 3 additions & 0 deletions lib/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ malloc_feed(jose_io_t *io, const void *in, size_t len)
if (len == 0)
return true;

if (SIZE_MAX - *i->len < len)
return false;

tmp = jose_realloc(*i->buf, *i->len + len);
if (!tmp)
return false;
Expand Down
Loading
Loading