/* Converts a JSON object into a krb5_responder_otp_challenge. */ static krb5_responder_otp_challenge * codec_decode_challenge(krb5_context ctx, const char *json) { krb5_responder_otp_challenge *chl = NULL; k5_json_value obj = NULL, arr = NULL, tmp = NULL; krb5_error_code retval; size_t i; retval = k5_json_decode(json, &obj); if (retval != 0) goto error; if (k5_json_get_tid(obj) != K5_JSON_TID_OBJECT) goto error; arr = k5_json_object_get(obj, "tokenInfo"); if (arr == NULL) goto error; if (k5_json_get_tid(arr) != K5_JSON_TID_ARRAY) goto error; chl = calloc(1, sizeof(krb5_responder_otp_challenge)); if (chl == NULL) goto error; chl->tokeninfo = calloc(k5_json_array_length(arr) + 1, sizeof(krb5_responder_otp_tokeninfo*)); if (chl->tokeninfo == NULL) goto error; retval = codec_value_to_string(obj, "service", &chl->service); if (retval != 0 && retval != ENOENT) goto error; for (i = 0; i < k5_json_array_length(arr); i++) { tmp = k5_json_array_get(arr, i); if (k5_json_get_tid(tmp) != K5_JSON_TID_OBJECT) goto error; chl->tokeninfo[i] = codec_decode_tokeninfo(tmp); if (chl->tokeninfo[i] == NULL) goto error; } k5_json_release(obj); return chl; error: if (chl != NULL) { for (i = 0; chl->tokeninfo != NULL && chl->tokeninfo[i] != NULL; i++) free_tokeninfo(chl->tokeninfo[i]); free(chl->tokeninfo); free(chl); } k5_json_release(obj); return NULL; }
/* Converts a krb5_pa_otp_challenge into a JSON object. */ static krb5_error_code codec_encode_challenge(krb5_context ctx, krb5_pa_otp_challenge *chl, char **json) { k5_json_object obj = NULL, tmp = NULL; k5_json_string str = NULL; k5_json_array arr = NULL; krb5_error_code retval; int i; retval = k5_json_object_create(&obj); if (retval != 0) goto cleanup; if (chl->service.data) { retval = k5_json_string_create_len(chl->service.data, chl->service.length, &str); if (retval != 0) goto cleanup; retval = k5_json_object_set(obj, "service", str); k5_json_release(str); if (retval != 0) goto cleanup; } retval = k5_json_array_create(&arr); if (retval != 0) goto cleanup; for (i = 0; chl->tokeninfo[i] != NULL ; i++) { retval = codec_encode_tokeninfo(chl->tokeninfo[i], &tmp); if (retval != 0) goto cleanup; retval = k5_json_array_add(arr, tmp); k5_json_release(tmp); if (retval != 0) goto cleanup; } retval = k5_json_object_set(obj, "tokenInfo", arr); if (retval != 0) goto cleanup; retval = k5_json_encode(obj, json); if (retval) goto cleanup; cleanup: k5_json_release(arr); k5_json_release(obj); return retval; }
/* Converts a krb5_otp_tokeninfo into a JSON object. */ static krb5_error_code codec_encode_tokeninfo(krb5_otp_tokeninfo *ti, k5_json_object *out) { krb5_error_code retval; k5_json_object obj; krb5_flags flags; retval = k5_json_object_create(&obj); if (retval != 0) goto error; flags = KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN; if (ti->flags & KRB5_OTP_FLAG_COLLECT_PIN) { flags |= KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN; if (ti->flags & KRB5_OTP_FLAG_SEPARATE_PIN) flags |= KRB5_RESPONDER_OTP_FLAGS_NEXTOTP; } if (ti->flags & KRB5_OTP_FLAG_NEXTOTP) flags |= KRB5_RESPONDER_OTP_FLAGS_NEXTOTP; retval = codec_int32_to_value(flags, obj, "flags"); if (retval != 0) goto error; retval = codec_data_to_value(&ti->vendor, obj, "vendor"); if (retval != 0) goto error; retval = codec_data_to_value(&ti->challenge, obj, "challenge"); if (retval != 0) goto error; retval = codec_int32_to_value(ti->length, obj, "length"); if (retval != 0) goto error; if (ti->format != KRB5_OTP_FORMAT_BASE64 && ti->format != KRB5_OTP_FORMAT_BINARY) { retval = codec_int32_to_value(ti->format, obj, "format"); if (retval != 0) goto error; } retval = codec_data_to_value(&ti->token_id, obj, "tokenID"); if (retval != 0) goto error; retval = codec_data_to_value(&ti->alg_id, obj, "algID"); if (retval != 0) goto error; *out = obj; return 0; error: k5_json_release(obj); return retval; }
static void array_dealloc(void *ptr) { k5_json_array array = ptr; size_t i; for (i = 0; i < array->len; i++) k5_json_release(array->values[i]); free(array->values); }
OM_uint32 KRB5_CALLCONV krb5_gss_import_cred(OM_uint32 *minor_status, gss_buffer_t token, gss_cred_id_t *cred_handle) { OM_uint32 status = GSS_S_COMPLETE; krb5_context context; krb5_error_code ret; krb5_gss_cred_id_t cred; k5_json_value v = NULL; k5_json_array array; k5_json_string str; char *copy = NULL; ret = krb5_gss_init_context(&context); if (ret) { *minor_status = ret; return GSS_S_FAILURE; } /* Decode token. */ copy = k5memdup0(token->value, token->length, &ret); if (copy == NULL) { status = GSS_S_FAILURE; *minor_status = ret; goto cleanup; } if (k5_json_decode(copy, &v)) goto invalid; /* Decode the CRED_EXPORT_MAGIC array wrapper. */ if (k5_json_get_tid(v) != K5_JSON_TID_ARRAY) goto invalid; array = v; if (k5_json_array_length(array) != 2) goto invalid; str = check_element(array, 0, K5_JSON_TID_STRING); if (str == NULL || strcmp(k5_json_string_utf8(str), CRED_EXPORT_MAGIC) != 0) goto invalid; if (json_to_kgcred(context, k5_json_array_get(array, 1), &cred)) goto invalid; *cred_handle = (gss_cred_id_t)cred; cleanup: free(copy); k5_json_release(v); krb5_free_context(context); return status; invalid: status = GSS_S_DEFECTIVE_TOKEN; goto cleanup; }
/* Decode the responder answer into a tokeninfo, a value and a pin. */ static krb5_error_code codec_decode_answer(krb5_context context, const char *answer, krb5_otp_tokeninfo **tis, krb5_otp_tokeninfo **ti, krb5_data *value, krb5_data *pin) { krb5_error_code retval; k5_json_value val = NULL; krb5_int32 indx, i; krb5_data tmp; if (answer == NULL) return EBADMSG; retval = k5_json_decode(answer, &val); if (retval != 0) goto cleanup; if (k5_json_get_tid(val) != K5_JSON_TID_OBJECT) goto cleanup; retval = codec_value_to_int32(val, "tokeninfo", &indx); if (retval != 0) goto cleanup; for (i = 0; tis[i] != NULL; i++) { if (i == indx) { retval = codec_value_to_data(val, "value", &tmp); if (retval != 0 && retval != ENOENT) goto cleanup; retval = codec_value_to_data(val, "pin", pin); if (retval != 0 && retval != ENOENT) { krb5_free_data_contents(context, &tmp); goto cleanup; } *value = tmp; *ti = tis[i]; retval = 0; goto cleanup; } } retval = EINVAL; cleanup: k5_json_release(val); return retval; }
/* Converts a krb5_data struct into a property of a JSON object. */ static krb5_error_code codec_data_to_value(krb5_data *data, k5_json_object obj, const char *key) { krb5_error_code retval; k5_json_string str; if (data->data == NULL) return 0; retval = k5_json_string_create_len(data->data, data->length, &str); if (retval) return retval; retval = k5_json_object_set(obj, key, str); k5_json_release(str); return retval; }
/* Converts a krb5_int32 into a property of a JSON object. */ static krb5_error_code codec_int32_to_value(krb5_int32 int32, k5_json_object obj, const char *key) { krb5_error_code retval; k5_json_number num; if (int32 == -1) return 0; retval = k5_json_number_create(int32, &num); if (retval) return retval; retval = k5_json_object_set(obj, key, num); k5_json_release(num); return retval; }
static krb5_error_code responder(krb5_context ctx, void *rawdata, krb5_responder_context rctx) { krb5_error_code err; char *key, *value, *pin, *encoded1, *encoded2; const char *challenge; k5_json_value decoded1, decoded2; k5_json_object ids; k5_json_number val; krb5_int32 token_flags; struct responder_data *data = rawdata; krb5_responder_pkinit_challenge *chl; krb5_responder_otp_challenge *ochl; unsigned int i, n; data->called = TRUE; /* Check that a particular challenge has the specified expected value. */ if (data->challenge != NULL) { /* Separate the challenge name and its expected value. */ key = strdup(data->challenge); if (key == NULL) exit(ENOMEM); value = key + strcspn(key, "="); if (*value != '\0') *value++ = '\0'; /* Read the challenge. */ challenge = krb5_responder_get_challenge(ctx, rctx, key); err = k5_json_decode(value, &decoded1); /* Check for "no challenge". */ if (challenge == NULL && *value == '\0') { fprintf(stderr, "OK: (no challenge) == (no challenge)\n"); } else if (err != 0) { /* It's not JSON, so assume we're just after a string compare. */ if (strcmp(challenge, value) == 0) { fprintf(stderr, "OK: \"%s\" == \"%s\"\n", challenge, value); } else { fprintf(stderr, "ERROR: \"%s\" != \"%s\"\n", challenge, value); exit(1); } } else { /* Assume we're after a JSON compare - decode the actual value. */ err = k5_json_decode(challenge, &decoded2); if (err != 0) { fprintf(stderr, "error decoding \"%s\"\n", challenge); exit(1); } /* Re-encode the expected challenge and the actual challenge... */ err = k5_json_encode(decoded1, &encoded1); if (err != 0) { fprintf(stderr, "error encoding json data\n"); exit(1); } err = k5_json_encode(decoded2, &encoded2); if (err != 0) { fprintf(stderr, "error encoding json data\n"); exit(1); } k5_json_release(decoded1); k5_json_release(decoded2); /* ... and see if they look the same. */ if (strcmp(encoded1, encoded2) == 0) { fprintf(stderr, "OK: \"%s\" == \"%s\"\n", encoded1, encoded2); } else { fprintf(stderr, "ERROR: \"%s\" != \"%s\"\n", encoded1, encoded2); exit(1); } free(encoded1); free(encoded2); } free(key); } /* Provide a particular response for a challenge. */ if (data->response != NULL) { /* Separate the challenge and its data content... */ key = strdup(data->response); if (key == NULL) exit(ENOMEM); value = key + strcspn(key, "="); if (*value != '\0') *value++ = '\0'; /* ... and pass it in. */ err = krb5_responder_set_answer(ctx, rctx, key, value); if (err != 0) { fprintf(stderr, "error setting response\n"); exit(1); } free(key); } if (data->print_pkinit_challenge) { /* Read the PKINIT challenge, formatted as a structure. */ err = krb5_responder_pkinit_get_challenge(ctx, rctx, &chl); if (err != 0) { fprintf(stderr, "error getting pkinit challenge\n"); exit(1); } if (chl != NULL) { for (n = 0; chl->identities[n] != NULL; n++) continue; for (i = 0; chl->identities[i] != NULL; i++) { if (chl->identities[i]->token_flags != -1) { printf("identity %u/%u: %s (flags=0x%lx)\n", i + 1, n, chl->identities[i]->identity, (long)chl->identities[i]->token_flags); } else { printf("identity %u/%u: %s\n", i + 1, n, chl->identities[i]->identity); } } } krb5_responder_pkinit_challenge_free(ctx, rctx, chl); } /* Provide a particular response for the PKINIT challenge. */ if (data->pkinit_answer != NULL) { /* Read the PKINIT challenge, formatted as a structure. */ err = krb5_responder_pkinit_get_challenge(ctx, rctx, &chl); if (err != 0) { fprintf(stderr, "error getting pkinit challenge\n"); exit(1); } /* * In case order matters, if the identity starts with "FILE:", exercise * the set_answer function, with the real answer second. */ if (chl != NULL && chl->identities != NULL && chl->identities[0] != NULL) { if (strncmp(chl->identities[0]->identity, "FILE:", 5) == 0) krb5_responder_pkinit_set_answer(ctx, rctx, "foo", "bar"); } /* Provide the real answer. */ key = strdup(data->pkinit_answer); if (key == NULL) exit(ENOMEM); value = strrchr(key, '='); if (value != NULL) *value++ = '\0'; else value = ""; err = krb5_responder_pkinit_set_answer(ctx, rctx, key, value); if (err != 0) { fprintf(stderr, "error setting response\n"); exit(1); } free(key); /* * In case order matters, if the identity starts with "PKCS12:", * exercise the set_answer function, with the real answer first. */ if (chl != NULL && chl->identities != NULL && chl->identities[0] != NULL) { if (strncmp(chl->identities[0]->identity, "PKCS12:", 5) == 0) krb5_responder_pkinit_set_answer(ctx, rctx, "foo", "bar"); } krb5_responder_pkinit_challenge_free(ctx, rctx, chl); } /* * Something we always check: read the PKINIT challenge, both as a * structure and in JSON form, reconstruct the JSON form from the * structure's contents, and check that they're the same. */ challenge = krb5_responder_get_challenge(ctx, rctx, KRB5_RESPONDER_QUESTION_PKINIT); if (challenge != NULL) { krb5_responder_pkinit_get_challenge(ctx, rctx, &chl); if (chl == NULL) { fprintf(stderr, "pkinit raw challenge set, " "but structure is NULL\n"); exit(1); } if (k5_json_object_create(&ids) != 0) { fprintf(stderr, "error creating json objects\n"); exit(1); } for (i = 0; chl->identities[i] != NULL; i++) { token_flags = chl->identities[i]->token_flags; if (k5_json_number_create(token_flags, &val) != 0) { fprintf(stderr, "error creating json number\n"); exit(1); } if (k5_json_object_set(ids, chl->identities[i]->identity, val) != 0) { fprintf(stderr, "error adding json number to object\n"); exit(1); } k5_json_release(val); } /* Encode the structure... */ err = k5_json_encode(ids, &encoded1); if (err != 0) { fprintf(stderr, "error encoding json data\n"); exit(1); } k5_json_release(ids); /* ... and see if they look the same. */ if (strcmp(encoded1, challenge) != 0) { fprintf(stderr, "\"%s\" != \"%s\"\n", encoded1, challenge); exit(1); } krb5_responder_pkinit_challenge_free(ctx, rctx, chl); free(encoded1); } /* Provide a particular response for an OTP challenge. */ if (data->otp_answer != NULL) { if (krb5_responder_otp_get_challenge(ctx, rctx, &ochl) == 0) { key = strchr(data->otp_answer, '='); if (key != NULL) { /* Make a copy of the answer that we can chop up. */ key = strdup(data->otp_answer); if (key == NULL) return ENOMEM; /* Isolate the ti value. */ value = strchr(key, '='); *value++ = '\0'; n = atoi(key); /* Break the value and PIN apart. */ pin = strchr(value, ':'); if (pin != NULL) *pin++ = '\0'; err = krb5_responder_otp_set_answer(ctx, rctx, n, value, pin); if (err != 0) { fprintf(stderr, "error setting response\n"); exit(1); } free(key); } krb5_responder_otp_challenge_free(ctx, rctx, ochl); } } return 0; }