Merge nommu branch
[pandora-kernel.git] / net / sunrpc / auth_gss / gss_krb5_crypto.c
1 /*
2  *  linux/net/sunrpc/gss_krb5_crypto.c
3  *
4  *  Copyright (c) 2000 The Regents of the University of Michigan.
5  *  All rights reserved.
6  *
7  *  Andy Adamson   <andros@umich.edu>
8  *  Bruce Fields   <bfields@umich.edu>
9  */
10
11 /*
12  * Copyright (C) 1998 by the FundsXpress, INC.
13  *
14  * All rights reserved.
15  *
16  * Export of this software from the United States of America may require
17  * a specific license from the United States Government.  It is the
18  * responsibility of any person or organization contemplating export to
19  * obtain such a license before exporting.
20  *
21  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
22  * distribute this software and its documentation for any purpose and
23  * without fee is hereby granted, provided that the above copyright
24  * notice appear in all copies and that both that copyright notice and
25  * this permission notice appear in supporting documentation, and that
26  * the name of FundsXpress. not be used in advertising or publicity pertaining
27  * to distribution of the software without specific, written prior
28  * permission.  FundsXpress makes no representations about the suitability of
29  * this software for any purpose.  It is provided "as is" without express
30  * or implied warranty.
31  *
32  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
33  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
34  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
35  */
36
37 #include <linux/err.h>
38 #include <linux/types.h>
39 #include <linux/mm.h>
40 #include <linux/slab.h>
41 #include <linux/scatterlist.h>
42 #include <linux/crypto.h>
43 #include <linux/highmem.h>
44 #include <linux/pagemap.h>
45 #include <linux/sunrpc/gss_krb5.h>
46
47 #ifdef RPC_DEBUG
48 # define RPCDBG_FACILITY        RPCDBG_AUTH
49 #endif
50
51 u32
52 krb5_encrypt(
53         struct crypto_blkcipher *tfm,
54         void * iv,
55         void * in,
56         void * out,
57         int length)
58 {
59         u32 ret = -EINVAL;
60         struct scatterlist sg[1];
61         u8 local_iv[16] = {0};
62         struct blkcipher_desc desc = { .tfm = tfm, .info = local_iv };
63
64         dprintk("RPC:      krb5_encrypt: input data:\n");
65         print_hexl((u32 *)in, length, 0);
66
67         if (length % crypto_blkcipher_blocksize(tfm) != 0)
68                 goto out;
69
70         if (crypto_blkcipher_ivsize(tfm) > 16) {
71                 dprintk("RPC:      gss_k5encrypt: tfm iv size to large %d\n",
72                          crypto_blkcipher_ivsize(tfm));
73                 goto out;
74         }
75
76         if (iv)
77                 memcpy(local_iv, iv, crypto_blkcipher_ivsize(tfm));
78
79         memcpy(out, in, length);
80         sg_set_buf(sg, out, length);
81
82         ret = crypto_blkcipher_encrypt_iv(&desc, sg, sg, length);
83
84         dprintk("RPC:      krb5_encrypt: output data:\n");
85         print_hexl((u32 *)out, length, 0);
86 out:
87         dprintk("RPC:      krb5_encrypt returns %d\n",ret);
88         return(ret);
89 }
90
91 EXPORT_SYMBOL(krb5_encrypt);
92
93 u32
94 krb5_decrypt(
95      struct crypto_blkcipher *tfm,
96      void * iv,
97      void * in,
98      void * out,
99      int length)
100 {
101         u32 ret = -EINVAL;
102         struct scatterlist sg[1];
103         u8 local_iv[16] = {0};
104         struct blkcipher_desc desc = { .tfm = tfm, .info = local_iv };
105
106         dprintk("RPC:      krb5_decrypt: input data:\n");
107         print_hexl((u32 *)in, length, 0);
108
109         if (length % crypto_blkcipher_blocksize(tfm) != 0)
110                 goto out;
111
112         if (crypto_blkcipher_ivsize(tfm) > 16) {
113                 dprintk("RPC:      gss_k5decrypt: tfm iv size to large %d\n",
114                         crypto_blkcipher_ivsize(tfm));
115                 goto out;
116         }
117         if (iv)
118                 memcpy(local_iv,iv, crypto_blkcipher_ivsize(tfm));
119
120         memcpy(out, in, length);
121         sg_set_buf(sg, out, length);
122
123         ret = crypto_blkcipher_decrypt_iv(&desc, sg, sg, length);
124
125         dprintk("RPC:      krb5_decrypt: output_data:\n");
126         print_hexl((u32 *)out, length, 0);
127 out:
128         dprintk("RPC:      gss_k5decrypt returns %d\n",ret);
129         return(ret);
130 }
131
132 EXPORT_SYMBOL(krb5_decrypt);
133
134 static int
135 process_xdr_buf(struct xdr_buf *buf, int offset, int len,
136                 int (*actor)(struct scatterlist *, void *), void *data)
137 {
138         int i, page_len, thislen, page_offset, ret = 0;
139         struct scatterlist      sg[1];
140
141         if (offset >= buf->head[0].iov_len) {
142                 offset -= buf->head[0].iov_len;
143         } else {
144                 thislen = buf->head[0].iov_len - offset;
145                 if (thislen > len)
146                         thislen = len;
147                 sg_set_buf(sg, buf->head[0].iov_base + offset, thislen);
148                 ret = actor(sg, data);
149                 if (ret)
150                         goto out;
151                 offset = 0;
152                 len -= thislen;
153         }
154         if (len == 0)
155                 goto out;
156
157         if (offset >= buf->page_len) {
158                 offset -= buf->page_len;
159         } else {
160                 page_len = buf->page_len - offset;
161                 if (page_len > len)
162                         page_len = len;
163                 len -= page_len;
164                 page_offset = (offset + buf->page_base) & (PAGE_CACHE_SIZE - 1);
165                 i = (offset + buf->page_base) >> PAGE_CACHE_SHIFT;
166                 thislen = PAGE_CACHE_SIZE - page_offset;
167                 do {
168                         if (thislen > page_len)
169                                 thislen = page_len;
170                         sg->page = buf->pages[i];
171                         sg->offset = page_offset;
172                         sg->length = thislen;
173                         ret = actor(sg, data);
174                         if (ret)
175                                 goto out;
176                         page_len -= thislen;
177                         i++;
178                         page_offset = 0;
179                         thislen = PAGE_CACHE_SIZE;
180                 } while (page_len != 0);
181                 offset = 0;
182         }
183         if (len == 0)
184                 goto out;
185
186         if (offset < buf->tail[0].iov_len) {
187                 thislen = buf->tail[0].iov_len - offset;
188                 if (thislen > len)
189                         thislen = len;
190                 sg_set_buf(sg, buf->tail[0].iov_base + offset, thislen);
191                 ret = actor(sg, data);
192                 len -= thislen;
193         }
194         if (len != 0)
195                 ret = -EINVAL;
196 out:
197         return ret;
198 }
199
200 static int
201 checksummer(struct scatterlist *sg, void *data)
202 {
203         struct hash_desc *desc = data;
204
205         return crypto_hash_update(desc, sg, sg->length);
206 }
207
208 /* checksum the plaintext data and hdrlen bytes of the token header */
209 s32
210 make_checksum(s32 cksumtype, char *header, int hdrlen, struct xdr_buf *body,
211                    int body_offset, struct xdr_netobj *cksum)
212 {
213         char                            *cksumname;
214         struct hash_desc                desc; /* XXX add to ctx? */
215         struct scatterlist              sg[1];
216         int err;
217
218         switch (cksumtype) {
219                 case CKSUMTYPE_RSA_MD5:
220                         cksumname = "md5";
221                         break;
222                 default:
223                         dprintk("RPC:      krb5_make_checksum:"
224                                 " unsupported checksum %d", cksumtype);
225                         return GSS_S_FAILURE;
226         }
227         desc.tfm = crypto_alloc_hash(cksumname, 0, CRYPTO_ALG_ASYNC);
228         if (IS_ERR(desc.tfm))
229                 return GSS_S_FAILURE;
230         cksum->len = crypto_hash_digestsize(desc.tfm);
231         desc.flags = CRYPTO_TFM_REQ_MAY_SLEEP;
232
233         err = crypto_hash_init(&desc);
234         if (err)
235                 goto out;
236         sg_set_buf(sg, header, hdrlen);
237         err = crypto_hash_update(&desc, sg, hdrlen);
238         if (err)
239                 goto out;
240         err = process_xdr_buf(body, body_offset, body->len - body_offset,
241                               checksummer, &desc);
242         if (err)
243                 goto out;
244         err = crypto_hash_final(&desc, cksum->data);
245
246 out:
247         crypto_free_hash(desc.tfm);
248         return err ? GSS_S_FAILURE : 0;
249 }
250
251 EXPORT_SYMBOL(make_checksum);
252
253 struct encryptor_desc {
254         u8 iv[8]; /* XXX hard-coded blocksize */
255         struct blkcipher_desc desc;
256         int pos;
257         struct xdr_buf *outbuf;
258         struct page **pages;
259         struct scatterlist infrags[4];
260         struct scatterlist outfrags[4];
261         int fragno;
262         int fraglen;
263 };
264
265 static int
266 encryptor(struct scatterlist *sg, void *data)
267 {
268         struct encryptor_desc *desc = data;
269         struct xdr_buf *outbuf = desc->outbuf;
270         struct page *in_page;
271         int thislen = desc->fraglen + sg->length;
272         int fraglen, ret;
273         int page_pos;
274
275         /* Worst case is 4 fragments: head, end of page 1, start
276          * of page 2, tail.  Anything more is a bug. */
277         BUG_ON(desc->fragno > 3);
278         desc->infrags[desc->fragno] = *sg;
279         desc->outfrags[desc->fragno] = *sg;
280
281         page_pos = desc->pos - outbuf->head[0].iov_len;
282         if (page_pos >= 0 && page_pos < outbuf->page_len) {
283                 /* pages are not in place: */
284                 int i = (page_pos + outbuf->page_base) >> PAGE_CACHE_SHIFT;
285                 in_page = desc->pages[i];
286         } else {
287                 in_page = sg->page;
288         }
289         desc->infrags[desc->fragno].page = in_page;
290         desc->fragno++;
291         desc->fraglen += sg->length;
292         desc->pos += sg->length;
293
294         fraglen = thislen & 7; /* XXX hardcoded blocksize */
295         thislen -= fraglen;
296
297         if (thislen == 0)
298                 return 0;
299
300         ret = crypto_blkcipher_encrypt_iv(&desc->desc, desc->outfrags,
301                                           desc->infrags, thislen);
302         if (ret)
303                 return ret;
304         if (fraglen) {
305                 desc->outfrags[0].page = sg->page;
306                 desc->outfrags[0].offset = sg->offset + sg->length - fraglen;
307                 desc->outfrags[0].length = fraglen;
308                 desc->infrags[0] = desc->outfrags[0];
309                 desc->infrags[0].page = in_page;
310                 desc->fragno = 1;
311                 desc->fraglen = fraglen;
312         } else {
313                 desc->fragno = 0;
314                 desc->fraglen = 0;
315         }
316         return 0;
317 }
318
319 int
320 gss_encrypt_xdr_buf(struct crypto_blkcipher *tfm, struct xdr_buf *buf,
321                     int offset, struct page **pages)
322 {
323         int ret;
324         struct encryptor_desc desc;
325
326         BUG_ON((buf->len - offset) % crypto_blkcipher_blocksize(tfm) != 0);
327
328         memset(desc.iv, 0, sizeof(desc.iv));
329         desc.desc.tfm = tfm;
330         desc.desc.info = desc.iv;
331         desc.desc.flags = 0;
332         desc.pos = offset;
333         desc.outbuf = buf;
334         desc.pages = pages;
335         desc.fragno = 0;
336         desc.fraglen = 0;
337
338         ret = process_xdr_buf(buf, offset, buf->len - offset, encryptor, &desc);
339         return ret;
340 }
341
342 EXPORT_SYMBOL(gss_encrypt_xdr_buf);
343
344 struct decryptor_desc {
345         u8 iv[8]; /* XXX hard-coded blocksize */
346         struct blkcipher_desc desc;
347         struct scatterlist frags[4];
348         int fragno;
349         int fraglen;
350 };
351
352 static int
353 decryptor(struct scatterlist *sg, void *data)
354 {
355         struct decryptor_desc *desc = data;
356         int thislen = desc->fraglen + sg->length;
357         int fraglen, ret;
358
359         /* Worst case is 4 fragments: head, end of page 1, start
360          * of page 2, tail.  Anything more is a bug. */
361         BUG_ON(desc->fragno > 3);
362         desc->frags[desc->fragno] = *sg;
363         desc->fragno++;
364         desc->fraglen += sg->length;
365
366         fraglen = thislen & 7; /* XXX hardcoded blocksize */
367         thislen -= fraglen;
368
369         if (thislen == 0)
370                 return 0;
371
372         ret = crypto_blkcipher_decrypt_iv(&desc->desc, desc->frags,
373                                           desc->frags, thislen);
374         if (ret)
375                 return ret;
376         if (fraglen) {
377                 desc->frags[0].page = sg->page;
378                 desc->frags[0].offset = sg->offset + sg->length - fraglen;
379                 desc->frags[0].length = fraglen;
380                 desc->fragno = 1;
381                 desc->fraglen = fraglen;
382         } else {
383                 desc->fragno = 0;
384                 desc->fraglen = 0;
385         }
386         return 0;
387 }
388
389 int
390 gss_decrypt_xdr_buf(struct crypto_blkcipher *tfm, struct xdr_buf *buf,
391                     int offset)
392 {
393         struct decryptor_desc desc;
394
395         /* XXXJBF: */
396         BUG_ON((buf->len - offset) % crypto_blkcipher_blocksize(tfm) != 0);
397
398         memset(desc.iv, 0, sizeof(desc.iv));
399         desc.desc.tfm = tfm;
400         desc.desc.info = desc.iv;
401         desc.desc.flags = 0;
402         desc.fragno = 0;
403         desc.fraglen = 0;
404         return process_xdr_buf(buf, offset, buf->len - offset, decryptor, &desc);
405 }
406
407 EXPORT_SYMBOL(gss_decrypt_xdr_buf);