Logo Search packages:      
Sourcecode: linux version File versions  Download package

rxkad.c

/* Kerberos-based RxRPC security
 *
 * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 */

#include <linux/module.h>
#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/udp.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include <linux/ctype.h>
#include <net/sock.h>
#include <net/af_rxrpc.h>
#define rxrpc_debug rxkad_debug
#include "ar-internal.h"

#define RXKAD_VERSION               2
#define MAXKRB5TICKETLEN            1024
#define RXKAD_TKT_TYPE_KERBEROS_V5  256
#define ANAME_SZ              40    /* size of authentication name */
#define INST_SZ                     40    /* size of principal's instance */
#define REALM_SZ              40    /* size of principal's auth domain */
#define SNAME_SZ              40    /* size of service name */

unsigned rxrpc_debug;
module_param_named(debug, rxrpc_debug, uint, S_IWUSR | S_IRUGO);
MODULE_PARM_DESC(rxrpc_debug, "rxkad debugging mask");

struct rxkad_level1_hdr {
      __be32      data_size;  /* true data size (excluding padding) */
};

struct rxkad_level2_hdr {
      __be32      data_size;  /* true data size (excluding padding) */
      __be32      checksum;   /* decrypted data checksum */
};

MODULE_DESCRIPTION("RxRPC network protocol type-2 security (Kerberos)");
MODULE_AUTHOR("Red Hat, Inc.");
MODULE_LICENSE("GPL");

/*
 * this holds a pinned cipher so that keventd doesn't get called by the cipher
 * alloc routine, but since we have it to hand, we use it to decrypt RESPONSE
 * packets
 */
static struct crypto_blkcipher *rxkad_ci;
static DEFINE_MUTEX(rxkad_ci_mutex);

/*
 * initialise connection security
 */
static int rxkad_init_connection_security(struct rxrpc_connection *conn)
{
      struct rxrpc_key_payload *payload;
      struct crypto_blkcipher *ci;
      int ret;

      _enter("{%d},{%x}", conn->debug_id, key_serial(conn->key));

      payload = conn->key->payload.data;
      conn->security_ix = payload->k.security_index;

      ci = crypto_alloc_blkcipher("pcbc(fcrypt)", 0, CRYPTO_ALG_ASYNC);
      if (IS_ERR(ci)) {
            _debug("no cipher");
            ret = PTR_ERR(ci);
            goto error;
      }

      if (crypto_blkcipher_setkey(ci, payload->k.session_key,
                            sizeof(payload->k.session_key)) < 0)
            BUG();

      switch (conn->security_level) {
      case RXRPC_SECURITY_PLAIN:
            break;
      case RXRPC_SECURITY_AUTH:
            conn->size_align = 8;
            conn->security_size = sizeof(struct rxkad_level1_hdr);
            conn->header_size += sizeof(struct rxkad_level1_hdr);
            break;
      case RXRPC_SECURITY_ENCRYPT:
            conn->size_align = 8;
            conn->security_size = sizeof(struct rxkad_level2_hdr);
            conn->header_size += sizeof(struct rxkad_level2_hdr);
            break;
      default:
            ret = -EKEYREJECTED;
            goto error;
      }

      conn->cipher = ci;
      ret = 0;
error:
      _leave(" = %d", ret);
      return ret;
}

/*
 * prime the encryption state with the invariant parts of a connection's
 * description
 */
static void rxkad_prime_packet_security(struct rxrpc_connection *conn)
{
      struct rxrpc_key_payload *payload;
      struct blkcipher_desc desc;
      struct scatterlist sg[2];
      struct rxrpc_crypt iv;
      struct {
            __be32 x[4];
      } tmpbuf __attribute__((aligned(16))); /* must all be in same page */

      _enter("");

      if (!conn->key)
            return;

      payload = conn->key->payload.data;
      memcpy(&iv, payload->k.session_key, sizeof(iv));

      desc.tfm = conn->cipher;
      desc.info = iv.x;
      desc.flags = 0;

      tmpbuf.x[0] = conn->epoch;
      tmpbuf.x[1] = conn->cid;
      tmpbuf.x[2] = 0;
      tmpbuf.x[3] = htonl(conn->security_ix);

      sg_init_one(&sg[0], &tmpbuf, sizeof(tmpbuf));
      sg_init_one(&sg[1], &tmpbuf, sizeof(tmpbuf));
      crypto_blkcipher_encrypt_iv(&desc, &sg[0], &sg[1], sizeof(tmpbuf));

      memcpy(&conn->csum_iv, &tmpbuf.x[2], sizeof(conn->csum_iv));
      ASSERTCMP(conn->csum_iv.n[0], ==, tmpbuf.x[2]);

      _leave("");
}

