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:
585
kdc/kx509.c
585
kdc/kx509.c
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -79,6 +79,7 @@ HEIMTOOLS_OBJS = \
|
||||
$(OBJ)\heimtools.obj \
|
||||
$(OBJ)\kswitch.obj \
|
||||
$(OBJ)\klist.obj \
|
||||
$(OBJ)\kx509.obj \
|
||||
$(OBJ)\copy_cred_cache.obj
|
||||
|
||||
HEIMTOOLSLIBS=\
|
||||
|
@@ -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 = "?"
|
||||
|
@@ -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
|
||||
|
102
kuser/klist.c
102
kuser/klist.c
@@ -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
128
kuser/kx509.1
Normal 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
256
kuser/kx509.c
Normal 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;
|
||||
}
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
|
||||
|
823
lib/krb5/kx509.c
823
lib/krb5/kx509.c
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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"
|
||||
|
Reference in New Issue
Block a user