c4acac27f29146828d8f96daf628c8a97681ba17
[libradsec.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 <ctype.h>
8 #include <errno.h>
9 #include <nettle/sha.h>
10 #include <nettle/hmac.h>
11
12 #include <regex.h>
13 #include <pthread.h>
14 #include <sys/time.h>
15 #include "radsecproxy.h"
16 #include "debug.h"
17
18 #include "fticks.h"
19
20 static void
21 _format_hash(const uint8_t *hash, size_t out_len, uint8_t *out)
22 {
23     int ir, iw;
24
25     for (ir = 0, iw = 0; iw <= out_len - 3; ir++, iw += 2)
26         sprintf((char *) out + iw, "%02x", hash[ir % SHA256_DIGEST_SIZE]);
27 }
28
29 static void
30 _hash(const uint8_t *in,
31       const uint8_t *key,
32       size_t out_len,
33       uint8_t *out)
34 {
35     if (key == NULL) {
36         struct sha256_ctx ctx;
37         uint8_t hash[SHA256_DIGEST_SIZE];
38
39         sha256_init(&ctx);
40         sha256_update(&ctx, strlen((char *) in), in);
41         sha256_digest(&ctx, sizeof(hash), hash);
42         _format_hash(hash, out_len, out);
43     }
44     else {
45         struct hmac_sha256_ctx ctx;
46         uint8_t hash[SHA256_DIGEST_SIZE];
47
48         hmac_sha256_set_key(&ctx, strlen((char *) key), key);
49         hmac_sha256_update(&ctx, strlen((char *) in), in);
50         hmac_sha256_digest(&ctx, sizeof(hash), hash);
51         _format_hash(hash, out_len, out);
52     }
53 }
54
55 int
56 fticks_configure(struct options *options,
57                  uint8_t **reportingp,
58                  uint8_t **macp,
59                  uint8_t **keyp)
60 {
61     int r = 0;
62     const char *reporting = (const char *) *reportingp;
63     const char *mac = (const char *) *macp;
64
65     if (reporting == NULL)
66         goto out;
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 (mac == NULL)
81         goto out;
82     if (strcasecmp(mac, "Static") == 0)
83         options->fticks_mac = RSP_FTICKS_MAC_STATIC;
84     else if (strcasecmp(mac, "Original") == 0)
85         options->fticks_mac = RSP_FTICKS_MAC_ORIGINAL;
86     else if (strcasecmp(mac, "VendorHashed") == 0)
87         options->fticks_mac = RSP_FTICKS_MAC_VENDOR_HASHED;
88     else if (strcasecmp(mac, "VendorKeyHashed") == 0)
89         options->fticks_mac = RSP_FTICKS_MAC_VENDOR_KEY_HASHED;
90     else if (strcasecmp(mac, "FullyHashed") == 0)
91         options->fticks_mac = RSP_FTICKS_MAC_FULLY_HASHED;
92     else if (strcasecmp(mac, "FullyKeyHashed") == 0)
93         options->fticks_mac = RSP_FTICKS_MAC_FULLY_KEY_HASHED;
94     else {
95         debugx(1, DBG_ERR, "config error: invalid FTicksMAC value: %s", mac);
96         r = 1;
97         goto out;
98     }
99
100     if (*keyp == NULL
101         && (options->fticks_mac == RSP_FTICKS_MAC_VENDOR_KEY_HASHED
102             || options->fticks_mac == RSP_FTICKS_MAC_FULLY_KEY_HASHED)) {
103         debugx(1, DBG_ERR,
104                "config error: FTicksMAC %s requires an FTicksKey", mac);
105         options->fticks_mac = RSP_FTICKS_MAC_STATIC;
106         r = 1;
107         goto out;
108     }
109
110     if (*keyp != NULL)
111         options->fticks_key = *keyp;
112
113 out:
114     if (*reportingp != NULL) {
115         free(*reportingp);
116         *reportingp = NULL;
117     }
118     if (*macp != NULL) {
119         free(*macp);
120         *macp = NULL;
121     }
122     return r;
123 }
124
125 /** Hash the Ethernet MAC address in \a IN, keying a HMAC with \a KEY
126     unless \a KEY is NULL.  If \a KEY is null \a IN is hashed with an
127     ordinary cryptographic hash function such as SHA-2.
128
129     \a IN and \a KEY are NULL terminated strings.
130
131     \a IN is supposed to be an Ethernet MAC address and is sanitised
132     by lowercasing it, removing all but [0-9a-f] and truncating it at
133     the first ';' found.  The truncation is done because RADIUS
134     supposedly has a praxis of tacking on SSID to the MAC address in
135     Calling-Station-Id.
136
137     \return 0 on success, -ENOMEM on out of memory.
138 */
139 int
140 fticks_hashmac(const uint8_t *in,
141                const uint8_t *key,
142                size_t out_len,
143                uint8_t *out)
144 {
145     uint8_t *in_copy = NULL;
146     uint8_t *p = NULL;
147     int i;
148
149     in_copy = calloc(1, strlen((const char *) in) + 1);
150     if (in_copy == NULL)
151         return -ENOMEM;
152
153     /* Sanitise and lowercase 'in' into 'in_copy'.  */
154     for (i = 0, p = in_copy; in[i] != '\0'; i++) {
155         if (in[i] == ';') {
156             *p++ = '\0';
157             break;
158         }
159         if (in[i] >= '0' && in[i] <= '9') {
160             *p++ = in[i];
161         }
162         else if (tolower(in[i]) >= 'a' && tolower(in[i]) <= 'f') {
163             *p++ = tolower(in[i]);
164         }
165     }
166
167     _hash(in_copy, key, out_len, out);
168     free(in_copy);
169     return 0;
170 }
171
172 void
173 fticks_log(const struct options *options,
174            const struct client *client,
175            const struct radmsg *msg,
176            const struct rqout *rqout)
177 {
178     uint8_t *username = NULL;
179     uint8_t *realm = NULL;
180     uint8_t visinst[8+40+1+1]; /* Room for 40 octets of VISINST.  */
181     uint8_t *macin = NULL;
182     uint8_t macout[2*32+1]; /* Room for ASCII representation of SHA256.  */
183
184     username = radattr2ascii(radmsg_gettype(rqout->rq->msg,
185                                             RAD_Attr_User_Name));
186     if (username != NULL) {
187         realm = (uint8_t *) strrchr((char *) username, '@');
188         if (realm != NULL)
189             realm++;
190     }
191     if (realm == NULL)
192         realm = (uint8_t *) "";
193
194     memset(visinst, 0, sizeof(visinst));
195     if (options->fticks_reporting == RSP_FTICKS_REPORTING_FULL) {
196         snprintf((char *) visinst, sizeof(visinst), "VISINST=%s#",
197                  client->conf->name);
198     }
199
200     memset(macout, 0, sizeof(macout));
201     if (options->fticks_mac == RSP_FTICKS_MAC_STATIC) {
202         strncpy((char *) macout, "undisclosed", sizeof(macout) - 1);
203     }
204     else {
205         macin = radattr2ascii(radmsg_gettype(rqout->rq->msg,
206                                              RAD_Attr_Calling_Station_Id));
207         if (macin) {
208             switch (options->fticks_mac)
209             {
210             case RSP_FTICKS_MAC_ORIGINAL:
211                 memcpy(macout, macin, sizeof(macout));
212                 break;
213             case RSP_FTICKS_MAC_VENDOR_HASHED:
214                 memcpy(macout, macin, 9);
215                 fticks_hashmac(macin, NULL, sizeof(macout) - 9, macout + 9);
216                 break;
217             case RSP_FTICKS_MAC_VENDOR_KEY_HASHED:
218                 memcpy(macout, macin, 9);
219                 /* We are hashing the first nine octets too for easier
220                  * correlation between vendor-key-hashed and
221                  * fully-key-hashed log records.  This opens up for a
222                  * known plaintext attack on the key but the
223                  * consequences of that is considered outweighed by
224                  * the convenience gained.  */
225                 fticks_hashmac(macin, options->fticks_key,
226                                sizeof(macout) - 9, macout + 9);
227                 break;
228             case RSP_FTICKS_MAC_FULLY_HASHED:
229                 fticks_hashmac(macin, NULL, sizeof(macout), macout);
230                 break;
231             case RSP_FTICKS_MAC_FULLY_KEY_HASHED:
232                 fticks_hashmac(macin, options->fticks_key, sizeof(macout),
233                                macout);
234                 break;
235             default:
236                 debugx(2, DBG_ERR, "invalid fticks mac configuration: %d",
237                        options->fticks_mac);
238             }
239         }
240     }
241     debug(0xff,
242           "F-TICKS/eduroam/1.0#REALM=%s#VISCOUNTRY=%s#%sCSI=%s#RESULT=%s#",
243           realm,
244           client->conf->fticks_viscountry,
245           visinst,
246           macout,
247           msg->code == RAD_Access_Accept ? "OK" : "FAIL");
248     if (macin != NULL)
249         free(macin);
250     if (username != NULL)
251         free(username);
252 }
253
254 /* Local Variables: */
255 /* c-file-style: "stroustrup" */
256 /* End: */