/*
 * partially encrypt a packet (level 1 security)
 */
static int rxkad_secure_packet_auth(const struct rxrpc_call *call,
                            struct sk_buff *skb,
                            u32 data_size,
                            void *sechdr)
{
      struct rxrpc_skb_priv *sp;
      struct blkcipher_desc desc;
      struct rxrpc_crypt iv;
      struct scatterlist sg[2];
      struct {
            struct rxkad_level1_hdr hdr;
            __be32      first;      /* first four bytes of data and padding */
      } tmpbuf __attribute__((aligned(8))); /* must all be in same page */
      u16 check;

      sp = rxrpc_skb(skb);

      _enter("");

      check = ntohl(sp->hdr.seq ^ sp->hdr.callNumber);
      data_size |= (u32) check << 16;

      tmpbuf.hdr.data_size = htonl(data_size);
      memcpy(&tmpbuf.first, sechdr + 4, sizeof(tmpbuf.first));

      /* start the encryption afresh */
      memset(&iv, 0, sizeof(iv));
      desc.tfm = call->conn->cipher;
      desc.info = iv.x;
      desc.flags = 0;

      sg_init_one(&sg[0], &tmpbuf, sizeof(tmpbuf));
      sg_init_one(&sg[1], &tmpbuf, sizeof(tmpbuf));
      crypto_blkcipher_encrypt_iv(&desc, &sg[0], &sg[1], sizeof(tmpbuf));

      memcpy(sechdr, &tmpbuf, sizeof(tmpbuf));

      _leave(" = 0");
      return 0;
}

/*
 * wholly encrypt a packet (level 2 security)
 */
static int rxkad_secure_packet_encrypt(const struct rxrpc_call *call,
                              struct sk_buff *skb,
                              u32 data_size,
                              void *sechdr)
{
      const struct rxrpc_key_payload *payload;
      struct rxkad_level2_hdr rxkhdr
            __attribute__((aligned(8))); /* must be all on one page */
      struct rxrpc_skb_priv *sp;
      struct blkcipher_desc desc;
      struct rxrpc_crypt iv;
      struct scatterlist sg[16];
      struct sk_buff *trailer;
      unsigned len;
      u16 check;
      int nsg;

      sp = rxrpc_skb(skb);

      _enter("");

      check = ntohl(sp->hdr.seq ^ sp->hdr.callNumber);

      rxkhdr.data_size = htonl(data_size | (u32) check << 16);
      rxkhdr.checksum = 0;

      /* encrypt from the session key */
      payload = call->conn->key->payload.data;
      memcpy(&iv, payload->k.session_key, sizeof(iv));
      desc.tfm = call->conn->cipher;
      desc.info = iv.x;
      desc.flags = 0;

      sg_init_one(&sg[0], sechdr, sizeof(rxkhdr));
      sg_init_one(&sg[1], &rxkhdr, sizeof(rxkhdr));
      crypto_blkcipher_encrypt_iv(&desc, &sg[0], &sg[1], sizeof(rxkhdr));

      /* we want to encrypt the skbuff in-place */
      nsg = skb_cow_data(skb, 0, &trailer);
      if (nsg < 0 || nsg > 16)
            return -ENOMEM;

      len = data_size + call->conn->size_align - 1;
      len &= ~(call->conn->size_align - 1);

      sg_init_table(sg, nsg);
      skb_to_sgvec(skb, sg, 0, len);
      crypto_blkcipher_encrypt_iv(&desc, sg, sg, len);

      _leave(" = 0");
      return 0;
}

/*
 * checksum an RxRPC packet header
 */
