From f352702d6785fc5f59698dba73d415f994b4ce7d Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Jun 26 2014 08:30:53 +0000 Subject: ipa-getkeytab: Add support for get_keytab extop This new extended operation is tried by default and then the code falls back to the old method if it fails. The new method allows for server side password generation as well as retrieval of existing credentials w/o causing regeneration of keys on the server. Resolves: https://fedorahosted.org/freeipa/ticket/3859 Reviewed-By: Nathaniel McCallum --- diff --git a/ipa-client/ipa-getkeytab.c b/ipa-client/ipa-getkeytab.c index 506fff1..74a8800 100644 --- a/ipa-client/ipa-getkeytab.c +++ b/ipa-client/ipa-getkeytab.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -431,7 +432,282 @@ error_out: if (ld) ldap_unbind_ext(ld, NULL, NULL); if (control) ber_bvfree(control); free(encs); - return 0; + return -1; +} + +/* Format of getkeytab control + * + * KeytabGetRequest ::= CHOICE { + * newkeys [0] Newkeys, + * curkeys [1] CurrentKeys, + * reply [2] Reply + * } + * + * NewKeys ::= SEQUENCE { + * serviceIdentity [0] OCTET STRING, + * enctypes [1] SEQUENCE OF Int16 + * password [2] OCTET STRING OPTIONAL, + * } + * + * CurrentKeys ::= SEQUENCE { + * serviceIdentity [0] OCTET STRING, + * } + * + * Reply ::= SEQUENCE { + * new_kvno Int32 + * keys SEQUENCE OF KrbKey, + * } + * + * KrbKey ::= SEQUENCE { + * key [0] EncryptionKey, + * salt [1] KrbSalt OPTIONAL, + * s2kparams [2] OCTET STRING OPTIONAL, + * } + * + * EncryptionKey ::= SEQUENCE { + * keytype [0] Int32, + * keyvalue [1] OCTET STRING + * } + * + * KrbSalt ::= SEQUENCE { + * type [0] Int32, + * salt [1] OCTET STRING + * } + */ + +#define GK_REQUEST_NEWKEYS (LBER_CLASS_CONTEXT | LBER_CONSTRUCTED | 0) +#define GK_REQUEST_CURKEYS (LBER_CLASS_CONTEXT | LBER_CONSTRUCTED | 1) +#define GKREQ_SVCNAME_TAG (LBER_CLASS_CONTEXT | LBER_CONSTRUCTED | 1) +#define GKREQ_ENCTYPES_TAG (LBER_CLASS_CONTEXT | LBER_CONSTRUCTED | 1) +#define GKREQ_PASSWORD_TAG (LBER_CLASS_CONTEXT | LBER_CONSTRUCTED | 2) + +static struct berval *create_getkeytab_control(const char *svc_princ, bool gen, + const char *password, + struct krb_key_salt *encsalts, + int num_encsalts) +{ + struct berval *bval = NULL; + BerElement *be; + ber_tag_t ctag; + ber_int_t e; + int ret, i; + + be = ber_alloc_t(LBER_USE_DER); + if (!be) { + return NULL; + } + + if (gen) { + ctag = GK_REQUEST_NEWKEYS; + } else { + ctag = GK_REQUEST_CURKEYS; + } + + ret = ber_printf(be, "t{t[s]", ctag, GKREQ_SVCNAME_TAG, svc_princ); + if (ret == -1) { + ber_free(be, 1); + goto done; + } + + if (gen) { + ret = ber_printf(be, "t{", GKREQ_ENCTYPES_TAG); + if (ret == -1) { + ber_free(be, 1); + goto done; + } + for (i = 0; i < num_encsalts; i++) { + e = encsalts[i].enctype; + ret = ber_printf(be, "i", e); + if (ret == -1) { + ber_free(be, 1); + goto done; + } + } + ret = ber_printf(be, "}"); + if (ret == -1) { + ber_free(be, 1); + goto done; + } + + if (password) { + ret = ber_printf(be, "t[s]", GKREQ_PASSWORD_TAG, password); + if (ret == -1) { + ber_free(be, 1); + goto done; + } + } + } + + ret = ber_printf(be, "}"); + if (ret == -1) { + ber_free(be, 1); + goto done; + } + + ret = ber_flatten(be, &bval); + if (ret == -1) { + ber_free(be, 1); + goto done; + } + +done: + ber_free(be, 1); + return bval; +} + +#define GK_REPLY_TAG (LBER_CLASS_CONTEXT | LBER_CONSTRUCTED | 2) +#define GKREP_KEY_TAG (LBER_CLASS_CONTEXT | LBER_CONSTRUCTED | 0) +#define GKREP_SALT_TAG (LBER_CLASS_CONTEXT | LBER_CONSTRUCTED | 1) + +static int ldap_get_keytab(krb5_context krbctx, bool generate, char *password, + const char *enctypes, const char *bind_server, + const char *svc_princ, krb5_principal bind_princ, + const char *bind_dn, const char *bind_pw, + struct keys_container *keys, int *kvno, + char **err_msg) +{ + struct krb_key_salt *es = NULL; + int num_es = 0; + struct berval *control = NULL; + LDAP *ld; + LDAPControl **srvctrl = NULL; + BerElement *ber = NULL; + ber_tag_t rtag; + ber_tag_t ctag; + ber_len_t tlen; + ber_int_t vno; + ber_int_t tint; + struct berval tbval; + int ret; + + *err_msg = NULL; + + if (enctypes) { + ret = ipa_string_to_enctypes(enctypes, &es, &num_es, err_msg); + if (ret || num_es == 0) { + return LDAP_OPERATIONS_ERROR; + } + } + + control = create_getkeytab_control(svc_princ, generate, + password, es, num_es); + if (!control) { + *err_msg = _("Failed to create control!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + ret = ipa_ldap_bind(bind_server, bind_princ, bind_dn, bind_pw, &ld); + if (ret != LDAP_SUCCESS) { + *err_msg = _("Failed to bind to server!\n"); + goto done; + } + + /* perform extedned opt to get keytab */ + ret = ipa_ldap_extended_op(ld, KEYTAB_GET_OID, control, &srvctrl); + if (ret != LDAP_SUCCESS) { + goto done; + } + + ber = get_control_data(srvctrl, KEYTAB_GET_OID); + if (!ber) { + *err_msg = _("Failed to find or parse reply control!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + rtag = ber_scanf(ber, "t{i{", &ctag, &vno); + if (rtag == LBER_ERROR || ctag != GK_REPLY_TAG) { + *err_msg = _("Failed to parse control head!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + keys->nkeys = 0; + keys->ksdata = NULL; + + rtag = ber_peek_tag(ber, &tlen); + for (int i = 0; rtag == LBER_SEQUENCE; i++) { + if ((i % 5) == 0) { + struct krb_key_salt *ksdata; + ksdata = realloc(keys->ksdata, + (i + 5) * sizeof(struct krb_key_salt)); + if (!ksdata) { + *err_msg = _("Out of memory!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + keys->ksdata = ksdata; + } + memset(&keys->ksdata[i], 0, sizeof(struct krb_key_salt)); + keys->nkeys = i + 1; + + rtag = ber_scanf(ber, "{t{[i][o]}]", &ctag, &tint, &tbval); + if (rtag == LBER_ERROR || ctag != GKREP_KEY_TAG) { + *err_msg = _("Failed to parse enctype in key data!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + keys->ksdata[i].enctype = tint; + keys->ksdata[i].key.enctype = tint; + keys->ksdata[i].key.length = tbval.bv_len; + keys->ksdata[i].key.contents = malloc(tbval.bv_len); + if (!keys->ksdata[i].key.contents) { + *err_msg = _("Out of memory!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + memcpy(keys->ksdata[i].key.contents, tbval.bv_val, tbval.bv_len); + ber_memfree(tbval.bv_val); + + rtag = ber_peek_tag(ber, &tlen); + if (rtag == GKREP_SALT_TAG) { + rtag = ber_scanf(ber, "t{[i][o]}", &ctag, &tint, &tbval); + if (rtag == LBER_ERROR) { + *err_msg = _("Failed to parse salt in key data!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + keys->ksdata[i].salttype = tint; + keys->ksdata[i].salt.length = tbval.bv_len; + keys->ksdata[i].salt.data = malloc(tbval.bv_len); + if (!keys->ksdata[i].salt.data) { + *err_msg = _("Out of memory!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + memcpy(keys->ksdata[i].salt.data, tbval.bv_val, tbval.bv_len); + ber_memfree(tbval.bv_val); + } + rtag = ber_scanf(ber, "}"); + if (rtag == LBER_ERROR) { + *err_msg = _("Failed to parse ending of key data!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + rtag = ber_peek_tag(ber, &tlen); + } + + rtag = ber_scanf(ber, "}}"); + if (rtag == LBER_ERROR) { + *err_msg = _("Failed to parse ending of control!\n"); + ret = LDAP_OPERATIONS_ERROR; + goto done; + } + + *kvno = vno; + ret = LDAP_SUCCESS; + +done: + if (ber) ber_free(ber, 1); + if (ld) ldap_unbind_ext(ld, NULL, NULL); + if (control) ber_bvfree(control); + free(es); + if (ret) { + free_keys_contents(krbctx, keys); + } + return ret; } static char *ask_password(krb5_context krbctx) @@ -483,6 +759,7 @@ int main(int argc, const char *argv[]) int quiet = 0; int askpass = 0; int permitted_enctypes = 0; + int retrieve = 0; struct poptOption options[] = { { "quiet", 'q', POPT_ARG_NONE, &quiet, 0, _("Print as little as possible"), _("Output only on errors")}, @@ -507,6 +784,8 @@ int main(int argc, const char *argv[]) _("LDAP DN"), _("DN to bind as if not using kerberos") }, { "bindpw", 'w', POPT_ARG_STRING, &bindpw, 0, _("LDAP password"), _("password to use if not using kerberos") }, + { "retrieve", 'r', POPT_ARG_NONE, &retrieve, 0, + _("Retrieve current keys without changing them"), NULL }, POPT_AUTOHELP POPT_TABLEEND }; @@ -518,7 +797,7 @@ int main(int argc, const char *argv[]) krb5_principal uprinc; krb5_principal sprinc; krb5_error_code krberr; - struct keys_container keys; + struct keys_container keys = { 0 }; krb5_keytab kt; int kvno; int i, ret; @@ -576,6 +855,11 @@ int main(int argc, const char *argv[]) exit(10); } + if (askpass && retrieve) { + fprintf(stderr, _("Incompatible options provided (-r and -P)\n")); + exit(2); + } + if (askpass) { password = ask_password(krbctx); if (!password) { @@ -623,6 +907,19 @@ int main(int argc, const char *argv[]) exit(7); } + kvno = -1; + ret = ldap_get_keytab(krbctx, (retrieve == 0), password, enctypes_string, + server, principal, uprinc, binddn, bindpw, + &keys, &kvno, &err_msg); + if (ret) { + if (!quiet && err_msg != NULL) { + fprintf(stderr, "%s", err_msg); + } + } + + if (password && (retrieve == 0) && (kvno == -1)) { + if (!quiet) fprintf(stderr, _("Retrying with old method\n")); + /* create key material */ ret = create_keys(krbctx, sprinc, password, enctypes_string, &keys, &err_msg); if (!ret) { @@ -634,9 +931,12 @@ int main(int argc, const char *argv[]) } kvno = ldap_set_keytab(krbctx, server, principal, uprinc, binddn, bindpw, &keys); - if (!kvno) { - exit(9); - } + } + + if (kvno == -1) { + fprintf(stderr, _("Failed to get keytab\n")); + exit(9); + } for (i = 0; i < keys.nkeys; i++) { krb5_keytab_entry kt_entry; diff --git a/util/ipa_krb5.c b/util/ipa_krb5.c index 2a94b19..6334ed3 100644 --- a/util/ipa_krb5.c +++ b/util/ipa_krb5.c @@ -779,6 +779,79 @@ void free_keys_contents(krb5_context krbctx, struct keys_container *keys) keys->nkeys = 0; } +int ipa_string_to_enctypes(const char *str, struct krb_key_salt **encsalts, + int *num_encsalts, char **err_msg) +{ + struct krb_key_salt *ksdata; + krb5_error_code krberr; + char *tmp, *t; + int count; + int num; + + *err_msg = NULL; + + tmp = strdup(str); + if (!tmp) { + *err_msg = _("Out of memory\n"); + return ENOMEM; + } + + /* count */ + count = 0; + for (t = tmp; t; t = strchr(t, ',')) { + count++; + t++; + } + count++; /* count the last one that is 0 terminated instead */ + + /* at the end we will have at most count entries + 1 terminating */ + ksdata = calloc(count + 1, sizeof(struct krb_key_salt)); + if (!ksdata) { + *err_msg = _("Out of memory\n"); + free(tmp); + return ENOMEM; + } + + num = 0; + t = tmp; + for (int i = 0; i < count; i++) { + char *p, *q; + + p = strchr(t, ','); + if (p) *p = '\0'; + + q = strchr(t, ':'); + if (q) *q++ = '\0'; + + krberr = krb5_string_to_enctype(t, &ksdata[num].enctype); + if (krberr) { + *err_msg = _("Warning unrecognized encryption type.\n"); + if (p) t = p + 1; + continue; + } + if (p) t = p + 1; + + if (!q) { + ksdata[num].salttype = KRB5_KDB_SALTTYPE_NORMAL; + num++; + continue; + } + + krberr = krb5_string_to_salttype(q, &ksdata[num].salttype); + if (krberr) { + *err_msg = _("Warning unrecognized salt type.\n"); + continue; + } + + num++; + } + + *num_encsalts = num; + *encsalts = ksdata; + free(tmp); + return 0; +} + /* Determines Encryption and Salt types, * allocates key_salt data storage, * filters out equivalent encodings, @@ -820,63 +893,10 @@ static int prep_ksdata(krb5_context krbctx, const char *str, nkeys = i; } else { - char *tmp, *t, *p, *q; - - t = tmp = strdup(str); - if (!tmp) { - *err_msg = _("Out of memory\n"); - return 0; - } - - /* count */ - n = 0; - while ((p = strchr(t, ','))) { - t = p+1; - n++; - } - n++; /* count the last one that is 0 terminated instead */ - - /* at the end we will have at most n entries + 1 terminating */ - ksdata = calloc(n + 1, sizeof(struct krb_key_salt)); - if (!ksdata) { - *err_msg = _("Out of memory\n"); + krberr = ipa_string_to_enctypes(str, &ksdata, &nkeys, err_msg); + if (krberr) { return 0; } - - for (i = 0, j = 0, t = tmp; i < n; i++) { - - p = strchr(t, ','); - if (p) *p = '\0'; - - q = strchr(t, ':'); - if (q) *q++ = '\0'; - - krberr = krb5_string_to_enctype(t, &ksdata[j].enctype); - if (krberr != 0) { - *err_msg = _("Warning unrecognized encryption type.\n"); - if (p) t = p + 1; - continue; - } - if (p) t = p + 1; - - if (!q) { - ksdata[j].salttype = KRB5_KDB_SALTTYPE_NORMAL; - j++; - continue; - } - - krberr = krb5_string_to_salttype(q, &ksdata[j].salttype); - if (krberr != 0) { - *err_msg = _("Warning unrecognized salt type.\n"); - continue; - } - - j++; - } - - nkeys = j; - - free(tmp); } /* Check we don't already have a key with a similar encoding, diff --git a/util/ipa_krb5.h b/util/ipa_krb5.h index 2431fd7..1e036e4 100644 --- a/util/ipa_krb5.h +++ b/util/ipa_krb5.h @@ -69,6 +69,9 @@ void free_keys_contents(krb5_context krbctx, struct keys_container *keys); struct berval *create_key_control(struct keys_container *keys, const char *principalName); +int ipa_string_to_enctypes(const char *str, struct krb_key_salt **encsalts, + int *num_encsalts, char **err_msg); + int create_keys(krb5_context krbctx, krb5_principal princ, char *password,