Exemple #1
0
/*
 * Try to find tokeninfos which match configuration data recorded in the input
 * ccache, and if exactly one is found, drop the rest.
 */
static krb5_error_code
filter_config_tokeninfos(krb5_context context,
                         krb5_clpreauth_callbacks cb,
                         krb5_clpreauth_rock rock,
                         krb5_otp_tokeninfo **tis)
{
    krb5_otp_tokeninfo *match = NULL;
    size_t i, j;
    const char *vendor, *alg_id, *token_id;

    /* Pull up what we know about the token we want to use. */
    vendor = cb->get_cc_config(context, rock, "vendor");
    alg_id = cb->get_cc_config(context, rock, "algID");
    token_id = cb->get_cc_config(context, rock, "tokenID");

    /* Look for a single matching entry. */
    for (i = 0; tis[i] != NULL; i++) {
        if (vendor != NULL && tis[i]->vendor.length > 0 &&
            !data_eq_string(tis[i]->vendor, vendor))
            continue;
        if (alg_id != NULL && tis[i]->alg_id.length > 0 &&
            !data_eq_string(tis[i]->alg_id, alg_id))
            continue;
        if (token_id != NULL && tis[i]->token_id.length > 0 &&
            !data_eq_string(tis[i]->token_id, token_id))
            continue;
        /* Oh, we already had a matching entry. More than one -> no change. */
        if (match != NULL)
            return 0;
        match = tis[i];
    }

    /* No matching entry -> no change. */
    if (match == NULL)
        return 0;

    /* Prune out everything except the best match. */
    for (i = 0, j = 0; tis[i] != NULL; i++) {
        if (tis[i] != match)
            k5_free_otp_tokeninfo(context, tis[i]);
        else
            tis[j++] = tis[i];
    }
    tis[j] = NULL;

    return 0;
}
Exemple #2
0
static krb5_error_code
ec_prep_questions(krb5_context context, krb5_clpreauth_moddata moddata,
                  krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
                  krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
                  krb5_kdc_req *request, krb5_data *encoded_request_body,
                  krb5_data *encoded_previous_request, krb5_pa_data *pa_data)
{
    cb->need_as_key(context, rock);
    return 0;
}
Exemple #3
0
static krb5_error_code
test_process(krb5_context context, krb5_clpreauth_moddata moddata,
             krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
             krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
             krb5_kdc_req *request, krb5_data *encoded_request_body,
             krb5_data *encoded_previous_request, krb5_pa_data *pa_data,
             krb5_prompter_fct prompter, void *prompter_data,
             krb5_pa_data ***out_pa_data)
{
    struct client_state *st = (struct client_state *)moddata;
    struct client_request_state *reqst = (struct client_request_state *)modreq;
    krb5_error_code ret;
    krb5_keyblock *k;
    krb5_enc_data enc;
    krb5_data plain;
    const char *indstr;

    if (pa_data->length == 0) {
        /* This is an optimistic preauth test.  Send a recognizable padata
         * value so the KDC knows not to expect a cookie. */
        if (st->fail_optimistic) {
            k5_setmsg(context, KRB5_PREAUTH_FAILED, "induced optimistic fail");
            return KRB5_PREAUTH_FAILED;
        }
        *out_pa_data = make_pa_list("optimistic", 10);
        return 0;
    } else if (reqst->second_round_trip) {
        printf("2rt: %.*s\n", pa_data->length, pa_data->contents);
        if (st->fail_2rt) {
            k5_setmsg(context, KRB5_PREAUTH_FAILED, "induced 2rt fail");
            return KRB5_PREAUTH_FAILED;
        }
    } else if (pa_data->length == 6 &&
               memcmp(pa_data->contents, "no key", 6) == 0) {
        printf("no key\n");
    } else {
        /* This fails during s4u_identify_user(), so don't assert. */
        ret = cb->get_as_key(context, rock, &k);
        if (ret)
            return ret;
        ret = alloc_data(&plain, pa_data->length);
        assert(!ret);
        enc.enctype = k->enctype;
        enc.ciphertext = make_data(pa_data->contents, pa_data->length);
        ret = krb5_c_decrypt(context, k, 1024, NULL, &enc, &plain);
        assert(!ret);
        printf("%.*s\n", plain.length, plain.data);
        free(plain.data);
    }
    reqst->second_round_trip = TRUE;

    indstr = (st->indicators != NULL) ? st->indicators : "";
    *out_pa_data = make_pa_list(indstr, strlen(indstr));
    return 0;
}
Exemple #4
0
/*
 * Save the vendor, algID, and tokenID values for the selected token to the
 * out_ccache, so that later we can try to use them to select the right one
 * without having ot ask the user.
 */
