Coverage Report

Created: 2026-03-04 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/winpr/libwinpr/sspi/Kerberos/krb5glue_mit.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Client
3
 * Kerberos Auth Protocol
4
 *
5
 * Copyright 2022 Isaac Klein <fifthdegree@protonmail.com>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 * http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
20
#ifndef WITH_KRB5_MIT
21
#error "This file must only be included with MIT kerberos"
22
#endif
23
24
#include <string.h>
25
26
#include <winpr/path.h>
27
#include <winpr/wlog.h>
28
#include <winpr/endian.h>
29
#include <winpr/crypto.h>
30
#include <winpr/print.h>
31
#include <winpr/assert.h>
32
#include <errno.h>
33
#include "krb5glue.h"
34
#include <profile.h>
35
36
static char* create_temporary_file(void)
37
0
{
38
0
  BYTE buffer[32];
39
0
  char* hex = nullptr;
40
0
  char* path = nullptr;
41
42
0
  if (winpr_RAND(buffer, sizeof(buffer)) < 0)
43
0
    return nullptr;
44
0
  hex = winpr_BinToHexString(buffer, sizeof(buffer), FALSE);
45
0
  path = GetKnownSubPath(KNOWN_PATH_TEMP, hex);
46
0
  free(hex);
47
0
  return path;
48
0
}
49
50
void krb5glue_keys_free(krb5_context ctx, struct krb5glue_keyset* keyset)
51
0
{
52
0
  WINPR_ASSERT(ctx);
53
0
  WINPR_ASSERT(keyset);
54
55
0
  krb5_k_free_key(ctx, keyset->session_key);
56
0
  krb5_k_free_key(ctx, keyset->initiator_key);
57
0
  krb5_k_free_key(ctx, keyset->acceptor_key);
58
0
}
59
60
krb5_error_code krb5glue_update_keyset(krb5_context ctx, krb5_auth_context auth_ctx, BOOL acceptor,
61
                                       struct krb5glue_keyset* keyset)
62
0
{
63
0
  WINPR_ASSERT(ctx);
64
0
  WINPR_ASSERT(auth_ctx);
65
0
  WINPR_ASSERT(keyset);
66
67
0
  krb5glue_keys_free(ctx, keyset);
68
0
  krb5_auth_con_getkey_k(ctx, auth_ctx, &keyset->session_key);
69
0
  if (acceptor)
70
0
  {
71
0
    krb5_auth_con_getsendsubkey_k(ctx, auth_ctx, &keyset->acceptor_key);
72
0
    krb5_auth_con_getrecvsubkey_k(ctx, auth_ctx, &keyset->initiator_key);
73
0
  }
74
0
  else
75
0
  {
76
0
    krb5_auth_con_getsendsubkey_k(ctx, auth_ctx, &keyset->initiator_key);
77
0
    krb5_auth_con_getrecvsubkey_k(ctx, auth_ctx, &keyset->acceptor_key);
78
0
  }
79
0
  return 0;
80
0
}
81
82
krb5_prompt_type krb5glue_get_prompt_type(krb5_context ctx, krb5_prompt prompts[], int index)
83
0
{
84
0
  WINPR_ASSERT(ctx);
85
0
  WINPR_ASSERT(prompts);
86
0
  WINPR_UNUSED(prompts);
87
88
0
  krb5_prompt_type* types = krb5_get_prompt_types(ctx);
89
0
  return types ? types[index] : 0;
90
0
}
91
92
krb5_error_code krb5glue_log_error(krb5_context ctx, krb5_data* msg, const char* tag)
93
0
{
94
0
  krb5_error* error = nullptr;
95
0
  krb5_error_code rv = 0;
96
97
0
  WINPR_ASSERT(ctx);
98
0
  WINPR_ASSERT(msg);
99
0
  WINPR_ASSERT(tag);
100
101
0
  if (!(rv = krb5_rd_error(ctx, msg, &error)))
102
0
  {
103
0
    WLog_ERR(tag, "KRB_ERROR: %s", error->text.data);
104
0
    krb5_free_error(ctx, error);
105
0
  }
106
107
0
  return rv;
108
0
}
109
110
BOOL krb5glue_authenticator_validate_chksum(krb5glue_authenticator authenticator, int cksumtype,
111
                                            uint32_t* flags)
112
0
{
113
0
  WINPR_ASSERT(flags);
114
115
0
  if (!authenticator || !authenticator->checksum ||
116
0
      authenticator->checksum->checksum_type != cksumtype || authenticator->checksum->length < 24)
117
0
    return FALSE;
118
0
  *flags = winpr_Data_Get_UINT32((authenticator->checksum->contents + 20));
119
0
  return TRUE;
120
0
}
121
122
krb5_error_code krb5glue_get_init_creds(krb5_context ctx, krb5_principal princ, krb5_ccache ccache,
123
                                        krb5_prompter_fct prompter, char* password,
124
                                        SEC_WINPR_KERBEROS_SETTINGS* krb_settings)
