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