static int rxkad_secure_packet(const struct rxrpc_call *call,
                        struct sk_buff *skb,
                        size_t data_size,
                        void *sechdr)
{
      struct rxrpc_skb_priv *sp;
      struct blkcipher_desc desc;
      struct rxrpc_crypt iv;
      struct scatterlist sg[2];
      struct {
            __be32 x[2];
      } tmpbuf __attribute__((aligned(8))); /* must all be in same page */
      __be32 x;
      int ret;

      sp = rxrpc_skb(skb);

      _enter("{%d{%x}},{#%u},%zu,",
             call->debug_id, key_serial(call->conn->key), ntohl(sp->hdr.seq),
             data_size);

      if (!call->conn->cipher)
            return 0;

      ret = key_validate(call->conn->key);
      if (ret < 0)
            return ret;

      /* continue encrypting from where we left off */
      memcpy(&iv, call->conn->csum_iv.x, sizeof(iv));
      desc.tfm = call->conn->cipher;
      desc.info = iv.x;
      desc.flags = 0;

      /* calculate the security checksum */
      x = htonl(call->channel << (32 - RXRPC_CIDSHIFT));
      x |= sp->hdr.seq & __constant_cpu_to_be32(0x3fffffff);
      tmpbuf.x[0] = sp->hdr.callNumber;
      tmpbuf.x[1] = x;

      sg_init_one(&sg[0], &tmpbuf, sizeof(tmpbuf));
      sg_init_one(&sg[1], &tmpbuf, sizeof(tmpbuf));
      crypto_blkcipher_encrypt_iv(&desc, &sg[0], &sg[1], sizeof(tmpbuf));

      x = ntohl(tmpbuf.x[1]);
      x = (x >> 16) & 0xffff;
      if (x == 0)
            x = 1; /* zero checksums are not permitted */
      sp->hdr.cksum = htons(x);

      switch (call->conn->security_level) {
      case RXRPC_SECURITY_PLAIN:
            ret = 0;
            break;
      case RXRPC_SECURITY_AUTH:
            ret = rxkad_secure_packet_auth(call, skb, data_size, sechdr);
            break;
      case RXRPC_SECURITY_ENCRYPT:
            ret = rxkad_secure_packet_encrypt(call, skb, data_size,
                                      sechdr);
            break;
      default:
            ret = -EPERM;
            break;
      }

      _leave(" = %d [set %hx]", ret, x);
      return ret;
}

/*
 * decrypt partial encryption on a packet (level 1 security)
 */
static int rxkad_verify_packet_auth(const struct rxrpc_call *call,
                            struct sk_buff *skb,
                            u32 *_abort_code)
{
      struct rxkad_level1_hdr sechdr;
      struct rxrpc_skb_priv *sp;
      struct blkcipher_desc desc;
      struct rxrpc_crypt iv;
      struct scatterlist sg[16];
      struct sk_buff *trailer;
      u32 data_size, buf;
      u16 check;
      int nsg;

      _enter("");

      sp = rxrpc_skb(skb);

      /* we want to decrypt the skbuff in-place */
      nsg = skb_cow_data(skb, 0, &trailer);
      if (nsg < 0 || nsg > 16)
            goto nomem;

      sg_init_table(sg, nsg);
      skb_to_sgvec(skb, sg, 0, 8);

      /* start the decryption afresh */
      memset(&iv, 0, sizeof(iv));
      desc.tfm = call->conn->cipher;
      desc.info = iv.x;
      desc.flags = 0;

      crypto_blkcipher_decrypt_iv(&desc, sg, sg, 8);

      /* remove the decrypted packet length */
      if (skb_copy_bits(skb, 0, &sechdr, sizeof(sechdr)) < 0)
            goto datalen_error;
      if (!skb_pull(skb, sizeof(sechdr)))
            BUG();

      buf = ntohl(sechdr.data_size);
      data_size = buf & 0xffff;

      check = buf >> 16;
      check ^= ntohl(sp->hdr.seq ^ sp->hdr.callNumber);
      check &= 0xffff;
      if (check != 0) {
            *_abort_code = RXKADSEALEDINCON;
            goto protocol_error;
      }

      /* shorten the packet to remove the padding */
      if (data_size > skb->len)
            goto datalen_error;
      else if (data_size < skb->len)
            skb->len = data_size;

      _leave(" = 0 [dlen=%x]", data_size);
      return 0;

datalen_error:
      *_abort_code = RXKADDATALEN;
protocol_error:
      _leave(" = -EPROTO");
      return -EPROTO;

nomem:
      _leave(" = -ENOMEM");
      return -ENOMEM;
}

/*
 * wholly decrypt a packet (level 2 security)
 */
