8.5 MD5选项

来源:互联网 发布:手机网络配置怎么设置 编辑:程序博客网 时间:2024/05/29 13:16

8.5.1 选项功能

  TCP MD5选项用于强化BGP协议的安全性,其基本原理是在TCP报文段的选项中携带MD5摘要。这个摘要的行为类似于这个报文的签名,其中包含这只有通信双方才能理解的信息。如果BGP协议使用TCP作为其传输层协议,使用MD5选项会有效减少安全隐患。

8.5.2 协议规范

  TCP MD5选项的规范由RFC 2385提出。

  每一个TCP报文段都应该携带MD5选项(包含一个16字节的MD5 digest)。MD5算法的输入数据如下(严格按照顺序):

(1)TCP伪首部(源IP,目的IP,填充0的协议号,报文长度)

(2)TCP首部,不包含选项,checksum计为0

(3)TCP数据段(如果有)

(4)密钥或口令,这个需要TCP通信双方和连接规范都知晓

  接收方收到TCP报文时,必须根据报文的信息以及自己的密钥来计算digest,并与报文中的digest进行比较。如果比较失败则必须丢弃报文,并且不能产生任何响应。这样就大大增加了攻击者通过伪造TCP报文实施对BGP协议的攻击的难度。

8.5.3 开启方法

  Linux内核需要开启CONFIG_TCP_MD5SIG编译选项才能支持TCP MD5选项功能。应用进程还需要使用TCP_MD5SIG socket选项导入密钥:

struct tcp_md5sig cmd;...setsockopt(sockfd, SOL_TCP, TCP_MD5SIG,  &cmd, sizeof(cmd));
  其中struct tcp_md5sig的定义为:
191 #define TCP_MD5SIG_MAXKEYLEN    80192 193 struct tcp_md5sig {194     struct __kernel_sockaddr_storage tcpm_addr; /* address associated */195     __u16   __tcpm_pad1;                /* zero */196     __u16   tcpm_keylen;                /* key length */197     __u32   __tcpm_pad2;                /* zero */198     __u8    tcpm_key[TCP_MD5SIG_MAXKEYLEN];     /* key (binary) */199 };

  其中tcpm_addr是要通信的服务器的地址(IP地址、端口等),如果sockfd要与N个机器进行通信则需要调用N此setsockopt系统调用来导入相应的地址-密钥对。举个例子,如果A要与B通信,则A需要调用setsockopt来导入B的地址和一个密钥Key,而B也需要调用setsockopt来导入A的地址和与A相同的密钥Key,然后双方才能使用MD5选项进行通信。

8.5.4 内核实现

  TCP_MD5SIG socket选项对应的内核代码为:

