xref: /netbsd-src/external/mpl/bind/dist/lib/dns/gssapi_link.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: gssapi_link.c,v 1.11 2025/01/26 16:25:22 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0
7  *
8  * This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11  *
12  * See the COPYRIGHT file distributed with this work for additional
13  * information regarding copyright ownership.
14  */
15 
16 #include <inttypes.h> /* IWYU pragma: keep */
17 #include <stdbool.h>
18 #include <time.h> /* IWYU pragma: keep */
19 
20 #if HAVE_GSSAPI_GSSAPI_H
21 #include <gssapi/gssapi.h>
22 #elif HAVE_GSSAPI_H
23 #include <gssapi.h>
24 #endif
25 
26 #if HAVE_GSSAPI_GSSAPI_KRB5_H
27 #include <gssapi/gssapi_krb5.h>
28 #elif HAVE_GSSAPI_KRB5_H
29 #include <gssapi_krb5.h>
30 #endif
31 
32 #include <isc/base64.h>
33 #include <isc/buffer.h>
34 #include <isc/mem.h>
35 #include <isc/result.h>
36 #include <isc/string.h>
37 #include <isc/util.h>
38 
39 #include <dst/gssapi.h>
40 
41 #include "dst_internal.h"
42 #include "dst_parse.h"
43 
44 #define INITIAL_BUFFER_SIZE 1024
45 #define BUFFER_EXTRA	    1024
46 
47 #define REGION_TO_GBUFFER(r, gb)          \
48 	do {                              \
49 		(gb).length = (r).length; \
50 		(gb).value = (r).base;    \
51 	} while (0)
52 
53 #define GBUFFER_TO_REGION(gb, r)                        \
54 	do {                                            \
55 		(r).length = (unsigned int)(gb).length; \
56 		(r).base = (gb).value;                  \
57 	} while (0)
58 
59 struct dst_gssapi_signverifyctx {
60 	isc_buffer_t *buffer;
61 };
62 
63 /*%
64  * Allocate a temporary "context" for use in gathering data for signing
65  * or verifying.
66  */
67 static isc_result_t
68 gssapi_create_signverify_ctx(dst_key_t *key, dst_context_t *dctx) {
69 	dst_gssapi_signverifyctx_t *ctx;
70 
71 	UNUSED(key);
72 
73 	ctx = isc_mem_get(dctx->mctx, sizeof(dst_gssapi_signverifyctx_t));
74 	ctx->buffer = NULL;
75 	isc_buffer_allocate(dctx->mctx, &ctx->buffer, INITIAL_BUFFER_SIZE);
76 
77 	dctx->ctxdata.gssctx = ctx;
78 
79 	return ISC_R_SUCCESS;
80 }
81 
82 /*%
83  * Destroy the temporary sign/verify context.
84  */
85 static void
86 gssapi_destroy_signverify_ctx(dst_context_t *dctx) {
87 	dst_gssapi_signverifyctx_t *ctx = dctx->ctxdata.gssctx;
88 
89 	if (ctx != NULL) {
90 		if (ctx->buffer != NULL) {
91 			isc_buffer_free(&ctx->buffer);
92 		}
93 		isc_mem_put(dctx->mctx, ctx,
94 			    sizeof(dst_gssapi_signverifyctx_t));
95 		dctx->ctxdata.gssctx = NULL;
96 	}
97 }
98 
99 /*%
100  * Add data to our running buffer of data we will be signing or verifying.
101  * This code will see if the new data will fit in our existing buffer, and
102  * copy it in if it will.  If not, it will attempt to allocate a larger
103  * buffer and copy old+new into it, and free the old buffer.
104  */
105 static isc_result_t
106 gssapi_adddata(dst_context_t *dctx, const isc_region_t *data) {
107 	dst_gssapi_signverifyctx_t *ctx = dctx->ctxdata.gssctx;
108 	isc_buffer_t *newbuffer = NULL;
109 	isc_region_t r;
110 	unsigned int length;
111 	isc_result_t result;
112 
113 	result = isc_buffer_copyregion(ctx->buffer, data);
114 	if (result == ISC_R_SUCCESS) {
115 		return ISC_R_SUCCESS;
116 	}
117 
118 	length = isc_buffer_length(ctx->buffer) + data->length + BUFFER_EXTRA;
119 
120 	isc_buffer_allocate(dctx->mctx, &newbuffer, length);
121 
122 	isc_buffer_usedregion(ctx->buffer, &r);
123 	(void)isc_buffer_copyregion(newbuffer, &r);
124 	(void)isc_buffer_copyregion(newbuffer, data);
125 
126 	isc_buffer_free(&ctx->buffer);
127 	ctx->buffer = newbuffer;
128 
129 	return ISC_R_SUCCESS;
130 }
131 
132 /*%
133  * Sign.
134  */
135 static isc_result_t
136 gssapi_sign(dst_context_t *dctx, isc_buffer_t *sig) {
137 	dst_gssapi_signverifyctx_t *ctx = dctx->ctxdata.gssctx;
138 	isc_region_t message;
139 	gss_buffer_desc gmessage, gsig;
140 	OM_uint32 minor, gret;
141 	gss_ctx_id_t gssctx = dctx->key->keydata.gssctx;
142 	char buf[1024];
143 
144 	/*
145 	 * Convert the data we wish to sign into a structure gssapi can
146 	 * understand.
147 	 */
148 	isc_buffer_usedregion(ctx->buffer, &message);
149 	REGION_TO_GBUFFER(message, gmessage);
150 
151 	/*
152 	 * Generate the signature.
153 	 */
154 	gret = gss_get_mic(&minor, gssctx, GSS_C_QOP_DEFAULT, &gmessage, &gsig);
155 
156 	/*
157 	 * If it did not complete, we log the result and return a generic
158 	 * failure code.
159 	 */
160 	if (gret != GSS_S_COMPLETE) {
161 		gss_log(3, "GSS sign error: %s",
162 			gss_error_tostring(gret, minor, buf, sizeof(buf)));
163 		return ISC_R_FAILURE;
164 	}
165 
166 	/*
167 	 * If it will not fit in our allocated buffer, return that we need
168 	 * more space.
169 	 */
170 	if (gsig.length > isc_buffer_availablelength(sig)) {
171 		gss_release_buffer(&minor, &gsig);
172 		return ISC_R_NOSPACE;
173 	}
174 
175 	/*
176 	 * Copy the output into our buffer space, and release the gssapi
177 	 * allocated space.
178 	 */
179 	isc_buffer_putmem(sig, gsig.value, (unsigned int)gsig.length);
180 	if (gsig.length != 0U) {
181 		gss_release_buffer(&minor, &gsig);
182 	}
183 
184 	return ISC_R_SUCCESS;
185 }
186 
187 /*%
188  * Verify.
189  */
190 static isc_result_t
191 gssapi_verify(dst_context_t *dctx, const isc_region_t *sig) {
192 	dst_gssapi_signverifyctx_t *ctx = dctx->ctxdata.gssctx;
193 	isc_region_t message;
194 	gss_buffer_desc gmessage, gsig;
195 	OM_uint32 minor, gret;
196 	gss_ctx_id_t gssctx = dctx->key->keydata.gssctx;
197 	char err[1024];
198 
199 	/*
200 	 * Convert the data we wish to sign into a structure gssapi can
201 	 * understand.
202 	 */
203 	isc_buffer_usedregion(ctx->buffer, &message);
204 	REGION_TO_GBUFFER(message, gmessage);
205 	REGION_TO_GBUFFER(*sig, gsig);
206 
207 	/*
208 	 * Verify the data.
209 	 */
210 	gret = gss_verify_mic(&minor, gssctx, &gmessage, &gsig, NULL);
211 
212 	/*
213 	 * Convert return codes into something useful to us.
214 	 */
215 	if (gret != GSS_S_COMPLETE) {
216 		gss_log(3, "GSS verify error: %s",
217 			gss_error_tostring(gret, minor, err, sizeof(err)));
218 		if (gret == GSS_S_DEFECTIVE_TOKEN || gret == GSS_S_BAD_SIG ||
219 		    gret == GSS_S_DUPLICATE_TOKEN || gret == GSS_S_OLD_TOKEN ||
220 		    gret == GSS_S_UNSEQ_TOKEN || gret == GSS_S_GAP_TOKEN ||
221 		    gret == GSS_S_CONTEXT_EXPIRED || gret == GSS_S_NO_CONTEXT ||
222 		    gret == GSS_S_FAILURE)
223 		{
224 			return DST_R_VERIFYFAILURE;
225 		} else {
226 			return ISC_R_FAILURE;
227 		}
228 	}
229 
230 	return ISC_R_SUCCESS;
231 }
232 
233 static bool
234 gssapi_compare(const dst_key_t *key1, const dst_key_t *key2) {
235 	gss_ctx_id_t gsskey1 = key1->keydata.gssctx;
236 	gss_ctx_id_t gsskey2 = key2->keydata.gssctx;
237 
238 	/* No idea */
239 	return gsskey1 == gsskey2;
240 }
241 
242 static isc_result_t
243 gssapi_generate(dst_key_t *key, int unused, void (*callback)(int)) {
244 	UNUSED(key);
245 	UNUSED(unused);
246 	UNUSED(callback);
247 
248 	/* No idea */
249 	return ISC_R_FAILURE;
250 }
251 
252 static bool
253 gssapi_isprivate(const dst_key_t *key) {
254 	UNUSED(key);
255 	return true;
256 }
257 
258 static void
259 gssapi_destroy(dst_key_t *key) {
260 	REQUIRE(key != NULL);
261 	dst_gssapi_deletectx(key->mctx, &key->keydata.gssctx);
262 	key->keydata.gssctx = NULL;
263 }
264 
265 static isc_result_t
266 gssapi_restore(dst_key_t *key, const char *keystr) {
267 	OM_uint32 major, minor;
268 	unsigned int len;
269 	isc_buffer_t *b = NULL;
270 	isc_region_t r;
271 	gss_buffer_desc gssbuffer;
272 	isc_result_t result;
273 
274 	len = strlen(keystr);
275 	if ((len % 4) != 0U) {
276 		return ISC_R_BADBASE64;
277 	}
278 
279 	len = (len / 4) * 3;
280 
281 	isc_buffer_allocate(key->mctx, &b, len);
282 
283 	result = isc_base64_decodestring(keystr, b);
284 	if (result != ISC_R_SUCCESS) {
285 		isc_buffer_free(&b);
286 		return result;
287 	}
288 
289 	isc_buffer_remainingregion(b, &r);
290 	REGION_TO_GBUFFER(r, gssbuffer);
291 	major = gss_import_sec_context(&minor, &gssbuffer,
292 				       (gss_ctx_id_t *)&key->keydata.gssctx);
293 	if (major != GSS_S_COMPLETE) {
294 		isc_buffer_free(&b);
295 		return ISC_R_FAILURE;
296 	}
297 
298 	isc_buffer_free(&b);
299 	return ISC_R_SUCCESS;
300 }
301 
302 static isc_result_t
303 gssapi_dump(dst_key_t *key, isc_mem_t *mctx, char **buffer, int *length) {
304 	OM_uint32 major, minor;
305 	gss_buffer_desc gssbuffer;
306 	size_t len;
307 	char *buf;
308 	isc_buffer_t b;
309 	isc_region_t r;
310 	isc_result_t result;
311 
312 	major = gss_export_sec_context(
313 		&minor, (gss_ctx_id_t *)&key->keydata.gssctx, &gssbuffer);
314 	if (major != GSS_S_COMPLETE) {
315 		fprintf(stderr, "gss_export_sec_context -> %u, %u\n", major,
316 			minor);
317 		return ISC_R_FAILURE;
318 	}
319 	if (gssbuffer.length == 0U) {
320 		return ISC_R_FAILURE;
321 	}
322 	len = ((gssbuffer.length + 2) / 3) * 4;
323 	buf = isc_mem_get(mctx, len);
324 	isc_buffer_init(&b, buf, (unsigned int)len);
325 	GBUFFER_TO_REGION(gssbuffer, r);
326 	result = isc_base64_totext(&r, 0, "", &b);
327 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
328 	gss_release_buffer(&minor, &gssbuffer);
329 	*buffer = buf;
330 	*length = (int)len;
331 	return ISC_R_SUCCESS;
332 }
333 
334 static dst_func_t gssapi_functions = {
335 	gssapi_create_signverify_ctx,
336 	NULL, /*%< createctx2 */
337 	gssapi_destroy_signverify_ctx,
338 	gssapi_adddata,
339 	gssapi_sign,
340 	gssapi_verify,
341 	NULL, /*%< verify2 */
342 	NULL, /*%< computesecret */
343 	gssapi_compare,
344 	NULL, /*%< paramcompare */
345 	gssapi_generate,
346 	gssapi_isprivate,
347 	gssapi_destroy,
348 	NULL, /*%< todns */
349 	NULL, /*%< fromdns */
350 	NULL, /*%< tofile */
351 	NULL, /*%< parse */
352 	NULL, /*%< cleanup */
353 	NULL, /*%< fromlabel */
354 	gssapi_dump,
355 	gssapi_restore,
356 };
357 
358 isc_result_t
359 dst__gssapi_init(dst_func_t **funcp) {
360 	REQUIRE(funcp != NULL);
361 	if (*funcp == NULL) {
362 		*funcp = &gssapi_functions;
363 	}
364 	return ISC_R_SUCCESS;
365 }
366