RPCSEC_GSS: Add support for privacy to krb5 rpcsec_gss mechanism.
authorJ. Bruce Fields <bfields@fieldses.org>
Thu, 13 Oct 2005 20:55:13 +0000 (16:55 -0400)
committerTrond Myklebust <Trond.Myklebust@netapp.com>
Wed, 19 Oct 2005 06:19:46 +0000 (23:19 -0700)
 Add support for privacy to the krb5 rpcsec_gss mechanism.

Signed-off-by: J. Bruce Fields <bfields@citi.umich.edu>
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
include/linux/sunrpc/gss_krb5.h
net/sunrpc/auth_gss/Makefile
net/sunrpc/auth_gss/gss_krb5_crypto.c
net/sunrpc/auth_gss/gss_krb5_mech.c
net/sunrpc/auth_gss/gss_krb5_seal.c
net/sunrpc/auth_gss/gss_krb5_unseal.c
net/sunrpc/auth_gss/gss_krb5_wrap.c [new file with mode: 0644]

index cb35833..7f93c2d 100644 (file)
@@ -116,7 +116,7 @@ enum seal_alg {
 
 s32
 make_checksum(s32 cksumtype, char *header, int hdrlen, struct xdr_buf *body,
-                  struct xdr_netobj *cksum);
+                  int body_offset, struct xdr_netobj *cksum);
 
 u32
 krb5_make_token(struct krb5_ctx *context_handle, int qop_req,
@@ -129,6 +129,15 @@ krb5_read_token(struct krb5_ctx *context_handle,
          struct xdr_buf *message_buffer, int *qop_state);
 
 u32
+gss_wrap_kerberos(struct gss_ctx *ctx_id, u32 qop, int offset,
+               struct xdr_buf *outbuf, struct page **pages);
+
+u32
+gss_unwrap_kerberos(struct gss_ctx *ctx_id, u32 *qop, int offset,
+               struct xdr_buf *buf);
+
+
+u32
 krb5_encrypt(struct crypto_tfm * key,
             void *iv, void *in, void *out, int length);
 
@@ -136,6 +145,13 @@ u32
 krb5_decrypt(struct crypto_tfm * key,
             void *iv, void *in, void *out, int length); 
 
+int
+gss_encrypt_xdr_buf(struct crypto_tfm *tfm, struct xdr_buf *outbuf, int offset,
+               struct page **pages);
+
+int
+gss_decrypt_xdr_buf(struct crypto_tfm *tfm, struct xdr_buf *inbuf, int offset);
+
 s32
 krb5_make_seq_num(struct crypto_tfm * key,
                int direction,
index fe1b874..f3431a7 100644 (file)
@@ -10,7 +10,7 @@ auth_rpcgss-objs := auth_gss.o gss_generic_token.o \
 obj-$(CONFIG_RPCSEC_GSS_KRB5) += rpcsec_gss_krb5.o
 
 rpcsec_gss_krb5-objs := gss_krb5_mech.o gss_krb5_seal.o gss_krb5_unseal.o \
-       gss_krb5_seqnum.o
+       gss_krb5_seqnum.o gss_krb5_wrap.o
 
 obj-$(CONFIG_RPCSEC_GSS_SPKM3) += rpcsec_gss_spkm3.o
 
index 2baf93f..3f3d543 100644 (file)
@@ -218,7 +218,7 @@ checksummer(struct scatterlist *sg, void *data)
 /* checksum the plaintext data and hdrlen bytes of the token header */
 s32
 make_checksum(s32 cksumtype, char *header, int hdrlen, struct xdr_buf *body,
-                  struct xdr_netobj *cksum)
+                  int body_offset, struct xdr_netobj *cksum)
 {
        char                            *cksumname;
        struct crypto_tfm               *tfm = NULL; /* XXX add to ctx? */
@@ -243,7 +243,8 @@ make_checksum(s32 cksumtype, char *header, int hdrlen, struct xdr_buf *body,
        crypto_digest_init(tfm);
        buf_to_sg(sg, header, hdrlen);
        crypto_digest_update(tfm, sg, 1);
-       process_xdr_buf(body, 0, body->len, checksummer, tfm);
+       process_xdr_buf(body, body_offset, body->len - body_offset,
+                       checksummer, tfm);
        crypto_digest_final(tfm, cksum->data);
        code = 0;
 out:
@@ -252,3 +253,154 @@ out:
 }
 
 EXPORT_SYMBOL(make_checksum);
+
+struct encryptor_desc {
+       u8 iv[8]; /* XXX hard-coded blocksize */
+       struct crypto_tfm *tfm;
+       int pos;
+       struct xdr_buf *outbuf;
+       struct page **pages;
+       struct scatterlist infrags[4];
+       struct scatterlist outfrags[4];
+       int fragno;
+       int fraglen;
+};
+
+static int
+encryptor(struct scatterlist *sg, void *data)
+{
+       struct encryptor_desc *desc = data;
+       struct xdr_buf *outbuf = desc->outbuf;
+       struct page *in_page;
+       int thislen = desc->fraglen + sg->length;
+       int fraglen, ret;
+       int page_pos;
+
+       /* Worst case is 4 fragments: head, end of page 1, start
+        * of page 2, tail.  Anything more is a bug. */
+       BUG_ON(desc->fragno > 3);
+       desc->infrags[desc->fragno] = *sg;
+       desc->outfrags[desc->fragno] = *sg;
+
+       page_pos = desc->pos - outbuf->head[0].iov_len;
+       if (page_pos >= 0 && page_pos < outbuf->page_len) {
+               /* pages are not in place: */
+               int i = (page_pos + outbuf->page_base) >> PAGE_CACHE_SHIFT;
+               in_page = desc->pages[i];
+       } else {
+               in_page = sg->page;
+       }
+       desc->infrags[desc->fragno].page = in_page;
+       desc->fragno++;
+       desc->fraglen += sg->length;
+       desc->pos += sg->length;
+
+       fraglen = thislen & 7; /* XXX hardcoded blocksize */
+       thislen -= fraglen;
+
+       if (thislen == 0)
+               return 0;
+
+       ret = crypto_cipher_encrypt_iv(desc->tfm, desc->outfrags, desc->infrags,
+                                       thislen, desc->iv);
+       if (ret)
+               return ret;
+       if (fraglen) {
+               desc->outfrags[0].page = sg->page;
+               desc->outfrags[0].offset = sg->offset + sg->length - fraglen;
+               desc->outfrags[0].length = fraglen;
+               desc->infrags[0] = desc->outfrags[0];
+               desc->infrags[0].page = in_page;
+               desc->fragno = 1;
+               desc->fraglen = fraglen;
+       } else {
+               desc->fragno = 0;
+               desc->fraglen = 0;
+       }
+       return 0;
+}
+
+int
+gss_encrypt_xdr_buf(struct crypto_tfm *tfm, struct xdr_buf *buf, int offset,
+               struct page **pages)
+{
+       int ret;
+       struct encryptor_desc desc;
+
+       BUG_ON((buf->len - offset) % crypto_tfm_alg_blocksize(tfm) != 0);
+
+       memset(desc.iv, 0, sizeof(desc.iv));
+       desc.tfm = tfm;
+       desc.pos = offset;
+       desc.outbuf = buf;
+       desc.pages = pages;
+       desc.fragno = 0;
+       desc.fraglen = 0;
+
+       ret = process_xdr_buf(buf, offset, buf->len - offset, encryptor, &desc);
+       return ret;
+}
+
+EXPORT_SYMBOL(gss_encrypt_xdr_buf);
+
+struct decryptor_desc {
+       u8 iv[8]; /* XXX hard-coded blocksize */
+       struct crypto_tfm *tfm;
+       struct scatterlist frags[4];
+       int fragno;
+       int fraglen;
+};
+
+static int
+decryptor(struct scatterlist *sg, void *data)
+{
+       struct decryptor_desc *desc = data;
+       int thislen = desc->fraglen + sg->length;
+       int fraglen, ret;
+
+       /* Worst case is 4 fragments: head, end of page 1, start
+        * of page 2, tail.  Anything more is a bug. */
+       BUG_ON(desc->fragno > 3);
+       desc->frags[desc->fragno] = *sg;
+       desc->fragno++;
+       desc->fraglen += sg->length;
+
+       fraglen = thislen & 7; /* XXX hardcoded blocksize */
+       thislen -= fraglen;
+
+       if (thislen == 0)
+               return 0;
+
+       ret = crypto_cipher_decrypt_iv(desc->tfm, desc->frags, desc->frags,
+                                       thislen, desc->iv);
+       if (ret)
+               return ret;
+       if (fraglen) {
+               desc->frags[0].page = sg->page;
+               desc->frags[0].offset = sg->offset + sg->length - fraglen;
+               desc->frags[0].length = fraglen;
+               desc->fragno = 1;
+               desc->fraglen = fraglen;
+       } else {
+               desc->fragno = 0;
+               desc->fraglen = 0;
+       }
+       return 0;
+}
+
+int
+gss_decrypt_xdr_buf(struct crypto_tfm *tfm, struct xdr_buf *buf, int offset)
+{
+       struct decryptor_desc desc;
+
+       /* XXXJBF: */
+       BUG_ON((buf->len - offset) % crypto_tfm_alg_blocksize(tfm) != 0);
+
+       memset(desc.iv, 0, sizeof(desc.iv));
+       desc.tfm = tfm;
+       desc.fragno = 0;
+       desc.fraglen = 0;
+       return process_xdr_buf(buf, offset, buf->len - offset, decryptor, &desc);
+}
+
+EXPORT_SYMBOL(gss_decrypt_xdr_buf);
index 8b9066f..37a9ad9 100644 (file)
@@ -226,6 +226,8 @@ static struct gss_api_ops gss_kerberos_ops = {
        .gss_import_sec_context = gss_import_sec_context_kerberos,
        .gss_get_mic            = gss_get_mic_kerberos,
        .gss_verify_mic         = gss_verify_mic_kerberos,
+       .gss_wrap               = gss_wrap_kerberos,
+       .gss_unwrap             = gss_unwrap_kerberos,
        .gss_delete_sec_context = gss_delete_sec_context_kerberos,
 };
 
@@ -240,6 +242,11 @@ static struct pf_desc gss_kerberos_pfs[] = {
                .service = RPC_GSS_SVC_INTEGRITY,
                .name = "krb5i",
        },
+       [2] = {
+               .pseudoflavor = RPC_AUTH_GSS_KRB5P,
+               .service = RPC_GSS_SVC_PRIVACY,
+               .name = "krb5p",
+       },
 };
 
 static struct gss_api_mech gss_kerberos_mech = {
index 2511834..fb852d9 100644 (file)
@@ -116,8 +116,8 @@ krb5_make_token(struct krb5_ctx *ctx, int qop_req,
        *(u16 *)(krb5_hdr + 2) = htons(ctx->signalg);
        memset(krb5_hdr + 4, 0xff, 4);
 
-       if (make_checksum(checksum_type, krb5_hdr, 8, text, &md5cksum))
-               goto out_err;
+       if (make_checksum(checksum_type, krb5_hdr, 8, text, 0, &md5cksum))
+                       goto out_err;
 
        switch (ctx->signalg) {
        case SGN_ALG_DES_MAC_MD5:
index 19eba3d..c3d6d1b 100644 (file)
@@ -136,7 +136,7 @@ krb5_read_token(struct krb5_ctx *ctx,
        switch (signalg) {
        case SGN_ALG_DES_MAC_MD5:
                ret = make_checksum(checksum_type, ptr - 2, 8,
-                                        message_buffer, &md5cksum);
+                                        message_buffer, 0, &md5cksum);
                if (ret)
                        goto out;
 
diff --git a/net/sunrpc/auth_gss/gss_krb5_wrap.c b/net/sunrpc/auth_gss/gss_krb5_wrap.c
new file mode 100644 (file)
index 0000000..ddcde6e
--- /dev/null
@@ -0,0 +1,370 @@
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/sunrpc/gss_krb5.h>
+#include <linux/random.h>
+#include <linux/pagemap.h>
+#include <asm/scatterlist.h>
+#include <linux/crypto.h>
+
+#ifdef RPC_DEBUG
+# define RPCDBG_FACILITY       RPCDBG_AUTH
+#endif
+
+static inline int
+gss_krb5_padding(int blocksize, int length)
+{
+       /* Most of the code is block-size independent but currently we
+        * use only 8: */
+       BUG_ON(blocksize != 8);
+       return 8 - (length & 7);
+}
+
+static inline void
+gss_krb5_add_padding(struct xdr_buf *buf, int offset, int blocksize)
+{
+       int padding = gss_krb5_padding(blocksize, buf->len - offset);
+       char *p;
+       struct kvec *iov;
+
+       if (buf->page_len || buf->tail[0].iov_len)
+               iov = &buf->tail[0];
+       else
+               iov = &buf->head[0];
+       p = iov->iov_base + iov->iov_len;
+       iov->iov_len += padding;
+       buf->len += padding;
+       memset(p, padding, padding);
+}
+
+static inline int
+gss_krb5_remove_padding(struct xdr_buf *buf, int blocksize)
+{
+       u8 *ptr;
+       u8 pad;
+       int len = buf->len;
+
+       if (len <= buf->head[0].iov_len) {
+               pad = *(u8 *)(buf->head[0].iov_base + len - 1);
+               if (pad > buf->head[0].iov_len)
+                       return -EINVAL;
+               buf->head[0].iov_len -= pad;
+               goto out;
+       } else
+               len -= buf->head[0].iov_len;
+       if (len <= buf->page_len) {
+               int last = (buf->page_base + len - 1)
+                                       >>PAGE_CACHE_SHIFT;
+               int offset = (buf->page_base + len - 1)
+                                       & (PAGE_CACHE_SIZE - 1);
+               ptr = kmap_atomic(buf->pages[last], KM_SKB_SUNRPC_DATA);
+               pad = *(ptr + offset);
+               kunmap_atomic(ptr, KM_SKB_SUNRPC_DATA);
+               goto out;
+       } else
+               len -= buf->page_len;
+       BUG_ON(len > buf->tail[0].iov_len);
+       pad = *(u8 *)(buf->tail[0].iov_base + len - 1);
+out:
+       /* XXX: NOTE: we do not adjust the page lengths--they represent
+        * a range of data in the real filesystem page cache, and we need
+        * to know that range so the xdr code can properly place read data.
+        * However adjusting the head length, as we do above, is harmless.
+        * In the case of a request that fits into a single page, the server
+        * also uses length and head length together to determine the original
+        * start of the request to copy the request for deferal; so it's
+        * easier on the server if we adjust head and tail length in tandem.
+        * It's not really a problem that we don't fool with the page and
+        * tail lengths, though--at worst badly formed xdr might lead the
+        * server to attempt to parse the padding.
+        * XXX: Document all these weird requirements for gss mechanism
+        * wrap/unwrap functions. */
+       if (pad > blocksize)
+               return -EINVAL;
+       if (buf->len > pad)
+               buf->len -= pad;
+       else
+               return -EINVAL;
+       return 0;
+}
+
+static inline void
+make_confounder(char *p, int blocksize)
+{
+       static u64 i = 0;
+       u64 *q = (u64 *)p;
+
+       /* rfc1964 claims this should be "random".  But all that's really
+        * necessary is that it be unique.  And not even that is necessary in
+        * our case since our "gssapi" implementation exists only to support
+        * rpcsec_gss, so we know that the only buffers we will ever encrypt
+        * already begin with a unique sequence number.  Just to hedge my bets
+        * I'll make a half-hearted attempt at something unique, but ensuring
+        * uniqueness would mean worrying about atomicity and rollover, and I
+        * don't care enough. */
+
+       BUG_ON(blocksize != 8);
+       *q = i++;
+}
+
+/* Assumptions: the head and tail of inbuf are ours to play with.
+ * The pages, however, may be real pages in the page cache and we replace
+ * them with scratch pages from **pages before writing to them. */
+/* XXX: obviously the above should be documentation of wrap interface,
+ * and shouldn't be in this kerberos-specific file. */
+
+/* XXX factor out common code with seal/unseal. */
+
+u32
+gss_wrap_kerberos(struct gss_ctx *ctx, u32 qop, int offset,
+               struct xdr_buf *buf, struct page **pages)
+{
+       struct krb5_ctx         *kctx = ctx->internal_ctx_id;
+       s32                     checksum_type;
+       struct xdr_netobj       md5cksum = {.len = 0, .data = NULL};
+       int                     blocksize = 0, plainlen;
+       unsigned char           *ptr, *krb5_hdr, *msg_start;
+       s32                     now;
+       int                     headlen;
+       struct page             **tmp_pages;
+
+       dprintk("RPC:     gss_wrap_kerberos\n");
+
+       now = get_seconds();
+
+       if (qop != 0)
+               goto out_err;
+
+       switch (kctx->signalg) {
+               case SGN_ALG_DES_MAC_MD5:
+                       checksum_type = CKSUMTYPE_RSA_MD5;
+                       break;
+               default:
+                       dprintk("RPC:      gss_krb5_seal: kctx->signalg %d not"
+                               " supported\n", kctx->signalg);
+                       goto out_err;
+       }
+       if (kctx->sealalg != SEAL_ALG_NONE && kctx->sealalg != SEAL_ALG_DES) {
+               dprintk("RPC:      gss_krb5_seal: kctx->sealalg %d not supported\n",
+                       kctx->sealalg);
+               goto out_err;
+       }
+
+       blocksize = crypto_tfm_alg_blocksize(kctx->enc);
+       gss_krb5_add_padding(buf, offset, blocksize);
+       BUG_ON((buf->len - offset) % blocksize);
+       plainlen = blocksize + buf->len - offset;
+
+       headlen = g_token_size(&kctx->mech_used, 22 + plainlen) -
+                                               (buf->len - offset);
+
+       ptr = buf->head[0].iov_base + offset;
+       /* shift data to make room for header. */
+       /* XXX Would be cleverer to encrypt while copying. */
+       /* XXX bounds checking, slack, etc. */
+       memmove(ptr + headlen, ptr, buf->head[0].iov_len - offset);
+       buf->head[0].iov_len += headlen;
+       buf->len += headlen;
+       BUG_ON((buf->len - offset - headlen) % blocksize);
+
+       g_make_token_header(&kctx->mech_used, 22 + plainlen, &ptr);
+
+
+       *ptr++ = (unsigned char) ((KG_TOK_WRAP_MSG>>8)&0xff);
+       *ptr++ = (unsigned char) (KG_TOK_WRAP_MSG&0xff);
+
+       /* ptr now at byte 2 of header described in rfc 1964, section 1.2.1: */
+       krb5_hdr = ptr - 2;
+       msg_start = krb5_hdr + 24;
+       /* XXXJBF: */ BUG_ON(buf->head[0].iov_base + offset + headlen != msg_start + blocksize);
+
+       *(u16 *)(krb5_hdr + 2) = htons(kctx->signalg);
+       memset(krb5_hdr + 4, 0xff, 4);
+       *(u16 *)(krb5_hdr + 4) = htons(kctx->sealalg);
+
+       make_confounder(msg_start, blocksize);
+
+       /* XXXJBF: UGH!: */
+       tmp_pages = buf->pages;
+       buf->pages = pages;
+       if (make_checksum(checksum_type, krb5_hdr, 8, buf,
+                               offset + headlen - blocksize, &md5cksum))
+               goto out_err;
+       buf->pages = tmp_pages;
+
+       switch (kctx->signalg) {
+       case SGN_ALG_DES_MAC_MD5:
+               if (krb5_encrypt(kctx->seq, NULL, md5cksum.data,
+                                 md5cksum.data, md5cksum.len))
+                       goto out_err;
+               memcpy(krb5_hdr + 16,
+                      md5cksum.data + md5cksum.len - KRB5_CKSUM_LENGTH,
+                      KRB5_CKSUM_LENGTH);
+
+               dprintk("RPC:      make_seal_token: cksum data: \n");
+               print_hexl((u32 *) (krb5_hdr + 16), KRB5_CKSUM_LENGTH, 0);
+               break;
+       default:
+               BUG();
+       }
+
+       kfree(md5cksum.data);
+
+       /* XXX would probably be more efficient to compute checksum
+        * and encrypt at the same time: */
+       if ((krb5_make_seq_num(kctx->seq, kctx->initiate ? 0 : 0xff,
+                              kctx->seq_send, krb5_hdr + 16, krb5_hdr + 8)))
+               goto out_err;
+
+       if (gss_encrypt_xdr_buf(kctx->enc, buf, offset + headlen - blocksize,
+                                                                       pages))
+               goto out_err;
+
+       kctx->seq_send++;
+
+       return ((kctx->endtime < now) ? GSS_S_CONTEXT_EXPIRED : GSS_S_COMPLETE);
+out_err:
+       if (md5cksum.data) kfree(md5cksum.data);
+       return GSS_S_FAILURE;
+}
+
+u32
+gss_unwrap_kerberos(struct gss_ctx *ctx, u32 *qop, int offset,
+                       struct xdr_buf *buf)
+{
+       struct krb5_ctx         *kctx = ctx->internal_ctx_id;
+       int                     signalg;
+       int                     sealalg;
+       s32                     checksum_type;
+       struct xdr_netobj       md5cksum = {.len = 0, .data = NULL};
+       s32                     now;
+       int                     direction;
+       s32                     seqnum;
+       unsigned char           *ptr;
+       int                     bodysize;
+       u32                     ret = GSS_S_DEFECTIVE_TOKEN;
+       void                    *data_start, *orig_start;
+       int                     data_len;
+       int                     blocksize;
+
+       dprintk("RPC:      gss_unwrap_kerberos\n");
+
+       ptr = (u8 *)buf->head[0].iov_base + offset;
+       if (g_verify_token_header(&kctx->mech_used, &bodysize, &ptr,
+                                       buf->len - offset))
+               goto out;
+
+       if ((*ptr++ != ((KG_TOK_WRAP_MSG>>8)&0xff)) ||
+           (*ptr++ !=  (KG_TOK_WRAP_MSG    &0xff))   )
+               goto out;
+
+       /* XXX sanity-check bodysize?? */
+
+       /* get the sign and seal algorithms */
+
+       signalg = ptr[0] + (ptr[1] << 8);
+       sealalg = ptr[2] + (ptr[3] << 8);
+
+       /* Sanity checks */
+
+       if ((ptr[4] != 0xff) || (ptr[5] != 0xff))
+               goto out;
+
+       if (sealalg == 0xffff)
+               goto out;
+
+       /* in the current spec, there is only one valid seal algorithm per
+          key type, so a simple comparison is ok */
+
+       if (sealalg != kctx->sealalg)
+               goto out;
+
+       /* there are several mappings of seal algorithms to sign algorithms,
+          but few enough that we can try them all. */
+
+       if ((kctx->sealalg == SEAL_ALG_NONE && signalg > 1) ||
+           (kctx->sealalg == SEAL_ALG_1 && signalg != SGN_ALG_3) ||
+           (kctx->sealalg == SEAL_ALG_DES3KD &&
+            signalg != SGN_ALG_HMAC_SHA1_DES3_KD))
+               goto out;
+
+       if (gss_decrypt_xdr_buf(kctx->enc, buf,
+                       ptr + 22 - (unsigned char *)buf->head[0].iov_base))
+               goto out;
+
+       /* compute the checksum of the message */
+
+       /* initialize the the cksum */
+       switch (signalg) {
+       case SGN_ALG_DES_MAC_MD5:
+               checksum_type = CKSUMTYPE_RSA_MD5;
+               break;
+       default:
+               ret = GSS_S_DEFECTIVE_TOKEN;
+               goto out;
+       }
+
+       switch (signalg) {
+       case SGN_ALG_DES_MAC_MD5:
+               ret = make_checksum(checksum_type, ptr - 2, 8, buf,
+                        ptr + 22 - (unsigned char *)buf->head[0].iov_base, &md5cksum);
+               if (ret)
+                       goto out;
+
+               ret = krb5_encrypt(kctx->seq, NULL, md5cksum.data,
+                                  md5cksum.data, md5cksum.len);
+               if (ret)
+                       goto out;
+
+               if (memcmp(md5cksum.data + 8, ptr + 14, 8)) {
+                       ret = GSS_S_BAD_SIG;
+                       goto out;
+               }
+               break;
+       default:
+               ret = GSS_S_DEFECTIVE_TOKEN;
+               goto out;
+       }
+
+       /* it got through unscathed.  Make sure the context is unexpired */
+
+       if (qop)
+               *qop = GSS_C_QOP_DEFAULT;
+
+       now = get_seconds();
+
+       ret = GSS_S_CONTEXT_EXPIRED;
+       if (now > kctx->endtime)
+               goto out;
+
+       /* do sequencing checks */
+
+       ret = GSS_S_BAD_SIG;
+       if ((ret = krb5_get_seq_num(kctx->seq, ptr + 14, ptr + 6, &direction,
+                                   &seqnum)))
+               goto out;
+
+       if ((kctx->initiate && direction != 0xff) ||
+           (!kctx->initiate && direction != 0))
+               goto out;
+
+       /* Copy the data back to the right position.  XXX: Would probably be
+        * better to copy and encrypt at the same time. */
+
+       blocksize = crypto_tfm_alg_blocksize(kctx->enc);
+       data_start = ptr + 22 + blocksize;
+       orig_start = buf->head[0].iov_base + offset;
+       data_len = (buf->head[0].iov_base + buf->head[0].iov_len) - data_start;
+       memmove(orig_start, data_start, data_len);
+       buf->head[0].iov_len -= (data_start - orig_start);
+       buf->len -= (data_start - orig_start);
+
+       ret = GSS_S_DEFECTIVE_TOKEN;
+       if (gss_krb5_remove_padding(buf, blocksize))
+               goto out;
+
+       ret = GSS_S_COMPLETE;
+out:
+       if (md5cksum.data) kfree(md5cksum.data);
+       return ret;
+}