kx509: Add CSR support

This commit adds support for proof of posession to the kx509 protocol by
using PKCS#10 CSRs.

This allows conveyance of extReq CSR attributes requesting desired
Certificate Extensions.
This commit is contained in:
Nicolas Williams
2019-07-15 23:27:30 -05:00
parent c838abdf1a
commit dfada0ccad
16 changed files with 1690 additions and 457 deletions

View File

@@ -43,7 +43,7 @@
* This file implements the kx509 service.
*
* The protocol, its shortcomings, and its future are described in
* lib/krb5/hx509.c.
* lib/krb5/hx509.c. See also lib/asn1/kx509.asn1.
*
* The service handles requests, decides whether to issue a certificate, and
* does so by populating a "template" to generate a TBSCertificate and signing
@@ -57,8 +57,8 @@
* Besides future protocol improvements described in lib/krb5/hx509.c, here is
* a list of KDC functionality we'd like to add:
*
* - support templates as strings in configuration?
* - lookup an hx509 template for the client principal in its HDB entry
* - support templates as strings (rather than filenames) in configuration?
* - lookup an hx509 template for the client principal in its HDB entry?
* - lookup subjectName, SANs for a principal in its HDB entry
* - lookup a host-based client principal's HDB entry and add its canonical
* name / aliases as dNSName SANs
@@ -76,6 +76,23 @@
static const unsigned char version_2_0[4] = {0 , 0, 2, 0};
typedef struct kx509_req_context {
krb5_kdc_configuration *config;
const struct Kx509Request *req;
Kx509CSRPlus csr_plus;
krb5_auth_context ac;
const char *realm; /* XXX Confusion: is this crealm or srealm? */
char *sname;
char *cname;
struct sockaddr *addr;
const char *from;
krb5_keyblock *key;
hx509_request csr;
krb5_data *reply;
unsigned int have_auth_data:1; /* Relevant authz data in the AP-REQ */
unsigned int send_chain:1; /* Client expects a full chain */
} *kx509_req_context;
/*
* Taste the request to see if it's a kx509 request.
*/
@@ -150,6 +167,37 @@ verify_req_hash(krb5_context context,
return 0;
}
/* Wrapper around kdc_log() that adds contextual information */
static void
kx509_log(krb5_context context,
kx509_req_context reqctx,
int level,
const char *fmt,
...)
{
va_list ap;
char *msg;
va_start(ap, fmt);
if (vasprintf(&msg, fmt, ap) == -1 || msg == NULL) {
kdc_log(context, reqctx->config, level,
"Out of memory while formatting log message");
va_end(ap);
va_start(ap, fmt);
kdc_vlog(context, reqctx->config, level, fmt, ap);
va_end(ap);
return;
}
va_end(ap);
kdc_log(context, reqctx->config, level,
"kx509 %s (from %s for %s, service %s)", msg,
reqctx->from ? reqctx->from : "<unknown>",
reqctx->cname ? reqctx->cname : "<unknown-client-principal>",
reqctx->sname ? reqctx->sname : "<unknown-service-principal>");
free(msg);
}
/*
* Set the HMAC in the response.
*/
@@ -206,6 +254,22 @@ calculate_reply_hash(krb5_context context,
return 0;
}
/*
* Lookup the principal's HDB entry, authorize the requested extensions, add
* authorized extensions to the `tbs', and indicate whether to add any of the
* EKUs/SANs we'd normally add automatically.
*/
static krb5_error_code
get_hdb_ekus_and_sans(krb5_context context,
kx509_req_context reqctx,
krb5_principal principal,
hx509_ca_tbs tbs,
int *add_auto_exts)
{
*add_auto_exts = 1;
return ENOTSUP;
}
/*
* Finds a template in the configuration that is appropriate to the form of the
* client principal. Also sets some variables in `env' and adds some SANs to
@@ -222,7 +286,7 @@ calculate_reply_hash(krb5_context context,
*/
static krb5_error_code
get_template(krb5_context context,
krb5_kdc_configuration *config,
kx509_req_context reqctx,
krb5_principal principal,
const char *princ_no_realm,
const char *princ,
@@ -230,13 +294,20 @@ get_template(krb5_context context,
hx509_env *env,
hx509_ca_tbs tbs)
{
krb5_error_code ret = KRB5KDC_ERR_POLICY;
unsigned int ncomp = krb5_principal_get_num_comp(context, principal);
const char *crealm = krb5_principal_get_realm(context, principal);
const char *kx509_template = NULL;
const char *comp0, *comp1, *comp2;
char *domain = NULL;
char *email = NULL;
krb5_error_code ret = KRB5KDC_ERR_POLICY;
int add_auto_exts = 1;
/* Populate extensions from CSR / HDB entry as requested and permitted */
ret = get_hdb_ekus_and_sans(context, reqctx, principal, tbs,
&add_auto_exts);
if (ret != 0 && ret != ENOTSUP)
return ret;
if (ncomp == 1) {
/* 1-component, user principal */
@@ -246,7 +317,7 @@ get_template(krb5_context context,
crealm, "kx509_template",
NULL);
if (kx509_template == NULL)
kx509_template = config->kx509_template;
kx509_template = reqctx->config->kx509_template;
if (kx509_template == NULL)
goto out;
@@ -264,7 +335,7 @@ get_template(krb5_context context,
* XXX Dicey feature! Maybe this should be a string param whose value
* is the domainname to use for the email address.
*/
if (ret == 0 &&
if (ret == 0 && add_auto_exts &&
get_bool_param(context, FALSE, crealm, "kx509_include_email_san")) {
char *p;
@@ -319,7 +390,7 @@ get_template(krb5_context context,
if (ret == 0 && ncomp == 3)
ret = hx509_env_add(context->hx509ctx, env, "principal-domain-name", comp2);
if (ret == 0 &&
if (ret == 0 && add_auto_exts &&
get_bool_param(context, FALSE, crealm,
"kx509_include_dnsname_san")) {
ret = hx509_ca_tbs_add_san_hostname(context->hx509ctx, tbs, comp1);
@@ -342,13 +413,13 @@ get_template(krb5_context context,
"kx509_templates",
config_label, comp0, NULL);
if (kx509_template == NULL) {
kdc_log(context, config, 0, "kx509 template not found for %s",
kdc_log(context, reqctx->config, 0, "kx509 template not found for %s",
princ);
ret = KRB5KDC_ERR_POLICY;
goto out;
}
} else {
kdc_log(context, config, 0, "kx509 client %s has too many components!",
kdc_log(context, reqctx->config, 0, "kx509 client %s has too many components!",
princ);
ret = KRB5KDC_ERR_POLICY;
}
@@ -360,13 +431,60 @@ out:
return ret;
}
static int
chain_add1_func(hx509_context context, void *d, hx509_cert c)
{
heim_octet_string os;
Certificates *cs = d;
Certificate c2;
int ret;
ret = hx509_cert_binary(context, c, &os);
if (ret)
return ret;
ret = decode_Certificate(os.data, os.length, &c2, NULL);
der_free_octet_string(&os);
if (ret)
return ret;
ret = add_Certificates(cs, &c2);
free_Certificate(&c2);
return ret;
}
static krb5_error_code
encode_cert_and_chain(hx509_context hx509ctx,
hx509_cert cert,
const char *chain_store,
krb5_data *out)
{
krb5_error_code ret;
Certificates cs;
hx509_certs certs = NULL;
size_t len;
cs.len = 0;
cs.val = 0;
ret = chain_add1_func(hx509ctx, &cs, cert);
if (ret == 0)
ret = hx509_certs_init(hx509ctx, chain_store, 0, NULL, &certs);
if (ret == 0)
ret = hx509_certs_iter_f(hx509ctx, certs, chain_add1_func, &cs);
hx509_certs_free(&certs);
if (ret == 0)
ASN1_MALLOC_ENCODE(Certificates, out->data, out->length,
&cs, &len, ret);
free_Certificates(&cs);
return ret;
}
/*
* Build a certifate for `principal´ that will expire at `endtime´.
*/
static krb5_error_code
build_certificate(krb5_context context,
krb5_kdc_configuration *config,
const krb5_data *key,
kx509_req_context reqctx,
time_t endtime,
krb5_principal principal,
krb5_data *certificate)
@@ -390,11 +508,11 @@ build_certificate(krb5_context context,
kx509_ca = krb5_config_get_string(context, NULL, "kdc", "realms", crealm,
"kx509_ca", NULL);
if (kx509_ca == NULL)
kx509_ca = config->kx509_ca;
kx509_ca = reqctx->config->kx509_ca;
if (kx509_ca == NULL) {
ret = KRB5KDC_ERR_POLICY;
kdc_log(context, config, 0, "No kx509 CA credential specified for "
"realm %s", crealm);
kdc_log(context, reqctx->config, 0,
"No kx509 CA credential specified for realm %s", crealm);
goto out;
}
@@ -409,18 +527,19 @@ build_certificate(krb5_context context,
goto out;
/* Get a template and set things in `env' and `tbs' as appropriate */
ret = get_template(context, config, principal, name, princ,
ret = get_template(context, reqctx, principal, name, princ,
&kx509_template, &env, tbs);
if (ret)
goto out;
if (kx509_template == NULL) {
kdc_log(context, config, 0, "No kx509 certificate template specified");
kdc_log(context, reqctx->config, 0,
"No kx509 certificate template specified");
ret = KRB5KDC_ERR_POLICY;
goto out;
}
kdc_log(context, config, 0, "Issuing kx509 certificate to %s using "
"template %s", princ, kx509_template);
kdc_log(context, reqctx->config, 0, "Issuing kx509 certificate to %s "
"using template %s", princ, kx509_template);
/*
* Populate additional template "env" variables
@@ -442,7 +561,8 @@ build_certificate(krb5_context context,
ret = hx509_certs_init(context->hx509ctx, kx509_ca, 0, NULL, &certs);
if (ret) {
kdc_log(context, config, 0, "Failed to load CA %s", kx509_ca);
kdc_log(context, reqctx->config, 0,
"Failed to load CA %s", kx509_ca);
goto out;
}
ret = hx509_query_alloc(context->hx509ctx, &q);
@@ -458,7 +578,8 @@ build_certificate(krb5_context context,
hx509_query_free(context->hx509ctx, q);
hx509_certs_free(&certs);
if (ret) {
kdc_log(context, config, 0, "Failed to find a CA in %s", kx509_ca);
kdc_log(context, reqctx->config, 0,
"Failed to find a CA in %s", kx509_ca);
goto out;
}
}
@@ -466,22 +587,13 @@ build_certificate(krb5_context context,
/* Populate the subject public key in the TBS context */
{
SubjectPublicKeyInfo spki;
heim_any any;
memset(&spki, 0, sizeof(spki));
spki.subjectPublicKey.data = key->data;
spki.subjectPublicKey.length = key->length * 8;
ret = der_copy_oid(&asn1_oid_id_pkcs1_rsaEncryption,
&spki.algorithm.algorithm);
any.data = "\x05\x00";
any.length = 2;
spki.algorithm.parameters = &any;
ret = hx509_request_get_SubjectPublicKeyInfo(context->hx509ctx,
reqctx->csr,
&spki);
if (ret == 0)
ret = hx509_ca_tbs_set_spki(context->hx509ctx, tbs, &spki);
der_free_oid(&spki.algorithm.algorithm);
free_SubjectPublicKeyInfo(&spki);
if (ret)
goto out;
}
@@ -497,8 +609,8 @@ build_certificate(krb5_context context,
ret = hx509_get_one_cert(context->hx509ctx, certs, &template);
hx509_certs_free(&certs);
if (ret) {
kdc_log(context, config, 0, "Failed to load template from %s",
kx509_template);
kdc_log(context, reqctx->config, 0,
"Failed to load template from %s", kx509_template);
goto out;
}
@@ -507,8 +619,8 @@ build_certificate(krb5_context context,
* certificate.
*/
ret = hx509_ca_tbs_set_template(context->hx509ctx, tbs,
HX509_CA_TEMPLATE_SUBJECT|
HX509_CA_TEMPLATE_KU|
HX509_CA_TEMPLATE_SUBJECT |
HX509_CA_TEMPLATE_KU |
HX509_CA_TEMPLATE_EKU,
template);
hx509_cert_free(template);
@@ -528,6 +640,9 @@ build_certificate(krb5_context context,
* PKIX (w/ softtoken) -> Kerberos ->
* PKIX (w/ softtoken) -> Kerberos ->
* ...
*
* Note that we may not have added the PKINIT EKU -- that depends on the
* template, and host-based service templates might well not include it.
*/
if (ret == 0 &&
get_bool_param(context, TRUE, crealm, "kx509_include_pkinit_san")) {
@@ -536,22 +651,30 @@ build_certificate(krb5_context context,
goto out;
}
/*
* Note that we set the certificate's end time to the client's *Ticket*'s
* end time. For server certs this may not always be appropriate. We
* might want to have a configurable setting for this, in which case maybe
* we should move this to get_template().
*/
hx509_ca_tbs_set_notAfter(context->hx509ctx, tbs, endtime);
/* Finally, expand the subjectName in the TBS context and sign to issue */
/* Expand the subjectName template in the TBS */
hx509_ca_tbs_subject_expand(context->hx509ctx, tbs, env);
hx509_env_free(&env);
/* All done with the TBS, sign/issue the certificate */
ret = hx509_ca_sign(context->hx509ctx, tbs, signer, &cert);
if (ret)
goto out;
/* Encode and output the certificate */
if (reqctx->send_chain)
ret = encode_cert_and_chain(context->hx509ctx, cert, kx509_ca, certificate);
else
ret = hx509_cert_binary(context->hx509ctx, cert, certificate);
out:
if (ret)
kdc_log(context, config, 0, "Failed to build a certificate for %s",
princ);
krb5_xfree(name);
krb5_xfree(princ);
if (env)
@@ -568,7 +691,7 @@ out:
/* Check that a krbtgt's second component is a local realm */
static krb5_error_code
is_local_realm(krb5_context context,
krb5_kdc_configuration *config,
kx509_req_context reqctx,
const char *realm)
{
krb5_error_code ret;
@@ -580,8 +703,8 @@ is_local_realm(krb5_context context,
if (ret)
return ret;
if (ret == 0)
ret = _kdc_db_fetch(context, config, tgs, HDB_F_GET_KRBTGT, NULL, NULL,
&ent);
ret = _kdc_db_fetch(context, reqctx->config, tgs, HDB_F_GET_KRBTGT,
NULL, NULL, &ent);
if (ent)
_kdc_free_ent(context, ent);
krb5_free_principal(context, tgs);
@@ -598,7 +721,7 @@ is_local_realm(krb5_context context,
*
* We allow cross-realm requests.
*
* XXX Maybe x-realm support should be configurable. Requiring INITIAL tickets
* Maybe x-realm support should be configurable. Requiring INITIAL tickets
* does NOT preclude x-realm support! (Cross-realm TGTs can be INITIAL.)
*
* Support for specific client realms is configurable by configuring issuer
@@ -606,10 +729,9 @@ is_local_realm(krb5_context context,
* default. But maybe we should have an explicit configuration parameter
* to enable support for clients from different realms than the service.
*/
krb5_error_code
static krb5_error_code
kdc_kx509_verify_service_principal(krb5_context context,
krb5_kdc_configuration *config,
const char *cname,
kx509_req_context reqctx,
krb5_principal sprincipal)
{
krb5_error_code ret = 0;
@@ -624,8 +746,8 @@ kdc_kx509_verify_service_principal(krb5_context context,
if (strcmp(krb5_principal_get_comp_string(context, sprincipal, 0),
KRB5_TGS_NAME) == 0) {
const char *r = krb5_principal_get_comp_string(context, sprincipal, 1);
if ((ret = is_local_realm(context, config, r)))
kdc_log(context, config, 0, "client used wrong krbtgt for kx509");
if ((ret = is_local_realm(context, reqctx, r)))
kx509_log(context, reqctx, 0, "client used wrong krbtgt for kx509");
goto out;
}
@@ -653,10 +775,8 @@ err:
goto out;
ret = KRB5KDC_ERR_SERVER_NOMATCH;
krb5_set_error_message(context, ret,
"User %s used wrong Kx509 service "
"principal, expected: %s",
cname, expected);
kx509_log(context, reqctx, 0, "client used wrong kx509 service principal "
"(expected %s)", expected);
out:
krb5_xfree(expected);
@@ -667,39 +787,37 @@ out:
static krb5_error_code
encode_reply(krb5_context context,
krb5_kdc_configuration *config,
krb5_data *reply,
kx509_req_context reqctx,
Kx509Response *r)
{
krb5_error_code ret;
krb5_data data;
size_t size;
reply->data = NULL;
reply->length = 0;
reqctx->reply->data = NULL;
reqctx->reply->length = 0;
ASN1_MALLOC_ENCODE(Kx509Response, data.data, data.length, r, &size, ret);
if (ret) {
kdc_log(context, config, 0, "Failed to encode kx509 reply");
kdc_log(context, reqctx->config, 0, "Failed to encode kx509 reply");
return ret;
}
if (size != data.length)
krb5_abortx(context, "ASN1 internal error");
ret = krb5_data_alloc(reply, data.length + sizeof(version_2_0));
ret = krb5_data_alloc(reqctx->reply, data.length + sizeof(version_2_0));
if (ret == 0) {
memcpy(reply->data, version_2_0, sizeof(version_2_0));
memcpy(((unsigned char *)reply->data) + sizeof(version_2_0),
memcpy(reqctx->reply->data, version_2_0, sizeof(version_2_0));
memcpy(((unsigned char *)reqctx->reply->data) + sizeof(version_2_0),
data.data, data.length);
}
free(data.data);
return ret;
}
/* Make an error response, and log the error message as well */
static krb5_error_code
mk_error_response(krb5_context context,
krb5_kdc_configuration *config,
krb5_keyblock *key,
krb5_data *reply,
kx509_req_context reqctx,
int32_t code,
const char *fmt,
...)
@@ -712,7 +830,7 @@ mk_error_response(krb5_context context,
char *freeme1 = NULL;
va_list ap;
if (!config->enable_kx509)
if (!reqctx->config->enable_kx509)
code = KRB5KDC_ERR_POLICY;
/* Make sure we only send RFC4120 and friends wire protocol error codes */
@@ -736,13 +854,13 @@ mk_error_response(krb5_context context,
msg = freeme0;
va_end(ap);
if (!config->enable_kx509 &&
if (!reqctx->config->enable_kx509 &&
asprintf(&freeme1, "kx509 service is disabled (%s)", msg) > -1 &&
freeme1 != NULL) {
msg = freeme1;
}
kdc_log(context, config, 0, "%s", msg);
kdc_log(context, reqctx->config, 0, "%s", msg);
rep.hash = NULL;
rep.certificate = NULL;
@@ -750,15 +868,15 @@ mk_error_response(krb5_context context,
if (ALLOC(rep.e_text))
*rep.e_text = (void *)(uintptr_t)msg;
if (key) {
if (reqctx->key) {
if (ALLOC(rep.hash) != NULL &&
calculate_reply_hash(context, key, &rep)) {
calculate_reply_hash(context, reqctx->key, &rep)) {
free(rep.hash);
rep.hash = NULL;
}
}
if ((ret2 = encode_reply(context, config, reply, &rep)))
if ((ret2 = encode_reply(context, reqctx, &rep)))
ret = ret2;
if (rep.hash)
krb5_data_free(rep.hash);
@@ -769,6 +887,159 @@ mk_error_response(krb5_context context,
return ret;
}
/* Wrap a bare public (RSA) key with a CSR (not signed it, since we can't) */
static krb5_error_code
make_csr(krb5_context context, kx509_req_context reqctx, krb5_data *key)
{
krb5_error_code ret;
SubjectPublicKeyInfo spki;
heim_any any;
ret = hx509_request_init(context->hx509ctx, &reqctx->csr);
if (ret)
return ret;
memset(&spki, 0, sizeof(spki));
spki.subjectPublicKey.data = key->data;
spki.subjectPublicKey.length = key->length * 8;
ret = der_copy_oid(&asn1_oid_id_pkcs1_rsaEncryption,
&spki.algorithm.algorithm);
any.data = "\x05\x00";
any.length = 2;
spki.algorithm.parameters = &any;
if (ret == 0)
ret = hx509_request_set_SubjectPublicKeyInfo(context->hx509ctx,
reqctx->csr, &spki);
der_free_oid(&spki.algorithm.algorithm);
if (ret)
hx509_request_free(&reqctx->csr);
/*
* TODO: Move a lot of the templating stuff here so we can let clients
* leave out extensions they don't want.
*/
return ret;
}
/* Update a CSR with desired Certificate Extensions */
static krb5_error_code
update_csr(krb5_context context, kx509_req_context reqctx, Extensions *exts)
{
krb5_error_code ret = 0;
size_t i, k;
if (exts == NULL)
return 0;
for (i = 0; ret == 0 && i < exts->len; i++) {
Extension *e = &exts->val[i];
if (der_heim_oid_cmp(&e->extnID, &asn1_oid_id_x509_ce_keyUsage) == 0) {
KeyUsage ku;
ret = decode_KeyUsage(e->extnValue.data, e->extnValue.length, &ku,
NULL);
if (ret)
return ret;
ret = hx509_request_set_ku(context->hx509ctx, reqctx->csr, ku);
} else if (der_heim_oid_cmp(&e->extnID,
&asn1_oid_id_x509_ce_extKeyUsage) == 0) {
ExtKeyUsage eku;
ret = decode_ExtKeyUsage(e->extnValue.data, e->extnValue.length,
&eku, NULL);
for (k = 0; ret == 0 && k < eku.len; k++) {
ret = hx509_request_add_eku(context->hx509ctx, reqctx->csr,
&eku.val[k]);
}
free_ExtKeyUsage(&eku);
} else if (der_heim_oid_cmp(&e->extnID,
&asn1_oid_id_x509_ce_subjectAltName) == 0) {
GeneralNames san;
ret = decode_GeneralNames(e->extnValue.data, e->extnValue.length,
&san, NULL);
for (k = 0; ret == 0 && k < san.len; k++)
ret = hx509_request_add_GeneralName(context->hx509ctx,
reqctx->csr, &san.val[k]);
free_GeneralNames(&san);
}
}
if (ret)
kx509_log(context, reqctx, 0,
"request has bad desired certificate extensions");
return ret;
}
/*
* Parse the `pk_key' from the request as a CSR or raw public key, and if the
* latter, wrap it in a non-signed CSR.
*/
static krb5_error_code
get_csr(krb5_context context, kx509_req_context reqctx)
{
krb5_error_code ret;
RSAPublicKey rsapkey;
heim_octet_string pk_key = reqctx->req->pk_key;
size_t size;
ret = decode_Kx509CSRPlus(pk_key.data, pk_key.length, &reqctx->csr_plus,
&size);
if (ret == 0) {
reqctx->send_chain = 1;
if (reqctx->csr_plus.authz_datas.len)
reqctx->have_auth_data = 1;
/* Parse CSR */
ret = hx509_request_parse_der(context->hx509ctx, &reqctx->csr_plus.csr,
&reqctx->csr);
if (ret)
kx509_log(context, reqctx, 0, "invalid CSR");
/*
* Handle any additional Certificate Extensions requested out of band
* of the CSR.
*/
if (ret == 0)
return update_csr(context, reqctx, reqctx->csr_plus.exts);
return ret;
}
reqctx->send_chain = 0;
/* Check if proof of possession is required by configuration */
if (!get_bool_param(context, FALSE, reqctx->realm, "require_csr"))
return mk_error_response(context, reqctx, KX509_STATUS_CLIENT_USE_CSR,
"CSRs required but client did not send one");
/* Attempt to decode pk_key as RSAPublicKey */
ret = decode_RSAPublicKey(reqctx->req->pk_key.data,
reqctx->req->pk_key.length,
&rsapkey, &size);
free_RSAPublicKey(&rsapkey);
if (ret == 0 && size == reqctx->req->pk_key.length)
return make_csr(context, reqctx, &pk_key); /* Make pretend CSR */
/* Not an RSAPublicKey or garbage follows it */
if (ret == 0)
kx509_log(context, reqctx, 0, "request has garbage after key");
return mk_error_response(context, reqctx, KRB5KDC_ERR_NULL_KEY,
"Could not decode CSR or RSA subject public key");
}
/* Stub for later work */
static krb5_error_code
verify_auth_data(krb5_context context,
struct kx509_req_context *reqctx,
krb5_principal cprincipal,
krb5_principal *actual_cprincipal)
{
return EACCES;
}
/*
* Process a request, produce a reply.
*/
@@ -782,13 +1053,29 @@ _kdc_do_kx509(krb5_context context,
krb5_error_code ret;
krb5_ticket *ticket = NULL;
krb5_flags ap_req_options;
krb5_auth_context ac = NULL;
krb5_principal actual_cprincipal = NULL;
krb5_principal cprincipal = NULL;
krb5_principal sprincipal = NULL;
krb5_keytab id = NULL;
krb5_principal sprincipal = NULL, cprincipal = NULL;
char *sname = NULL;
char *cname = NULL;
Kx509Response rep;
krb5_keyblock *key = NULL;
struct kx509_req_context reqctx;
int is_probe = 0;
memset(&reqctx, 0, sizeof(reqctx));
reqctx.csr_plus.authz_datas.val = NULL;
reqctx.csr_plus.csr.data = NULL;
reqctx.csr_plus.exts = NULL;
reqctx.config = config;
reqctx.sname = NULL;
reqctx.cname = NULL;
reqctx.realm = NULL;
reqctx.reply = reply;
reqctx.from = from;
reqctx.addr = addr;
reqctx.key = NULL;
reqctx.csr = NULL;
reqctx.req = req;
reqctx.ac = NULL;
/*
* In order to support authenticated error messages we defer checking
@@ -798,7 +1085,6 @@ _kdc_do_kx509(krb5_context context,
krb5_data_zero(reply);
memset(&rep, 0, sizeof(rep));
kdc_log(context, config, 0, "Kx509 request from %s", from);
if (req->authenticator.length == 0) {
/*
@@ -807,8 +1093,9 @@ _kdc_do_kx509(krb5_context context,
* mk_error_response() will check whether the service is enabled and
* possibly change the error code and message.
*/
ret = mk_error_response(context, config, key, reply,
KRB5KDC_ERR_NULL_KEY,
is_probe = 1;
kx509_log(context, &reqctx, 0, "unauthenticated probe request");
ret = mk_error_response(context, &reqctx, KRB5KDC_ERR_NULL_KEY,
"kx509 service is available");
goto out;
}
@@ -816,22 +1103,22 @@ _kdc_do_kx509(krb5_context context,
/* Consume the AP-REQ */
ret = krb5_kt_resolve(context, "HDBGET:", &id);
if (ret) {
mk_error_response(context, config, key, reply,
ret = mk_error_response(context, &reqctx,
KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN,
"Can't open database for digest");
"Can't open HDB/keytab for kx509");
goto out;
}
ret = krb5_rd_req(context,
&ac,
&reqctx.ac,
&req->authenticator,
NULL,
id,
&ap_req_options,
&ticket);
if (ret == 0)
ret = krb5_auth_con_getkey(context, ac, &key);
if (ret == 0 && key == NULL)
ret = krb5_auth_con_getkey(context, reqctx.ac, &reqctx.key);
if (ret == 0 && reqctx.key == NULL)
ret = KRB5KDC_ERR_NULL_KEY;
/*
* Provided we got the session key, errors past this point will be
@@ -840,9 +1127,8 @@ _kdc_do_kx509(krb5_context context,
if (ret == 0)
ret = krb5_ticket_get_client(context, ticket, &cprincipal);
if (ret) {
mk_error_response(context, config, key, reply, ret,
"Could not get Ticket client principal from %s",
from);
ret = mk_error_response(context, &reqctx, ret,
"authentication failed");
goto out;
}
@@ -852,40 +1138,34 @@ _kdc_do_kx509(krb5_context context,
!get_bool_param(context, TRUE,
krb5_principal_get_realm(context, cprincipal),
"require_initial_kca_tickets")) {
ret = mk_error_response(context, config, key, reply,
KRB5KDC_ERR_POLICY, /* XXX */
"kx509 client %s used non-INITIAL tickets, "
"but kx509 service is configured to require "
"INITIAL tickets", from);
ret = mk_error_response(context, &reqctx, KRB5KDC_ERR_POLICY, /* XXX */
"client used non-INITIAL tickets, but kx509"
"kx509 service is configured to require "
"INITIAL tickets");
goto out;
}
ret = krb5_unparse_name(context, cprincipal, &cname);
ret = krb5_unparse_name(context, cprincipal, &reqctx.cname);
/* Check that the service name is a valid kx509 service name */
if (ret == 0)
ret = krb5_ticket_get_server(context, ticket, &sprincipal);
if (ret == 0)
ret = krb5_unparse_name(context, sprincipal, &sname);
reqctx.realm = krb5_principal_get_realm(context, sprincipal);
if (ret == 0)
ret = krb5_unparse_name(context, sprincipal, &reqctx.sname);
if (ret == 0)
ret = kdc_kx509_verify_service_principal(context, config, cname,
sprincipal);
ret = kdc_kx509_verify_service_principal(context, &reqctx, sprincipal);
if (ret) {
mk_error_response(context, config, key, reply, ret, "kx509 client %s from "
"%s used incorrect service name (%s) for kx509 "
"service",
cname ? cname : "<could not unparse>", from,
sname ? sname : "<could not unparse>");
mk_error_response(context, &reqctx, ret,
"client used incorrect service name");
goto out;
}
/* Authenticate the rest of the request */
ret = verify_req_hash(context, req, key);
ret = verify_req_hash(context, req, reqctx.key);
if (ret) {
mk_error_response(context, config, key, reply, ret, "Incorrect HMAC "
"for kx509 request for client %s from %s for %s",
cname, from, sname);
mk_error_response(context, &reqctx, ret, "Incorrect request HMAC");
goto out;
}
@@ -896,35 +1176,23 @@ _kdc_do_kx509(krb5_context context,
* mk_error_response() will check whether the service is enabled and
* possibly change the error code and message.
*/
ret = mk_error_response(context, config, key, reply, 0,
"kx509 probe request");
is_probe = 1;
ret = mk_error_response(context, &reqctx, 0,
"kx509 authenticated probe request");
goto out;
}
/*
* Verify that the key is a DER-encoded RSA key
*
* TODO: Try decoding `req->pk_key' as a DER-encoded CSR, or as a
* DER-encoded Certificate (and check that the subject key signed the
* thing).
*
* That will add proof-of-possesion.
*
* That will also add algorithm agility.
*/
{
RSAPublicKey rsapkey;
size_t rsapkeysize;
/* Extract and parse CSR or a DER-encoded RSA public key */
ret = get_csr(context, &reqctx);
if (ret)
goto out;
ret = decode_RSAPublicKey(req->pk_key.data, req->pk_key.length,
&rsapkey, &rsapkeysize);
free_RSAPublicKey(&rsapkey);
if (ret || rsapkeysize != req->pk_key.length) {
ret = KRB5KDC_ERR_NULL_KEY;
mk_error_response(context, config, key, reply, ret,
"Could not decode RSA subject public key for "
"kx509 client %s from %s for %s", cname, from,
sname);
if (reqctx.have_auth_data) {
ret = verify_auth_data(context, &reqctx, cprincipal,
&actual_cprincipal);
if (ret) {
ret = mk_error_response(context, &reqctx, ret,
"authorization data validation failure");
goto out;
}
}
@@ -932,49 +1200,44 @@ _kdc_do_kx509(krb5_context context,
ALLOC(rep.hash);
ALLOC(rep.certificate);
if (rep.certificate == NULL || rep.hash == NULL) {
ret = mk_error_response(context, config, key, reply, ENOMEM,
"Could allocate memory for response for kx509 "
"client %s from %s for %s",
cname, from, sname);
ret = mk_error_response(context, &reqctx, ENOMEM,
"could allocate memory for response");
goto out;
}
/* Issue the certificate */
krb5_data_zero(rep.hash);
krb5_data_zero(rep.certificate);
ret = build_certificate(context, config, &req->pk_key,
ret = build_certificate(context, &reqctx,
krb5_ticket_get_endtime(context, ticket),
cprincipal, rep.certificate);
actual_cprincipal ? actual_cprincipal : cprincipal,
rep.certificate);
if (ret) {
mk_error_response(context, config, key, reply, ret, "Failed to build "
"certificate for kx509 client %s from %s for %s",
cname, from, sname);
mk_error_response(context, &reqctx, ret, "Failed to build certificate");
goto out;
}
/* Authenticate the response */
ret = calculate_reply_hash(context, key, &rep);
ret = calculate_reply_hash(context, reqctx.key, &rep);
if (ret) {
mk_error_response(context, config, key, reply, ret, "Failed to HMAC "
"certificate for kx509 client %s from %s for %s",
cname, from, sname);
mk_error_response(context, &reqctx, ret,
"Failed to compute response HMAC");
goto out;
}
/* Encode and output reply */
ret = encode_reply(context, config, reply, &rep);
ret = encode_reply(context, &reqctx, &rep);
if (ret)
mk_error_response(context, config, key, reply, ret, "Could not encode "
"kx509 response to client %s from %s for %s", cname,
from, sname);
/* Can't send an error message either in this case, surely */
kx509_log(context, &reqctx, 0, "Could not encode response");
out:
if (ret == 0)
kdc_log(context, config, 0, "Successful Kx509 request for %s", cname);
if (ac)
krb5_auth_con_free(context, ac);
if (ret)
krb5_warn(context, ret, "Kx509 request from %s failed", from);
if (ret == 0 && !is_probe)
kx509_log(context, &reqctx, 0, "Issued certificate");
else
kx509_log(context, &reqctx, 0, "Did not issue certificate");
if (reqctx.ac)
krb5_auth_con_free(context, reqctx.ac);
if (ticket)
krb5_free_ticket(context, ticket);
if (id)
@@ -983,12 +1246,16 @@ out:
krb5_free_principal(context, sprincipal);
if (cprincipal)
krb5_free_principal(context, cprincipal);
if (key)
krb5_free_keyblock (context, key);
if (sname)
free(sname);
if (cname)
free(cname);
if (actual_cprincipal)
krb5_free_principal(context, actual_cprincipal);
if (reqctx.key)
krb5_free_keyblock (context, reqctx.key);
if (reqctx.sname)
free(reqctx.sname);
if (reqctx.cname)
free(reqctx.cname);
hx509_request_free(&reqctx.csr);
free_Kx509CSRPlus(&reqctx.csr_plus);
free_Kx509Response(&rep);
return ret;

View File

@@ -17,7 +17,8 @@ man_MANS = \
kswitch.1 \
kdigest.8 \
kgetcred.1 \
kimpersonate.8
kimpersonate.8 \
kx509.1
bin_PROGRAMS = kinit kdestroy kgetcred heimtools
libexec_PROGRAMS = kdigest kimpersonate
@@ -45,7 +46,7 @@ heimtools_LDADD = \
$(LIB_readline) \
$(LIB_hx509)
dist_heimtools_SOURCES = heimtools.c klist.c kswitch.c copy_cred_cache.c
dist_heimtools_SOURCES = heimtools.c klist.c kx509.c kswitch.c copy_cred_cache.c
nodist_heimtools_SOURCES = heimtools-commands.c
$(heimtools_OBJECTS): heimtools-commands.h
@@ -94,5 +95,6 @@ EXTRA_DIST = NTMakefile $(man_MANS) \
# make sure install-exec-hook doesn't have any commands in Makefile.am.common
install-exec-hook:
(cd $(DESTDIR)$(bindir) && rm -f klist && $(LN_S) heimtools klist)
(cd $(DESTDIR)$(bindir) && rm -f kx509 && $(LN_S) heimtools kx509)
(cd $(DESTDIR)$(bindir) && rm -f kswitch && $(LN_S) heimtools kswitch)

View File

@@ -79,6 +79,7 @@ HEIMTOOLS_OBJS = \
$(OBJ)\heimtools.obj \
$(OBJ)\kswitch.obj \
$(OBJ)\klist.obj \
$(OBJ)\kx509.obj \
$(OBJ)\copy_cred_cache.obj
HEIMTOOLSLIBS=\

View File

@@ -114,11 +114,6 @@ command = {
type = "flag"
help = "Verbose output"
}
option = {
long = "extract-kx509-cert"
type = "string"
help = "hx509 store for kx509 certificate and private key"
}
}
command = {
name = "kgetcred"
@@ -245,6 +240,60 @@ command = {
help = "Copies credential caches"
argument = "[source] destination"
}
command = {
name = "kx509"
help = "Acquire or extract certificates"
option = {
long = "cache"
short = "c"
type = "string"
help = "Kerberos credential cache"
}
option = {
long = "save"
short = "s"
type = "flag"
help = "save the certificate and private key in the Kerberos credential cache"
}
option = {
long = "out"
short = "o"
type = "string"
help = "hx509 store for kx509 certificate and private key"
}
option = {
long = "extract"
short = "x"
type = "flag"
help = "extract certificate and private key from credential cache"
}
option = {
long = "test"
short = "t"
type = "flag"
help = "exit successfully if certificate and private key are in credential cache"
}
option = {
name = "private-key"
short = "K"
type = "string"
help = "hx509 store containing private key"
}
option = {
name = "csr"
short = "C"
type = "string"
help = "file containing DER-encoded PKCS#10 certificate request"
}
option = {
name = "realm"
short = "r"
type = "string"
help = "realm from which to acquire certificate"
}
min_args = "0"
max_args = "0"
}
command = {
name = "help"
name = "?"

View File

@@ -44,8 +44,6 @@
.Fl Fl cache= Ns Ar cache
.Xc
.Oc
.Oo Fl Fl extract-kx509-cert= Ns Ar hx509-store
.Oc
.Op Fl s | Fl t | Fl Fl test
.Op Fl T | Fl Fl tokens
.Op Fl 5 | Fl Fl v5
@@ -67,17 +65,6 @@ credential cache to list
.It Fl s , Fl t , Fl Fl test
Test for there being an active and valid TGT for the local realm of
the user in the credential cache.
.It Fl Fl extract-kx509-cert= Ns Ar hx509-store
An hx509 store specification, such as
.Va DER-FILE:/path/to/der/file ,
.Va PEM-FILE:/path/to/PEM/file ,
.Va FILE:/path/to/PEM/file ,
or
.Va PKCS12:/path/to/PKCS#12/file
into which to store any PKIX certificate and private key
(unencrypted) that may have been acquired with the kx509 protocol
and stored in the
.Ns Ar ccache.
.It Fl T , Fl Fl tokens
display AFS tokens
.It Fl 5 , Fl Fl v5

View File

@@ -36,10 +36,7 @@
#include "kuser_locl.h"
#include "parse_units.h"
#include "heimtools-commands.h"
#include <kx509_asn1.h>
#undef HC_DEPRECATED_CRYPTO
#include "../lib/hx509/hx_locl.h"
#include "hx509-private.h"
static char*
printable_time_internal(time_t t, int x)
@@ -264,28 +261,15 @@ print_tickets(krb5_context context,
int do_verbose,
int do_flags,
int do_hidden,
int do_json,
const char *hx509_store)
int do_json)
{
char *str, *name, *fullname;
char *kx509_realm = NULL;
hx509_private_key key = NULL;
hx509_context hx509ctx = NULL;
hx509_certs certs = NULL;
hx509_cert cert = NULL;
krb5_error_code kx509_ret = 0;
krb5_error_code ret;
krb5_cc_cursor cursor;
krb5_creds creds;
krb5_deltat sec;
rtbl_t ct = NULL;
int print_comma = 0;
int kx509_disabled = 0;
int cert_stored = 0;
int cert_seen = 0;
if (hx509_store)
kx509_ret = hx509_context_init(&hx509ctx);
ret = krb5_unparse_name (context, principal, &str);
if (ret)
@@ -358,45 +342,6 @@ print_tickets(krb5_context context,
if (do_verbose && do_json)
printf("\"tickets\" : [");
while ((ret = krb5_cc_next_cred(context, ccache, &cursor, &creds)) == 0) {
if (krb5_is_config_principal(context, creds.server) &&
krb5_principal_get_num_comp(context, creds.server) == 2 &&
hx509_store) {
const char *s;
s = krb5_principal_get_comp_string(context, creds.server, 1);
if (strcmp(s, "kx509_service_status") == 0) {
kx509_disabled = 1;
} else if (strcmp(s, "kx509_service_realm") == 0) {
kx509_realm = strndup(creds.ticket.data, creds.ticket.length);
} else if (strcmp(s, "kx509cert") == 0) {
cert = hx509_cert_init_data(hx509ctx, creds.ticket.data,
creds.ticket.length, NULL);
} else if (strcmp(s, "kx509key") == 0) {
(void) hx509_parse_private_key(hx509ctx, NULL,
creds.ticket.data,
creds.ticket.length,
HX509_KEY_FORMAT_PKCS8, &key);
}
if (hx509ctx && cert && key && !cert_seen) {
/* Now store the cert and key into the given hx509 store */
(void) _hx509_cert_assign_key(cert, key);
kx509_ret = hx509_certs_init(hx509ctx, hx509_store,
HX509_CERTS_CREATE, NULL, &certs);
if (kx509_ret == 0)
kx509_ret = hx509_certs_add(hx509ctx, certs, cert);
if (kx509_ret == 0)
kx509_ret = hx509_certs_store(hx509ctx, certs, 0, NULL);
/*
* Wait till we're done listing the ccache to complain about
* failing to extract the cert and priv key.
*/
cert_seen = 1;
if (kx509_ret == 0)
cert_stored = 1;
}
}
if (!do_hidden && krb5_is_config_principal(context, creds.server)) {
;
} else if (do_verbose) {
@@ -415,40 +360,6 @@ print_tickets(krb5_context context,
if (ret)
krb5_err(context, 1, ret, "krb5_cc_end_seq_get");
/* Finish kx509 extraction error checking */
if (hx509_store && cert_seen && !cert_stored) {
if (!hx509ctx)
krb5_err(context, 1, kx509_ret,
N_("Failed to store certificate and private key "
"in %s due to failure to initialize context: %s", ""),
hx509_store, hx509_get_error_string(hx509ctx, kx509_ret));
if (!cert || !key)
krb5_err(context, 1, kx509_ret,
N_("Failed to store certificate and private key "
"in %s due to failure to parse them: %s", ""),
hx509_store, hx509_get_error_string(hx509ctx, kx509_ret));
krb5_err(context, 1, kx509_ret, N_("Failed to store certificate and "
"private key in %s", ""),
hx509_store);
}
if (hx509_store && !cert_seen) {
/* No PKIX creds in ccache, but maybe we can run kx509 now */
if (kx509_disabled)
krb5_errx(context, 1, N_("The kx509 protocol is disabled at the "
"KDC for realm %s", ""),
kx509_realm ? kx509_realm : "<out of memory>");
ret = krb5_kx509_ext(context, ccache, NULL, NULL, NULL, 0, hx509_store,
NULL);
if (ret)
krb5_err(context, 1, ret, N_("Failed to acquire certificate and "
"store it and private key in %s", ""),
hx509_store);
}
hx509_private_key_free(&key);
hx509_certs_free(&certs);
hx509_cert_free(cert);
hx509_context_free(&hx509ctx);
print_comma = 0;
if(!do_verbose) {
rtbl_format(ct, stdout);
@@ -566,7 +477,7 @@ static int
display_v5_ccache (krb5_context context, krb5_ccache ccache,
int do_test, int do_verbose,
int do_flags, int do_hidden,
int do_json, const char *hx509_store)
int do_json)
{
krb5_error_code ret;
krb5_principal principal;
@@ -591,7 +502,7 @@ display_v5_ccache (krb5_context context, krb5_ccache ccache,
exit_status = check_expiration(context, ccache, NULL);
else
print_tickets (context, ccache, principal, do_verbose,
do_flags, do_hidden, do_json, hx509_store);
do_flags, do_hidden, do_json);
ret = krb5_cc_close (context, ccache);
if (ret)
@@ -738,8 +649,8 @@ klist(struct klist_options *opt, int argc, char **argv)
exit_status |= display_v5_ccache(heimtools_context, id, do_test,
do_verbose, opt->flags_flag,
opt->hidden_flag, opt->json_flag,
opt->extract_kx509_cert_string);
opt->hidden_flag,
opt->json_flag);
if (!opt->json_flag)
printf("\n\n");
@@ -760,8 +671,7 @@ klist(struct klist_options *opt, int argc, char **argv)
}
exit_status = display_v5_ccache(heimtools_context, id, do_test,
do_verbose, opt->flags_flag,
opt->hidden_flag, opt->json_flag,
opt->extract_kx509_cert_string);
opt->hidden_flag, opt->json_flag);
}
}

128
kuser/kx509.1 Normal file
View File

@@ -0,0 +1,128 @@
.\" Copyright (c) 2019 Kungliga Tekniska Högskolan
.\" (Royal Institute of Technology, Stockholm, Sweden).
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\"
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\"
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" 3. Neither the name of the Institute nor the names of its contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $Id$
.\"
.Dd October 6, 2005
.Dt KLIST 1
.Os HEIMDAL
.Sh NAME
.Nm kx509
.Nd acquire or extract certificates using Kerberos credentials
.Sh SYNOPSIS
.Nm
.Bk -words
.Oo Fl c Ar cache \*(Ba Xo
.Fl Fl cache= Ns Ar cache
.Xc
.Oc
.Oo Fl s \*(Ba Xo
.Fl Fl save
.Xc
.Oc
.Oo Fl o Ar store \*(Ba Xo
.Fl Fl out= Ns Ar store
.Xc
.Oc
.Oo Fl x \*(Ba Xo
.Fl Fl extract
.Xc
.Oc
.Oo Fl t \*(Ba Xo
.Fl Fl test
.Xc
.Oc
.Oo Fl C Ar PKCS10:filename \*(Ba Xo
.Fl Fl csr= Ns Ar PKCS10:filename
.Xc
.Oc
.Oo Fl C Ar PKCS10:filename \*(Ba Xo
.Fl Fl csr= Ns Ar PKCS10:filename
.Xc
.Oc
.Oo Fl K Ar hx509-store \*(Ba Xo
.Fl Fl private-key= Ns Ar hx509-store
.Xc
.Oc
.Oo Fl r Ar realm \*(Ba Xo
.Fl Fl realm= Ns Ar realm
.Xc
.Oc
.Op Fl Fl help
.Ek
.Sh DESCRIPTION
.Nm
acquires PKIX credentials from a credential cache using the kx509
protocol, or extracts PKIX credentials stored in a credential
cache.
.Pp
Options supported:
.Bl -tag -width Ds
.It Fl c Ar cache , Fl Fl cache= Ns Ar cache
credential cache to use (if not given, then the default will be
used).
.It Fl t , Fl Fl test
Test for there being an active and valid certificate in the
credential cache.
.It Fl x , Fl Fl extract
Extract, rather than acquire credentials.
.It Fl s , Fl Fl save
save the acquired certificate and the private key used in the
given credential cache.
.It Fl o , Fl Fl out= Ns Ar hx509-store
An hx509 store specification, such as
.Va DER-FILE:/path/to/der/file ,
.Va PEM-FILE:/path/to/PEM/file ,
.Va FILE:/path/to/PEM/file ,
or
.Va PKCS12:/path/to/PKCS#12/file
into which to store any PKIX certificate and private key
(unencrypted) that may have been acquired with the kx509 protocol
and stored in the
.Ns Ar ccache.
.It Fl r Ar realm, Fl Fl realm= Ns Ar realm
specify the name of the realm whose kx509 service to use.
.It Fl K Ar store, Fl Fl private-key= Ns Ar store
use the private key from the given hx509 store for requesting a
certificate.
.It Fl C Ar csr, Fl Fl csr= Ns Ar certificate-request
specify a CSR to use, which must be a string of the form
PKCS10:filename and which must contain the DER encoding of a
PKCS#10 certification request.
.El
.Pp
The
.Nm hxtool(1)
command can be used to create private keys and CSRs.
.Sh SEE ALSO
.Xr kdestroy 1 ,
.Xr kinit 1 ,
.Xr hxtool 1

256
kuser/kx509.c Normal file
View File

@@ -0,0 +1,256 @@
/*
* Copyright (c) 2019 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "kuser_locl.h"
#include "heimtools-commands.h"
#include <kx509_asn1.h>
#undef HC_DEPRECATED_CRYPTO
#include "../lib/hx509/hx_locl.h"
#include "../lib/krb5/krb5_locl.h"
#include "hx509-private.h"
static void
validate(krb5_context context,
const char *hx509_store,
krb5_data *der_cert,
krb5_data *pkcs8_priv_key)
{
hx509_context hx509ctx = NULL;
hx509_private_key key = NULL;
hx509_cert cert;
krb5_error_code ret;
ret = hx509_context_init(&hx509ctx);
if (ret)
krb5_err(context, 1, ret, "hx509 context init");
cert = hx509_cert_init_data(hx509ctx, der_cert->data,
der_cert->length, NULL);
if (cert == NULL)
krb5_err(context, 1, errno, "certificate could not be loaded");
ret = hx509_parse_private_key(hx509ctx, NULL, pkcs8_priv_key->data,
pkcs8_priv_key->length,
HX509_KEY_FORMAT_PKCS8, &key);
if (ret)
krb5_err(context, 1, ret, "certificate could not be loaded");
if (hx509_cert_get_notAfter(cert) < time(NULL))
krb5_errx(context, 1, "certificate is expired");
hx509_private_key_free(&key);
hx509_cert_free(cert);
hx509_context_free(&hx509ctx);
}
static krb5_error_code
add1_2chain(hx509_context hx509ctx, void *d, hx509_cert cert)
{
heim_octet_string os;
krb5_error_code ret;
Certificates *cs = d;
Certificate c;
size_t len;
ret = hx509_cert_binary(hx509ctx, cert, &os);
if (ret == 0)
ASN1_MALLOC_ENCODE(Certificate, os.data, os.length, &c, &len, ret);
der_free_octet_string(&os);
if (ret == 0) {
add_Certificates(cs, &c);
free_Certificate(&c);
}
return ret;
}
static krb5_error_code
add_chain(hx509_context hx509ctx, hx509_certs certs, krb5_data *chain)
{
krb5_error_code ret;
Certificates cs;
size_t len;
ret = decode_Certificates(chain->data, chain->length, &cs, &len);
if (ret == 0) {
ret = hx509_certs_iter_f(hx509ctx, certs, add1_2chain, &cs);
free_Certificates(&cs);
}
return ret;
}
static void
store(krb5_context context,
const char *hx509_store,
krb5_data *der_cert,
krb5_data *pkcs8_priv_key,
krb5_data *chain)
{
hx509_context hx509ctx = NULL;
hx509_private_key key = NULL;
hx509_certs certs;
hx509_cert cert;
char *store_exp = NULL;
krb5_error_code ret;
if (hx509_store == NULL) {
hx509_store = krb5_config_get_string(context, NULL, "libdefaults",
"kx509_store", NULL);
if (hx509_store) {
ret = _krb5_expand_path_tokens(context, hx509_store, 1, &store_exp);
if (ret)
krb5_err(context, 1, ret, "expanding tokens in default "
"hx509 store");
hx509_store = store_exp;
}
}
if (hx509_store == NULL)
krb5_errx(context, 1, "no hx509 store given and no default hx509 "
"store configured");
ret = hx509_context_init(&hx509ctx);
if (ret)
krb5_err(context, 1, ret, "hx509 context init");
cert = hx509_cert_init_data(hx509ctx, der_cert->data,
der_cert->length, NULL);
if (cert == NULL)
krb5_err(context, 1, errno, "certificate could not be loaded");
ret = hx509_parse_private_key(hx509ctx, NULL, pkcs8_priv_key->data,
pkcs8_priv_key->length,
HX509_KEY_FORMAT_PKCS8, &key);
if (ret)
krb5_err(context, 1, ret, "certificate could not be loaded");
(void) _hx509_cert_assign_key(cert, key);
ret = hx509_certs_init(hx509ctx, hx509_store, HX509_CERTS_CREATE, NULL,
&certs);
if (ret == 0)
ret = hx509_certs_add(hx509ctx, certs, cert);
if (ret == 0)
add_chain(hx509ctx, certs, chain);
if (ret == 0)
ret = hx509_certs_store(hx509ctx, certs, 0, NULL);
if (ret)
krb5_err(context, 1, ret, "certificate could not be stored");
hx509_private_key_free(&key);
hx509_certs_free(&certs);
hx509_cert_free(cert);
hx509_context_free(&hx509ctx);
free(store_exp);
}
static void
set_csr(krb5_context context, krb5_kx509_req_ctx req, const char *csr_file)
{
krb5_error_code ret;
krb5_data d;
if (strncmp(csr_file, "PKCS10:", sizeof("PKCS10:") - 1) != 0)
krb5_errx(context, 1, "CSR filename must start with \"PKCS10:\"");
ret = rk_undumpdata(csr_file + sizeof("PKCS10:") - 1, &d.data, &d.length);
if (ret)
krb5_err(context, 1, ret, "could not read CSR");
ret = krb5_kx509_ctx_set_csr_der(context, req, &d);
if (ret)
krb5_err(context, 1, ret, "hx509 context init");
}
int
kx509(struct kx509_options *opt, int argc, char **argv)
{
krb5_kx509_req_ctx req = NULL;
krb5_context context = heimtools_context;
krb5_error_code ret;
krb5_ccache ccout = NULL;
krb5_ccache cc = NULL;
if (opt->cache_string)
ret = krb5_cc_resolve(context, opt->cache_string, &cc);
else if (opt->save_flag || opt->extract_flag)
ret = krb5_cc_default(context, &cc);
if (ret)
krb5_err(context, 1, ret, "no input credential cache");
if (opt->save_flag)
ccout = cc;
if (opt->test_flag &&
(opt->extract_flag || opt->csr_string || opt->private_key_string))
krb5_errx(context, 1, "--test is exclusive of --extract, --csr, and "
"--private-key");
if (opt->extract_flag && (opt->csr_string || opt->private_key_string))
krb5_errx(context, 1, "--extract is exclusive of --csr and --private-key");
if (opt->test_flag || opt->extract_flag) {
krb5_data der_cert, pkcs8_key, chain;
der_cert.data = pkcs8_key.data = chain.data = NULL;
der_cert.length = pkcs8_key.length = chain.length = 0;
ret = krb5_cc_get_config(context, cc, NULL, "kx509cert", &der_cert);
if (ret == 0)
ret = krb5_cc_get_config(context, cc, NULL, "kx509key", &pkcs8_key);
if (ret == 0)
ret = krb5_cc_get_config(context, cc, NULL, "kx509cert-chain", &chain);
if (ret)
krb5_err(context, 1, ret, "no certificate in credential cache");
if (opt->test_flag)
validate(context, opt->out_string, &der_cert, &pkcs8_key);
else
store(context, opt->out_string, &der_cert, &pkcs8_key, &chain);
krb5_data_free(&pkcs8_key);
krb5_data_free(&der_cert);
krb5_data_free(&chain);
} else {
/*
* XXX We should delete any cc configs that indicate that kx509 is
* disabled.
*/
ret = krb5_kx509_ctx_init(context, &req);
if (ret == 0 && opt->realm_string)
ret = krb5_kx509_ctx_set_realm(context, req, opt->realm_string);
if (ret == 0 && opt->csr_string)
set_csr(context, req, opt->csr_string);
if (ret == 0 && opt->private_key_string)
ret = krb5_kx509_ctx_set_key(context, req, opt->private_key_string);
if (ret)
krb5_err(context, 1, ret, "could not setup kx509 request options");
ret = krb5_kx509_ext(context, req, cc, opt->out_string, ccout);
if (ret)
krb5_err(context, 1, ret, "could not acquire certificate with kx509");
krb5_kx509_ctx_free(context, &req);
}
krb5_cc_close(context, cc);
return 0;
}

View File

@@ -212,7 +212,11 @@ AUTHDATA-TYPE ::= INTEGER {
KRB5-AUTHDATA-GSS-API-ETYPE-NEGOTIATION(129), -- Authenticator only
KRB5-AUTHDATA-SIGNTICKET-OLDER(-17),
KRB5-AUTHDATA-SIGNTICKET-OLD(142),
KRB5-AUTHDATA-SIGNTICKET(512)
KRB5-AUTHDATA-SIGNTICKET(512),
-- N.B. these assignments have not been confirmed yet.
--
-- DO NOT USE in production yet!
KRB5-AUTHDATA-ON-BEHALF-OF(580) -- UTF8String princ name
}
-- checksumtypes

View File

@@ -1,9 +1,13 @@
-- $Id$
-- The kx509 protocol is documented in RFC6717.
-- Version 2 of the kx509 protocol is documented in RFC6717.
--
-- Our version here has extensions without changing the version number on the
-- wire.
KX509 DEFINITIONS ::=
BEGIN
KX509 DEFINITIONS ::= BEGIN
IMPORTS Extensions FROM rfc2459
AUTHDATA-TYPE FROM krb5;
KX509-ERROR-CODE ::= INTEGER {
KX509-STATUS-GOOD(0),
@@ -13,15 +17,68 @@ KX509-ERROR-CODE ::= INTEGER {
KX509-STATUS-SERVER-BAD(4),
KX509-STATUS-SERVER-TEMP(5),
-- 6 is used internally in the umich client, avoid that
KX509-STATUS-SERVER-KEY(7)
KX509-STATUS-SERVER-KEY(7),
-- CSR use negotiation:
KX509-STATUS-CLIENT-USE-CSR(8)
-- Let us reserve 1000+ for Kebreros protocol wire error codes -Nico
}
-- Version 2, which has no proof of possession
-- Originally kx509 requests carried only a public key. We'd like to have
-- proof of possession, and the ability to carry additional options, both, in
-- cleartext and otherwise.
--
-- We'll use a CSR for proof of posession and desired certificate extensions.
--
-- We'll also provide a non-CSR-based method of conveying desired certificate
-- extensions. The reason for this is simply that we may want to have a [e.g.,
-- RESTful HTTP] proxy for the kx509 service, and we want clients to be able to
-- be as simple as possible -cargo-culted even- with support for attributes
-- (desired certificate extensions) as parameters outside the CSR that the
-- proxy can encode without having the private key for the CSR (naturally).
--
-- I.e., ultimately we'll have a REST endpoint, /kx509, say, with query
-- parameters like:
--
-- - csr=<base64-encoding-of-DER-encoded-CSR>
-- - eku=<OID>
-- - ku=<key-usage-flag-name>
-- - rfc822Name=<URL-escaped-email-address>
-- - xMPPName=<URL-escaped-jabber-address>
-- - dNSName=<URL-escaped-FQDN>
-- - dNSSrv=<URL-escaped-_service.FQDN>
-- - registeredID=<OID>
-- - principalName=<URL-escaped-RFC1964-format-Kerberos-Principal-Name>
--
-- with exactly one CSR and zero, one, or more of the other parameters.
--
-- We'll even have a way to convey a bearer token from the REST proxy so that
-- we may have a way to get PKIX credentials using bearer tokens. And then,
-- using PKINIT, we may have a way to get Kerberos credentials using bearer
-- tokens.
--
-- To do this we define a Kx509CSRPlus that we can use in the `pk-key' field of
-- Kx509Request (see below):
Kx509CSRPlus ::= [APPLICATION 35] SEQUENCE {
-- PKCS#10, DER-encoded CSR, with or without meaningful attributes
csr OCTET STRING,
-- The AP-REQ's Authenticator may contain authz-data of interest here
-- for carrying confidential payloads. E.g., a bearer token for a user
-- to impersonate. This sequence tells the server what authz-data
-- elements there might be, effectively making them critical even if
-- they are in AD-IF-RELEVANT containers.
authz-datas SEQUENCE OF AUTHDATA-TYPE,
-- Desired certificate Extensions such as KeyUsage, ExtKeyUsage, or
-- subjectAlternativeName (SAN)
exts Extensions OPTIONAL
}
-- Version 2
Kx509Request ::= SEQUENCE {
authenticator OCTET STRING,
pk-hash OCTET STRING, -- HMAC(ticket_session_key, pk-key)
pk-key OCTET STRING -- the public key, DER-encoded (RSA, basically)
pk-key OCTET STRING -- one of:
-- - the public key, DER-encoded (RSA, basically)
-- - a Kx509CSRPlus
}
-- Kx509ErrorCode is a Heimdal-specific enhancement with no change on the wire,
@@ -31,7 +88,10 @@ Kx509ErrorCode ::= INTEGER (-2147483648..2147483647)
Kx509Response ::= SEQUENCE {
error-code[0] Kx509ErrorCode DEFAULT 0,
hash[1] OCTET STRING OPTIONAL, -- HMAC(session_key, ...)
certificate[2] OCTET STRING OPTIONAL, -- Certificates (plural)
certificate[2] OCTET STRING OPTIONAL, -- DER-encoded Certificate
-- if client sent raw RSA SPK
-- or DER-encoded Certificates
-- (i.e., SEQ. OF Certificate)
-- if client used a
-- Kx509CSRPlus
e-text[3] VisibleString OPTIONAL

View File

@@ -989,6 +989,7 @@ typedef struct krb5_name_canon_iterator_data *krb5_name_canon_iterator;
*/
struct hx509_certs_data;
typedef struct krb5_kx509_req_ctx_data *krb5_kx509_req_ctx;
#include <krb5-protos.h>

File diff suppressed because it is too large Load Diff

View File

@@ -439,6 +439,23 @@ EXPORTS
krb5_kt_start_seq_get
krb5_kuserok
krb5_kx509
krb5_kx509
krb5_kx509_ctx_add_auth_data
krb5_kx509_ctx_add_eku
krb5_kx509_ctx_add_san_dns_name
krb5_kx509_ctx_add_san_ms_upn
krb5_kx509_ctx_add_san_pkinit
krb5_kx509_ctx_add_san_registeredID
krb5_kx509_ctx_add_san_rfc822Name
krb5_kx509_ctx_add_san_xmpp
krb5_kx509_ctx_free
krb5_kx509_ctx_free
krb5_kx509_ctx_init
krb5_kx509_ctx_init
krb5_kx509_ctx_set_csr_der
krb5_kx509_ctx_set_key
krb5_kx509_ctx_set_realm
krb5_kx509_ext
krb5_kx509_ext
krb5_log
krb5_log_msg

View File

@@ -432,6 +432,19 @@ HEIMDAL_KRB5_2.0 {
krb5_kt_start_seq_get;
krb5_kuserok;
krb5_kx509;
krb5_kx509_ctx_add_auth_data;
krb5_kx509_ctx_add_eku;
krb5_kx509_ctx_add_san_dns_name;
krb5_kx509_ctx_add_san_ms_upn;
krb5_kx509_ctx_add_san_pkinit;
krb5_kx509_ctx_add_san_registeredID;
krb5_kx509_ctx_add_san_rfc822Name;
krb5_kx509_ctx_add_san_xmpp;
krb5_kx509_ctx_free;
krb5_kx509_ctx_init;
krb5_kx509_ctx_set_csr_der;
krb5_kx509_ctx_set_key;
krb5_kx509_ctx_set_realm;
krb5_kx509_ext;
krb5_log;
krb5_log_msg;

View File

@@ -32,6 +32,7 @@ klist="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/heimtools klist"
kpasswd="${TESTS_ENVIRONMENT} ${top_builddir}/kpasswd/kpasswd"
kpasswdd="${TESTS_ENVIRONMENT} ${top_builddir}/kpasswd/kpasswdd"
kswitch="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/heimtools kswitch"
kx509="${TESTS_ENVIRONMENT} ${top_builddir}/kuser/heimtools kx509"
ktutil="${TESTS_ENVIRONMENT} ${top_builddir}/admin/ktutil"
gsstool="${TESTS_ENVIRONMENT} ${top_builddir}/lib/gssapi/gsstool"

View File

@@ -58,10 +58,7 @@ kinit="${kinit} -c $cache ${afs_no_afslog}"
klist="${klist} --hidden -v -c $cache"
kgetcred="${kgetcred} -c $cache"
kdestroy="${kdestroy} -c $cache ${afs_no_unlog}"
kextract() {
${klist} --extract-kx509-cert="$1"
}
kx509="${kx509} -c $cache"
KRB5_CONFIG="${objdir}/krb5-pkinit.conf"
export KRB5_CONFIG
@@ -202,7 +199,8 @@ ${kgetcred} ${server}@${R} || { ec=1 ; eval "${testfailed}"; }
${klist}
echo "Check kx509 certificate acquisition"
kextract PEM-FILE:${objdir}/kx509.pem || { ec=1 ; eval "${testfailed}"; }
${kx509} -s || { ec=1 ; eval "${testfailed}"; }
${kx509} -o PEM-FILE:${objdir}/kx509.pem || { ec=1 ; eval "${testfailed}"; }
${kdestroy}
echo "Check PKINIT w/ kx509 certificate"