static int rxkad_verify_packet_encrypt(const struct rxrpc_call *call,
                               struct sk_buff *skb,
                               u32 *_abort_code)
{
      const struct rxrpc_key_payload *payload;
      struct rxkad_level2_hdr sechdr;
      struct rxrpc_skb_priv *sp;
      struct blkcipher_desc desc;
      struct rxrpc_crypt iv;
      struct scatterlist _sg[4], *sg;
      struct sk_buff *trailer;
      u32 data_size, buf;
      u16 check;
      int nsg;

      _enter(",{%d}", skb->len);

      sp = rxrpc_skb(skb);

      /* we want to decrypt the skbuff in-place */
      nsg = skb_cow_data(skb, 0, &trailer);
      if (nsg < 0)
            goto nomem;

      sg = _sg;
      if (unlikely(nsg > 4)) {
            sg = kmalloc(sizeof(*sg) * nsg, GFP_NOIO);
            if (!sg)
                  goto nomem;
      }

      sg_init_table(sg, nsg);
      skb_to_sgvec(skb, sg, 0, skb->len);

      /* decrypt from the session key */
      payload = call->conn->key->payload.data;
      memcpy(&iv, payload->k.session_key, sizeof(iv));
      desc.tfm = call->conn->cipher;
      desc.info = iv.x;
      desc.flags = 0;

      crypto_blkcipher_decrypt_iv(&desc, sg, sg, skb->len);
      if (sg != _sg)
            kfree(sg);

      /* remove the decrypted packet length */
      if (skb_copy_bits(skb, 0, &sechdr, sizeof(sechdr)) < 0)
            goto datalen_error;
      if (!skb_pull(skb, sizeof(sechdr)))
            BUG();

      buf = ntohl(sechdr.data_size);
      data_size = buf & 0xffff;

      check = buf >> 16;
      check ^= ntohl(sp->hdr.seq ^ sp->hdr.callNumber);
      check &= 0xffff;
      if (check != 0) {
            *_abort_code = RXKADSEALEDINCON;
            goto protocol_error;
      }

      /* shorten the packet to remove the padding */
      if (data_size > skb->len)
            goto datalen_error;
      else if (data_size < skb->len)
            skb->len = data_size;

      _leave(" = 0 [dlen=%x]", data_size);
      return 0;

datalen_error:
      *_abort_code = RXKADDATALEN;
protocol_error:
      _leave(" = -EPROTO");
      return -EPROTO;

nomem:
      _leave(" = -ENOMEM");
      return -ENOMEM;
}

/*
 * verify the security on a received packet
 */
static int rxkad_verify_packet(const struct rxrpc_call *call,
                         struct sk_buff *skb,
                         u32 *_abort_code)
{
      struct blkcipher_desc desc;
      struct rxrpc_skb_priv *sp;
      struct rxrpc_crypt iv;
      struct scatterlist sg[2];
      struct {
            __be32 x[2];
      } tmpbuf __attribute__((aligned(8))); /* must all be in same page */
      __be32 x;
      __be16 cksum;
      int ret;

      sp = rxrpc_skb(skb);

      _enter("{%d{%x}},{#%u}",
             call->debug_id, key_serial(call->conn->key),
             ntohl(sp->hdr.seq));

      if (!call->conn->cipher)
            return 0;

      if (sp->hdr.securityIndex != 2) {
            *_abort_code = RXKADINCONSISTENCY;
            _leave(" = -EPROTO [not rxkad]");
            return -EPROTO;
      }

      /* continue encrypting from where we left off */
      memcpy(&iv, call->conn->csum_iv.x, sizeof(iv));
      desc.tfm = call->conn->cipher;
      desc.info = iv.x;
      desc.flags = 0;

      /* validate the security checksum */
      x = htonl(call->channel << (32 - RXRPC_CIDSHIFT));
      x |= sp->hdr.seq & __constant_cpu_to_be32(0x3fffffff);
      tmpbuf.x[0] = call->call_id;
      tmpbuf.x[1] = x;

      sg_init_one(&sg[0], &tmpbuf, sizeof(tmpbuf));
      sg_init_one(&sg[1], &tmpbuf, sizeof(tmpbuf));
      crypto_blkcipher_encrypt_iv(&desc, &sg[0], &sg[1], sizeof(tmpbuf));

      x = ntohl(tmpbuf.x[1]);
      x = (x >> 16) & 0xffff;
      if (x == 0)
            x = 1; /* zero checksums are not permitted */

      cksum = htons(x);
      if (sp->hdr.cksum != cksum) {
            *_abort_code = RXKADSEALEDINCON;
            _leave(" = -EPROTO [csum failed]");
            return -EPROTO;
      }

      switch (call->conn->security_level) {
      case RXRPC_SECURITY_PLAIN:
            ret = 0;
            break;
      case RXRPC_SECURITY_AUTH:
            ret = rxkad_verify_packet_auth(call, skb, _abort_code);
            break;
      case RXRPC_SECURITY_ENCRYPT:
            ret = rxkad_verify_packet_encrypt(call, skb, _abort_code);
            break;
      default:
            ret = -ENOANO;
            break;
      }

      _leave(" = %d", ret);
      return ret;
}

/*
 * issue a challenge
 */
