Line data Source code
1 : /*
2 : * Copyright (c) 1997 - 2003 Kungliga Tekniska Högskolan
3 : * (Royal Institute of Technology, Stockholm, Sweden).
4 : * 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
8 : * are met:
9 : *
10 : * 1. Redistributions of source code must retain the above copyright
11 : * notice, this list of conditions and the following disclaimer.
12 : *
13 : * 2. Redistributions in binary form must reproduce the above copyright
14 : * notice, this list of conditions and the following disclaimer in the
15 : * documentation and/or other materials provided with the distribution.
16 : *
17 : * 3. Neither the name of the Institute nor the names of its contributors
18 : * may be used to endorse or promote products derived from this software
19 : * without specific prior written permission.
20 : *
21 : * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 : * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 : * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 : * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 : * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 : * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 : * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 : * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 : * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 : * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 : * SUCH DAMAGE.
32 : */
33 :
34 : #include "gsskrb5_locl.h"
35 :
36 : /*
37 : * Return initiator subkey, or if that doesn't exists, the subkey.
38 : */
39 :
40 : krb5_error_code
41 100 : _gsskrb5i_get_initiator_subkey(const gsskrb5_ctx ctx,
42 : krb5_context context,
43 : krb5_keyblock **key)
44 : {
45 0 : krb5_error_code ret;
46 100 : *key = NULL;
47 :
48 100 : if (ctx->more_flags & LOCAL) {
49 0 : ret = krb5_auth_con_getlocalsubkey(context,
50 : ctx->auth_context,
51 : key);
52 : } else {
53 100 : ret = krb5_auth_con_getremotesubkey(context,
54 : ctx->auth_context,
55 : key);
56 : }
57 100 : if (ret == 0 && *key == NULL)
58 0 : ret = krb5_auth_con_getkey(context,
59 : ctx->auth_context,
60 : key);
61 100 : if (ret == 0 && *key == NULL) {
62 0 : krb5_set_error_message(context, 0, "No initiator subkey available");
63 0 : return GSS_KRB5_S_KG_NO_SUBKEY;
64 : }
65 100 : return ret;
66 : }
67 :
68 : krb5_error_code
69 372863 : _gsskrb5i_get_acceptor_subkey(const gsskrb5_ctx ctx,
70 : krb5_context context,
71 : krb5_keyblock **key)
72 : {
73 3503 : krb5_error_code ret;
74 372863 : *key = NULL;
75 :
76 372863 : if (ctx->more_flags & LOCAL) {
77 123092 : ret = krb5_auth_con_getremotesubkey(context,
78 : ctx->auth_context,
79 : key);
80 : } else {
81 249771 : ret = krb5_auth_con_getlocalsubkey(context,
82 : ctx->auth_context,
83 : key);
84 : }
85 372863 : if (ret == 0 && *key == NULL) {
86 100 : krb5_set_error_message(context, 0, "No acceptor subkey available");
87 100 : return GSS_KRB5_S_KG_NO_SUBKEY;
88 : }
89 369260 : return ret;
90 : }
91 :
92 : OM_uint32
93 372863 : _gsskrb5i_get_token_key(const gsskrb5_ctx ctx,
94 : krb5_context context,
95 : krb5_keyblock **key)
96 : {
97 372863 : _gsskrb5i_get_acceptor_subkey(ctx, context, key);
98 372863 : if(*key == NULL) {
99 : /*
100 : * Only use the initiator subkey or ticket session key if an
101 : * acceptor subkey was not required.
102 : */
103 100 : if ((ctx->more_flags & ACCEPTOR_SUBKEY) == 0)
104 100 : _gsskrb5i_get_initiator_subkey(ctx, context, key);
105 : }
106 372863 : if (*key == NULL) {
107 0 : krb5_set_error_message(context, 0, "No token key available");
108 0 : return GSS_KRB5_S_KG_NO_SUBKEY;
109 : }
110 369360 : return 0;
111 : }
112 :
113 : static OM_uint32
114 0 : sub_wrap_size (
115 : OM_uint32 req_output_size,
116 : OM_uint32 * max_input_size,
117 : int blocksize,
118 : int extrasize
119 : )
120 : {
121 0 : size_t len, total_len;
122 :
123 0 : len = 8 + req_output_size + blocksize + extrasize;
124 :
125 0 : _gsskrb5_encap_length(len, &len, &total_len, GSS_KRB5_MECHANISM);
126 :
127 0 : total_len -= req_output_size; /* token length */
128 0 : if (total_len < req_output_size) {
129 0 : *max_input_size = (req_output_size - total_len);
130 0 : (*max_input_size) &= (~(OM_uint32)(blocksize - 1));
131 : } else {
132 0 : *max_input_size = 0;
133 : }
134 0 : return GSS_S_COMPLETE;
135 : }
136 :
137 : OM_uint32 GSSAPI_CALLCONV
138 25688 : _gsskrb5_wrap_size_limit (
139 : OM_uint32 * minor_status,
140 : gss_const_ctx_id_t context_handle,
141 : int conf_req_flag,
142 : gss_qop_t qop_req,
143 : OM_uint32 req_output_size,
144 : OM_uint32 * max_input_size
145 : )
146 : {
147 244 : krb5_context context;
148 244 : krb5_keyblock *key;
149 244 : OM_uint32 ret;
150 25688 : const gsskrb5_ctx ctx = (const gsskrb5_ctx) context_handle;
151 :
152 25688 : GSSAPI_KRB5_INIT (&context);
153 :
154 25688 : if (ctx->more_flags & IS_CFX)
155 24192 : return _gssapi_wrap_size_cfx(minor_status, ctx, context,
156 : conf_req_flag, qop_req,
157 : req_output_size, max_input_size);
158 :
159 0 : HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex);
160 1496 : ret = _gsskrb5i_get_token_key(ctx, context, &key);
161 0 : HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
162 1496 : if (ret) {
163 0 : *minor_status = ret;
164 0 : return GSS_S_FAILURE;
165 : }
166 :
167 1496 : switch (key->keytype) {
168 0 : case KRB5_ENCTYPE_DES_CBC_CRC :
169 : case KRB5_ENCTYPE_DES_CBC_MD4 :
170 : case KRB5_ENCTYPE_DES_CBC_MD5 :
171 : #ifdef HEIM_WEAK_CRYPTO
172 : ret = sub_wrap_size(req_output_size, max_input_size, 8, 22);
173 : #else
174 0 : ret = GSS_S_FAILURE;
175 : #endif
176 0 : break;
177 1496 : case KRB5_ENCTYPE_ARCFOUR_HMAC_MD5:
178 : case KRB5_ENCTYPE_ARCFOUR_HMAC_MD5_56:
179 1496 : ret = _gssapi_wrap_size_arcfour(minor_status, ctx, context,
180 : conf_req_flag, qop_req,
181 : req_output_size, max_input_size, key);
182 1496 : break;
183 0 : case KRB5_ENCTYPE_DES3_CBC_MD5 :
184 : case KRB5_ENCTYPE_DES3_CBC_SHA1 :
185 0 : ret = sub_wrap_size(req_output_size, max_input_size, 8, 34);
186 0 : break;
187 0 : default :
188 0 : abort();
189 0 : break;
190 : }
191 1496 : krb5_free_keyblock (context, key);
192 1496 : *minor_status = 0;
193 1496 : return ret;
194 : }
195 :
196 : #ifdef HEIM_WEAK_CRYPTO
197 :
198 : static OM_uint32
199 : wrap_des
200 : (OM_uint32 * minor_status,
201 : const gsskrb5_ctx ctx,
202 : krb5_context context,
203 : int conf_req_flag,
204 : gss_qop_t qop_req,
205 : const gss_buffer_t input_message_buffer,
206 : int * conf_state,
207 : gss_buffer_t output_message_buffer,
208 : krb5_keyblock *key
209 : )
210 : {
211 : u_char *p;
212 : EVP_MD_CTX *md5;
213 : u_char hash[16];
214 : DES_key_schedule schedule;
215 : EVP_CIPHER_CTX des_ctx;
216 : DES_cblock deskey;
217 : DES_cblock zero;
218 : size_t i;
219 : int32_t seq_number;
220 : size_t len, total_len, padlength, datalen;
221 :
222 : if (IS_DCE_STYLE(ctx)) {
223 : padlength = 0;
224 : datalen = input_message_buffer->length;
225 : len = 22 + 8;
226 : _gsskrb5_encap_length (len, &len, &total_len, GSS_KRB5_MECHANISM);
227 : total_len += datalen;
228 : datalen += 8;
229 : } else {
230 : padlength = 8 - (input_message_buffer->length % 8);
231 : datalen = input_message_buffer->length + padlength + 8;
232 : len = datalen + 22;
233 : _gsskrb5_encap_length (len, &len, &total_len, GSS_KRB5_MECHANISM);
234 : }
235 :
236 : output_message_buffer->length = total_len;
237 : output_message_buffer->value = malloc (total_len);
238 : if (output_message_buffer->value == NULL) {
239 : output_message_buffer->length = 0;
240 : *minor_status = ENOMEM;
241 : return GSS_S_FAILURE;
242 : }
243 :
244 : p = _gsskrb5_make_header(output_message_buffer->value,
245 : len,
246 : "\x02\x01", /* TOK_ID */
247 : GSS_KRB5_MECHANISM);
248 :
249 : /* SGN_ALG */
250 : memcpy (p, "\x00\x00", 2);
251 : p += 2;
252 : /* SEAL_ALG */
253 : if(conf_req_flag)
254 : memcpy (p, "\x00\x00", 2);
255 : else
256 : memcpy (p, "\xff\xff", 2);
257 : p += 2;
258 : /* Filler */
259 : memcpy (p, "\xff\xff", 2);
260 : p += 2;
261 :
262 : /* fill in later */
263 : memset (p, 0, 16);
264 : p += 16;
265 :
266 : /* confounder + data + pad */
267 : krb5_generate_random_block(p, 8);
268 : memcpy (p + 8, input_message_buffer->value,
269 : input_message_buffer->length);
270 : memset (p + 8 + input_message_buffer->length, padlength, padlength);
271 :
272 : /* checksum */
273 : md5 = EVP_MD_CTX_create();
274 : EVP_DigestInit_ex(md5, EVP_md5(), NULL);
275 : EVP_DigestUpdate(md5, p - 24, 8);
276 : EVP_DigestUpdate(md5, p, datalen);
277 : EVP_DigestFinal_ex(md5, hash, NULL);
278 : EVP_MD_CTX_destroy(md5);
279 :
280 : memset (&zero, 0, sizeof(zero));
281 : memcpy (&deskey, key->keyvalue.data, sizeof(deskey));
282 : DES_set_key_unchecked (&deskey, &schedule);
283 : DES_cbc_cksum ((void *)hash, (void *)hash, sizeof(hash),
284 : &schedule, &zero);
285 : memcpy (p - 8, hash, 8);
286 :
287 : /* sequence number */
288 : HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex);
289 : krb5_auth_con_getlocalseqnumber (context,
290 : ctx->auth_context,
291 : &seq_number);
292 :
293 : p -= 16;
294 : p[0] = (seq_number >> 0) & 0xFF;
295 : p[1] = (seq_number >> 8) & 0xFF;
296 : p[2] = (seq_number >> 16) & 0xFF;
297 : p[3] = (seq_number >> 24) & 0xFF;
298 : memset (p + 4,
299 : (ctx->more_flags & LOCAL) ? 0 : 0xFF,
300 : 4);
301 :
302 : EVP_CIPHER_CTX_init(&des_ctx);
303 : EVP_CipherInit_ex(&des_ctx, EVP_des_cbc(), NULL, key->keyvalue.data, p + 8, 1);
304 : EVP_Cipher(&des_ctx, p, p, 8);
305 : EVP_CIPHER_CTX_cleanup(&des_ctx);
306 :
307 : krb5_auth_con_setlocalseqnumber (context,
308 : ctx->auth_context,
309 : ++seq_number);
310 : HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
311 :
312 : /* encrypt the data */
313 : p += 16;
314 :
315 : if(conf_req_flag) {
316 : memcpy (&deskey, key->keyvalue.data, sizeof(deskey));
317 :
318 : for (i = 0; i < sizeof(deskey); ++i)
319 : deskey[i] ^= 0xf0;
320 :
321 : EVP_CIPHER_CTX_init(&des_ctx);
322 : EVP_CipherInit_ex(&des_ctx, EVP_des_cbc(), NULL, deskey, zero, 1);
323 : EVP_Cipher(&des_ctx, p, p, datalen);
324 : EVP_CIPHER_CTX_cleanup(&des_ctx);
325 : }
326 : memset (deskey, 0, sizeof(deskey));
327 : memset (&schedule, 0, sizeof(schedule));
328 :
329 : if(conf_state != NULL)
330 : *conf_state = conf_req_flag;
331 : *minor_status = 0;
332 : return GSS_S_COMPLETE;
333 : }
334 :
335 : #endif
336 :
337 : static OM_uint32
338 0 : wrap_des3
339 : (OM_uint32 * minor_status,
340 : const gsskrb5_ctx ctx,
341 : krb5_context context,
342 : int conf_req_flag,
343 : gss_qop_t qop_req,
344 : const gss_buffer_t input_message_buffer,
345 : int * conf_state,
346 : gss_buffer_t output_message_buffer,
347 : krb5_keyblock *key
348 : )
349 : {
350 0 : u_char *p;
351 0 : u_char seq[8];
352 0 : int32_t seq_number;
353 0 : size_t len, total_len, padlength, datalen;
354 0 : uint32_t ret;
355 0 : krb5_crypto crypto;
356 0 : Checksum cksum;
357 0 : krb5_data encdata;
358 :
359 0 : if (IS_DCE_STYLE(ctx)) {
360 0 : padlength = 0;
361 0 : datalen = input_message_buffer->length;
362 0 : len = 34 + 8;
363 0 : _gsskrb5_encap_length (len, &len, &total_len, GSS_KRB5_MECHANISM);
364 0 : total_len += datalen;
365 0 : datalen += 8;
366 : } else {
367 0 : padlength = 8 - (input_message_buffer->length % 8);
368 0 : datalen = input_message_buffer->length + padlength + 8;
369 0 : len = datalen + 34;
370 0 : _gsskrb5_encap_length (len, &len, &total_len, GSS_KRB5_MECHANISM);
371 : }
372 :
373 0 : output_message_buffer->length = total_len;
374 0 : output_message_buffer->value = malloc (total_len);
375 0 : if (output_message_buffer->value == NULL) {
376 0 : output_message_buffer->length = 0;
377 0 : *minor_status = ENOMEM;
378 0 : return GSS_S_FAILURE;
379 : }
380 :
381 0 : p = _gsskrb5_make_header(output_message_buffer->value,
382 : len,
383 : "\x02\x01", /* TOK_ID */
384 : GSS_KRB5_MECHANISM);
385 :
386 : /* SGN_ALG */
387 0 : memcpy (p, "\x04\x00", 2); /* HMAC SHA1 DES3-KD */
388 0 : p += 2;
389 : /* SEAL_ALG */
390 0 : if(conf_req_flag)
391 0 : memcpy (p, "\x02\x00", 2); /* DES3-KD */
392 : else
393 0 : memcpy (p, "\xff\xff", 2);
394 0 : p += 2;
395 : /* Filler */
396 0 : memcpy (p, "\xff\xff", 2);
397 0 : p += 2;
398 :
399 : /* calculate checksum (the above + confounder + data + pad) */
400 :
401 0 : memcpy (p + 20, p - 8, 8);
402 0 : krb5_generate_random_block(p + 28, 8);
403 0 : memcpy (p + 28 + 8, input_message_buffer->value,
404 : input_message_buffer->length);
405 0 : memset (p + 28 + 8 + input_message_buffer->length, padlength, padlength);
406 :
407 0 : ret = krb5_crypto_init(context, key, 0, &crypto);
408 0 : if (ret) {
409 0 : free (output_message_buffer->value);
410 0 : output_message_buffer->length = 0;
411 0 : output_message_buffer->value = NULL;
412 0 : *minor_status = ret;
413 0 : return GSS_S_FAILURE;
414 : }
415 :
416 0 : ret = krb5_create_checksum (context,
417 : crypto,
418 : KRB5_KU_USAGE_SIGN,
419 : 0,
420 0 : p + 20,
421 : datalen + 8,
422 : &cksum);
423 0 : krb5_crypto_destroy (context, crypto);
424 0 : if (ret) {
425 0 : free (output_message_buffer->value);
426 0 : output_message_buffer->length = 0;
427 0 : output_message_buffer->value = NULL;
428 0 : *minor_status = ret;
429 0 : return GSS_S_FAILURE;
430 : }
431 :
432 : /* zero out SND_SEQ + SGN_CKSUM in case */
433 0 : memset (p, 0, 28);
434 :
435 0 : memcpy (p + 8, cksum.checksum.data, cksum.checksum.length);
436 0 : free_Checksum (&cksum);
437 :
438 0 : HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex);
439 : /* sequence number */
440 0 : krb5_auth_con_getlocalseqnumber (context,
441 : ctx->auth_context,
442 : &seq_number);
443 :
444 0 : seq[0] = (seq_number >> 0) & 0xFF;
445 0 : seq[1] = (seq_number >> 8) & 0xFF;
446 0 : seq[2] = (seq_number >> 16) & 0xFF;
447 0 : seq[3] = (seq_number >> 24) & 0xFF;
448 0 : memset (seq + 4,
449 0 : (ctx->more_flags & LOCAL) ? 0 : 0xFF,
450 : 4);
451 :
452 :
453 0 : ret = krb5_crypto_init(context, key, ETYPE_DES3_CBC_NONE,
454 : &crypto);
455 0 : if (ret) {
456 0 : free (output_message_buffer->value);
457 0 : output_message_buffer->length = 0;
458 0 : output_message_buffer->value = NULL;
459 0 : *minor_status = ret;
460 0 : return GSS_S_FAILURE;
461 : }
462 :
463 : {
464 0 : DES_cblock ivec;
465 :
466 0 : memcpy (&ivec, p + 8, 8);
467 0 : ret = krb5_encrypt_ivec (context,
468 : crypto,
469 : KRB5_KU_USAGE_SEQ,
470 : seq, 8, &encdata,
471 : &ivec);
472 : }
473 0 : krb5_crypto_destroy (context, crypto);
474 0 : if (ret) {
475 0 : free (output_message_buffer->value);
476 0 : output_message_buffer->length = 0;
477 0 : output_message_buffer->value = NULL;
478 0 : *minor_status = ret;
479 0 : return GSS_S_FAILURE;
480 : }
481 :
482 0 : assert (encdata.length == 8);
483 :
484 0 : memcpy (p, encdata.data, encdata.length);
485 0 : krb5_data_free (&encdata);
486 :
487 0 : krb5_auth_con_setlocalseqnumber (context,
488 : ctx->auth_context,
489 : ++seq_number);
490 0 : HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
491 :
492 : /* encrypt the data */
493 0 : p += 28;
494 :
495 0 : if(conf_req_flag) {
496 0 : krb5_data tmp;
497 :
498 0 : ret = krb5_crypto_init(context, key,
499 : ETYPE_DES3_CBC_NONE, &crypto);
500 0 : if (ret) {
501 0 : free (output_message_buffer->value);
502 0 : output_message_buffer->length = 0;
503 0 : output_message_buffer->value = NULL;
504 0 : *minor_status = ret;
505 0 : return GSS_S_FAILURE;
506 : }
507 0 : ret = krb5_encrypt(context, crypto, KRB5_KU_USAGE_SEAL,
508 : p, datalen, &tmp);
509 0 : krb5_crypto_destroy(context, crypto);
510 0 : if (ret) {
511 0 : free (output_message_buffer->value);
512 0 : output_message_buffer->length = 0;
513 0 : output_message_buffer->value = NULL;
514 0 : *minor_status = ret;
515 0 : return GSS_S_FAILURE;
516 : }
517 0 : assert (tmp.length == datalen);
518 :
519 0 : memcpy (p, tmp.data, datalen);
520 0 : krb5_data_free(&tmp);
521 : }
522 0 : if(conf_state != NULL)
523 0 : *conf_state = conf_req_flag;
524 0 : *minor_status = 0;
525 0 : return GSS_S_COMPLETE;
526 : }
527 :
528 : OM_uint32 GSSAPI_CALLCONV
529 744499 : _gsskrb5_wrap
530 : (OM_uint32 * minor_status,
531 : gss_const_ctx_id_t context_handle,
532 : int conf_req_flag,
533 : gss_qop_t qop_req,
534 : const gss_buffer_t input_message_buffer,
535 : int * conf_state,
536 : gss_buffer_t output_message_buffer
537 : )
538 : {
539 930 : krb5_context context;
540 930 : krb5_keyblock *key;
541 930 : OM_uint32 ret;
542 744499 : const gsskrb5_ctx ctx = (const gsskrb5_ctx) context_handle;
543 :
544 744499 : output_message_buffer->value = NULL;
545 744499 : output_message_buffer->length = 0;
546 :
547 744499 : GSSAPI_KRB5_INIT (&context);
548 :
549 744499 : if (ctx->more_flags & IS_CFX)
550 693166 : return _gssapi_wrap_cfx (minor_status, ctx, context, conf_req_flag,
551 : input_message_buffer, conf_state,
552 : output_message_buffer);
553 :
554 0 : HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex);
555 51333 : ret = _gsskrb5i_get_token_key(ctx, context, &key);
556 0 : HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
557 51333 : if (ret) {
558 0 : *minor_status = ret;
559 0 : return GSS_S_FAILURE;
560 : }
561 :
562 51333 : switch (key->keytype) {
563 0 : case KRB5_ENCTYPE_DES_CBC_CRC :
564 : case KRB5_ENCTYPE_DES_CBC_MD4 :
565 : case KRB5_ENCTYPE_DES_CBC_MD5 :
566 : #ifdef HEIM_WEAK_CRYPTO
567 : ret = wrap_des (minor_status, ctx, context, conf_req_flag,
568 : qop_req, input_message_buffer, conf_state,
569 : output_message_buffer, key);
570 : #else
571 0 : ret = GSS_S_FAILURE;
572 : #endif
573 0 : break;
574 0 : case KRB5_ENCTYPE_DES3_CBC_MD5 :
575 : case KRB5_ENCTYPE_DES3_CBC_SHA1 :
576 0 : ret = wrap_des3 (minor_status, ctx, context, conf_req_flag,
577 : qop_req, input_message_buffer, conf_state,
578 : output_message_buffer, key);
579 0 : break;
580 51333 : case KRB5_ENCTYPE_ARCFOUR_HMAC_MD5:
581 : case KRB5_ENCTYPE_ARCFOUR_HMAC_MD5_56:
582 51333 : ret = _gssapi_wrap_arcfour (minor_status, ctx, context, conf_req_flag,
583 : qop_req, input_message_buffer, conf_state,
584 : output_message_buffer, key);
585 51333 : break;
586 0 : default :
587 0 : abort();
588 0 : break;
589 : }
590 51333 : krb5_free_keyblock (context, key);
591 51333 : return ret;
592 : }
|