Commit | Line | Data |
---|---|---|
14ae162c BF |
1 | #include <linux/types.h> |
2 | #include <linux/slab.h> | |
3 | #include <linux/jiffies.h> | |
4 | #include <linux/sunrpc/gss_krb5.h> | |
5 | #include <linux/random.h> | |
6 | #include <linux/pagemap.h> | |
7 | #include <asm/scatterlist.h> | |
8 | #include <linux/crypto.h> | |
9 | ||
10 | #ifdef RPC_DEBUG | |
11 | # define RPCDBG_FACILITY RPCDBG_AUTH | |
12 | #endif | |
13 | ||
14 | static inline int | |
15 | gss_krb5_padding(int blocksize, int length) | |
16 | { | |
17 | /* Most of the code is block-size independent but currently we | |
18 | * use only 8: */ | |
19 | BUG_ON(blocksize != 8); | |
20 | return 8 - (length & 7); | |
21 | } | |
22 | ||
23 | static inline void | |
24 | gss_krb5_add_padding(struct xdr_buf *buf, int offset, int blocksize) | |
25 | { | |
26 | int padding = gss_krb5_padding(blocksize, buf->len - offset); | |
27 | char *p; | |
28 | struct kvec *iov; | |
29 | ||
30 | if (buf->page_len || buf->tail[0].iov_len) | |
31 | iov = &buf->tail[0]; | |
32 | else | |
33 | iov = &buf->head[0]; | |
34 | p = iov->iov_base + iov->iov_len; | |
35 | iov->iov_len += padding; | |
36 | buf->len += padding; | |
37 | memset(p, padding, padding); | |
38 | } | |
39 | ||
40 | static inline int | |
41 | gss_krb5_remove_padding(struct xdr_buf *buf, int blocksize) | |
42 | { | |
43 | u8 *ptr; | |
44 | u8 pad; | |
45 | int len = buf->len; | |
46 | ||
47 | if (len <= buf->head[0].iov_len) { | |
48 | pad = *(u8 *)(buf->head[0].iov_base + len - 1); | |
49 | if (pad > buf->head[0].iov_len) | |
50 | return -EINVAL; | |
51 | buf->head[0].iov_len -= pad; | |
52 | goto out; | |
53 | } else | |
54 | len -= buf->head[0].iov_len; | |
55 | if (len <= buf->page_len) { | |
56 | int last = (buf->page_base + len - 1) | |
57 | >>PAGE_CACHE_SHIFT; | |
58 | int offset = (buf->page_base + len - 1) | |
59 | & (PAGE_CACHE_SIZE - 1); | |
60 | ptr = kmap_atomic(buf->pages[last], KM_SKB_SUNRPC_DATA); | |
61 | pad = *(ptr + offset); | |
62 | kunmap_atomic(ptr, KM_SKB_SUNRPC_DATA); | |
63 | goto out; | |
64 | } else | |
65 | len -= buf->page_len; | |
66 | BUG_ON(len > buf->tail[0].iov_len); | |
67 | pad = *(u8 *)(buf->tail[0].iov_base + len - 1); | |
68 | out: | |
69 | /* XXX: NOTE: we do not adjust the page lengths--they represent | |
70 | * a range of data in the real filesystem page cache, and we need | |
71 | * to know that range so the xdr code can properly place read data. | |
72 | * However adjusting the head length, as we do above, is harmless. | |
73 | * In the case of a request that fits into a single page, the server | |
74 | * also uses length and head length together to determine the original | |
75 | * start of the request to copy the request for deferal; so it's | |
76 | * easier on the server if we adjust head and tail length in tandem. | |
77 | * It's not really a problem that we don't fool with the page and | |
78 | * tail lengths, though--at worst badly formed xdr might lead the | |
79 | * server to attempt to parse the padding. | |
80 | * XXX: Document all these weird requirements for gss mechanism | |
81 | * wrap/unwrap functions. */ | |
82 | if (pad > blocksize) | |
83 | return -EINVAL; | |
84 | if (buf->len > pad) | |
85 | buf->len -= pad; | |
86 | else | |
87 | return -EINVAL; | |
88 | return 0; | |
89 | } | |
90 | ||
91 | static inline void | |
92 | make_confounder(char *p, int blocksize) | |
93 | { | |
94 | static u64 i = 0; | |
95 | u64 *q = (u64 *)p; | |
96 | ||
97 | /* rfc1964 claims this should be "random". But all that's really | |
98 | * necessary is that it be unique. And not even that is necessary in | |
99 | * our case since our "gssapi" implementation exists only to support | |
100 | * rpcsec_gss, so we know that the only buffers we will ever encrypt | |
101 | * already begin with a unique sequence number. Just to hedge my bets | |
102 | * I'll make a half-hearted attempt at something unique, but ensuring | |
103 | * uniqueness would mean worrying about atomicity and rollover, and I | |
104 | * don't care enough. */ | |
105 | ||
106 | BUG_ON(blocksize != 8); | |
107 | *q = i++; | |
108 | } | |
109 | ||
110 | /* Assumptions: the head and tail of inbuf are ours to play with. | |
111 | * The pages, however, may be real pages in the page cache and we replace | |
112 | * them with scratch pages from **pages before writing to them. */ | |
113 | /* XXX: obviously the above should be documentation of wrap interface, | |
114 | * and shouldn't be in this kerberos-specific file. */ | |
115 | ||
116 | /* XXX factor out common code with seal/unseal. */ | |
117 | ||
118 | u32 | |
00fd6e14 | 119 | gss_wrap_kerberos(struct gss_ctx *ctx, int offset, |
14ae162c BF |
120 | struct xdr_buf *buf, struct page **pages) |
121 | { | |
122 | struct krb5_ctx *kctx = ctx->internal_ctx_id; | |
123 | s32 checksum_type; | |
9e57b302 BF |
124 | char cksumdata[16]; |
125 | struct xdr_netobj md5cksum = {.len = 0, .data = cksumdata}; | |
14ae162c BF |
126 | int blocksize = 0, plainlen; |
127 | unsigned char *ptr, *krb5_hdr, *msg_start; | |
128 | s32 now; | |
129 | int headlen; | |
130 | struct page **tmp_pages; | |
eaa82edf | 131 | u32 seq_send; |
14ae162c BF |
132 | |
133 | dprintk("RPC: gss_wrap_kerberos\n"); | |
134 | ||
135 | now = get_seconds(); | |
136 | ||
14ae162c BF |
137 | switch (kctx->signalg) { |
138 | case SGN_ALG_DES_MAC_MD5: | |
139 | checksum_type = CKSUMTYPE_RSA_MD5; | |
140 | break; | |
141 | default: | |
142 | dprintk("RPC: gss_krb5_seal: kctx->signalg %d not" | |
143 | " supported\n", kctx->signalg); | |
144 | goto out_err; | |
145 | } | |
146 | if (kctx->sealalg != SEAL_ALG_NONE && kctx->sealalg != SEAL_ALG_DES) { | |
147 | dprintk("RPC: gss_krb5_seal: kctx->sealalg %d not supported\n", | |
148 | kctx->sealalg); | |
149 | goto out_err; | |
150 | } | |
151 | ||
152 | blocksize = crypto_tfm_alg_blocksize(kctx->enc); | |
153 | gss_krb5_add_padding(buf, offset, blocksize); | |
154 | BUG_ON((buf->len - offset) % blocksize); | |
155 | plainlen = blocksize + buf->len - offset; | |
156 | ||
157 | headlen = g_token_size(&kctx->mech_used, 22 + plainlen) - | |
158 | (buf->len - offset); | |
159 | ||
160 | ptr = buf->head[0].iov_base + offset; | |
161 | /* shift data to make room for header. */ | |
162 | /* XXX Would be cleverer to encrypt while copying. */ | |
163 | /* XXX bounds checking, slack, etc. */ | |
164 | memmove(ptr + headlen, ptr, buf->head[0].iov_len - offset); | |
165 | buf->head[0].iov_len += headlen; | |
166 | buf->len += headlen; | |
167 | BUG_ON((buf->len - offset - headlen) % blocksize); | |
168 | ||
169 | g_make_token_header(&kctx->mech_used, 22 + plainlen, &ptr); | |
170 | ||
171 | ||
172 | *ptr++ = (unsigned char) ((KG_TOK_WRAP_MSG>>8)&0xff); | |
173 | *ptr++ = (unsigned char) (KG_TOK_WRAP_MSG&0xff); | |
174 | ||
175 | /* ptr now at byte 2 of header described in rfc 1964, section 1.2.1: */ | |
176 | krb5_hdr = ptr - 2; | |
177 | msg_start = krb5_hdr + 24; | |
178 | /* XXXJBF: */ BUG_ON(buf->head[0].iov_base + offset + headlen != msg_start + blocksize); | |
179 | ||
180 | *(u16 *)(krb5_hdr + 2) = htons(kctx->signalg); | |
181 | memset(krb5_hdr + 4, 0xff, 4); | |
182 | *(u16 *)(krb5_hdr + 4) = htons(kctx->sealalg); | |
183 | ||
184 | make_confounder(msg_start, blocksize); | |
185 | ||
186 | /* XXXJBF: UGH!: */ | |
187 | tmp_pages = buf->pages; | |
188 | buf->pages = pages; | |
189 | if (make_checksum(checksum_type, krb5_hdr, 8, buf, | |
190 | offset + headlen - blocksize, &md5cksum)) | |
191 | goto out_err; | |
192 | buf->pages = tmp_pages; | |
193 | ||
194 | switch (kctx->signalg) { | |
195 | case SGN_ALG_DES_MAC_MD5: | |
196 | if (krb5_encrypt(kctx->seq, NULL, md5cksum.data, | |
197 | md5cksum.data, md5cksum.len)) | |
198 | goto out_err; | |
199 | memcpy(krb5_hdr + 16, | |
200 | md5cksum.data + md5cksum.len - KRB5_CKSUM_LENGTH, | |
201 | KRB5_CKSUM_LENGTH); | |
202 | ||
203 | dprintk("RPC: make_seal_token: cksum data: \n"); | |
204 | print_hexl((u32 *) (krb5_hdr + 16), KRB5_CKSUM_LENGTH, 0); | |
205 | break; | |
206 | default: | |
207 | BUG(); | |
208 | } | |
209 | ||
eaa82edf BF |
210 | spin_lock(&krb5_seq_lock); |
211 | seq_send = kctx->seq_send++; | |
212 | spin_unlock(&krb5_seq_lock); | |
213 | ||
14ae162c BF |
214 | /* XXX would probably be more efficient to compute checksum |
215 | * and encrypt at the same time: */ | |
216 | if ((krb5_make_seq_num(kctx->seq, kctx->initiate ? 0 : 0xff, | |
eaa82edf | 217 | seq_send, krb5_hdr + 16, krb5_hdr + 8))) |
14ae162c BF |
218 | goto out_err; |
219 | ||
220 | if (gss_encrypt_xdr_buf(kctx->enc, buf, offset + headlen - blocksize, | |
221 | pages)) | |
222 | goto out_err; | |
223 | ||
14ae162c BF |
224 | return ((kctx->endtime < now) ? GSS_S_CONTEXT_EXPIRED : GSS_S_COMPLETE); |
225 | out_err: | |
14ae162c BF |
226 | return GSS_S_FAILURE; |
227 | } | |
228 | ||
229 | u32 | |
00fd6e14 | 230 | gss_unwrap_kerberos(struct gss_ctx *ctx, int offset, struct xdr_buf *buf) |
14ae162c BF |
231 | { |
232 | struct krb5_ctx *kctx = ctx->internal_ctx_id; | |
233 | int signalg; | |
234 | int sealalg; | |
235 | s32 checksum_type; | |
9e57b302 BF |
236 | char cksumdata[16]; |
237 | struct xdr_netobj md5cksum = {.len = 0, .data = cksumdata}; | |
14ae162c BF |
238 | s32 now; |
239 | int direction; | |
240 | s32 seqnum; | |
241 | unsigned char *ptr; | |
242 | int bodysize; | |
243 | u32 ret = GSS_S_DEFECTIVE_TOKEN; | |
244 | void *data_start, *orig_start; | |
245 | int data_len; | |
246 | int blocksize; | |
247 | ||
248 | dprintk("RPC: gss_unwrap_kerberos\n"); | |
249 | ||
250 | ptr = (u8 *)buf->head[0].iov_base + offset; | |
251 | if (g_verify_token_header(&kctx->mech_used, &bodysize, &ptr, | |
252 | buf->len - offset)) | |
253 | goto out; | |
254 | ||
255 | if ((*ptr++ != ((KG_TOK_WRAP_MSG>>8)&0xff)) || | |
256 | (*ptr++ != (KG_TOK_WRAP_MSG &0xff)) ) | |
257 | goto out; | |
258 | ||
259 | /* XXX sanity-check bodysize?? */ | |
260 | ||
261 | /* get the sign and seal algorithms */ | |
262 | ||
263 | signalg = ptr[0] + (ptr[1] << 8); | |
264 | sealalg = ptr[2] + (ptr[3] << 8); | |
265 | ||
266 | /* Sanity checks */ | |
267 | ||
268 | if ((ptr[4] != 0xff) || (ptr[5] != 0xff)) | |
269 | goto out; | |
270 | ||
271 | if (sealalg == 0xffff) | |
272 | goto out; | |
273 | ||
274 | /* in the current spec, there is only one valid seal algorithm per | |
275 | key type, so a simple comparison is ok */ | |
276 | ||
277 | if (sealalg != kctx->sealalg) | |
278 | goto out; | |
279 | ||
280 | /* there are several mappings of seal algorithms to sign algorithms, | |
281 | but few enough that we can try them all. */ | |
282 | ||
283 | if ((kctx->sealalg == SEAL_ALG_NONE && signalg > 1) || | |
284 | (kctx->sealalg == SEAL_ALG_1 && signalg != SGN_ALG_3) || | |
285 | (kctx->sealalg == SEAL_ALG_DES3KD && | |
286 | signalg != SGN_ALG_HMAC_SHA1_DES3_KD)) | |
287 | goto out; | |
288 | ||
289 | if (gss_decrypt_xdr_buf(kctx->enc, buf, | |
290 | ptr + 22 - (unsigned char *)buf->head[0].iov_base)) | |
291 | goto out; | |
292 | ||
293 | /* compute the checksum of the message */ | |
294 | ||
295 | /* initialize the the cksum */ | |
296 | switch (signalg) { | |
297 | case SGN_ALG_DES_MAC_MD5: | |
298 | checksum_type = CKSUMTYPE_RSA_MD5; | |
299 | break; | |
300 | default: | |
301 | ret = GSS_S_DEFECTIVE_TOKEN; | |
302 | goto out; | |
303 | } | |
304 | ||
305 | switch (signalg) { | |
306 | case SGN_ALG_DES_MAC_MD5: | |
307 | ret = make_checksum(checksum_type, ptr - 2, 8, buf, | |
308 | ptr + 22 - (unsigned char *)buf->head[0].iov_base, &md5cksum); | |
309 | if (ret) | |
310 | goto out; | |
311 | ||
312 | ret = krb5_encrypt(kctx->seq, NULL, md5cksum.data, | |
313 | md5cksum.data, md5cksum.len); | |
314 | if (ret) | |
315 | goto out; | |
316 | ||
317 | if (memcmp(md5cksum.data + 8, ptr + 14, 8)) { | |
318 | ret = GSS_S_BAD_SIG; | |
319 | goto out; | |
320 | } | |
321 | break; | |
322 | default: | |
323 | ret = GSS_S_DEFECTIVE_TOKEN; | |
324 | goto out; | |
325 | } | |
326 | ||
327 | /* it got through unscathed. Make sure the context is unexpired */ | |
328 | ||
14ae162c BF |
329 | now = get_seconds(); |
330 | ||
331 | ret = GSS_S_CONTEXT_EXPIRED; | |
332 | if (now > kctx->endtime) | |
333 | goto out; | |
334 | ||
335 | /* do sequencing checks */ | |
336 | ||
337 | ret = GSS_S_BAD_SIG; | |
338 | if ((ret = krb5_get_seq_num(kctx->seq, ptr + 14, ptr + 6, &direction, | |
339 | &seqnum))) | |
340 | goto out; | |
341 | ||
342 | if ((kctx->initiate && direction != 0xff) || | |
343 | (!kctx->initiate && direction != 0)) | |
344 | goto out; | |
345 | ||
346 | /* Copy the data back to the right position. XXX: Would probably be | |
347 | * better to copy and encrypt at the same time. */ | |
348 | ||
349 | blocksize = crypto_tfm_alg_blocksize(kctx->enc); | |
350 | data_start = ptr + 22 + blocksize; | |
351 | orig_start = buf->head[0].iov_base + offset; | |
352 | data_len = (buf->head[0].iov_base + buf->head[0].iov_len) - data_start; | |
353 | memmove(orig_start, data_start, data_len); | |
354 | buf->head[0].iov_len -= (data_start - orig_start); | |
355 | buf->len -= (data_start - orig_start); | |
356 | ||
357 | ret = GSS_S_DEFECTIVE_TOKEN; | |
358 | if (gss_krb5_remove_padding(buf, blocksize)) | |
359 | goto out; | |
360 | ||
361 | ret = GSS_S_COMPLETE; | |
362 | out: | |
14ae162c BF |
363 | return ret; |
364 | } |