static int rxkad_issue_challenge(struct rxrpc_connection *conn)
{
      struct rxkad_challenge challenge;
      struct rxrpc_header hdr;
      struct msghdr msg;
      struct kvec iov[2];
      size_t len;
      int ret;

      _enter("{%d,%x}", conn->debug_id, key_serial(conn->key));

      ret = key_validate(conn->key);
      if (ret < 0)
            return ret;

      get_random_bytes(&conn->security_nonce, sizeof(conn->security_nonce));

      challenge.version = htonl(2);
      challenge.nonce         = htonl(conn->security_nonce);
      challenge.min_level     = htonl(0);
      challenge.__padding     = 0;

      msg.msg_name      = &conn->trans->peer->srx.transport.sin;
      msg.msg_namelen   = sizeof(conn->trans->peer->srx.transport.sin);
      msg.msg_control   = NULL;
      msg.msg_controllen = 0;
      msg.msg_flags     = 0;

      hdr.epoch   = conn->epoch;
      hdr.cid           = conn->cid;
      hdr.callNumber    = 0;
      hdr.seq           = 0;
      hdr.type    = RXRPC_PACKET_TYPE_CHALLENGE;
      hdr.flags   = conn->out_clientflag;
      hdr.userStatus    = 0;
      hdr.securityIndex = conn->security_ix;
      hdr._rsvd   = 0;
      hdr.serviceId     = conn->service_id;

      iov[0].iov_base   = &hdr;
      iov[0].iov_len    = sizeof(hdr);
      iov[1].iov_base   = &challenge;
      iov[1].iov_len    = sizeof(challenge);

      len = iov[0].iov_len + iov[1].iov_len;

      hdr.serial = htonl(atomic_inc_return(&conn->serial));
      _proto("Tx CHALLENGE %%%u", ntohl(hdr.serial));

      ret = kernel_sendmsg(conn->trans->local->socket, &msg, iov, 2, len);
      if (ret < 0) {
            _debug("sendmsg failed: %d", ret);
            return -EAGAIN;
      }

      _leave(" = 0");
      return 0;
}

/*
 * send a Kerberos security response
 */
static int rxkad_send_response(struct rxrpc_connection *conn,
                         struct rxrpc_header *hdr,
                         struct rxkad_response *resp,
                         const struct rxkad_key *s2)
{
      struct msghdr msg;
      struct kvec iov[3];
      size_t len;
      int ret;

      _enter("");

      msg.msg_name      = &conn->trans->peer->srx.transport.sin;
      msg.msg_namelen   = sizeof(conn->trans->peer->srx.transport.sin);
      msg.msg_control   = NULL;
      msg.msg_controllen = 0;
      msg.msg_flags     = 0;

      hdr->epoch  = conn->epoch;
      hdr->seq    = 0;
      hdr->type   = RXRPC_PACKET_TYPE_RESPONSE;
      hdr->flags  = conn->out_clientflag;
      hdr->userStatus   = 0;
      hdr->_rsvd  = 0;

      iov[0].iov_base   = hdr;
      iov[0].iov_len    = sizeof(*hdr);
      iov[1].iov_base   = resp;
      iov[1].iov_len    = sizeof(*resp);
      iov[2].iov_base   = (void *) s2->ticket;
      iov[2].iov_len    = s2->ticket_len;

      len = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len;

      hdr->serial = htonl(atomic_inc_return(&conn->serial));
      _proto("Tx RESPONSE %%%u", ntohl(hdr->serial));

      ret = kernel_sendmsg(conn->trans->local->socket, &msg, iov, 3, len);
      if (ret < 0) {
            _debug("sendmsg failed: %d", ret);
            return -EAGAIN;
      }

      _leave(" = 0");
      return 0;
}

/*
 * calculate the response checksum
 */
static void rxkad_calc_response_checksum(struct rxkad_response *response)
{
      u32 csum = 1000003;
      int loop;
      u8 *p = (u8 *) response;

      for (loop = sizeof(*response); loop > 0; loop--)
            csum = csum * 0x10204081 + *p++;

      response->encrypted.checksum = htonl(csum);
}

/*
 * load a scatterlist with a potentially split-page buffer
 */
static void rxkad_sg_set_buf2(struct scatterlist sg[2],
                        void *buf, size_t buflen)
{
      int nsg = 1;

      sg_init_table(sg, 2);

      sg_set_buf(&sg[0], buf, buflen);
      if (sg[0].offset + buflen > PAGE_SIZE) {
            /* the buffer was split over two pages */
            sg[0].length = PAGE_SIZE - sg[0].offset;
            sg_set_buf(&sg[1], buf + sg[0].length, buflen - sg[0].length);
            nsg++;
      }

      sg_mark_end(&sg[nsg - 1]);

      ASSERTCMP(sg[0].length + sg[1].length, ==, buflen);
}

/*
 * encrypt the response packet
 */
static void rxkad_encrypt_response(struct rxrpc_connection *conn,
                           struct rxkad_response *resp,
                           const struct rxkad_key *s2)
{
      struct blkcipher_desc desc;
      struct rxrpc_crypt iv;
      struct scatterlist sg[2];

      /* continue encrypting from where we left off */
      memcpy(&iv, s2->session_key, sizeof(iv));
      desc.tfm = conn->cipher;
      desc.info = iv.x;
      desc.flags = 0;

      rxkad_sg_set_buf2(sg, &resp->encrypted, sizeof(resp->encrypted));
      crypto_blkcipher_encrypt_iv(&desc, sg, sg, sizeof(resp->encrypted));
}

