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