2371 static int do_tcp_setsockopt(struct sock *sk, int level,2372         int optname, char __user *optval, unsigned int optlen)2373 {...2605 #ifdef CONFIG_TCP_MD5SIG2606     case TCP_MD5SIG:2607         /* Read the IP->Key mappings from userspace */2608         err = tp->af_specific->md5_parse(sk, optval, optlen); //指向tcp_v4_parse_md5_keys函数2609         break;2610 #endif...
  tcp_v4_parse_md5_keys用于导入MD5签名的密钥(key):

1083 static int tcp_v4_parse_md5_keys(struct sock *sk, char __user *optval,1084                  int optlen)1085 {  1086     struct tcp_md5sig cmd;1087     struct sockaddr_in *sin = (struct sockaddr_in *)&cmd.tcpm_addr;1088    1089     if (optlen < sizeof(cmd))1090         return -EINVAL;1091    1092     if (copy_from_user(&cmd, optval, sizeof(cmd)))1093         return -EFAULT;1094 1095     if (sin->sin_family != AF_INET)1096         return -EINVAL;1097 1098     if (!cmd.tcpm_key || !cmd.tcpm_keylen)1099         return tcp_md5_do_del(sk, (union tcp_md5_addr *)&sin->sin_addr.s_addr,1100                       AF_INET); //删除key1101    1102     if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN)1103         return -EINVAL;1104 1105     return tcp_md5_do_add(sk, (union tcp_md5_addr *)&sin->sin_addr.s_addr,1106                   AF_INET, cmd.tcpm_key, cmd.tcpm_keylen,1107                   GFP_KERNEL);1108 }
  tcp_md5_do_add和tcp_md5_do_del用于添加和删除key:

 998 int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr, 999            int family, const u8 *newkey, u8 newkeylen, gfp_t gfp)1000 {1001     /* Add Key to the list */1002     struct tcp_md5sig_key *key;1003     struct tcp_sock *tp = tcp_sk(sk);1004     struct tcp_md5sig_info *md5sig;1005 1006     key = tcp_md5_do_lookup(sk, addr, family);1007     if (key) { //如果有现成的1008         /* Pre-existing entry - just update that one. */1009         memcpy(key->key, newkey, newkeylen); //更新之1010         key->keylen = newkeylen;1011         return 0;1012     }1013 1014     md5sig = rcu_dereference_protected(tp->md5sig_info,1015                        sock_owned_by_user(sk));1016     if (!md5sig) {1017         md5sig = kmalloc(sizeof(*md5sig), gfp);1018         if (!md5sig)1019             return -ENOMEM;1020 1021         sk_nocaps_add(sk, NETIF_F_GSO_MASK);1022         INIT_HLIST_HEAD(&md5sig->head);1023         rcu_assign_pointer(tp->md5sig_info, md5sig);1024     }1025 1026     key = sock_kmalloc(sk, sizeof(*key), gfp);1027     if (!key)1028         return -ENOMEM;1029     if (hlist_empty(&md5sig->head) && !tcp_alloc_md5sig_pool(sk)) {1030         sock_kfree_s(sk, key, sizeof(*key));1031         return -ENOMEM;1032     }1033 1034     memcpy(key->key, newkey, newkeylen); //导入密钥1035     key->keylen = newkeylen;1036     key->family = family;1037     memcpy(&key->addr, addr,1038            (family == AF_INET6) ? sizeof(struct in6_addr) :1039                       sizeof(struct in_addr)); //导入地址信息1040     hlist_add_head_rcu(&key->node, &md5sig->head);1041     return 0;1042 }1043 EXPORT_SYMBOL(tcp_md5_do_add);1044 1045 int tcp_md5_do_del(struct sock *sk, const union tcp_md5_addr *addr, int family)1046 {1047     struct tcp_sock *tp = tcp_sk(sk);1048     struct tcp_md5sig_key *key;1049     struct tcp_md5sig_info *md5sig;1050 1051     key = tcp_md5_do_lookup(sk, addr, family);1052     if (!key)1053         return -ENOENT;1054     hlist_del_rcu(&key->node);1055     atomic_sub(sizeof(*key), &sk->sk_omem_alloc);1056     kfree_rcu(key, rcu);1057     md5sig = rcu_dereference_protected(tp->md5sig_info,1058                        sock_owned_by_user(sk));1059     if (hlist_empty(&md5sig->head))1060         tcp_free_md5sig_pool();1061     return 0;1062 }
  在TCP发送数据前构建选项信息(tcp_syn_options、tcp_established_options、tcp_synack_options)时都会执行类似下面的代码:
#ifdef CONFIG_TCP_MD5SIG 507     *md5 = tp->af_specific->md5_lookup(sk, sk); //指向tcp_v4_md5_lookup 508     if (*md5) { 509         opts->options |= OPTION_MD5; 510         remaining -= TCPOLEN_MD5SIG_ALIGNED; 511     } 512 #else 513     *md5 = NULL; 514 #endif
  tcp_v4_md5_lookup用于查找MD5签名的key:
 949 struct tcp_md5sig_key *tcp_md5_do_lookup(struct sock *sk, 950                      const union tcp_md5_addr *addr, 951                      int family) 952 { 953     struct tcp_sock *tp = tcp_sk(sk); 954     struct tcp_md5sig_key *key; 955     unsigned int size = sizeof(struct in_addr); 956     struct tcp_md5sig_info *md5sig; 957  958     /* caller either holds rcu_read_lock() or socket lock */ 959     md5sig = rcu_dereference_check(tp->md5sig_info, 960                        sock_owned_by_user(sk) || 961                        lockdep_is_held(&sk->sk_lock.slock)); 962     if (!md5sig) 963         return NULL; 964 #if IS_ENABLED(CONFIG_IPV6) 965     if (family == AF_INET6) 966         size = sizeof(struct in6_addr); 967 #endif 968     hlist_for_each_entry_rcu(key, &md5sig->head, node) { 969         if (key->family != family) 970             continue;    971         if (!memcmp(&key->addr, addr, size)) //地址匹配 972             return key; 973     } 974     return NULL; 975 } 976 EXPORT_SYMBOL(tcp_md5_do_lookup); 977  978 struct tcp_md5sig_key *tcp_v4_md5_lookup(struct sock *sk, 979                      struct sock *addr_sk) 980 {    981     union tcp_md5_addr *addr; 982      983     addr = (union tcp_md5_addr *)&inet_sk(addr_sk)->inet_daddr; 984     return tcp_md5_do_lookup(sk, addr, AF_INET); 985 }
  可见如果应用进程导入了key,在构建选项时就会找到。选项信息构建完毕后,tcp_options_write函数会将选项信息写入TCP报头中:
 409 static void tcp_options_write(__be32 *ptr, struct tcp_sock *tp, 410                   struct tcp_out_options *opts) 411 { 412     u16 options = opts->options;    /* mungable copy */ 413  414     if (unlikely(OPTION_MD5 & options)) { 415         *ptr++ = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) | 416                    (TCPOPT_MD5SIG << 8) | TCPOLEN_MD5SIG); 417         /* overload cookie hash location */ 418         opts->hash_location = (__u8 *)ptr; //hash_location指向digest所在内存的首地址 419         ptr += 4; //digest大小为16个字节 420     }...
  tcp_options_write并没有写入MD5 digest,这个工作在后面完成:
 828 static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, 829                 gfp_t gfp_mask) 830 {... 870     if (unlikely(tcb->tcp_flags & TCPHDR_SYN)) 871         tcp_options_size = tcp_syn_options(sk, skb, &opts, &md5); 872     else 873         tcp_options_size = tcp_established_options(sk, skb, &opts, 874                                &md5);... 925     tcp_options_write((__be32 *)(th + 1), tp, &opts);... 929 #ifdef CONFIG_TCP_MD5SIG 930     /* Calculate the MD5 hash, as we have all we need now */ 931     if (md5) { 932         sk_nocaps_add(sk, NETIF_F_GSO_MASK); 933         tp->af_specific->calc_md5_hash(opts.hash_location, 934                            md5, sk, NULL, skb); //指向tcp_v4_md5_hash_skb 935     } 936 #endif...
  tcp_v4_md5_hash_skb函数计算MD5 digest:
1165 int tcp_v4_md5_hash_skb(char *md5_hash, struct tcp_md5sig_key *key,1166             const struct sock *sk, const struct request_sock *req,1167             const struct sk_buff *skb)1168 {1169     struct tcp_md5sig_pool *hp;1170     struct hash_desc *desc;1171     const struct tcphdr *th = tcp_hdr(skb);1172     __be32 saddr, daddr;1173 1174     if (sk) {1175         saddr = inet_sk(sk)->inet_saddr;1176         daddr = inet_sk(sk)->inet_daddr;1177     } else if (req) {1178         saddr = inet_rsk(req)->loc_addr;1179         daddr = inet_rsk(req)->rmt_addr;1180     } else {1181         const struct iphdr *iph = ip_hdr(skb);1182         saddr = iph->saddr; 1183         daddr = iph->daddr;1184     }1185 1186     hp = tcp_get_md5sig_pool();1187     if (!hp)1188         goto clear_hash_noput;1189     desc = &hp->md5_desc;1190 1191     if (crypto_hash_init(desc))1192         goto clear_hash;1193 1194     if (tcp_v4_md5_hash_pseudoheader(hp, daddr, saddr, skb->len)) //伪首部1195         goto clear_hash;1196     if (tcp_md5_hash_header(hp, th)) //TCP头1197         goto clear_hash;1198     if (tcp_md5_hash_skb_data(hp, skb, th->doff << 2)) //TCP数据1199         goto clear_hash;1200     if (tcp_md5_hash_key(hp, key)) //key1201         goto clear_hash;1202     if (crypto_hash_final(desc, md5_hash)) //将MD5 digest写入1203         goto clear_hash;1204 1205     tcp_put_md5sig_pool();1206     return 0;1207 1208 clear_hash:1209     tcp_put_md5sig_pool();1210 clear_hash_noput:1211     memset(md5_hash, 0, 16);1212     return 1;1213 }
  TCP在收到报文时会在入口函数检查MD5选项:
1800 int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)1801 {1802     struct sock *rsk;1803 #ifdef CONFIG_TCP_MD5SIG1804     /*1805      * We really want to reject the packet as early as possible1806      * if:1807      *  o We're expecting an MD5'd packet and this is no MD5 tcp option1808      *  o There is an MD5 option and we're not expecting one1809      */1810     if (tcp_v4_inbound_md5_hash(sk, skb))1811         goto discard;1812 #endif...
  tcp_v4_inbound_md5_hash函数返回false时检查通过:
1216 static bool tcp_v4_inbound_md5_hash(struct sock *sk, const struct sk_buff *skb)1217 {1218     /*1219      * This gets called for each TCP segment that arrives1220      * so we want to be efficient.1221      * We have 3 drop cases:1222      * o No MD5 hash and one expected.1223      * o MD5 hash and we're not expecting one.1224      * o MD5 hash and its wrong.1225      */1226     const __u8 *hash_location = NULL;1227     struct tcp_md5sig_key *hash_expected;1228     const struct iphdr *iph = ip_hdr(skb);1229     const struct tcphdr *th = tcp_hdr(skb);1230     int genhash;1231     unsigned char newhash[16];1232 1233     hash_expected = tcp_md5_do_lookup(sk, (union tcp_md5_addr *)&iph->saddr,1234                       AF_INET); //根据源IP地址查找key1235     hash_location = tcp_parse_md5sig_option(th); //找到MD5 digest在TCP报头中的位置1236 1237     /* We've parsed the options - do we have a hash? */1238     if (!hash_expected && !hash_location) //进程没有导入key信息且没有找到MD5选项1239         return false; //OK1240 1241     if (hash_expected && !hash_location) { //进程导入了key信息且没有找到MD5选项1242         NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPMD5NOTFOUND);1243         return true; //接收端期望有MD5选项而发送端没有,不行1244     }1245 1246     if (!hash_expected && hash_location) { //进程没有导入key信息但找到了MD5选项1247         NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPMD5UNEXPECTED);1248         return true; //接收端不期望有MD5选项而发送端有,也不行1249     }1250 1251     /* Okay, so this is hash_expected and hash_location -1252      * so we need to calculate the checksum.1253      */1254     genhash = tcp_v4_md5_hash_skb(newhash,1255                       hash_expected,1256                       NULL, NULL, skb); //使用key计算digest1257 1258     if (genhash || memcmp(hash_location, newhash, 16) != 0) { //生成digest失败或digest不一样则检查不通过,丢弃之1259         net_info_ratelimited("MD5 Hash failed for (%pI4, %d)->(%pI4, %d)%s\n",1260                      &iph->saddr, ntohs(th->source),1261                      &iph->daddr, ntohs(th->dest),1262                      genhash ? " tcp_v4_calc_md5_hash failed"1263                      : "");1264         return true;1265     }1266     return false;1267 }
  tcp_parse_md5sig_option用于解析MD5选项:
3635 #ifdef CONFIG_TCP_MD5SIG3636 /*  3637  * Parse MD5 Signature option3638  */         3639 const u8 *tcp_parse_md5sig_option(const struct tcphdr *th)3640 {                     3641     int length = (th->doff << 2) - sizeof(*th);3642     const u8 *ptr = (const u8 *)(th + 1);3643 3644     /* If the TCP option is too short, we can short cut */3645     if (length < TCPOLEN_MD5SIG)3646         return NULL;3647     3648     while (length > 0) {3649         int opcode = *ptr++;3650         int opsize;3651 3652         switch(opcode) {3653         case TCPOPT_EOL:3654             return NULL;3655         case TCPOPT_NOP:3656             length--;3657             continue;3658         default:3659             opsize = *ptr++;3660             if (opsize < 2 || opsize > length)3661                 return NULL;3662             if (opcode == TCPOPT_MD5SIG)3663                 return opsize == TCPOLEN_MD5SIG ? ptr : NULL;3664         }3665         ptr += opsize - 2;3666         length -= opsize;3667     }3668     return NULL;3669 }3670 EXPORT_SYMBOL(tcp_parse_md5sig_option);3671 #endif
  使用TCP MD5选项带来安全性的同时,由于需要计算MD5 digest会带来一些性能损耗,且每包都携带18字节的MD5选项字段也会降低数据发送效率。不过对于类似BGP这样对安全性要求较高的应用来说,这些代码应该是可以承受的。

0 0
原创粉丝点击