/*
 * respond to a challenge packet
 */
static int rxkad_respond_to_challenge(struct rxrpc_connection *conn,
                              struct sk_buff *skb,
                              u32 *_abort_code)
{
      const struct rxrpc_key_payload *payload;
      struct rxkad_challenge challenge;
      struct rxkad_response resp
            __attribute__((aligned(8))); /* must be aligned for crypto */
      struct rxrpc_skb_priv *sp;
      u32 version, nonce, min_level, abort_code;
      int ret;

      _enter("{%d,%x}", conn->debug_id, key_serial(conn->key));

      if (!conn->key) {
            _leave(" = -EPROTO [no key]");
            return -EPROTO;
      }

      ret = key_validate(conn->key);
      if (ret < 0) {
            *_abort_code = RXKADEXPIRED;
            return ret;
      }

      abort_code = RXKADPACKETSHORT;
      sp = rxrpc_skb(skb);
      if (skb_copy_bits(skb, 0, &challenge, sizeof(challenge)) < 0)
            goto protocol_error;

      version = ntohl(challenge.version);
      nonce = ntohl(challenge.nonce);
      min_level = ntohl(challenge.min_level);

      _proto("Rx CHALLENGE %%%u { v=%u n=%u ml=%u }",
             ntohl(sp->hdr.serial), version, nonce, min_level);

      abort_code = RXKADINCONSISTENCY;
      if (version != RXKAD_VERSION)
            goto protocol_error;

      abort_code = RXKADLEVELFAIL;
      if (conn->security_level < min_level)
            goto protocol_error;

      payload = conn->key->payload.data;

      /* build the response packet */
      memset(&resp, 0, sizeof(resp));

      resp.version = RXKAD_VERSION;
      resp.encrypted.epoch = conn->epoch;
      resp.encrypted.cid = conn->cid;
      resp.encrypted.securityIndex = htonl(conn->security_ix);
      resp.encrypted.call_id[0] =
            (conn->channels[0] ? conn->channels[0]->call_id : 0);
      resp.encrypted.call_id[1] =
            (conn->channels[1] ? conn->channels[1]->call_id : 0);
      resp.encrypted.call_id[2] =
            (conn->channels[2] ? conn->channels[2]->call_id : 0);
      resp.encrypted.call_id[3] =
            (conn->channels[3] ? conn->channels[3]->call_id : 0);
      resp.encrypted.inc_nonce = htonl(nonce + 1);
      resp.encrypted.level = htonl(conn->security_level);
      resp.kvno = htonl(payload->k.kvno);
      resp.ticket_len = htonl(payload->k.ticket_len);

      /* calculate the response checksum and then do the encryption */
      rxkad_calc_response_checksum(&resp);
      rxkad_encrypt_response(conn, &resp, &payload->k);
      return rxkad_send_response(conn, &sp->hdr, &resp, &payload->k);

protocol_error:
      *_abort_code = abort_code;
      _leave(" = -EPROTO [%d]", abort_code);
      return -EPROTO;
}

/*
 * decrypt the kerberos IV ticket in the response
 */
