1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* tests/hooks.c - test harness for KDC send and recv hooks */ 3 /* 4 * Copyright (C) 2016 by the Massachusetts Institute of Technology. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * * Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 * OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 #include "k5-int.h" 34 35 static krb5_context ctx; 36 37 static void 38 check_code(krb5_error_code code, const char *file, int line) 39 { 40 const char *errmsg; 41 42 if (code) { 43 errmsg = krb5_get_error_message(ctx, code); 44 fprintf(stderr, "%s:%d -- %s (code=%d)\n", file, line, errmsg, 45 (int)code); 46 krb5_free_error_message(ctx, errmsg); 47 exit(1); 48 } 49 } 50 51 #define check(code) check_code((code), __FILE__, __LINE__) 52 53 /* Verify that the canonicalize bit is set in an AS-REQ and remove it. */ 54 static krb5_error_code 55 test_send_as_req(krb5_context context, void *data, const krb5_data *realm, 56 const krb5_data *message, krb5_data **new_message_out, 57 krb5_data **reply_out) 58 { 59 krb5_kdc_req *as_req; 60 int cmp; 61 62 assert(krb5_is_as_req(message)); 63 check(decode_krb5_as_req(message, &as_req)); 64 65 assert(as_req->msg_type == KRB5_AS_REQ); 66 assert(as_req->kdc_options & KDC_OPT_CANONICALIZE); 67 assert(as_req->client->realm.length == realm->length); 68 cmp = memcmp(as_req->client->realm.data, realm->data, realm->length); 69 assert(cmp == 0); 70 71 /* Remove the canonicalize flag and create a new message. */ 72 as_req->kdc_options &= ~KDC_OPT_CANONICALIZE; 73 check(encode_krb5_as_req(as_req, new_message_out)); 74 75 krb5_free_kdc_req(context, as_req); 76 return 0; 77 } 78 79 /* Verify that reply is an AS-REP with kvno 1 and a valid enctype. */ 80 static krb5_error_code 81 test_recv_as_rep(krb5_context context, void *data, krb5_error_code code, 82 const krb5_data *realm, const krb5_data *message, 83 const krb5_data *reply, krb5_data **new_reply) 84 { 85 krb5_kdc_rep *as_rep; 86 87 assert(code == 0); 88 assert(krb5_is_as_rep(reply)); 89 check(decode_krb5_as_rep(reply, &as_rep)); 90 91 assert(as_rep->msg_type == KRB5_AS_REP); 92 assert(as_rep->ticket->enc_part.kvno == 1); 93 assert(krb5_c_valid_enctype(as_rep->ticket->enc_part.enctype)); 94 95 krb5_free_kdc_rep(context, as_rep); 96 return 0; 97 } 98 99 /* Create a fake error reply. */ 100 static krb5_error_code 101 test_send_error(krb5_context context, void *data, const krb5_data *realm, 102 const krb5_data *message, krb5_data **new_message_out, 103 krb5_data **reply_out) 104 { 105 krb5_error_code ret; 106 krb5_error err; 107 krb5_principal client, server; 108 char *realm_str, *princ_str; 109 int r; 110 111 realm_str = k5memdup0(realm->data, realm->length, &ret); 112 check(ret); 113 114 r = asprintf(&princ_str, "invalid@%s", realm_str); 115 assert(r > 0); 116 check(krb5_parse_name(ctx, princ_str, &client)); 117 free(princ_str); 118 119 r = asprintf(&princ_str, "krbtgt@%s", realm_str); 120 assert(r > 0); 121 check(krb5_parse_name(ctx, princ_str, &server)); 122 free(princ_str); 123 free(realm_str); 124 125 err.magic = KV5M_ERROR; 126 err.ctime = 1971196337; 127 err.cusec = 0; 128 err.susec = 97008; 129 err.stime = 1458219390; 130 err.error = 6; 131 err.client = client; 132 err.server = server; 133 err.text = string2data("CLIENT_NOT_FOUND"); 134 err.e_data = empty_data(); 135 check(encode_krb5_error(&err, reply_out)); 136 137 krb5_free_principal(ctx, client); 138 krb5_free_principal(ctx, server); 139 return 0; 140 } 141 142 static krb5_error_code 143 test_recv_error(krb5_context context, void *data, krb5_error_code code, 144 const krb5_data *realm, const krb5_data *message, 145 const krb5_data *reply, krb5_data **new_reply) 146 { 147 /* The send hook created a reply, so this hook should not be executed. */ 148 abort(); 149 } 150 151 /* Modify an AS-REP reply, change the msg_type to KRB5_TGS_REP. */ 152 static krb5_error_code 153 test_recv_modify_reply(krb5_context context, void *data, krb5_error_code code, 154 const krb5_data *realm, const krb5_data *message, 155 const krb5_data *reply, krb5_data **new_reply) 156 { 157 krb5_kdc_rep *as_rep; 158 159 assert(code == 0); 160 assert(krb5_is_as_rep(reply)); 161 check(decode_krb5_as_rep(reply, &as_rep)); 162 163 as_rep->msg_type = KRB5_TGS_REP; 164 check(encode_krb5_as_rep(as_rep, new_reply)); 165 166 krb5_free_kdc_rep(context, as_rep); 167 return 0; 168 } 169 170 /* Return an error given by the callback data argument. */ 171 static krb5_error_code 172 test_send_return_value(krb5_context context, void *data, 173 const krb5_data *realm, const krb5_data *message, 174 krb5_data **new_message_out, krb5_data **reply_out) 175 { 176 assert(data != NULL); 177 return *(krb5_error_code *)data; 178 } 179 180 /* Return an error given by the callback argument. */ 181 static krb5_error_code 182 test_recv_return_value(krb5_context context, void *data, krb5_error_code code, 183 const krb5_data *realm, const krb5_data *message, 184 const krb5_data *reply, krb5_data **new_reply) 185 { 186 assert(data != NULL); 187 return *(krb5_error_code *)data; 188 } 189 190 int 191 main(int argc, char *argv[]) 192 { 193 const char *principal, *password; 194 krb5_principal client; 195 krb5_get_init_creds_opt *opts; 196 krb5_creds creds; 197 krb5_error_code ret, test_return_code; 198 199 if (argc != 3) { 200 fprintf(stderr, "Usage: %s princname password\n", argv[0]); 201 exit(1); 202 } 203 principal = argv[1]; 204 password = argv[2]; 205 206 check(krb5_init_context(&ctx)); 207 check(krb5_parse_name(ctx, principal, &client)); 208 209 /* Use a send hook to modify an outgoing AS-REQ. The library will detect 210 * the modification in the reply. */ 211 check(krb5_get_init_creds_opt_alloc(ctx, &opts)); 212 krb5_get_init_creds_opt_set_canonicalize(opts, 1); 213 krb5_set_kdc_send_hook(ctx, test_send_as_req, NULL); 214 krb5_set_kdc_recv_hook(ctx, test_recv_as_rep, NULL); 215 ret = krb5_get_init_creds_password(ctx, &creds, client, password, NULL, 216 NULL, 0, NULL, opts); 217 assert(ret == KRB5_KDCREP_MODIFIED); 218 krb5_get_init_creds_opt_free(ctx, opts); 219 220 /* Use a send hook to synthesize a KRB-ERROR reply. */ 221 krb5_set_kdc_send_hook(ctx, test_send_error, NULL); 222 krb5_set_kdc_recv_hook(ctx, test_recv_error, NULL); 223 ret = krb5_get_init_creds_password(ctx, &creds, client, password, NULL, 224 NULL, 0, NULL, NULL); 225 assert(ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN); 226 227 /* Use a recv hook to modify a KDC reply. */ 228 krb5_set_kdc_send_hook(ctx, NULL, NULL); 229 krb5_set_kdc_recv_hook(ctx, test_recv_modify_reply, NULL); 230 ret = krb5_get_init_creds_password(ctx, &creds, client, password, NULL, 231 NULL, 0, NULL, NULL); 232 assert(ret == KRB5KRB_AP_ERR_MSG_TYPE); 233 234 /* Verify that the user data pointer works in the send hook. */ 235 test_return_code = KRB5KDC_ERR_PREAUTH_FAILED; 236 krb5_set_kdc_send_hook(ctx, test_send_return_value, &test_return_code); 237 krb5_set_kdc_recv_hook(ctx, NULL, NULL); 238 ret = krb5_get_init_creds_password(ctx, &creds, client, password, NULL, 239 NULL, 0, NULL, NULL); 240 assert(ret == KRB5KDC_ERR_PREAUTH_FAILED); 241 242 /* Verify that the user data pointer works in the recv hook. */ 243 test_return_code = KRB5KDC_ERR_NULL_KEY; 244 krb5_set_kdc_send_hook(ctx, NULL, NULL); 245 krb5_set_kdc_recv_hook(ctx, test_recv_return_value, &test_return_code); 246 ret = krb5_get_init_creds_password(ctx, &creds, client, password, NULL, 247 NULL, 0, NULL, NULL); 248 assert(ret == KRB5KDC_ERR_NULL_KEY); 249 250 krb5_free_principal(ctx, client); 251 krb5_free_context(ctx); 252 return 0; 253 } 254