Cast to avoid warning.
[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 <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
68     if (strcasecmp(reporting, "None") == 0)
69         options->fticks_reporting = RSP_FTICKS_REPORTING_NONE;
70     else if (strcasecmp(reporting, "Basic") == 0)
71         options->fticks_reporting = RSP_FTICKS_REPORTING_BASIC;
72     else if (strcasecmp(reporting, "Full") == 0)
73         options->fticks_reporting = RSP_FTICKS_REPORTING_FULL;
74     else {
75         debugx(1, DBG_ERR, "config error: invalid FTicksReporting value: %s",
76                reporting);
77         r = 1;
78         goto out;
79     }
80
81     if (strcasecmp(mac, "Static") == 0)
82         options->fticks_mac = RSP_FTICKS_MAC_STATIC;
83     else if (strcasecmp(mac, "Original") == 0)
84         options->fticks_mac = RSP_FTICKS_MAC_ORIGINAL;
85     else if (strcasecmp(mac, "VendorHashed") == 0)
86         options->fticks_mac = RSP_FTICKS_MAC_VENDOR_HASHED;
87     else if (strcasecmp(mac, "VendorKeyHashed") == 0)
88         options->fticks_mac = RSP_FTICKS_MAC_VENDOR_KEY_HASHED;
89     else if (strcasecmp(mac, "FullyHashed") == 0)
90         options->fticks_mac = RSP_FTICKS_MAC_FULLY_HASHED;
91     else if (strcasecmp(mac, "FullyKeyHashed") == 0)
92         options->fticks_mac = RSP_FTICKS_MAC_FULLY_KEY_HASHED;
93     else {
94         debugx(1, DBG_ERR, "config error: invalid FTicksMAC value: %s", mac);
95         r = 1;
96         goto out;
97     }
98
99     if (*keyp == NULL
100         && (options->fticks_mac == RSP_FTICKS_MAC_VENDOR_KEY_HASHED
101             || options->fticks_mac == RSP_FTICKS_MAC_FULLY_KEY_HASHED)) {
102         debugx(1, DBG_ERR,
103                "config error: FTicksMAC %s requires an FTicksKey", mac);
104         options->fticks_mac = RSP_FTICKS_MAC_STATIC;
105         r = 1;
106         goto out;
107     }
108
109     if (*keyp != NULL)
110         options->fticks_key = *keyp;
111
112 out:
113     if (*reportingp != NULL) {
114         free(*reportingp);
115         *reportingp = NULL;
116     }
117     if (*macp != NULL) {
118         free(*macp);
119         *macp = NULL;
120     }
121     return r;
122 }
123
124 /** Hash the Ethernet MAC address in \a IN, keying a HMAC with \a KEY
125     unless \a KEY is NULL.  If \a KEY is null \a IN is hashed with an
126     ordinary cryptographic hash function such as SHA-2.
127
128     \a IN and \a KEY are NULL terminated strings.
129
130     \a IN is supposed to be an Ethernet MAC address and is sanitised
131     by lowercasing it, removing all but [0-9a-f] and truncating it at
132     the first ';' found.  The truncation is done because RADIUS
133     supposedly has a praxis of tacking on SSID to the MAC address in
134     Calling-Station-Id.
135
136     \return 0 on success, -ENOMEM on out of memory.
137 */
138 int
139 fticks_hashmac(const uint8_t *in,
140                const uint8_t *key,
141                size_t out_len,
142                uint8_t *out)
143 {
144     uint8_t *in_copy = NULL;
145     uint8_t *p = NULL;
146     int i;
147
148     in_copy = calloc(1, strlen((const char *) in) + 1);
149     if (in_copy == NULL)
150         return -ENOMEM;
151
152     /* Sanitise and lowercase 'in' into 'in_copy'.  */
153     for (i = 0, p = in_copy; in[i] != '\0'; i++) {
154         if (in[i] == ';') {
155             *p++ = '\0';
156             break;
157         }
158         if (in[i] >= '0' && in[i] <= '9') {
159             *p++ = in[i];
160         }
161         else if (tolower(in[i]) >= 'a' && tolower(in[i]) <= 'f') {
162             *p++ = tolower(in[i]);
163         }
164     }
165
166     _hash(in_copy, key, out_len, out);
167     free(in_copy);
168     return 0;
169 }
170
171 void
172 fticks_log(const struct options *options,
173            const struct client *client,
174            const struct radmsg *msg,
175            const struct rqout *rqout)
176 {
177     uint8_t *username = NULL;
178     uint8_t *realm = NULL;
179     uint8_t visinst[8+40+1+1]; /* Room for 40 octets of VISINST.  */
180     uint8_t *macin = NULL;
181     uint8_t macout[2*32+1]; /* Room for ASCII representation of SHA256.  */
182
183     username = radattr2ascii(radmsg_gettype(rqout->rq->msg,
184                                             RAD_Attr_User_Name));
185     if (username != NULL) {
186         realm = (uint8_t *) strrchr((char *) username, '@');
187         if (realm != NULL)
188             realm++;
189     }
190     if (realm == NULL)
191         realm = (uint8_t *) "";
192
193     memset(visinst, 0, sizeof(visinst));
194     if (options->fticks_reporting == RSP_FTICKS_REPORTING_FULL) {
195         snprintf((char *) visinst, sizeof(visinst), "VISINST=%s#",
196                  client->conf->name);
197     }
198
199     memset(macout, 0, sizeof(macout));
200     if (options->fticks_mac == RSP_FTICKS_MAC_STATIC) {
201         strncpy((char *) macout, "undisclosed", sizeof(macout) - 1);
202     }
203     else {
204         macin = radattr2ascii(radmsg_gettype(rqout->rq->msg,
205                                              RAD_Attr_Calling_Station_Id));
206         if (macin) {
207             switch (options->fticks_mac)
208             {
209             case RSP_FTICKS_MAC_ORIGINAL:
210                 memcpy(macout, macin, sizeof(macout));
211                 break;
212             case RSP_FTICKS_MAC_VENDOR_HASHED:
213                 memcpy(macout, macin, 9);
214                 fticks_hashmac(macin, NULL, sizeof(macout) - 9, macout + 9);
215                 break;
216             case RSP_FTICKS_MAC_VENDOR_KEY_HASHED:
217                 memcpy(macout, macin, 9);
218                 /* We are hashing the first nine octets too for easier
219                  * correlation between vendor-key-hashed and
220                  * fully-key-hashed log records.  This opens up for a
221                  * known plaintext attack on the key but the
222                  * consequences of that is considered outweighed by
223                  * the convenience gained.  */
224                 fticks_hashmac(macin, options->fticks_key,
225                                sizeof(macout) - 9, macout + 9);
226                 break;
227             case RSP_FTICKS_MAC_FULLY_HASHED:
228                 fticks_hashmac(macin, NULL, sizeof(macout), macout);
229                 break;
230             case RSP_FTICKS_MAC_FULLY_KEY_HASHED:
231                 fticks_hashmac(macin, options->fticks_key, sizeof(macout),
232                                macout);
233                 break;
234             default:
235                 debugx(2, DBG_ERR, "invalid fticks mac configuration: %d",
236                        options->fticks_mac);
237             }
238         }
239     }
240     debug(0xff,
241           "F-TICKS/eduroam/1.0#REALM=%s#VISCOUNTRY=%s#%sCSI=%s#RESULT=%s#",
242           realm,
243           client->conf->fticks_viscountry,
244           visinst,
245           macout,
246           msg->code == RAD_Access_Accept ? "OK" : "FAIL");
247     if (macin != NULL)
248         free(macin);
249     if (username != NULL)
250         free(username);
251 }
252
253 /* Local Variables: */
254 /* c-file-style: "stroustrup" */
255 /* End: */