krb5_error_code KRB5_CALLCONV krb5_responder_otp_set_answer(krb5_context ctx, krb5_responder_context rctx, size_t ti, const char *value, const char *pin) { krb5_error_code retval; k5_json_object obj = NULL; k5_json_number num; k5_json_string str; char *tmp; retval = k5_json_object_create(&obj); if (retval != 0) goto error; retval = k5_json_number_create(ti, &num); if (retval != 0) goto error; retval = k5_json_object_set(obj, "tokeninfo", num); k5_json_release(num); if (retval != 0) goto error; if (value != NULL) { retval = k5_json_string_create(value, &str); if (retval != 0) goto error; retval = k5_json_object_set(obj, "value", str); k5_json_release(str); if (retval != 0) goto error; } if (pin != NULL) { retval = k5_json_string_create(pin, &str); if (retval != 0) goto error; retval = k5_json_object_set(obj, "pin", str); k5_json_release(str); if (retval != 0) goto error; } retval = k5_json_encode(obj, &tmp); if (retval != 0) goto error; k5_json_release(obj); retval = krb5_responder_set_answer(ctx, rctx, KRB5_RESPONDER_QUESTION_OTP, tmp); free(tmp); return retval; error: k5_json_release(obj); return retval; }
/* 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_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; }