Prepend static functions with underscore.
[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 Ethernet MAC address in \a IN, keying a HMAC with \a KEY
124     unless \a KEY is NULL.  If \a KEY is null \a IN is hashed with an
125     ordinary cryptographic hash function such as SHA-2.
126
127     \a IN and \a KEY are NULL terminated strings.
128
129     \a IN is supposed to be an Ethernet MAC address and is sanitised
130     by lowercasing it, removing all but [0-9a-f] and truncating it at
131     the first ';' found.  The truncation is done because RADIUS
132     supposedly has a praxis of tacking on SSID to the MAC address in
133     Calling-Station-Id.  */
134 void
135 fticks_hashmac(const uint8_t *in,
136                const uint8_t *key,
137                size_t out_len,
138                uint8_t *out)
139 {
140     /* TODO: lowercase */
141     /* TODO: s/[!0-9a-f]//1 */
142     /* TODO: truncate after first ';', if any */
143
144     _hash(in, key, out_len, out);
145 }
146
147 void
148 fticks_log(const struct options *options,
149            const struct client *client,
150            const struct radmsg *msg,
151            const struct rqout *rqout)
152 {
153     uint8_t *username = NULL;
154     uint8_t *realm = NULL;
155     uint8_t visinst[8+40+1+1]; /* Room for 40 octets of VISINST.  */
156     uint8_t *macin = NULL;
157     uint8_t macout[2*32+1]; /* Room for ASCII representation of SHA256.  */
158
159     username = radattr2ascii(radmsg_gettype(rqout->rq->msg,
160                                             RAD_Attr_User_Name));
161     if (username != NULL) {
162         realm = (uint8_t *) strrchr((char *) username, '@');
163         if (realm != NULL)
164             realm++;
165     }
166     if (realm == NULL)
167         realm = (uint8_t *) "";
168
169     memset(visinst, 0, sizeof(visinst));
170     if (options->fticks_reporting == RSP_FTICKS_REPORTING_FULL) {
171         snprintf((char *) visinst, sizeof(visinst), "VISINST=%s#",
172                  client->conf->name);
173     }
174
175     memset(macout, 0, sizeof(macout));
176     if (options->fticks_mac == RSP_FTICKS_MAC_STATIC) {
177         strncpy((char *) macout, "undisclosed", sizeof(macout) - 1);
178     }
179     else {
180         macin = radattr2ascii(radmsg_gettype(rqout->rq->msg,
181                                              RAD_Attr_Calling_Station_Id));
182         if (macin) {
183             switch (options->fticks_mac)
184             {
185             case RSP_FTICKS_MAC_ORIGINAL:
186                 memcpy(macout, macin, sizeof(macout));
187                 break;
188             case RSP_FTICKS_MAC_VENDOR_HASHED:
189                 memcpy(macout, macin, 9);
190                 fticks_hashmac(macin, NULL, sizeof(macout) - 9, macout + 9);
191                 break;
192             case RSP_FTICKS_MAC_VENDOR_KEY_HASHED:
193                 memcpy(macout, macin, 9);
194                 /* We are hashing the first nine octets too for easier
195                  * correlation between vendor-key-hashed and
196                  * fully-key-hashed log records.  This opens up for a
197                  * known plaintext attack on the key but the
198                  * consequences of that is considered outweighed by
199                  * the convenience gained.  */
200                 fticks_hashmac(macin, options->fticks_key,
201                                sizeof(macout) - 9, macout + 9);
202                 break;
203             case RSP_FTICKS_MAC_FULLY_HASHED:
204                 fticks_hashmac(macin, NULL, sizeof(macout), macout);
205                 break;
206             case RSP_FTICKS_MAC_FULLY_KEY_HASHED:
207                 fticks_hashmac(macin, options->fticks_key, sizeof(macout),
208                                macout);
209                 break;
210             default:
211                 debugx(2, DBG_ERR, "invalid fticks mac configuration: %d",
212                        options->fticks_mac);
213             }
214         }
215     }
216     debug(0xff,
217           "F-TICKS/eduroam/1.0#REALM=%s#VISCOUNTRY=%s#%sCSI=%s#RESULT=%s#",
218           realm,
219           client->conf->fticks_viscountry,
220           visinst,
221           macout,
222           msg->code == RAD_Access_Accept ? "OK" : "FAIL");
223     if (macin != NULL)
224         free(macin);
225     if (username != NULL)
226         free(username);
227 }
228
229 /* Local Variables: */
230 /* c-file-style: "stroustrup" */
231 /* End: */