xref: /freebsd/crypto/krb5/src/lib/krb5/krb/preauth_pkinit.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/krb/preauth_pkinit.c - PKINIT clpreauth helpers */
3 /*
4  * Copyright 2013 Red Hat, Inc.  All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *    1. Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *
12  *    2. Redistributions in binary form must reproduce the above copyright
13  *       notice, this list of conditions and the following disclaimer in
14  *       the documentation and/or other materials provided with the
15  *       distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 /*
31  * This file defines libkrb5 APIs for manipulating PKINIT responder questions
32  * and answers.  The main body of the PKINIT clpreauth module is in the
33  * plugins/preauth/pkinit directory.
34  */
35 
36 #include "k5-int.h"
37 #include "k5-json.h"
38 #include "int-proto.h"
39 #include "init_creds_ctx.h"
40 
41 struct get_one_challenge_data {
42     krb5_responder_pkinit_identity **identities;
43     krb5_error_code err;
44 };
45 
46 static void
get_one_challenge(void * arg,const char * key,k5_json_value val)47 get_one_challenge(void *arg, const char *key, k5_json_value val)
48 {
49     struct get_one_challenge_data *data;
50     unsigned long token_flags;
51     size_t i;
52 
53     data = arg;
54     if (data->err != 0)
55         return;
56     if (k5_json_get_tid(val) != K5_JSON_TID_NUMBER) {
57         data->err = EINVAL;
58         return;
59     }
60 
61     token_flags = k5_json_number_value(val);
62     /* Find the slot for this entry. */
63     for (i = 0; data->identities[i] != NULL; i++)
64         continue;
65     /* Set the identity (a copy of the key) and the token flags. */
66     data->identities[i] = k5alloc(sizeof(*data->identities[i]), &data->err);
67     if (data->identities[i] == NULL)
68         return;
69     data->identities[i]->identity = strdup(key);
70     if (data->identities[i]->identity == NULL) {
71         data->err = ENOMEM;
72         return;
73     }
74     data->identities[i]->token_flags = token_flags;
75 }
76 
77 krb5_error_code KRB5_CALLCONV
krb5_responder_pkinit_get_challenge(krb5_context ctx,krb5_responder_context rctx,krb5_responder_pkinit_challenge ** chl_out)78 krb5_responder_pkinit_get_challenge(krb5_context ctx,
79                                     krb5_responder_context rctx,
80                                     krb5_responder_pkinit_challenge **chl_out)
81 {
82     const char *challenge;
83     k5_json_value j;
84     struct get_one_challenge_data get_one_challenge_data;
85     krb5_responder_pkinit_challenge *chl = NULL;
86     unsigned int n_ids;
87     krb5_error_code ret;
88 
89     *chl_out = NULL;
90     challenge = krb5_responder_get_challenge(ctx, rctx,
91                                              KRB5_RESPONDER_QUESTION_PKINIT);
92     if (challenge == NULL)
93        return 0;
94 
95     ret = k5_json_decode(challenge, &j);
96     if (ret != 0)
97         return ret;
98 
99     /* Create the returned object. */
100     chl = k5alloc(sizeof(*chl), &ret);
101     if (chl == NULL)
102         goto failed;
103 
104     /* Create the list of identities. */
105     n_ids = k5_json_object_count(j);
106     chl->identities = k5calloc(n_ids + 1, sizeof(chl->identities[0]), &ret);
107     if (chl->identities == NULL)
108         goto failed;
109 
110     /* Populate the object with identities. */
111     memset(&get_one_challenge_data, 0, sizeof(get_one_challenge_data));
112     get_one_challenge_data.identities = chl->identities;
113     k5_json_object_iterate(j, get_one_challenge, &get_one_challenge_data);
114     if (get_one_challenge_data.err != 0) {
115         ret = get_one_challenge_data.err;
116         goto failed;
117     }
118 
119     /* All done. */
120     k5_json_release(j);
121     *chl_out = chl;
122     return 0;
123 
124 failed:
125     k5_json_release(j);
126     krb5_responder_pkinit_challenge_free(ctx, rctx, chl);
127     return ret;
128 }
129 
130 krb5_error_code KRB5_CALLCONV
krb5_responder_pkinit_set_answer(krb5_context ctx,krb5_responder_context rctx,const char * identity,const char * pin)131 krb5_responder_pkinit_set_answer(krb5_context ctx, krb5_responder_context rctx,
132                                  const char *identity, const char *pin)
133 {
134     char *answer = NULL;
135     const char *old_answer;
136     k5_json_value answers = NULL;
137     k5_json_string jpin = NULL;
138     krb5_error_code ret = ENOMEM;
139 
140     /* If there's an answer already set, we're adding/removing a value. */
141     old_answer = k5_response_items_get_answer(rctx->items,
142                                               KRB5_RESPONDER_QUESTION_PKINIT);
143 
144     /* If we're removing a value, and we have no values, we're done. */
145     if (old_answer == NULL && pin == NULL)
146         return 0;
147 
148     /* Decode the old answers. */
149     if (old_answer == NULL)
150         old_answer = "{}";
151     ret = k5_json_decode(old_answer, &answers);
152     if (ret != 0)
153         goto cleanup;
154 
155     if (k5_json_get_tid(answers) != K5_JSON_TID_OBJECT) {
156         ret = EINVAL;
157         goto cleanup;
158     }
159 
160     /* Create and add the new pin string, if we're adding a value. */
161     if (pin != NULL) {
162         ret = k5_json_string_create(pin, &jpin);
163         if (ret != 0)
164             goto cleanup;
165         ret = k5_json_object_set(answers, identity, jpin);
166         if (ret != 0)
167             goto cleanup;
168     } else {
169         ret = k5_json_object_set(answers, identity, NULL);
170         if (ret != 0)
171             goto cleanup;
172     }
173 
174     /* Encode and we're done. */
175     ret = k5_json_encode(answers, &answer);
176     if (ret != 0)
177         goto cleanup;
178 
179     ret = krb5_responder_set_answer(ctx, rctx, KRB5_RESPONDER_QUESTION_PKINIT,
180                                     answer);
181 
182 cleanup:
183     k5_json_release(jpin);
184     k5_json_release(answers);
185     free(answer);
186     return ret;
187 }
188 
189 void KRB5_CALLCONV
krb5_responder_pkinit_challenge_free(krb5_context ctx,krb5_responder_context rctx,krb5_responder_pkinit_challenge * chl)190 krb5_responder_pkinit_challenge_free(krb5_context ctx,
191                                      krb5_responder_context rctx,
192                                      krb5_responder_pkinit_challenge *chl)
193 {
194    size_t i;
195 
196    if (chl == NULL)
197        return;
198    for (i = 0; chl->identities != NULL && chl->identities[i] != NULL; i++) {
199        free(chl->identities[i]->identity);
200        free(chl->identities[i]);
201    }
202    free(chl->identities);
203    free(chl);
204 }
205