static void
save_config_tokeninfo(krb5_context context,
                      krb5_clpreauth_callbacks cb,
                      krb5_clpreauth_rock rock,
                      krb5_otp_tokeninfo *ti)
{
    char *tmp;
    if (ti->vendor.length > 0 &&
        asprintf(&tmp, "%.*s", ti->vendor.length, ti->vendor.data) >= 0) {
        cb->set_cc_config(context, rock, "vendor", tmp);
        free(tmp);
    }
    if (ti->alg_id.length > 0 &&
        asprintf(&tmp, "%.*s", ti->alg_id.length, ti->alg_id.data) >= 0) {
        cb->set_cc_config(context, rock, "algID", tmp);
        free(tmp);
    }
    if (ti->token_id.length > 0 &&
        asprintf(&tmp, "%.*s", ti->token_id.length, ti->token_id.data) >= 0) {
        cb->set_cc_config(context, rock, "tokenID", tmp);
        free(tmp);
    }
}
Exemple #5
0
static krb5_error_code
otp_client_prep_questions(krb5_context context, krb5_clpreauth_moddata moddata,
                          krb5_clpreauth_modreq modreq,
                          krb5_get_init_creds_opt *opt,
                          krb5_clpreauth_callbacks cb,
                          krb5_clpreauth_rock rock, krb5_kdc_req *request,
                          krb5_data *encoded_request_body,
                          krb5_data *encoded_previous_request,
                          krb5_pa_data *pa_data)
{
    krb5_pa_otp_challenge *chl;
    krb5_error_code retval;
    krb5_data tmp;
    char *json;

    if (modreq == NULL)
        return ENOMEM;

    /* Decode the challenge. */
    tmp = make_data(pa_data->contents, pa_data->length);
    retval = decode_krb5_pa_otp_challenge(&tmp,
                                          (krb5_pa_otp_challenge **)modreq);
    if (retval != 0)
        return retval;
    chl = *(krb5_pa_otp_challenge **)modreq;

    /* Remove unsupported tokeninfos. */
    retval = filter_supported_tokeninfos(context, chl->tokeninfo);
    if (retval != 0)
        return retval;

    /* Remove tokeninfos that don't match the recorded description, if that
     * results in there being only one that does. */
    retval = filter_config_tokeninfos(context, cb, rock, chl->tokeninfo);
    if (retval != 0)
        return retval;

    /* Make the JSON representation. */
    retval = codec_encode_challenge(context, chl, &json);
    if (retval != 0)
        return retval;

    /* Ask the question. */
    retval = cb->ask_responder_question(context, rock,
                                        KRB5_RESPONDER_QUESTION_OTP,
                                        json);
    free(json);
    return retval;
}
static krb5_error_code
encts_process(krb5_context context, krb5_clpreauth_moddata moddata,
              krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
              krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
              krb5_kdc_req *request, krb5_data *encoded_request_body,
              krb5_data *encoded_previous_request, krb5_pa_data *padata,
              krb5_prompter_fct prompter, void *prompter_data,
              krb5_pa_data ***out_padata)
{
    krb5_error_code ret;
    krb5_pa_enc_ts pa_enc;
    krb5_data *ts = NULL, *enc_ts = NULL;
    krb5_enc_data enc_data;
    krb5_pa_data **pa = NULL;
    krb5_keyblock *as_key;

    enc_data.ciphertext = empty_data();

    ret = cb->get_as_key(context, rock, &as_key);
    if (ret)
        goto cleanup;
    TRACE_PREAUTH_ENC_TS_KEY_GAK(context, as_key);

    /* now get the time of day, and encrypt it accordingly */
    ret = krb5_us_timeofday(context, &pa_enc.patimestamp, &pa_enc.pausec);
    if (ret)
        goto cleanup;

    ret = encode_krb5_pa_enc_ts(&pa_enc, &ts);
    if (ret)
        goto cleanup;

    ret = krb5_encrypt_helper(context, as_key, KRB5_KEYUSAGE_AS_REQ_PA_ENC_TS,
                              ts, &enc_data);
    if (ret)
        goto cleanup;
    TRACE_PREAUTH_ENC_TS(context, pa_enc.patimestamp, pa_enc.pausec,
                         ts, &enc_data.ciphertext);

    ret = encode_krb5_enc_data(&enc_data, &enc_ts);
    if (ret)
        goto cleanup;

    pa = k5alloc(2 * sizeof(krb5_pa_data *), &ret);
    if (pa == NULL)
        goto cleanup;

    pa[0] = k5alloc(sizeof(krb5_pa_data), &ret);
    if (pa[0] == NULL)
        goto cleanup;

    pa[0]->magic = KV5M_PA_DATA;
    pa[0]->pa_type = KRB5_PADATA_ENC_TIMESTAMP;
    pa[0]->length = enc_ts->length;
    pa[0]->contents = (krb5_octet *) enc_ts->data;
    enc_ts->data = NULL;
    pa[1] = NULL;
    *out_padata = pa;
    pa = NULL;

cleanup:
    krb5_free_data(context, ts);
    krb5_free_data(context, enc_ts);
    free(enc_data.ciphertext.data);
    free(pa);
    return ret;
}
Exemple #7
0
static krb5_error_code
encts_process(krb5_context context, krb5_clpreauth_moddata moddata,
              krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
              krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
              krb5_kdc_req *request, krb5_data *encoded_request_body,
              krb5_data *encoded_previous_request, krb5_pa_data *padata,
              krb5_prompter_fct prompter, void *prompter_data,
              krb5_clpreauth_get_as_key_fn gak_fct, void *gak_data,
              krb5_data *salt, krb5_data *s2kparams, krb5_keyblock *as_key,
              krb5_pa_data ***out_padata)
{
    krb5_error_code ret;
    krb5_pa_enc_ts pa_enc;
    krb5_data *ts = NULL, *enc_ts = NULL;
    krb5_enc_data enc_data;
    krb5_pa_data **pa = NULL;
    krb5_enctype etype = cb->get_etype(context, rock);

    enc_data.ciphertext = empty_data();

    if (as_key->length == 0) {
#ifdef DEBUG
        fprintf (stderr, "%s:%d: salt len=%d", __FILE__, __LINE__,
                 salt->length);
        if ((int) salt->length > 0)
            fprintf (stderr, " '%.*s'", salt->length, salt->data);
        fprintf (stderr, "; *etype=%d request->ktype[0]=%d\n",
                 etype, request->ktype[0]);
#endif
        ret = (*gak_fct)(context, request->client, etype, prompter,
                         prompter_data, salt, s2kparams, as_key, gak_data);
        if (ret)
            goto cleanup;
        TRACE_PREAUTH_ENC_TS_KEY_GAK(context, as_key);
    }

    /* now get the time of day, and encrypt it accordingly */
    ret = krb5_us_timeofday(context, &pa_enc.patimestamp, &pa_enc.pausec);
    if (ret)
        goto cleanup;

    ret = encode_krb5_pa_enc_ts(&pa_enc, &ts);
    if (ret)
        goto cleanup;

    ret = krb5_encrypt_helper(context, as_key, KRB5_KEYUSAGE_AS_REQ_PA_ENC_TS,
                              ts, &enc_data);
    if (ret)
        goto cleanup;
    TRACE_PREAUTH_ENC_TS(context, pa_enc.patimestamp, pa_enc.pausec,
                         ts, &enc_data.ciphertext);

    ret = encode_krb5_enc_data(&enc_data, &enc_ts);
    if (ret)
        goto cleanup;

    pa = k5alloc(2 * sizeof(krb5_pa_data *), &ret);
    if (pa == NULL)
        goto cleanup;

    pa[0] = k5alloc(sizeof(krb5_pa_data), &ret);
    if (pa[0] == NULL)
        goto cleanup;

    pa[0]->magic = KV5M_PA_DATA;
    pa[0]->pa_type = KRB5_PADATA_ENC_TIMESTAMP;
    pa[0]->length = enc_ts->length;
    pa[0]->contents = (krb5_octet *) enc_ts->data;
    enc_ts->data = NULL;
    pa[1] = NULL;
    *out_padata = pa;
    pa = NULL;

cleanup:
    krb5_free_data(context, ts);
    krb5_free_data(context, enc_ts);
    free(enc_data.ciphertext.data);
    free(pa);
    return ret;
}
Exemple #8
0
static krb5_error_code
ec_process(krb5_context context, krb5_clpreauth_moddata moddata,
           krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
           krb5_clpreauth_callbacks cb,
           krb5_clpreauth_rock rock, krb5_kdc_req *request,
           krb5_data *encoded_request_body,
           krb5_data *encoded_previous_request, krb5_pa_data *padata,
           krb5_prompter_fct prompter, void *prompter_data,
           krb5_pa_data ***out_padata)
{
    krb5_error_code retval = 0;
    krb5_keyblock *challenge_key = NULL, *armor_key, *as_key;

    armor_key = cb->fast_armor(context, rock);
    if (armor_key == NULL)
        return ENOENT;
    retval = cb->get_as_key(context, rock, &as_key);
    if (retval == 0 && padata->length) {
        krb5_enc_data *enc = NULL;
        krb5_data scratch;
        scratch.length = padata->length;
        scratch.data = (char *) padata->contents;
        retval = krb5_c_fx_cf2_simple(context,armor_key, "kdcchallengearmor",
                                      as_key, "challengelongterm",
                                      &challenge_key);
        if (retval == 0)
            retval = decode_krb5_enc_data(&scratch, &enc);
        scratch.data = NULL;
        if (retval == 0) {
            scratch.data = malloc(enc->ciphertext.length);
            scratch.length = enc->ciphertext.length;
            if (scratch.data == NULL)
                retval = ENOMEM;
        }
        if (retval == 0)
            retval = krb5_c_decrypt(context, challenge_key,
                                    KRB5_KEYUSAGE_ENC_CHALLENGE_KDC, NULL,
                                    enc, &scratch);
        /*
         * Per draft 11 of the preauth framework, the client MAY but is not
         * required to actually check the timestamp from the KDC other than to
         * confirm it decrypts. This code does not perform that check.
         */
        if (scratch.data)
            krb5_free_data_contents(context, &scratch);
        /* If we had a callback to assert that the KDC is verified, we would
         * call it here. */
        if (enc)
            krb5_free_enc_data(context, enc);
    } else if (retval == 0) { /*No padata; we send*/
        krb5_enc_data enc;
        krb5_pa_data **pa = NULL;
        krb5_data *encoded_ts = NULL;
        krb5_pa_enc_ts ts;
        enc.ciphertext.data = NULL;
        /* Use the timestamp from the preauth-required error if possible.
         * This time should always be secured by the FAST channel. */
        retval = cb->get_preauth_time(context, rock, FALSE, &ts.patimestamp,
                                      &ts.pausec);
        if (retval == 0)
            retval = encode_krb5_pa_enc_ts(&ts, &encoded_ts);
        if (retval == 0)
            retval = krb5_c_fx_cf2_simple(context,
                                          armor_key, "clientchallengearmor",
                                          as_key, "challengelongterm",
                                          &challenge_key);
        if (retval == 0)
            retval = krb5_encrypt_helper(context, challenge_key,
                                         KRB5_KEYUSAGE_ENC_CHALLENGE_CLIENT,
                                         encoded_ts, &enc);
        if (encoded_ts)
            krb5_free_data(context, encoded_ts);
        encoded_ts = NULL;
        if (retval == 0) {
            retval = encode_krb5_enc_data(&enc, &encoded_ts);
            krb5_free_data_contents(context, &enc.ciphertext);
        }
        if (retval == 0) {
            pa = calloc(2, sizeof(krb5_pa_data *));
            if (pa == NULL)
                retval = ENOMEM;
        }
        if (retval == 0) {
            pa[0] = calloc(1, sizeof(krb5_pa_data));
            if (pa[0] == NULL)
                retval = ENOMEM;
        }
        if (retval == 0) {
            pa[0]->length = encoded_ts->length;
            pa[0]->contents = (unsigned char *) encoded_ts->data;
            pa[0]->pa_type = KRB5_PADATA_ENCRYPTED_CHALLENGE;
            encoded_ts->data = NULL;
            *out_padata = pa;
            pa = NULL;
        }
        free(pa);
        krb5_free_data(context, encoded_ts);
    }
    if (challenge_key)
        krb5_free_keyblock(context, challenge_key);
    return retval;
}
Exemple #9
0
static krb5_error_code
otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
                   krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
                   krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
                   krb5_kdc_req *request, krb5_data *encoded_request_body,
                   krb5_data *encoded_previous_request, krb5_pa_data *pa_data,
                   krb5_prompter_fct prompter, void *prompter_data,
                   krb5_pa_data ***pa_data_out)
{
    krb5_pa_otp_challenge *chl = NULL;
    krb5_otp_tokeninfo *ti = NULL;
    krb5_keyblock *as_key = NULL;
    krb5_pa_otp_req *req = NULL;
    krb5_error_code retval = 0;
    krb5_data value, pin;
    const char *answer;

    if (modreq == NULL)
        return ENOMEM;
    chl = *(krb5_pa_otp_challenge **)modreq;

    *pa_data_out = NULL;

    /* Get FAST armor key. */
    as_key = cb->fast_armor(context, rock);
    if (as_key == NULL)
        return ENOENT;

    /* Use FAST armor key as response key. */
    retval = cb->set_as_key(context, rock, as_key);
    if (retval != 0)
        return retval;

    /* Attempt to get token selection from the responder. */
    pin = empty_data();
    value = empty_data();
    answer = cb->get_responder_answer(context, rock,
                                      KRB5_RESPONDER_QUESTION_OTP);
    retval = codec_decode_answer(context, answer, chl->tokeninfo, &ti, &value,
                                 &pin);
    if (retval != 0) {
        /* If the responder doesn't have a token selection,
         * we need to select the token via prompting. */
        retval = prompt_for_token(context, prompter, prompter_data,
                                  chl->tokeninfo, &ti, &value, &pin);
        if (retval != 0)
            goto error;
    }

    /* Make the request. */
    retval = make_request(context, ti, &value, &pin, &req);
    if (retval != 0)
        goto error;

    /* Save information about the token which was used. */
    save_config_tokeninfo(context, cb, rock, ti);

    /* Encrypt the challenge's nonce and set it in the request. */
    retval = encrypt_nonce(context, as_key, chl, req);
    if (retval != 0)
        goto error;

    /* Encode the request into the pa_data output. */
    retval = set_pa_data(req, pa_data_out);
error:
    krb5_free_data_contents(context, &value);
    krb5_free_data_contents(context, &pin);
    k5_free_pa_otp_req(context, req);
    return retval;
}
Exemple #10
0
static krb5_error_code
test_process(krb5_context context, krb5_clpreauth_moddata moddata,
             krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
             krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
             krb5_kdc_req *request, krb5_data *encoded_request_body,
             krb5_data *encoded_previous_request, krb5_pa_data *pa_data,
             krb5_prompter_fct prompter, void *prompter_data,
             krb5_pa_data ***out_pa_data)
{
    struct client_state *st = (struct client_state *)moddata;
    struct client_request_state *reqst = (struct client_request_state *)modreq;
    krb5_error_code ret;
    krb5_pa_data **list, *pa;
    krb5_keyblock *k;
    krb5_enc_data enc;
    krb5_data plain;
    const char *indstr;

    if (pa_data->length == 0) {
        /* This is an optimistic preauth test.  Send a recognizable padata
         * value so the KDC knows not to expect a cookie. */
        list = k5calloc(2, sizeof(*list), &ret);
        assert(!ret);
        pa = k5alloc(sizeof(*pa), &ret);
        assert(!ret);
        pa->pa_type = TEST_PA_TYPE;
        pa->contents = (uint8_t *)strdup("optimistic");
        assert(pa->contents != NULL);
        pa->length = 10;
        list[0] = pa;
        list[1] = NULL;
        *out_pa_data = list;
        return 0;
    } else if (reqst->second_round_trip) {
        printf("2rt: %.*s\n", pa_data->length, pa_data->contents);
    } else if (pa_data->length == 6 &&
               memcmp(pa_data->contents, "no key", 6) == 0) {
        printf("no key\n");
    } else {
        /* This fails during s4u_identify_user(), so don't assert. */
        ret = cb->get_as_key(context, rock, &k);
        if (ret)
            return ret;
        ret = alloc_data(&plain, pa_data->length);
        assert(!ret);
        enc.enctype = k->enctype;
        enc.ciphertext = make_data(pa_data->contents, pa_data->length);
        ret = krb5_c_decrypt(context, k, 1024, NULL, &enc, &plain);
        assert(!ret);
        printf("%.*s\n", plain.length, plain.data);
        free(plain.data);
    }
    reqst->second_round_trip = TRUE;

    indstr = (st->indicators != NULL) ? st->indicators : "";
    list = k5calloc(2, sizeof(*list), &ret);
    assert(!ret);
    pa = k5alloc(sizeof(*pa), &ret);
    assert(!ret);
    pa->pa_type = TEST_PA_TYPE;
    pa->contents = (uint8_t *)strdup(indstr);
    assert(pa->contents != NULL);
    pa->length = strlen(indstr);
    list[0] = pa;
    list[1] = NULL;
    *out_pa_data = list;
    return 0;
}
Exemple #11
0
static krb5_error_code
encts_process(krb5_context context, krb5_clpreauth_moddata moddata,
              krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
              krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
              krb5_kdc_req *request, krb5_data *encoded_request_body,
              krb5_data *encoded_previous_request, krb5_pa_data *padata,
              krb5_prompter_fct prompter, void *prompter_data,
              krb5_pa_data ***out_padata)
{
    krb5_error_code ret;
    krb5_pa_enc_ts pa_enc;
    krb5_data *ts = NULL, *enc_ts = NULL;
    krb5_enc_data enc_data;
    krb5_pa_data **pa = NULL;
    krb5_keyblock *as_key;

    enc_data.ciphertext = empty_data();

    ret = cb->get_as_key(context, rock, &as_key);
    if (ret)
        goto cleanup;
    TRACE_PREAUTH_ENC_TS_KEY_GAK(context, as_key);

    /*
     * Try and use the timestamp of the preauth request, even if it's
     * unauthenticated.  We could be fooled into making a preauth response for
     * a future time, but that has no security consequences other than the
     * KDC's audit logs.  If kdc_timesync is not configured, then this will
     * just use local time.
     */
    ret = cb->get_preauth_time(context, rock, TRUE, &pa_enc.patimestamp,
                               &pa_enc.pausec);
    if (ret)
        goto cleanup;

    ret = encode_krb5_pa_enc_ts(&pa_enc, &ts);
    if (ret)
        goto cleanup;

    ret = krb5_encrypt_helper(context, as_key, KRB5_KEYUSAGE_AS_REQ_PA_ENC_TS,
                              ts, &enc_data);
    if (ret)
        goto cleanup;
    TRACE_PREAUTH_ENC_TS(context, pa_enc.patimestamp, pa_enc.pausec,
                         ts, &enc_data.ciphertext);

    ret = encode_krb5_enc_data(&enc_data, &enc_ts);
    if (ret)
        goto cleanup;

    pa = k5alloc(2 * sizeof(krb5_pa_data *), &ret);
    if (pa == NULL)
        goto cleanup;

    pa[0] = k5alloc(sizeof(krb5_pa_data), &ret);
    if (pa[0] == NULL)
        goto cleanup;

    pa[0]->magic = KV5M_PA_DATA;
    pa[0]->pa_type = KRB5_PADATA_ENC_TIMESTAMP;
    pa[0]->length = enc_ts->length;
    pa[0]->contents = (krb5_octet *) enc_ts->data;
    enc_ts->data = NULL;
    pa[1] = NULL;
    *out_padata = pa;
    pa = NULL;

cleanup:
    krb5_free_data(context, ts);
    krb5_free_data(context, enc_ts);
    free(enc_data.ciphertext.data);
    free(pa);
    return ret;
}