f73b6641b30b353996fc526ef9a256523e024c5b
[fwknop.git] / lib / fko_decode.c
1 /*
2  *****************************************************************************
3  *
4  * File:    fko_decode.c
5  *
6  * Author:  Damien S. Stuart
7  *
8  * Purpose: Decrypt and decode an FKO SPA message.
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 /* Decrypt the encoded SPA data.
38 */
39 int
40 fko_decode_spa_data(fko_ctx_t ctx)
41 {
42     char       *tbuf, *ndx;
43     int         edata_size, t_size;
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     edata_size = strlen(ctx->encoded_msg);
52
53     /* Move the Digest to its place in the context.
54     */
55     ndx = strrchr(ctx->encoded_msg, ':'); /* Find the last : in the data */
56     if(ndx == NULL)
57         return(FKO_ERROR_INVALID_DATA);
58
59     ndx++;
60
61     t_size = strlen(ndx);
62
63     switch(t_size)
64     {
65         case MD5_B64_LENGTH:
66             ctx->digest_type = FKO_DIGEST_MD5;
67             break;
68
69         case SHA1_B64_LENGTH:
70             ctx->digest_type = FKO_DIGEST_SHA1;
71             break;
72
73         case SHA256_B64_LENGTH:
74             ctx->digest_type = FKO_DIGEST_SHA256;
75             break;
76
77         case SHA384_B64_LENGTH:
78             ctx->digest_type = FKO_DIGEST_SHA384;
79             break;
80
81         case SHA512_B64_LENGTH:
82             ctx->digest_type = FKO_DIGEST_SHA512;
83             break;
84
85         default: /* Invalid or unsupported digest */
86             return(FKO_ERROR_INVALID_DIGEST_TYPE);
87     }
88
89     /* Copy the digest into the context and terminate the encoded data
90      * at that point so the original digest is not part of the
91      * encoded string.
92     */
93     ctx->digest = strdup(ndx);
94     if(ctx->digest == NULL)
95         return(FKO_ERROR_MEMORY_ALLOCATION);
96
97     /* Zero out the rest of the encoded_msg bucket...
98     */
99     bzero((ndx-1), t_size);
100
101     /* Make a tmp bucket for processing base64 encoded data and
102      * other general use.
103     */
104     tbuf = malloc(FKO_ENCODE_TMP_BUF_SIZE);
105     if(tbuf == NULL)
106         return(FKO_ERROR_MEMORY_ALLOCATION);
107
108     /* Can now verify the digest.
109     */
110     switch(ctx->digest_type)
111     {
112         case FKO_DIGEST_MD5:
113             md5_base64(tbuf, (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
114             break;
115
116         case FKO_DIGEST_SHA1:
117             sha1_base64(tbuf, (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
118             break;
119
120         case FKO_DIGEST_SHA256:
121             sha256_base64(tbuf, (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
122             break;
123
124         case FKO_DIGEST_SHA384:
125             sha384_base64(tbuf, (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
126             break;
127
128         case FKO_DIGEST_SHA512:
129             sha512_base64(tbuf, (unsigned char*)ctx->encoded_msg, strlen(ctx->encoded_msg));
130             break;
131
132     }
133
134     /* We give up here if the computed digest does not match the
135      * digest in the message data.
136     */
137     if(strcmp(ctx->digest, tbuf))
138     {
139         free(tbuf);
140         return(FKO_ERROR_DIGEST_VERIFICATION_FAILED);
141     }
142
143     /* Now we will work through the encoded data and extract (and base64-
144      * decode where necessary), the SPA data fields and populate the context.
145     */
146     ndx = ctx->encoded_msg;
147
148     /* The rand val data */
149     if((t_size = strcspn(ndx, ":")) < FKO_RAND_VAL_SIZE)
150     {
151         free(tbuf);
152         return(FKO_ERROR_INVALID_DATA);
153     }
154
155     ctx->rand_val = calloc(1, FKO_RAND_VAL_SIZE+1);
156     if(ctx->rand_val == NULL)
157     {
158         free(tbuf);
159         return(FKO_ERROR_MEMORY_ALLOCATION);
160     }
161     ctx->rand_val = strncpy(ctx->rand_val, ndx, FKO_RAND_VAL_SIZE);
162
163     /* Jump to the next field (username).  We need to use the temp buffer
164      * for the base64 decode step.
165     */
166     ndx += t_size + 1;
167     if((t_size = strcspn(ndx, ":")) < 1)
168     {
169         free(tbuf);
170         return(FKO_ERROR_INVALID_DATA);
171     }
172
173     strlcpy(tbuf, ndx, t_size+1);
174
175     ctx->username = malloc(t_size+1); /* Yes, more than we need */
176     if(ctx->username == NULL)
177     {
178         free(tbuf);
179         return(FKO_ERROR_MEMORY_ALLOCATION);
180     }
181
182     b64_decode(tbuf, (unsigned char*)ctx->username);
183
184     /* Extract the timestamp value.
185     */
186     ndx += t_size + 1;
187     if((t_size = strcspn(ndx, ":")) < 1)
188     {
189         free(tbuf);
190         return(FKO_ERROR_INVALID_DATA);
191     }
192
193     strlcpy(tbuf, ndx, t_size+1);
194
195     ctx->timestamp = (unsigned int)atoi(tbuf);
196
197     /* Extract the version string.
198     */
199     ndx += t_size + 1;
200     if((t_size = strcspn(ndx, ":")) < 1)
201     {
202         free(tbuf);
203         return(FKO_ERROR_INVALID_DATA);
204     }
205
206     ctx->version = malloc(t_size+1);
207     if(ctx->version == NULL)
208     {
209         free(tbuf);
210         return(FKO_ERROR_MEMORY_ALLOCATION);
211     }
212
213     strlcpy(ctx->version, ndx, t_size+1);
214
215     /* Extract the message type value.
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     strlcpy(tbuf, ndx, t_size+1);
225
226     ctx->message_type = (unsigned int)atoi(tbuf);
227
228     /* Extract the SPA message string.
229     */
230     ndx += t_size + 1;
231     if((t_size = strcspn(ndx, ":")) < 1)
232     {
233         free(tbuf);
234         return(FKO_ERROR_INVALID_DATA);
235     }
236
237     strlcpy(tbuf, ndx, t_size+1);
238
239     ctx->message = malloc(t_size+1); /* Yes, more than we need */
240     if(ctx->message == NULL)
241     {
242         free(tbuf);
243         return(FKO_ERROR_MEMORY_ALLOCATION);
244     }
245
246     b64_decode(tbuf, (unsigned char*)ctx->message);
247
248     /* Extract nat_access string if the message_type indicates so.
249     */
250     if(  ctx->message_type == FKO_NAT_ACCESS_MSG
251       || ctx->message_type == FKO_LOCAL_NAT_ACCESS_MSG
252       || ctx->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG
253       || ctx->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG)
254     {
255         ndx += t_size + 1;
256         if((t_size = strcspn(ndx, ":")) < 1)
257         {
258             free(tbuf);
259             return(FKO_ERROR_INVALID_DATA);
260         }
261
262         strlcpy(tbuf, ndx, t_size+1);
263
264         ctx->nat_access = malloc(t_size+1); /* Yes, more than we need */
265         if(ctx->nat_access == NULL)
266         {
267             free(tbuf);
268             return(FKO_ERROR_MEMORY_ALLOCATION);
269         }
270
271         b64_decode(tbuf, (unsigned char*)ctx->nat_access);
272     }
273
274     /* Now look for a server_auth string.
275     */
276     ndx += t_size + 1;
277     if((t_size = strlen(ndx)) > 0)
278     {
279         /* There is data, but what is it?
280          * If the message_type does not have a timeout, assume it is a
281          * server_auth field.
282         */
283         if(  ctx->message_type != FKO_CLIENT_TIMEOUT_ACCESS_MSG
284           && ctx->message_type != FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG
285           && ctx->message_type != FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG)
286         {
287             strlcpy(tbuf, ndx, t_size+1);
288
289             ctx->server_auth = malloc(t_size+1); /* Yes, more than we need */
290             if(ctx->server_auth == NULL)
291             {
292                 free(tbuf);
293                 return(FKO_ERROR_MEMORY_ALLOCATION);
294             }
295
296             b64_decode(tbuf, (unsigned char*)ctx->server_auth);
297
298             /* At this point we should be done.
299             */
300             free(tbuf);
301
302             /* Call the context initialized.
303             */
304             ctx->initval = FKO_CTX_INITIALIZED;
305             FKO_SET_CTX_INITIALIZED(ctx);
306
307             return(FKO_SUCCESS);
308         }
309
310         /* If we are here then we may still have a server_auth string,
311          * or a timeout, or both. So we look for a ':' delimiter.  If
312          * it is there we have both, if not we check the message_type
313          * again.
314         */
315         if(strchr(ndx, ':'))
316         {
317             t_size = strcspn(ndx, ":");
318
319             /* Looks like we have both, so assume this is the 
320             */
321             strlcpy(tbuf, ndx, t_size+1);
322
323             ctx->server_auth = malloc(t_size+1); /* Yes, more than we need */
324             if(ctx->server_auth == NULL)
325             {
326                 free(tbuf);
327                 return(FKO_ERROR_MEMORY_ALLOCATION);
328             }
329
330             b64_decode(tbuf, (unsigned char*)ctx->server_auth);
331
332             ndx += t_size + 1;
333         }
334
335         /* Now we look for a timeout value if one is supposed to be there.
336         */
337         if(  ctx->message_type == FKO_CLIENT_TIMEOUT_ACCESS_MSG
338           || ctx->message_type == FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG
339           || ctx->message_type == FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG)
340         {
341             if((t_size = strlen(ndx)) < 1)
342             {
343                 free(tbuf);
344                 return(FKO_ERROR_INVALID_DATA);
345             }
346
347             /* Should be a number only.
348             */
349             if(strspn(ndx, "0123456789") != t_size)
350             {
351                 free(tbuf);
352                 return(FKO_ERROR_INVALID_DATA);
353             }
354
355             ctx->client_timeout = (unsigned int)atoi(ndx);
356         }
357     }
358
359     /* Done with the tmp buffer.
360     */
361     free(tbuf);
362
363     /* Call the context initialized.
364     */
365     ctx->initval = FKO_CTX_INITIALIZED;
366     FKO_SET_CTX_INITIALIZED(ctx);
367
368     return(FKO_SUCCESS);
369 }
370
371 /***EOF***/