Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend identity cert #823

Merged
merged 5 commits into from
Feb 7, 2025
Merged
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
7 changes: 4 additions & 3 deletions inc_internal/internal_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ XX(expires, timestamp, none, expiresAt, __VA_ARGS__) \
XX(expireSeconds, model_number, none, expirationSeconds, __VA_ARGS__) \
XX(updated, timestamp, none, updatedAt, __VA_ARGS__) \
XX(cached_last_activity_at, timestamp, none, cachedLastActivityAt, __VA_ARGS__) \
XX(identity_id, model_string, none, identityId, __VA_ARGS__) \
XX(identity, ziti_identity, none, identity, __VA_ARGS__) \
XX(posture_query_set, ziti_posture_query_set, array, postureQueries, __VA_ARGS__) \
XX(is_mfa_required, model_bool, none, IsMfaRequired, __VA_ARGS__) \
XX(is_mfa_complete, model_bool, none, IsMfaComplete, __VA_ARGS__) \
XX(is_cert_extendable, model_bool, none, IsCertExtendable, __VA_ARGS__) \
XX(is_mfa_required, model_bool, none, isMfaRequired, __VA_ARGS__) \
XX(is_mfa_complete, model_bool, none, isMfaComplete, __VA_ARGS__) \
XX(is_cert_extendable, model_bool, none, isCertExtendable, __VA_ARGS__) \
XX(auth_queries, ziti_auth_query_mfa, list, authQueries, __VA_ARGS__) \
XX(authenticator_id, model_string, none, authenticatorId, __VA_ARGS__)

Expand Down
12 changes: 12 additions & 0 deletions includes/ziti/ziti.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,18 @@ typedef struct ziti_options_s {
* \brief callback invoked is response to subscribed events.
*/
ziti_event_cb event_cb;

/**
* \brief this setting allows SDK to auto-extend identity certificate.
*
* This only applies if certificate was issued by the OpenZiti network.
* The application must handle [ZitiConfigEvent] to capture and save
* the newly issued certificate.
* SDK will extend certificate when expiration date falls
* in the next [cert_extension_window] days.
* To enable certificate extension the value must be greater than 0
*/
unsigned int cert_extension_window;
} ziti_options;

typedef struct ziti_dial_opts_s {
Expand Down
133 changes: 132 additions & 1 deletion library/ziti.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <auth_queries.h>
#include <uv.h>
#include <assert.h>
#include <time.h>

#if _WIN32

Expand All @@ -39,6 +40,8 @@
#endif
#endif

#define ONE_DAY (60 * 60 * 24)

#define ztx_controller(ztx) \
((ztx)->ctrl.url ? (ztx)->ctrl.url : (ztx)->config.controller_url)

Expand Down Expand Up @@ -75,6 +78,8 @@
static int ztx_init_controller(ziti_context ztx);
static void ztx_config_update(ziti_context ztx);

static void api_session_cb(ziti_api_session *, const ziti_error *, void *);

static uint32_t ztx_seq;

struct ztx_req_s {
Expand Down Expand Up @@ -416,6 +421,26 @@
ziti_ctrl_create_api_certificate(ztx_get_controller(ztx), ztx->sessionCsr, on_create_cert, ztx);
}

if (ztx->opts.cert_extension_window && ztx->id_creds.cert) {
struct tm exp;

ztx->id_creds.cert->get_expiration(ztx->id_creds.cert, &exp);
time_t now = time(0);
time_t exptime = mktime(&exp);

bool renew = exptime - now < ztx->opts.cert_extension_window * ONE_DAY;
if (renew) {
if (ztx->opts.events & ZitiConfigEvent) {
ZTX_LOG(INFO, "renewing identity certificate exp[%04d-%02d-%02d %02d:%02d]",
1900 + exp.tm_year, exp.tm_mon + 1, exp.tm_mday, exp.tm_hour, exp.tm_min);
ziti_ctrl_current_api_session(ztx_get_controller(ztx), api_session_cb, ztx);
} else {
ZTX_LOG(WARN, "identity certificate needs to be renewed but application is not handling ZitiConfigEvent");
}

}
}

ziti_services_refresh(ztx, true);
ziti_posture_init(ztx, 20);
}
Expand Down Expand Up @@ -1722,7 +1747,7 @@
id_it = model_list_it_remove(id_it);
}

