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