Hash full MAC even for VendorHashed and VendorKeyHashed.
[radsecproxy.git] / fticks.c
1 /* Copyright (C) 2011 NORDUnet A/S
2  * See LICENSE for information about licensing.
3  */
4
5 #include <stdio.h>              /* For sprintf().  */
6 #include <string.h>
7 #include <nettle/sha.h>
8 #include <nettle/hmac.h>
9
10 #include <regex.h>
11 #include <pthread.h>
12 #include <sys/time.h>
13 #include "list.h"
14 #include "radsecproxy.h"
15 #include "debug.h"
16
17 #include "fticks.h"
18
19 static void
20 format_hash(const uint8_t *hash, size_t out_len, uint8_t *out)
21 {
22     int ir, iw;
23
24     for (ir = 0, iw = 0; iw <= out_len - 3; ir++, iw += 2)
25         sprintf((char *) out + iw, "%02x", hash[ir % SHA256_DIGEST_SIZE]);
26 }
27
28 static void
29 hash(const uint8_t *in,
30      const uint8_t *key,
31      size_t out_len,
32      uint8_t *out)
33 {
34     if (key == NULL) {
35         struct sha256_ctx ctx;
36         uint8_t hash[SHA256_DIGEST_SIZE];
37
38         sha256_init(&ctx);
39         sha256_update(&ctx, strlen((char *) in), in);
40         sha256_digest(&ctx, sizeof(hash), hash);
41         format_hash(hash, out_len, out);
42     }
43     else {
44         struct hmac_sha256_ctx ctx;
45         uint8_t hash[SHA256_DIGEST_SIZE];
46
47         hmac_sha256_set_key(&ctx, strlen((char *) key), key);
48         hmac_sha256_update(&ctx, strlen((char *) in), in);
49         hmac_sha256_digest(&ctx, sizeof(hash), hash);
50         format_hash(hash, out_len, out);
51     }
52 }
53
54 int
55 fticks_configure(struct options *options,
56                  uint8_t **reportingp,
57                  uint8_t **macp,
58                  uint8_t **keyp)
59 {
60     int r = 0;
61     const char *reporting = (const char *) *reportingp;
62     const char *mac = (const char *) *macp;
63
64     if (reporting == NULL)
65         goto out;
66
67     if (strcasecmp(reporting, "None") == 0)
68         options->fticks_reporting = RSP_FTICKS_REPORTING_NONE;
69     else if (strcasecmp(reporting, "Basic") == 0)
70         options->fticks_reporting = RSP_FTICKS_REPORTING_BASIC;
71     else if (strcasecmp(reporting, "Full") == 0)
72         options->fticks_reporting = RSP_FTICKS_REPORTING_FULL;
73     else {
74         debugx(1, DBG_ERR, "config error: invalid FTicksReporting value: %s",
75                reporting);
76         r = 1;
77         goto out;
78     }
79
80     if (strcasecmp(mac, "Static") == 0)
81         options->fticks_mac = RSP_FTICKS_MAC_STATIC;
82     else if (strcasecmp(mac, "Original") == 0)
83         options->fticks_mac = RSP_FTICKS_MAC_ORIGINAL;
84     else if (strcasecmp(mac, "VendorHashed") == 0)
85         options->fticks_mac = RSP_FTICKS_MAC_VENDOR_HASHED;
86     else if (strcasecmp(mac, "VendorKeyHashed") == 0)
87         options->fticks_mac = RSP_FTICKS_MAC_VENDOR_KEY_HASHED;
88     else if (strcasecmp(mac, "FullyHashed") == 0)
89         options->fticks_mac = RSP_FTICKS_MAC_FULLY_HASHED;
90     else if (strcasecmp(mac, "FullyKeyHashed") == 0)
91         options->fticks_mac = RSP_FTICKS_MAC_FULLY_KEY_HASHED;
92     else {
93         debugx(1, DBG_ERR, "config error: invalid FTicksMAC value: %s", mac);
94         r = 1;
95         goto out;
96     }
97
98     if (*keyp == NULL
99         && (options->fticks_mac == RSP_FTICKS_MAC_VENDOR_KEY_HASHED
100             || options->fticks_mac == RSP_FTICKS_MAC_FULLY_KEY_HASHED)) {
101         debugx(1, DBG_ERR,
102                "config error: FTicksMAC %s requires an FTicksKey", mac);
103         options->fticks_mac = RSP_FTICKS_MAC_STATIC;
104         r = 1;
105         goto out;
106     }
107
108     if (*keyp != NULL)
109         options->fticks_key = *keyp;
110
111 out:
112     if (*reportingp != NULL) {
113         free(*reportingp);
114         *reportingp = NULL;
115     }
116     if (*macp != NULL) {
117         free(*macp);
118         *macp = NULL;
119     }
120     return r;
121 }
122
123 /** Hash the MAC in \a IN, keying with \a KEY if it's not NULL.
124
125     \a IN and \a KEY are NULL terminated strings.
126
127     \a IN is sanitised by lowercasing it, removing all but [0-9a-f]
128     and truncating it at first ';' (due to RADIUS praxis with tacking
129     on SSID to MAC in Calling-Station-Id).  */
130 void
131 fticks_hashmac(const uint8_t *in,
132                const uint8_t *key,
133                size_t out_len,
134                uint8_t *out)
135 {
136     /* TODO: lowercase */
137     /* TODO: s/[!0-9a-f]//1 */
138     /* TODO: truncate after first ';', if any */
139
140     hash(in, key, out_len, out);
141 }
142
143 void
144 fticks_log(const struct options *options,
145            const struct client *client,
146            const struct radmsg *msg,
147            const struct rqout *rqout)
148 {
149     uint8_t *username = NULL;
150     uint8_t *realm = NULL;
151     uint8_t visinst[8+40+1+1]; /* Room for 40 octets of VISINST.  */
152     uint8_t *macin = NULL;
153     uint8_t macout[2*32+1]; /* Room for ASCII representation of SHA256.  */
154
155     username = radattr2ascii(radmsg_gettype(rqout->rq->msg,
156                                             RAD_Attr_User_Name));
157     if (username != NULL) {
158         realm = (uint8_t *) strrchr((char *) username, '@');
159         if (realm != NULL)
160             realm++;
161     }
162     if (realm == NULL)
163         realm = (uint8_t *) "";
164
165     memset(visinst, 0, sizeof(visinst));
166     if (options->fticks_reporting == RSP_FTICKS_REPORTING_FULL) {
167         snprintf((char *) visinst, sizeof(visinst), "VISINST=%s#",
168                  client->conf->name);
169     }
170
171     memset(macout, 0, sizeof(macout));
172     if (options->fticks_mac == RSP_FTICKS_MAC_STATIC) {
173         strncpy((char *) macout, "undisclosed", sizeof(macout) - 1);
174     }
175     else {
176         macin = radattr2ascii(radmsg_gettype(rqout->rq->msg,
177                                              RAD_Attr_Calling_Station_Id));
178         if (macin) {
179             switch (options->fticks_mac)
180             {
181             case RSP_FTICKS_MAC_ORIGINAL:
182                 memcpy(macout, macin, sizeof(macout));
183                 break;
184             case RSP_FTICKS_MAC_VENDOR_HASHED:
185                 memcpy(macout, macin, 9);
186                 fticks_hashmac(macin, NULL, sizeof(macout) - 9, macout + 9);
187                 break;
188             case RSP_FTICKS_MAC_VENDOR_KEY_HASHED:
189                 memcpy(macout, macin, 9);
190                 /* We are hashing the first nine octets too for easier
191                  * correlation between vendor-key-hashed and
192                  * fully-key-hashed log records.  This opens up for a
193                  * known plaintext attack on the key but the
194                  * consequences of that is considered outweighed by
195                  * the convenience gained.  */
196                 fticks_hashmac(macin, options->fticks_key,
197                                sizeof(macout) - 9, macout + 9);
198                 break;
199             case RSP_FTICKS_MAC_FULLY_HASHED:
200                 fticks_hashmac(macin, NULL, sizeof(macout), macout);
201                 break;
202             case RSP_FTICKS_MAC_FULLY_KEY_HASHED:
203                 fticks_hashmac(macin, options->fticks_key, sizeof(macout),
204                                macout);
205                 break;
206             default:
207                 debugx(2, DBG_ERR, "invalid fticks mac configuration: %d",
208                        options->fticks_mac);
209             }
210         }
211     }
212     debug(0xff,
213           "F-TICKS/eduroam/1.0#REALM=%s#VISCOUNTRY=%s#%sCSI=%s#RESULT=%s#",
214           realm,
215           client->conf->fticks_viscountry,
216           visinst,
217           macout,
218           msg->code == RAD_Access_Accept ? "OK" : "FAIL");
219     if (macin != NULL)
220         free(macin);
221     if (username != NULL)
222         free(username);
223 }
224
225 /* Local Variables: */
226 /* c-file-style: "stroustrup" */
227 /* End: */