MODEL_MAP_FOREACH(conn_id, conn, &ztx->connections) {

Check warning on line 1750 in library/ziti.c

View workflow job for this annotation

GitHub Actions / MacOS arm64

comparison between pointer and integer ('uint32_t' (aka 'unsigned int') and 'void *') [-Wpointer-integer-compare]

Check warning on line 1750 in library/ziti.c

View workflow job for this annotation

GitHub Actions / MacOS x86_64

comparison between pointer and integer ('uint32_t' (aka 'unsigned int') and 'void *') [-Wpointer-integer-compare]
if (conn->type == Server) {
update_bindings(conn);
}
Expand Down Expand Up @@ -1866,6 +1891,7 @@
copy_opt(pq_mac_cb);
copy_opt(pq_os_cb);
copy_opt(pq_process_cb);
copy_opt(cert_extension_window);

#undef copy_opt
}
Expand Down Expand Up @@ -1999,4 +2025,109 @@
ziti_channel_connect(ztx, er->name, url);
}
return ch;
}
}

struct cert_ext_req {
ziti_api_session *session;
ziti_context ztx;
ziti_extend_cert_authenticator_resp *cert_resp;
tlsuv_certificate_t new_cert;
};

static void cert_verify_cb(void *r, const ziti_error *err, void *ctx) {
struct cert_ext_req *req = ctx;
ziti_context ztx = req->ztx;
if (err) {
ZTX_LOG(ERROR, "failed to verify extended identity certificate: %s", err->message);
goto done;
}

if (ztx->tlsCtx->set_own_cert(ztx->tlsCtx, ztx->id_creds.key, req->new_cert) != 0) {
ZTX_LOG(ERROR, "extended certificate did not match key");
goto done;
}


struct tm exp;
req->new_cert->get_expiration(req->new_cert, &exp);

ZTX_LOG(INFO, "successfully verified extended cert. good until %04d-%02d-%02d %02d:%02d",
1900 + exp.tm_year, exp.tm_mon + 1, exp.tm_mday, exp.tm_hour, exp.tm_min);
ztx->id_creds.cert = req->new_cert;
req->new_cert = NULL;

FREE(ztx->config.id.ca);
FREE(ztx->config.id.cert);
ztx->config.id.ca = req->cert_resp->cas_pem;
ztx->config.id.cert = req->cert_resp->client_cert_pem;
req->cert_resp->cas_pem = NULL;
req->cert_resp->client_cert_pem = NULL;

ztx_config_update(ztx);

done:
if (req->new_cert) req->new_cert->free(req->new_cert);
free_ziti_api_session_ptr(req->session);
free_ziti_extend_cert_authenticator_resp_ptr(req->cert_resp);
free(req);
}

static void cert_extend_cb(ziti_extend_cert_authenticator_resp *resp, const ziti_error *err, void *ctx) {
struct cert_ext_req *req = ctx;
ziti_context ztx = req->ztx;
if (err) {
ZTX_LOG(ERROR, "failed to extend identity certificate: %s", err->message);
free_ziti_api_session_ptr(req->session);
free(req);
return;
}

assert(resp);
if (ztx->tlsCtx->load_cert(&req->new_cert, resp->client_cert_pem, strlen(resp->client_cert_pem)) != 0) {
ZTX_LOG(ERROR, "failed to parse new certificate");
free_ziti_extend_cert_authenticator_resp_ptr(resp);
free_ziti_api_session_ptr(req->session);
free(req);
return;
}

ZTX_LOG(INFO, "successfully generated extended cert");
req->cert_resp = resp;
ziti_ctrl_verify_extend_cert_authenticator(
ztx_get_controller(ztx), req->session->authenticator_id,
resp->client_cert_pem, cert_verify_cb, req);

}

static void api_session_cb(ziti_api_session *api_sess, const ziti_error *err, void *ctx) {
ziti_context ztx = ctx;
char *csr = NULL;
size_t len = 0;
if (api_sess) {
if (!api_sess->is_cert_extendable) {
ZTX_LOG(WARN, "identity certificate is not renewable");
goto done;
}

if (ztx->tlsCtx->generate_csr_to_pem(ztx->id_creds.key, &csr, &len, "O", "OpenZiti",
"DC", ztx->config.controller_url,
"CN", api_sess->identity_id,
NULL) != 0) {
ZTX_LOG(WARN, "failed to generate certificate request");
goto done;
}

NEWP(ext_req, struct cert_ext_req);
ext_req->session = api_sess;
ext_req->ztx = ztx;

api_sess = NULL;
ziti_ctrl_extend_cert_authenticator(ztx_get_controller(ztx),
ext_req->session->authenticator_id, csr,
cert_extend_cb, ext_req);
}

done:
free_ziti_api_session_ptr(api_sess);
free(csr);
}
Loading