static int rxkad_decrypt_ticket(struct rxrpc_connection *conn,
                        void *ticket, size_t ticket_len,
                        struct rxrpc_crypt *_session_key,
                        time_t *_expiry,
                        u32 *_abort_code)
{
      struct blkcipher_desc desc;
      struct rxrpc_crypt iv, key;
      struct scatterlist sg[1];
      struct in_addr addr;
      unsigned life;
      time_t issue, now;
      bool little_endian;
      int ret;
      u8 *p, *q, *name, *end;

      _enter("{%d},{%x}", conn->debug_id, key_serial(conn->server_key));

      *_expiry = 0;

      ret = key_validate(conn->server_key);
      if (ret < 0) {
            switch (ret) {
            case -EKEYEXPIRED:
                  *_abort_code = RXKADEXPIRED;
                  goto error;
            default:
                  *_abort_code = RXKADNOAUTH;
                  goto error;
            }
      }

      ASSERT(conn->server_key->payload.data != NULL);
      ASSERTCMP((unsigned long) ticket & 7UL, ==, 0);

      memcpy(&iv, &conn->server_key->type_data, sizeof(iv));

      desc.tfm = conn->server_key->payload.data;
      desc.info = iv.x;
      desc.flags = 0;

      sg_init_one(&sg[0], ticket, ticket_len);
      crypto_blkcipher_decrypt_iv(&desc, sg, sg, ticket_len);

      p = ticket;
      end = p + ticket_len;

#define Z(size)                                 \
      ({                                  \
            u8 *__str = p;                      \
            q = memchr(p, 0, end - p);          \
            if (!q || q - p > (size))           \
                  goto bad_ticket;        \
            for (; p < q; p++)                  \
                  if (!isprint(*p))       \
                        goto bad_ticket;  \
            p++;                          \
            __str;                              \
      })

      /* extract the ticket flags */
      _debug("KIV FLAGS: %x", *p);
      little_endian = *p & 1;
      p++;

      /* extract the authentication name */
      name = Z(ANAME_SZ);
      _debug("KIV ANAME: %s", name);

      /* extract the principal's instance */
      name = Z(INST_SZ);
      _debug("KIV INST : %s", name);

      /* extract the principal's authentication domain */
      name = Z(REALM_SZ);
      _debug("KIV REALM: %s", name);

      if (end - p < 4 + 8 + 4 + 2)
            goto bad_ticket;

      /* get the IPv4 address of the entity that requested the ticket */
      memcpy(&addr, p, sizeof(addr));
      p += 4;
      _debug("KIV ADDR : "NIPQUAD_FMT, NIPQUAD(addr));

      /* get the session key from the ticket */
      memcpy(&key, p, sizeof(key));
      p += 8;
      _debug("KIV KEY  : %08x %08x", ntohl(key.n[0]), ntohl(key.n[1]));
      memcpy(_session_key, &key, sizeof(key));

      /* get the ticket's lifetime */
      life = *p++ * 5 * 60;
      _debug("KIV LIFE : %u", life);

      /* get the issue time of the ticket */
      if (little_endian) {
            __le32 stamp;
            memcpy(&stamp, p, 4);
            issue = le32_to_cpu(stamp);
      } else {
            __be32 stamp;
            memcpy(&stamp, p, 4);
            issue = be32_to_cpu(stamp);
      }
      p += 4;
      now = get_seconds();
      _debug("KIV ISSUE: %lx [%lx]", issue, now);

      /* check the ticket is in date */
      if (issue > now) {
            *_abort_code = RXKADNOAUTH;
            ret = -EKEYREJECTED;
            goto error;
      }

      if (issue < now - life) {
            *_abort_code = RXKADEXPIRED;
            ret = -EKEYEXPIRED;
            goto error;
      }

      *_expiry = issue + life;

      /* get the service name */
      name = Z(SNAME_SZ);
      _debug("KIV SNAME: %s", name);

      /* get the service instance name */
      name = Z(INST_SZ);
      _debug("KIV SINST: %s", name);

      ret = 0;
error:
      _leave(" = %d", ret);
      return ret;

bad_ticket:
      *_abort_code = RXKADBADTICKET;
      ret = -EBADMSG;
      goto error;
}

/*
 * decrypt the response packet
 */
static void rxkad_decrypt_response(struct rxrpc_connection *conn,
                           struct rxkad_response *resp,
                           const struct rxrpc_crypt *session_key)
{
      struct blkcipher_desc desc;
      struct scatterlist sg[2];
      struct rxrpc_crypt iv;

      _enter(",,%08x%08x",
             ntohl(session_key->n[0]), ntohl(session_key->n[1]));

      ASSERT(rxkad_ci != NULL);

      mutex_lock(&rxkad_ci_mutex);
      if (crypto_blkcipher_setkey(rxkad_ci, session_key->x,
                            sizeof(*session_key)) < 0)
            BUG();

      memcpy(&iv, session_key, sizeof(iv));
      desc.tfm = rxkad_ci;
      desc.info = iv.x;
      desc.flags = 0;

      rxkad_sg_set_buf2(sg, &resp->encrypted, sizeof(resp->encrypted));
      crypto_blkcipher_decrypt_iv(&desc, sg, sg, sizeof(resp->encrypted));
      mutex_unlock(&rxkad_ci_mutex);

      _leave("");
}

/*
 * verify a response
 */
