xref: /illumos-gate/usr/src/lib/krb5/kadm5/clnt/chpw.c (revision 2aeafac3612e19716bf8164f89c3c9196342979c)
1 /*
2  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 
7 #include <string.h>
8 
9 #include "k5-int.h"
10 #include <kadm5/admin.h>
11 #include <client_internal.h>
12 #include "auth_con.h"
13 #include <locale.h>
14 
15 
16 krb5_error_code
17 krb5int_mk_chpw_req(
18 	krb5_context context,
19 	krb5_auth_context auth_context,
20 	krb5_data *ap_req,
21 	char *passwd,
22 	krb5_data *packet)
23 {
24     krb5_error_code ret = 0;
25     krb5_data clearpw;
26     krb5_data cipherpw;
27     krb5_replay_data replay;
28     char *ptr;
29 
30     cipherpw.data = NULL;
31 
32     if ((ret = krb5_auth_con_setflags(context, auth_context,
33 				      KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
34 	  goto cleanup;
35 
36     clearpw.length = strlen(passwd);
37     clearpw.data = passwd;
38 
39     if ((ret = krb5_mk_priv(context, auth_context,
40 			    &clearpw, &cipherpw, &replay)))
41       goto cleanup;
42 
43     packet->length = 6 + ap_req->length + cipherpw.length;
44     packet->data = (char *) malloc(packet->length);
45     if (packet->data == NULL)
46 	{
47 	    ret = ENOMEM;
48 	    goto cleanup;
49 	}
50     ptr = packet->data;
51 
52     /* length */
53 
54     *ptr++ = (packet->length>> 8) & 0xff;
55     *ptr++ = packet->length & 0xff;
56 
57     /* version == 0x0001 big-endian
58      * NOTE: when MS and MIT start supporting the latest
59      *	version of the passwd change protocol (v2),
60      *	this value will change to 2.
61      */
62     *ptr++ = 0;
63     *ptr++ = 1;
64 
65     /* ap_req length, big-endian */
66 
67     *ptr++ = (ap_req->length>>8) & 0xff;
68     *ptr++ = ap_req->length & 0xff;
69 
70     /* ap-req data */
71 
72     memcpy(ptr, ap_req->data, ap_req->length);
73     ptr += ap_req->length;
74 
75     /* krb-priv of password */
76 
77     memcpy(ptr, cipherpw.data, cipherpw.length);
78 
79 cleanup:
80     if(cipherpw.data != NULL)  /* allocated by krb5_mk_priv */
81       free(cipherpw.data);
82 
83     return(ret);
84 }
85 
86 krb5_error_code
87 krb5int_rd_chpw_rep(krb5_context context, krb5_auth_context auth_context, krb5_data *packet, int *result_code, krb5_data *result_data)
88 {
89     char *ptr;
90     int plen, vno;
91     krb5_data ap_rep;
92     krb5_ap_rep_enc_part *ap_rep_enc;
93     krb5_error_code ret;
94     krb5_data cipherresult;
95     krb5_data clearresult;
96     krb5_error *krberror;
97     krb5_replay_data replay;
98     krb5_keyblock *tmp;
99     int local_result_code;
100 
101     if (packet->length < 4)
102 	/* either this, or the server is printing bad messages,
103 	   or the caller passed in garbage */
104 	return(KRB5KRB_AP_ERR_MODIFIED);
105 
106     ptr = packet->data;
107 
108     /* verify length */
109 
110     plen = (*ptr++ & 0xff);
111     plen = (plen<<8) | (*ptr++ & 0xff);
112 
113     if (plen != packet->length)
114 	{
115 		/*
116 		 * MS KDCs *may* send back a KRB_ERROR.  Although
117 		 * not 100% correct via RFC3244, it's something
118 		 * we can workaround here.
119 		 */
120 		if (krb5_is_krb_error(packet)) {
121 
122 			if ((ret = krb5_rd_error(context, packet, &krberror)))
123 			return(ret);
124 
125 			if (krberror->e_data.data  == NULL) {
126 				ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code) krberror->error;
127 				krb5_free_error(context, krberror);
128 				return (ret);
129 			}
130 		}
131 		else
132 		{
133 			return(KRB5KRB_AP_ERR_MODIFIED);
134 		}
135 	}
136 
137 
138     /* verify version number */
139 
140     vno = (*ptr++ & 0xff);
141     vno = (vno<<8) | (*ptr++ & 0xff);
142 
143 	/*
144 	 * when the servers update to v2 of the protocol,
145 	 * "2" will be a valid version number here
146 	 */
147 	if (vno != 1 && vno != 2)
148 		return (KRB5KDC_ERR_BAD_PVNO);
149 
150     /* read, check ap-rep length */
151 
152     ap_rep.length = (*ptr++ & 0xff);
153     ap_rep.length = (ap_rep.length<<8) | (*ptr++ & 0xff);
154 
155     if (ptr + ap_rep.length >= packet->data + packet->length)
156 	return(KRB5KRB_AP_ERR_MODIFIED);
157 
158     if (ap_rep.length) {
159 	/* verify ap_rep */
160 	ap_rep.data = ptr;
161 	ptr += ap_rep.length;
162 
163 	/*
164 	 * Save send_subkey to later smash recv_subkey.
165 	 */
166 	ret = krb5_auth_con_getsendsubkey(context, auth_context, &tmp);
167 	if (ret)
168 	    return ret;
169 
170 	ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
171 	if (ret) {
172 	    krb5_free_keyblock(context, tmp);
173 	    return(ret);
174 	}
175 
176 	krb5_free_ap_rep_enc_part(context, ap_rep_enc);
177 
178 	/* extract and decrypt the result */
179 
180 	cipherresult.data = ptr;
181 	cipherresult.length = (packet->data + packet->length) - ptr;
182 
183 	/*
184 	 * Smash recv_subkey to be send_subkey, per spec.
185 	 */
186 	ret = krb5_auth_con_setrecvsubkey(context, auth_context, tmp);
187 	krb5_free_keyblock(context, tmp);
188 	if (ret)
189 	    return ret;
190 
191 	ret = krb5_rd_priv(context, auth_context, &cipherresult, &clearresult,
192 			   &replay);
193 
194 	if (ret)
195 	    return(ret);
196     } else {
197 	cipherresult.data = ptr;
198 	cipherresult.length = (packet->data + packet->length) - ptr;
199 
200 	if ((ret = krb5_rd_error(context, &cipherresult, &krberror)))
201 	    return(ret);
202 
203 	clearresult = krberror->e_data;
204     }
205 
206     if (clearresult.length < 2) {
207 	ret = KRB5KRB_AP_ERR_MODIFIED;
208 	goto cleanup;
209     }
210 
211     ptr = clearresult.data;
212 
213     local_result_code = (*ptr++ & 0xff);
214     local_result_code = (local_result_code<<8) | (*ptr++ & 0xff);
215 
216 	if (result_code)
217 		*result_code = local_result_code;
218 
219 	/*
220 	 * Make sure the result code is in range for this
221 	 * protocol.
222 	 */
223     if ((local_result_code < KRB5_KPASSWD_SUCCESS) ||
224 	 (local_result_code > KRB5_KPASSWD_ETYPE_NOSUPP)) {
225 	ret = KRB5KRB_AP_ERR_MODIFIED;
226 	goto cleanup;
227     }
228 
229     /* all success replies should be authenticated/encrypted */
230 
231     if ((ap_rep.length == 0) && (local_result_code == KRB5_KPASSWD_SUCCESS)) {
232 	ret = KRB5KRB_AP_ERR_MODIFIED;
233 	goto cleanup;
234     }
235 
236     result_data->length = (clearresult.data + clearresult.length) - ptr;
237 
238     if (result_data->length) {
239 	result_data->data = (char *) malloc(result_data->length);
240 	if (result_data->data == NULL) {
241 	    ret = ENOMEM;
242 	    goto cleanup;
243 	}
244 	memcpy(result_data->data, ptr, result_data->length);
245     } else {
246 	result_data->data = NULL;
247     }
248 
249     ret = 0;
250 
251 cleanup:
252     if (ap_rep.length) {
253 	krb5_xfree(clearresult.data);
254     } else {
255 	krb5_free_error(context, krberror);
256     }
257 
258     return(ret);
259 }
260