From patchwork Mon Oct 17 14:42:25 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Lebrun X-Patchwork-Id: 683043 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3syLd373MPz9s8x for ; Tue, 18 Oct 2016 01:46:47 +1100 (AEDT) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; secure) header.d=uclouvain.be header.i=@uclouvain.be header.b=fCVDjxai; dkim-atps=neutral Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S935144AbcJQOql (ORCPT ); Mon, 17 Oct 2016 10:46:41 -0400 Received: from smtp.sgsi.ucl.ac.be ([130.104.5.67]:33132 "EHLO smtp2.sgsi.ucl.ac.be" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S933474AbcJQOqf (ORCPT ); Mon, 17 Oct 2016 10:46:35 -0400 Received: from mailboxes.uclouvain.be (mailboxes.uclouvain.be [130.104.6.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-SHA384 (256/256 bits)) (No client certificate requested) by smtp2.sgsi.ucl.ac.be (Postfix) with ESMTPS id BE21367DD76 for ; Mon, 17 Oct 2016 16:41:10 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.9.2 smtp2.sgsi.ucl.ac.be BE21367DD76 DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=uclouvain.be; s=selucl; t=1476715270; bh=yzmkrTwRBHQ5JnKNJ282pkEt/gITpqGBsOP3cUFir8M=; h=From:To:CC:Subject:Date:In-Reply-To:References; b=fCVDjxaiGbSEXXL/5sKW1onyJUTqCWeunjAxNzYkYSor7S/P+BSfV/DgCq97cn4xE WXARhxiFTLJbObhy1T/ZwOEzvPHtanJzt1JCNZUTlKg5u395Gjnyoj3iyC2wb45vBn 3b2lCnVbtJZCtErQw+9WT4+RXJiT8n5EeHRFhUos= X-Virus-Status: Clean X-Virus-Scanned: clamav-milter 0.99 at smtp-2 Received: from localhost.localdomain (130.104.228.78) by ucl-mbx04.OASIS.UCLOUVAIN.BE (10.10.10.24) with Microsoft SMTP Server (TLS) id 15.0.1210.3; Mon, 17 Oct 2016 16:41:10 +0200 From: David Lebrun To: CC: David Lebrun Subject: [PATCH 4/9] ipv6: sr: add core files for SR HMAC support Date: Mon, 17 Oct 2016 16:42:25 +0200 Message-ID: <1476715350-18983-5-git-send-email-david.lebrun@uclouvain.be> X-Mailer: git-send-email 2.7.3 In-Reply-To: <1476715350-18983-1-git-send-email-david.lebrun@uclouvain.be> References: <1476715350-18983-1-git-send-email-david.lebrun@uclouvain.be> MIME-Version: 1.0 X-Originating-IP: [130.104.228.78] X-ClientProxiedBy: UCL-CAS03.OASIS.UCLOUVAIN.BE (10.10.10.43) To ucl-mbx04.OASIS.UCLOUVAIN.BE (10.10.10.24) X-SGSI-Information: X-SGSI-MailScanner-ID: BE21367DD76.A6585 X-SGSI-MailScanner: Found to be clean X-SGSI-SpamCheck: n'est pas un polluriel, SpamAssassin (cached, score=-4.31, requis 5, autolearn=not spam, ALL_TRUSTED -2.00, BAYES_00 -1.90, DKIM_SIGNED 0.10, DKIM_VALID -0.10, DKIM_VALID_AU -0.10, RP_MATCHES_RCVD -0.31) X-SGSI-From: david.lebrun@uclouvain.be X-SGSI-Spam-Status: No Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org This patch adds the necessary functions to compute and check the HMAC signature of an SR-enabled packet. Two HMAC algorithms are supported: hmac(sha1) and hmac(sha256). In order to avoid dynamic memory allocation for each HMAC computation, a per-cpu ring buffer is allocated for this purpose. Signed-off-by: David Lebrun --- include/linux/seg6_hmac.h | 6 + include/net/seg6_hmac.h | 61 ++++++ include/uapi/linux/seg6_hmac.h | 20 ++ net/ipv6/seg6_hmac.c | 432 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 519 insertions(+) create mode 100644 include/linux/seg6_hmac.h create mode 100644 include/net/seg6_hmac.h create mode 100644 include/uapi/linux/seg6_hmac.h create mode 100644 net/ipv6/seg6_hmac.c diff --git a/include/linux/seg6_hmac.h b/include/linux/seg6_hmac.h new file mode 100644 index 0000000..da437eb --- /dev/null +++ b/include/linux/seg6_hmac.h @@ -0,0 +1,6 @@ +#ifndef _LINUX_SEG6_HMAC_H +#define _LINUX_SEG6_HMAC_H + +#include + +#endif diff --git a/include/net/seg6_hmac.h b/include/net/seg6_hmac.h new file mode 100644 index 0000000..6e5ee6a --- /dev/null +++ b/include/net/seg6_hmac.h @@ -0,0 +1,61 @@ +/* + * SR-IPv6 implementation + * + * Author: + * David Lebrun + * + * + * 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. + */ + +#ifndef _NET_SEG6_HMAC_H +#define _NET_SEG6_HMAC_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SEG6_HMAC_MAX_DIGESTSIZE 160 +#define SEG6_HMAC_RING_SIZE 256 + +struct seg6_hmac_info { + struct list_head list; + + u32 hmackeyid; + char secret[SEG6_HMAC_SECRET_LEN]; + u8 slen; + u8 alg_id; +}; + +struct seg6_hmac_algo { + u8 alg_id; + char name[64]; + struct crypto_shash * __percpu *tfms; + struct shash_desc * __percpu *shashs; +}; + +extern int seg6_hmac_compute(struct seg6_hmac_info *hinfo, + struct ipv6_sr_hdr *hdr, struct in6_addr *saddr, + u8 *output); +extern struct seg6_hmac_info *seg6_hmac_info_lookup(struct net *net, u32 key); +extern int seg6_hmac_info_add(struct net *net, u32 key, + struct seg6_hmac_info *hinfo); +extern int seg6_hmac_info_del(struct net *net, u32 key, + struct seg6_hmac_info *hinfo); +extern int seg6_push_hmac(struct net *net, struct in6_addr *saddr, + struct ipv6_sr_hdr *srh); +extern bool seg6_hmac_validate_skb(struct sk_buff *skb); +extern int seg6_hmac_init(void); +extern void seg6_hmac_exit(void); +extern int seg6_hmac_net_init(struct net *net); +extern void seg6_hmac_net_exit(struct net *net); + +#endif diff --git a/include/uapi/linux/seg6_hmac.h b/include/uapi/linux/seg6_hmac.h new file mode 100644 index 0000000..0b5eda7 --- /dev/null +++ b/include/uapi/linux/seg6_hmac.h @@ -0,0 +1,20 @@ +#ifndef _UAPI_LINUX_SEG6_HMAC_H +#define _UAPI_LINUX_SEG6_HMAC_H + +#define SEG6_HMAC_SECRET_LEN 64 +#define SEG6_HMAC_FIELD_LEN 32 + +struct sr6_tlv_hmac { + __u8 type; + __u8 len; + __u16 reserved; + __be32 hmackeyid; + __u8 hmac[SEG6_HMAC_FIELD_LEN]; +} __attribute__((packed)); + +enum { + SEG6_HMAC_ALGO_SHA1 = 1, + SEG6_HMAC_ALGO_SHA256 = 2, +}; + +#endif diff --git a/net/ipv6/seg6_hmac.c b/net/ipv6/seg6_hmac.c new file mode 100644 index 0000000..91bd59a --- /dev/null +++ b/net/ipv6/seg6_hmac.c @@ -0,0 +1,432 @@ +/* + * SR-IPv6 implementation -- HMAC functions + * + * Author: + * David Lebrun + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static char * __percpu *hmac_ring; + +static struct seg6_hmac_algo hmac_algos[] = { + { + .alg_id = SEG6_HMAC_ALGO_SHA1, + .name = "hmac(sha1)", + }, + { + .alg_id = SEG6_HMAC_ALGO_SHA256, + .name = "hmac(sha256)", + }, +}; + +static struct seg6_hmac_algo *__hmac_get_algo(u8 alg_id) +{ + int i, alg_count; + struct seg6_hmac_algo *algo; + + alg_count = sizeof(hmac_algos) / sizeof(struct seg6_hmac_algo); + for (i = 0; i < alg_count; i++) { + algo = &hmac_algos[i]; + if (algo->alg_id == alg_id) + return algo; + } + + return NULL; +} + +static int __do_hmac(struct seg6_hmac_info *hinfo, const char *text, u8 psize, + u8 *output, int outlen) +{ + struct crypto_shash *tfm; + struct shash_desc *shash; + struct seg6_hmac_algo *algo; + int ret, dgsize; + + algo = __hmac_get_algo(hinfo->alg_id); + if (!algo) + return -ENOENT; + + tfm = *this_cpu_ptr(algo->tfms); + + dgsize = crypto_shash_digestsize(tfm); + if (dgsize > outlen) { + pr_debug("sr-ipv6: __do_hmac: digest size too big (%d / %d)\n", + dgsize, outlen); + return -ENOMEM; + } + + ret = crypto_shash_setkey(tfm, hinfo->secret, hinfo->slen); + if (ret < 0) { + pr_debug("sr-ipv6: crypto_shash_setkey failed: err %d\n", ret); + goto failed; + } + + shash = *this_cpu_ptr(algo->shashs); + shash->tfm = tfm; + + ret = crypto_shash_digest(shash, text, psize, output); + if (ret < 0) { + pr_debug("sr-ipv6: crypto_shash_digest failed: err %d\n", ret); + goto failed; + } + + return dgsize; + +failed: + return ret; +} + +int seg6_hmac_compute(struct seg6_hmac_info *hinfo, struct ipv6_sr_hdr *hdr, + struct in6_addr *saddr, u8 *output) +{ + int plen, i, dgsize, wrsize; + char *ring, *off; + u8 tmp_out[SEG6_HMAC_MAX_DIGESTSIZE]; + __be32 hmackeyid = cpu_to_be32(hinfo->hmackeyid); + + /* a 160-byte buffer for digest output allows to store highest known + * hash function (RadioGatun) with up to 1216 bits + */ + + /* saddr(16) + first_seg(1) + cleanup(1) + keyid(4) + seglist(16n) */ + plen = 16 + 1 + 1 + 4 + (hdr->first_segment + 1) * 16; + + /* this limit allows for 14 segments */ + if (plen >= SEG6_HMAC_RING_SIZE) + return -EMSGSIZE; + + local_bh_disable(); + ring = *this_cpu_ptr(hmac_ring); + off = ring; + memcpy(off, saddr, 16); + off += 16; + *off++ = hdr->first_segment; + *off++ = !!(sr_get_flags(hdr) & SR6_FLAG_CLEANUP) << 7; + memcpy(off, &hmackeyid, 4); + off += 4; + + for (i = 0; i < hdr->first_segment + 1; i++) { + memcpy(off, hdr->segments + i, 16); + off += 16; + } + + dgsize = __do_hmac(hinfo, ring, plen, tmp_out, + SEG6_HMAC_MAX_DIGESTSIZE); + local_bh_enable(); + + if (dgsize < 0) + return dgsize; + + wrsize = SEG6_HMAC_FIELD_LEN; + if (wrsize > dgsize) + wrsize = dgsize; + + memset(output, 0, SEG6_HMAC_FIELD_LEN); + memcpy(output, tmp_out, wrsize); + + return 0; +} +EXPORT_SYMBOL(seg6_hmac_compute); + +/* checks if an incoming SR-enabled packet's HMAC status matches + * the incoming policy. + * + * called with rcu_read_lock() + */ +bool seg6_hmac_validate_skb(struct sk_buff *skb) +{ + struct inet6_dev *idev; + struct ipv6_sr_hdr *srh; + struct sr6_tlv_hmac *tlv; + struct seg6_hmac_info *hinfo; + struct net *net = dev_net(skb->dev); + u8 hmac_output[SEG6_HMAC_FIELD_LEN]; + + idev = __in6_dev_get(skb->dev); + + srh = (struct ipv6_sr_hdr *)skb_transport_header(skb); + + tlv = seg6_get_tlv_hmac(srh); + + /* mandatory check but no tlv */ + if (idev->cnf.seg6_require_hmac > 0 && !tlv) + return false; + + /* no check */ + if (idev->cnf.seg6_require_hmac < 0) + return true; + + /* check only if present */ + if (idev->cnf.seg6_require_hmac == 0 && !tlv) + return true; + + /* now, seg6_require_hmac >= 0 && tlv */ + + hinfo = seg6_hmac_info_lookup(net, be32_to_cpu(tlv->hmackeyid)); + if (!hinfo) + return false; + + if (seg6_hmac_compute(hinfo, srh, &ipv6_hdr(skb)->saddr, hmac_output)) + return false; + + if (memcmp(hmac_output, tlv->hmac, SEG6_HMAC_FIELD_LEN) != 0) + return false; + + return true; +} + +/* called with rcu_read_lock() */ +struct seg6_hmac_info *seg6_hmac_info_lookup(struct net *net, u32 key) +{ + struct seg6_pernet_data *sdata = seg6_pernet(net); + struct seg6_hmac_info *hinfo; + + list_for_each_entry_rcu(hinfo, &sdata->hmac_infos, list) { + if (hinfo->hmackeyid == key) + return hinfo; + } + + return NULL; +} +EXPORT_SYMBOL(seg6_hmac_info_lookup); + +int seg6_hmac_info_add(struct net *net, u32 key, struct seg6_hmac_info *hinfo) +{ + struct seg6_pernet_data *sdata = seg6_pernet(net); + struct seg6_hmac_info *old_hinfo; + + old_hinfo = seg6_hmac_info_lookup(net, key); + if (old_hinfo) + return -EEXIST; + + list_add_rcu(&hinfo->list, &sdata->hmac_infos); + + return 0; +} +EXPORT_SYMBOL(seg6_hmac_info_add); + +int seg6_hmac_info_del(struct net *net, u32 key, struct seg6_hmac_info *hinfo) +{ + struct seg6_hmac_info *tmp; + + tmp = seg6_hmac_info_lookup(net, key); + if (!tmp) + return -ENOENT; + + /* entry was replaced, ignore deletion */ + if (tmp != hinfo) + return -ENOENT; + + list_del_rcu(&hinfo->list); + synchronize_net(); + + return 0; +} +EXPORT_SYMBOL(seg6_hmac_info_del); + +static void seg6_hmac_info_flush(struct net *net) +{ + struct seg6_pernet_data *sdata = seg6_pernet(net); + struct seg6_hmac_info *hinfo; + + seg6_pernet_lock(net); + while ((hinfo = list_first_or_null_rcu(&sdata->hmac_infos, + struct seg6_hmac_info, + list)) != NULL) { + list_del_rcu(&hinfo->list); + seg6_pernet_unlock(net); + synchronize_net(); + kfree(hinfo); + seg6_pernet_lock(net); + } + + seg6_pernet_unlock(net); +} + +int seg6_push_hmac(struct net *net, struct in6_addr *saddr, + struct ipv6_sr_hdr *srh) +{ + struct seg6_hmac_info *hinfo; + int err = -ENOENT; + struct sr6_tlv_hmac *tlv; + + tlv = seg6_get_tlv(srh, SR6_TLV_HMAC); + if (!tlv) + return -EINVAL; + + rcu_read_lock(); + + hinfo = seg6_hmac_info_lookup(net, be32_to_cpu(tlv->hmackeyid)); + if (!hinfo) + goto out; + + memset(tlv->hmac, 0, SEG6_HMAC_FIELD_LEN); + err = seg6_hmac_compute(hinfo, srh, saddr, tlv->hmac); + +out: + rcu_read_unlock(); + return err; +} +EXPORT_SYMBOL(seg6_push_hmac); + +static int seg6_hmac_init_ring(void) +{ + int i; + + hmac_ring = alloc_percpu(char *); + + if (!hmac_ring) + return -ENOMEM; + + for_each_possible_cpu(i) { + char *ring = kzalloc(SEG6_HMAC_RING_SIZE, GFP_KERNEL); + + if (!ring) + return -ENOMEM; + + *per_cpu_ptr(hmac_ring, i) = ring; + } + + return 0; +} + +static int seg6_hmac_init_algo(void) +{ + int i, alg_count, cpu; + struct seg6_hmac_algo *algo; + struct crypto_shash *tfm; + struct shash_desc *shash; + + alg_count = sizeof(hmac_algos) / sizeof(struct seg6_hmac_algo); + + for (i = 0; i < alg_count; i++) { + int shsize; + struct crypto_shash **p_tfm; + + algo = &hmac_algos[i]; + algo->tfms = alloc_percpu(struct crypto_shash *); + if (!algo->tfms) + return -ENOMEM; + + for_each_possible_cpu(cpu) { + tfm = crypto_alloc_shash(algo->name, 0, GFP_KERNEL); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + p_tfm = per_cpu_ptr(algo->tfms, cpu); + *p_tfm = tfm; + } + + p_tfm = this_cpu_ptr(algo->tfms); + tfm = *p_tfm; + + shsize = sizeof(*shash) + crypto_shash_descsize(tfm); + + algo->shashs = alloc_percpu(struct shash_desc *); + if (!algo->shashs) + return -ENOMEM; + + for_each_possible_cpu(cpu) { + shash = kzalloc(shsize, GFP_KERNEL); + if (!shash) + return -ENOMEM; + *per_cpu_ptr(algo->shashs, cpu) = shash; + } + } + + return 0; +} + +int __init seg6_hmac_init(void) +{ + int ret; + + ret = seg6_hmac_init_ring(); + if (ret < 0) + goto out; + + ret = seg6_hmac_init_algo(); + +out: + return ret; +} + +int __net_init seg6_hmac_net_init(struct net *net) +{ + struct seg6_pernet_data *sdata = seg6_pernet(net); + + INIT_LIST_HEAD(&sdata->hmac_infos); + return 0; +} + +void __exit seg6_hmac_exit(void) +{ + int i, alg_count, cpu; + struct seg6_hmac_algo *algo = NULL; + + for_each_possible_cpu(i) { + char *ring = *per_cpu_ptr(hmac_ring, i); + + kfree(ring); + } + free_percpu(hmac_ring); + + alg_count = sizeof(hmac_algos) / sizeof(struct seg6_hmac_algo); + for (i = 0; i < alg_count; i++) { + algo = &hmac_algos[i]; + for_each_possible_cpu(cpu) { + struct crypto_shash *tfm; + struct shash_desc *shash; + + shash = *per_cpu_ptr(algo->shashs, cpu); + kfree(shash); + tfm = *per_cpu_ptr(algo->tfms, cpu); + crypto_free_shash(tfm); + } + free_percpu(algo->tfms); + free_percpu(algo->shashs); + } +} + +void __net_exit seg6_hmac_net_exit(struct net *net) +{ + seg6_hmac_info_flush(net); +}