static int rxkad_verify_response(struct rxrpc_connection *conn,
                         struct sk_buff *skb,
                         u32 *_abort_code)
{
      struct rxkad_response response
            __attribute__((aligned(8))); /* must be aligned for crypto */
      struct rxrpc_skb_priv *sp;
      struct rxrpc_crypt session_key;
      time_t expiry;
      void *ticket;
      u32 abort_code, version, kvno, ticket_len, csum, level;
      int ret;

      _enter("{%d,%x}", conn->debug_id, key_serial(conn->server_key));

      abort_code = RXKADPACKETSHORT;
      if (skb_copy_bits(skb, 0, &response, sizeof(response)) < 0)
            goto protocol_error;
      if (!pskb_pull(skb, sizeof(response)))
            BUG();

      version = ntohl(response.version);
      ticket_len = ntohl(response.ticket_len);
      kvno = ntohl(response.kvno);
      sp = rxrpc_skb(skb);
      _proto("Rx RESPONSE %%%u { v=%u kv=%u tl=%u }",
             ntohl(sp->hdr.serial), version, kvno, ticket_len);

      abort_code = RXKADINCONSISTENCY;
      if (version != RXKAD_VERSION)
            goto protocol_error;

      abort_code = RXKADTICKETLEN;
      if (ticket_len < 4 || ticket_len > MAXKRB5TICKETLEN)
            goto protocol_error;

      abort_code = RXKADUNKNOWNKEY;
      if (kvno >= RXKAD_TKT_TYPE_KERBEROS_V5)
            goto protocol_error;

      /* extract the kerberos ticket and decrypt and decode it */
      ticket = kmalloc(ticket_len, GFP_NOFS);
      if (!ticket)
            return -ENOMEM;

      abort_code = RXKADPACKETSHORT;
      if (skb_copy_bits(skb, 0, ticket, ticket_len) < 0)
            goto protocol_error_free;

      ret = rxkad_decrypt_ticket(conn, ticket, ticket_len, &session_key,
                           &expiry, &abort_code);
      if (ret < 0) {
            *_abort_code = abort_code;
            kfree(ticket);
            return ret;
      }

      /* use the session key from inside the ticket to decrypt the
       * response */
      rxkad_decrypt_response(conn, &response, &session_key);

      abort_code = RXKADSEALEDINCON;
      if (response.encrypted.epoch != conn->epoch)
            goto protocol_error_free;
      if (response.encrypted.cid != conn->cid)
            goto protocol_error_free;
      if (ntohl(response.encrypted.securityIndex) != conn->security_ix)
            goto protocol_error_free;
      csum = response.encrypted.checksum;
      response.encrypted.checksum = 0;
      rxkad_calc_response_checksum(&response);
      if (response.encrypted.checksum != csum)
            goto protocol_error_free;

      if (ntohl(response.encrypted.call_id[0]) > INT_MAX ||
          ntohl(response.encrypted.call_id[1]) > INT_MAX ||
          ntohl(response.encrypted.call_id[2]) > INT_MAX ||
          ntohl(response.encrypted.call_id[3]) > INT_MAX)
            goto protocol_error_free;

      abort_code = RXKADOUTOFSEQUENCE;
      if (response.encrypted.inc_nonce != htonl(conn->security_nonce + 1))
            goto protocol_error_free;

      abort_code = RXKADLEVELFAIL;
      level = ntohl(response.encrypted.level);
      if (level > RXRPC_SECURITY_ENCRYPT)
            goto protocol_error_free;
      conn->security_level = level;

      /* create a key to hold the security data and expiration time - after
       * this the connection security can be handled in exactly the same way
       * as for a client connection */
      ret = rxrpc_get_server_data_key(conn, &session_key, expiry, kvno);
      if (ret < 0) {
            kfree(ticket);
            return ret;
      }

      kfree(ticket);
      _leave(" = 0");
      return 0;

protocol_error_free:
      kfree(ticket);
protocol_error:
      *_abort_code = abort_code;
      _leave(" = -EPROTO [%d]", abort_code);
      return -EPROTO;
}

/*
 * clear the connection security
 */
static void rxkad_clear(struct rxrpc_connection *conn)
{
      _enter("");

      if (conn->cipher)
            crypto_free_blkcipher(conn->cipher);
}

/*
 * RxRPC Kerberos-based security
 */
static struct rxrpc_security rxkad = {
      .owner                        = THIS_MODULE,
      .name                   = "rxkad",
      .security_index               = RXKAD_VERSION,
      .init_connection_security     = rxkad_init_connection_security,
      .prime_packet_security        = rxkad_prime_packet_security,
      .secure_packet                = rxkad_secure_packet,
      .verify_packet                = rxkad_verify_packet,
      .issue_challenge        = rxkad_issue_challenge,
      .respond_to_challenge         = rxkad_respond_to_challenge,
      .verify_response        = rxkad_verify_response,
      .clear                        = rxkad_clear,
};

static __init int rxkad_init(void)
{
      _enter("");

      /* pin the cipher we need so that the crypto layer doesn't invoke
       * keventd to go get it */
      rxkad_ci = crypto_alloc_blkcipher("pcbc(fcrypt)", 0, CRYPTO_ALG_ASYNC);
      if (IS_ERR(rxkad_ci))
            return PTR_ERR(rxkad_ci);

      return rxrpc_register_security(&rxkad);
}

module_init(rxkad_init);

static __exit void rxkad_exit(void)
{
      _enter("");

      rxrpc_unregister_security(&rxkad);
      crypto_free_blkcipher(rxkad_ci);
}

module_exit(rxkad_exit);

Generated by  Doxygen 1.6.0   Back to index