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