Better SPA message validation upon SPA decrypt/decode.
[fwknop.git] / lib / fko_decode.c
1 /*
2  *****************************************************************************
3  *
4  * File:    fko_decode.c
5  *
6  * Author:  Damien S. Stuart
7  *
8  * Purpose: Decode an FKO SPA message after decryption.
9  *
10  * Copyright 2009-2010 Damien Stuart (dstuart@dstuart.org)
11  *
12  *  License (GNU Public License):
13  *
14  *  This program is free software; you can redistribute it and/or
15  *  modify it under the terms of the GNU General Public License
16  *  as published by the Free Software Foundation; either version 2
17  *  of the License, or (at your option) any later version.
18  *
19  *  This program is distributed in the hope that it will be useful,
20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  *  GNU General Public License for more details.
23  *
24  *  You should have received a copy of the GNU General Public License
25  *  along with this program; if not, write to the Free Software
26  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
27  *  USA
28  *
29  *****************************************************************************
30 */
31 #include "fko_common.h"
32 #include "fko.h"
33 #include "cipher_funcs.h"
34 #include "base64.h"
35 #include "digest.h"
36
37 /* Decode the encoded SPA data.
38 */
39 int
40 fko_decode_spa_data(fko_ctx_t ctx)
41 {
42     char       *tbuf, *ndx, *tmp;
43     int         t_size, i;
44
45     /* Check for required data.
46     */
47     if(ctx->encoded_msg == NULL
48       || strlen(ctx->encoded_msg) < MIN_SPA_ENCODED_MSG_SIZE)
49         return(FKO_ERROR_INVALID_DATA);
50
51     /* Make sure there are enough fields in the SPA packet
52      * delimited with ':' chars
53     */
54     ndx = ctx->encoded_msg;
55     for (i=0; i < MAX_SPA_FIELDS; i++)
56     {
57         if ((tmp = strchr(ndx, ':')) == NULL)
58             break;
59
60         ndx = tmp;
61         ndx++;
62     }
63
64     if (i < MIN_SPA_FIELDS)
65         return(FKO_ERROR_INVALID_DATA);
66
67     t_size = strlen(ndx);
68
69     switch(t_size)
70     {
71         case MD5_B64_LENGTH:
72             ctx->digest_type = FKO_DIGEST_MD5;
73             break;
74
75         case SHA1_B64_LENGTH:
76             ctx->digest_type = FKO_DIGEST_SHA1;
77             break;
78
79         case SHA256_B64_LENGTH:
80             ctx->digest_type = FKO_DIGEST_SHA256;
81             break;
82
83         case SHA384_B64_LENGTH:
84             ctx->digest_type = FKO_DIGEST_SHA384;
85             break;
86
87         case SHA512_B64_LENGTH:
88             ctx->digest_type = FKO_DIGEST_SHA512;
89             break;
90
91         default: /* Invalid or unsupported digest */
92             return(FKO_ERROR_INVALID_DIGEST_TYPE);
93     }
94
95     /* Copy the digest into the context and terminate the encoded data
96      * at that point so the original digest is not part of the
97      * encoded string.
98     */
99     ctx->digest = strdup(ndx);
100     if(ctx->digest == NULL)
101         return(FKO_ERROR_MEMORY_ALLOCATION);
102
103     /* Zero out the rest of the encoded_msg bucket...
104     */
105     bzero((ndx-1), t_size);
106
107     /* Make a tmp bucket for processing base64 encoded data and
108      * other general use.
109     */
110     tbuf = malloc(FKO_ENCODE_TMP_BUF_SIZE);
111     if(tbuf == NULL)
112         return(FKO_ERROR_MEMORY_ALLOCATION);
113
114     /* Can now verify the digest.
115     */
116     switch(ctx->digest_type)
117     {
118         case FKO_DIGEST_MD5:
119             md5_base64(tbuf, (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
120             break;
121
122         case FKO_DIGEST_SHA1:
123             sha1_base64(tbuf, (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
124             break;
125
126         case FKO_DIGEST_SHA256:
127             sha256_base64(tbuf, (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
128             break;
129
130         case FKO_DIGEST_SHA384:
131             sha384_base64(tbuf, (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
132             break;
133
134         case FKO_DIGEST_SHA512:
135             sha512_base64(tbuf, (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
136             break;
137
138     }
139
140     /* We give up here if the computed digest does not match the
141      * digest in the message data.
142     */
143     if(strncmp(ctx->digest, tbuf, t_size))
144     {
145         free(tbuf);
146         return(FKO_ERROR_DIGEST_VERIFICATION_FAILED);
147     }
148
149     /* Now we will work through the encoded data and extract (and base64-
150      * decode where necessary), the SPA data fields and populate the context.
151     */
152     ndx = ctx->encoded_msg;
153
154     /* The rand val data */
155     if((t_size = strcspn(ndx, ":")) < FKO_RAND_VAL_SIZE)
156     {
157         free(tbuf);
158         return(FKO_ERROR_INVALID_DATA);
159     }
160
161     ctx->rand_val = calloc(1, FKO_RAND_VAL_SIZE+1);
162     if(ctx->rand_val == NULL)
163     {
164         free(tbuf);
165         return(FKO_ERROR_MEMORY_ALLOCATION);
166     }
167     ctx->rand_val = strncpy(ctx->rand_val, ndx, FKO_RAND_VAL_SIZE);
168
169     /* Jump to the next field (username).  We need to use the temp buffer
170      * for the base64 decode step.
171     */
172     ndx += t_size + 1;
173     if((t_size = strcspn(ndx, ":")) < 1)
174     {
175         free(tbuf);
176         return(FKO_ERROR_INVALID_DATA);
177     }
178
179     if (t_size > MAX_SPA_USERNAME_SIZE)
180     {
181         free(tbuf);
182         return(FKO_ERROR_INVALID_DATA);
183     }
184
185     strlcpy(tbuf, ndx, t_size+1);
186
187     ctx->username = malloc(t_size+1); /* Yes, more than we need */
188     if(ctx->username == NULL)
189     {
190         free(tbuf);
191         return(FKO_ERROR_MEMORY_ALLOCATION);
192     }
193
194     b64_decode(tbuf, (unsigned char*)ctx->username);
195
196     /* Extract the timestamp value.
197     */
198     ndx += t_size + 1;
199     if((t_size = strcspn(ndx, ":")) < 1)
200     {
201         free(tbuf);
202         return(FKO_ERROR_INVALID_DATA);
203     }
204
205     if (t_size > MAX_SPA_TIMESTAMP_SIZE)
206     {
207         free(tbuf);
208         return(FKO_ERROR_INVALID_DATA);
209     }
210
211     strlcpy(tbuf, ndx, t_size+1);
212
213     ctx->timestamp = (unsigned int)atoi(tbuf);
214
215     /* Extract the version string.
216     */
217     ndx += t_size + 1;
218     if((t_size = strcspn(ndx, ":")) < 1)
219     {
220         free(tbuf);
221         return(FKO_ERROR_INVALID_DATA);
222     }
223
224     if (t_size > MAX_SPA_VERSION_SIZE)
225     {
226         free(tbuf);
227         return(FKO_ERROR_INVALID_DATA);
228     }
229
230     ctx->version = malloc(t_size+1);
231     if(ctx->version == NULL)
232     {
233         free(tbuf);
234         return(FKO_ERROR_MEMORY_ALLOCATION);
235     }
236
237     strlcpy(ctx->version, ndx, t_size+1);
238
239     /* Extract the message type value.
240     */
241     ndx += t_size + 1;
242     if((t_size = strcspn(ndx, ":")) < 1)
243     {
244         free(tbuf);
245         return(FKO_ERROR_INVALID_DATA);
246     }
247
248     if (t_size > MAX_SPA_MESSAGE_TYPE_SIZE)
249     {
250         free(tbuf);
251         return(FKO_ERROR_INVALID_DATA);
252     }
253
254     strlcpy(tbuf, ndx, t_size+1);
255
256     ctx->message_type = (unsigned int)atoi(tbuf);
257
258     /* Extract the SPA message string.
259     */
260     ndx += t_size + 1;
261     if((t_size = strcspn(ndx, ":")) < 1)
262     {
263         free(tbuf);
264         return(FKO_ERROR_INVALID_DATA);
265     }
266
267     if (t_size > MAX_SPA_MESSAGE_SIZE)
268     {
269         free(tbuf);
270         return(FKO_ERROR_INVALID_DATA);
271     }
272
273     strlcpy(tbuf, ndx, t_size+1);
274
275     ctx->message = malloc(t_size+1); /* Yes, more than we need */
276     if(ctx->message == NULL)
277     {
278         free(tbuf);
279         return(FKO_ERROR_MEMORY_ALLOCATION);
280     }
281
282     b64_decode(tbuf, (unsigned char*)ctx->message);
283
284     /* Require a message similar to: 1.2.3.4,tcp/22
285     */
286     if(validate_access_msg(ctx->message) != FKO_SUCCESS)
287     {
288         free(tbuf);
289         return(FKO_ERROR_INVALID_DATA);
290     }
291
292     /* Extract nat_access string if the message_type indicates so.
293     */
294     if(  ctx->message_type == FKO_NAT_ACCESS_MSG
295       || ctx->message_type == FKO_LOCAL_NAT_ACCESS_MSG
296       || ctx->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG
297       || ctx->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG)
298     {
299         ndx += t_size + 1;
300         if((t_size = strcspn(ndx, ":")) < 1)
301         {
302             free(tbuf);
303             return(FKO_ERROR_INVALID_DATA);
304         }
305
306         if (t_size > MAX_SPA_MESSAGE_SIZE)
307         {
308             free(tbuf);
309             return(FKO_ERROR_INVALID_DATA);
310         }
311
312         strlcpy(tbuf, ndx, t_size+1);
313
314         ctx->nat_access = malloc(t_size+1); /* Yes, more than we need */
315         if(ctx->nat_access == NULL)
316         {
317             free(tbuf);
318             return(FKO_ERROR_MEMORY_ALLOCATION);
319         }
320
321         b64_decode(tbuf, (unsigned char*)ctx->nat_access);
322     }
323
324     /* Now look for a server_auth string.
325     */
326     ndx += t_size + 1;
327     if((t_size = strlen(ndx)) > 0)
328     {
329         if (t_size > MAX_SPA_MESSAGE_SIZE)
330         {
331             free(tbuf);
332             return(FKO_ERROR_INVALID_DATA);
333         }
334
335         /* There is data, but what is it?
336          * If the message_type does not have a timeout, assume it is a
337          * server_auth field.
338         */
339         if(  ctx->message_type != FKO_CLIENT_TIMEOUT_ACCESS_MSG
340           && ctx->message_type != FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG
341           && ctx->message_type != FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG)
342         {
343             strlcpy(tbuf, ndx, t_size+1);
344
345             ctx->server_auth = malloc(t_size+1); /* Yes, more than we need */
346             if(ctx->server_auth == NULL)
347             {
348                 free(tbuf);
349                 return(FKO_ERROR_MEMORY_ALLOCATION);
350             }
351
352             b64_decode(tbuf, (unsigned char*)ctx->server_auth);
353
354             /* At this point we should be done.
355             */
356             free(tbuf);
357
358             /* Call the context initialized.
359             */
360             ctx->initval = FKO_CTX_INITIALIZED;
361             FKO_SET_CTX_INITIALIZED(ctx);
362
363             return(FKO_SUCCESS);
364         }
365
366         /* If we are here then we may still have a server_auth string,
367          * or a timeout, or both. So we look for a ':' delimiter.  If
368          * it is there we have both, if not we check the message_type
369          * again.
370         */
371         if(strchr(ndx, ':'))
372         {
373             t_size = strcspn(ndx, ":");
374
375             if (t_size > MAX_SPA_MESSAGE_SIZE)
376             {
377                 free(tbuf);
378                 return(FKO_ERROR_INVALID_DATA);
379             }
380
381             /* Looks like we have both, so assume this is the 
382             */
383             strlcpy(tbuf, ndx, t_size+1);
384
385             ctx->server_auth = malloc(t_size+1); /* Yes, more than we need */
386             if(ctx->server_auth == NULL)
387             {
388                 free(tbuf);
389                 return(FKO_ERROR_MEMORY_ALLOCATION);
390             }
391
392             b64_decode(tbuf, (unsigned char*)ctx->server_auth);
393
394             ndx += t_size + 1;
395         }
396
397         /* Now we look for a timeout value if one is supposed to be there.
398         */
399         if(  ctx->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG
400           || ctx->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG
401           || ctx->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG)
402         {
403             if((t_size = strlen(ndx)) < 1)
404             {
405                 free(tbuf);
406                 return(FKO_ERROR_INVALID_DATA);
407             }
408             if (t_size > MAX_SPA_MESSAGE_SIZE)
409             {
410                 free(tbuf);
411                 return(FKO_ERROR_INVALID_DATA);
412             }
413
414             /* Should be a number only.
415             */
416             if(strspn(ndx, "0123456789") != t_size)
417             {
418                 free(tbuf);
419                 return(FKO_ERROR_INVALID_DATA);
420             }
421
422             ctx->client_timeout = (unsigned int)atoi(ndx);
423         }
424     }
425
426     /* Done with the tmp buffer.
427     */
428     free(tbuf);
429
430     /* Call the context initialized.
431     */
432     ctx->initval = FKO_CTX_INITIALIZED;
433     FKO_SET_CTX_INITIALIZED(ctx);
434
435     return(FKO_SUCCESS);
436 }
437
438 /***EOF***/