125
0
{
126
0
  krb5_error_code rv = 0;
127
0
  krb5_deltat start_time = 0;
128
0
  krb5_get_init_creds_opt* gic_opt = nullptr;
129
0
  krb5_init_creds_context creds_ctx = nullptr;
130
0
  char* tmp_profile_path = create_temporary_file();
131
0
  profile_t profile = nullptr;
132
0
  BOOL is_temp_ctx = FALSE;
133
134
0
  WINPR_ASSERT(ctx);
135
136
0
  rv = krb5_get_init_creds_opt_alloc(ctx, &gic_opt);
137
0
  if (rv)
138
0
    goto cleanup;
139
140
0
  krb5_get_init_creds_opt_set_forwardable(gic_opt, 0);
141
0
  krb5_get_init_creds_opt_set_proxiable(gic_opt, 0);
142
143
0
  if (krb_settings)
144
0
  {
145
0
    if (krb_settings->startTime)
146
0
      start_time = krb_settings->startTime;
147
0
    if (krb_settings->lifeTime)
148
0
      krb5_get_init_creds_opt_set_tkt_life(gic_opt, krb_settings->lifeTime);
149
0
    if (krb_settings->renewLifeTime)
150
0
      krb5_get_init_creds_opt_set_renew_life(gic_opt, krb_settings->renewLifeTime);
151
0
    if (krb_settings->withPac)
152
0
    {
153
0
      rv = krb5_get_init_creds_opt_set_pac_request(ctx, gic_opt, TRUE);
154
0
      if (rv)
155
0
        goto cleanup;
156
0
    }
157
0
    if (krb_settings->armorCache)
158
0
    {
159
0
      rv = krb5_get_init_creds_opt_set_fast_ccache_name(ctx, gic_opt,
160
0
                                                        krb_settings->armorCache);
161
0
      if (rv)
162
0
        goto cleanup;
163
0
    }
164
0
    if (krb_settings->pkinitX509Identity)
165
0
    {
166
0
      rv = krb5_get_init_creds_opt_set_pa(ctx, gic_opt, "X509_user_identity",
167
0
                                          krb_settings->pkinitX509Identity);
168
0
      if (rv)
169
0
        goto cleanup;
170
0
    }
171
0
    if (krb_settings->pkinitX509Anchors)
172
0
    {
173
0
      rv = krb5_get_init_creds_opt_set_pa(ctx, gic_opt, "X509_anchors",
174
0
                                          krb_settings->pkinitX509Anchors);
175
0
      if (rv)
176
0
        goto cleanup;
177
0
    }
178
0
    if (krb_settings->kdcUrl && (strnlen(krb_settings->kdcUrl, 2) > 0))
179
0
    {
180
0
      const char* names[4] = WINPR_C_ARRAY_INIT;
181
0
      char* realm = nullptr;
182
0
      char* kdc_url = nullptr;
183
0
      size_t size = 0;
184
185
0
      if ((rv = krb5_get_profile(ctx, &profile)))
186
0
        goto cleanup;
187
188
0
      rv = ENOMEM;
189
0
      if (winpr_asprintf(&kdc_url, &size, "https://%s/KdcProxy", krb_settings->kdcUrl) <= 0)
190
0
      {
191
0
        free(kdc_url);
192
0
        goto cleanup;
193
0
      }
194
195
0
      realm = calloc(princ->realm.length + 1, 1);
196
0
      if (!realm)
197
0
      {
198
0
        free(kdc_url);
199
0
        goto cleanup;
200
0
      }
201
0
      CopyMemory(realm, princ->realm.data, princ->realm.length);
202
203
0
      names[0] = "realms";
204
0
      names[1] = realm;
205
0
      names[2] = "kdc";
206
207
0
      profile_clear_relation(profile, names);
208
0
      profile_add_relation(profile, names, kdc_url);
209
210
      /* Since we know who the KDC is, tell krb5 that its certificate is valid for pkinit */
211
0
      names[2] = "pkinit_kdc_hostname";
212
0
      profile_add_relation(profile, names, krb_settings->kdcUrl);
213
214
0
      free(kdc_url);
215
0
      free(realm);
216
217
0
      long lrv = profile_flush_to_file(profile, tmp_profile_path);
218
0
      if (lrv)
219
0
        goto cleanup;
220
221
0
      profile_abandon(profile);
222
0
      profile = nullptr;
223
0
      lrv = profile_init_path(tmp_profile_path, &profile);
224
0
      if (lrv)
225
0
        goto cleanup;
226
227
0
      rv = krb5_init_context_profile(profile, 0, &ctx);
228
0
      if (rv)
229
0
        goto cleanup;
230
0
      is_temp_ctx = TRUE;
231
0
    }
232
0
  }
233
234
0
  if ((rv = krb5_get_init_creds_opt_set_in_ccache(ctx, gic_opt, ccache)))
235
0
    goto cleanup;
236
237
0
  if ((rv = krb5_get_init_creds_opt_set_out_ccache(ctx, gic_opt, ccache)))
238
0
    goto cleanup;
239
240
0
  if ((rv =
241
0
           krb5_init_creds_init(ctx, princ, prompter, password, start_time, gic_opt, &creds_ctx)))
242
0
    goto cleanup;
243
244
0
  if ((rv = krb5_init_creds_get(ctx, creds_ctx)))
245
0
    goto cleanup;
246
247
0
cleanup:
248
0
  krb5_init_creds_free(ctx, creds_ctx);
249
0
  krb5_get_init_creds_opt_free(ctx, gic_opt);
250
0
  if (is_temp_ctx)
251
0
    krb5_free_context(ctx);
252
0
  profile_abandon(profile);
253
0
  winpr_DeleteFile(tmp_profile_path);
254
0
  free(tmp_profile_path);
255
256
0
  return rv;
257
0
}
258