~ubuntu-branches/ubuntu/edgy/lasso/edgy

1 by Frederic Peters
Import upstream version 0.4.1
1
/* $Id: tools.c,v 1.34 2004/09/01 09:59:53 fpeters Exp $ 
2
 *
3
 * Lasso - A free implementation of the Liberty Alliance specifications.
4
 *
5
 * Copyright (C) 2004 Entr'ouvert
6
 * http://lasso.entrouvert.org
7
 * 
8
 * Authors: Nicolas Clapies <nclapies@entrouvert.com>
9
 *          Valery Febvre <vfebvre@easter-eggs.com>
10
 *
11
 * This program is free software; you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by
13
 * the Free Software Foundation; either version 2 of the License, or
14
 * (at your option) any later version.
15
 * 
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 * 
21
 * You should have received a copy of the GNU General Public License
22
 * along with this program; if not, write to the Free Software
23
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
 */
25
26
#include <string.h>
27
28
#include <libxml/uri.h>
29
30
#include <openssl/sha.h>
31
32
#include <xmlsec/xmltree.h>
33
#include <xmlsec/base64.h>
34
#include <xmlsec/xmldsig.h>
35
#include <xmlsec/templates.h>
36
#include <xmlsec/crypto.h>
37
38
#include <lasso/xml/tools.h>
39
40
xmlChar *
41
lasso_build_random_sequence(guint8 size)
42
{
43
  int i, val;
44
  xmlChar *seq;
45
46
  g_return_val_if_fail(size > 0, NULL);
47
48
  seq = xmlMalloc(size+1);
49
50
  for (i=0; i<size; i++) {
51
    val = g_random_int_range(0, 16);
52
    if (val < 10)
53
      seq[i] = 48 + val;
54
    else
55
      seq[i] = 65 + val-10;
56
  }
57
  seq[size] = '\0';
58
59
  return seq;
60
}
61
62
/**
63
 * lasso_build_unique_id:
64
 * @size: the ID's length (between 32 and 40)
65
 * 
66
 * Builds an ID which has an unicity probability of 2^(-size*4).
67
 * 
68
 * Return value: a "unique" ID (begin always with _ character)
69
 **/
70
xmlChar *
71
lasso_build_unique_id(guint8 size)
72
{
73
  /*
74
    The probability of 2 randomly chosen identifiers being identical MUST be
75
    less than 2^-128 and SHOULD be less than 2^-160.
76
    so we must have 128 <= exp <= 160
77
    we could build a 128-bit binary number but hexa system is shorter
78
    32 <= hexa number size <= 40
79
  */
80
  int i, val;
81
  xmlChar *id; /* , *enc_id; */
82
83
  g_return_val_if_fail((size >= 32 && size <= 40) || size == 0, NULL);
84
85
  if (size == 0) size = 32;
86
  id = g_malloc(size+1+1); /* one for _ and one for \0 */
87
88
  /* build hex number (<= 2^exp-1) */
89
  id[0] = '_';
90
  for (i=1; i<size+1; i++) {
91
    val = g_random_int_range(0, 16);
92
    if (val < 10)
93
      id[i] = 48 + val;
94
    else
95
      id[i] = 65 + val-10;
96
  }
97
  id[size+1] = '\0';
98
99
  /* base64 encoding of build string */
100
  /* enc_id = xmlSecBase64Encode((const xmlChar *)id, size+1, 0); */
101
102
  /* g_free(id); */
103
  /* return (enc_id); */
104
  return id;
105
}
106
107
/**
108
 * lasso_doc_get_node_content:
109
 * @doc: a doc
110
 * @name: the name
111
 * 
112
 * Gets the value of the first node having given @name.
113
 * 
114
 * Return value: a node value or NULL if no node found or if no content is
115
 * available
116
 **/
117
xmlChar *
118
lasso_doc_get_node_content(xmlDocPtr doc, const xmlChar *name)
119
{
120
  xmlNodePtr node;
121
122
  /* FIXME: bad namespace used */
123
  node = xmlSecFindNode(xmlDocGetRootElement(doc), name, xmlSecDSigNs);
124
  if (node != NULL)
125
    /* val returned must be xmlFree() */
126
    return xmlNodeGetContent(node);
127
  else
128
    return NULL;
129
}
130
131
/**
132
 * lasso_g_ptr_array_index:
133
 * @a: a GPtrArray
134
 * @i: the index
135
 * 
136
 * Gets the pointer at the given index @i of the pointer array.
137
 * 
138
 * Return value: the pointer at the given index.
139
 **/
140
xmlChar*
141
lasso_g_ptr_array_index(GPtrArray *a, guint i)
142
{
143
  if (a != NULL) {
144
    return g_ptr_array_index(a, i);
145
  }
146
  else {
147
    return NULL;
148
  }
149
}
150
151
/**
152
 * lasso_get_current_time:
153
 * 
154
 * Returns the current time, format is "yyyy-mm-ddThh:mm:ssZ".
155
 * 
156
 * Return value: a string
157
 **/
158
gchar *
159
lasso_get_current_time()
160
{
161
  struct tm *tm;
162
  GTimeVal time_val;
163
  gchar *ret = g_new0(gchar, 21);
164
165
  g_get_current_time(&time_val);
166
  tm = localtime(&(time_val.tv_sec));
167
  strftime((char *)ret, 21, "%Y-%m-%dT%H:%M:%SZ", tm);
168
169
  return ret;
170
}
171
172
/**
173
 * lasso_query_get_value:
174
 * @query: a query (an url-encoded node)
175
 * @param: the parameter
176
 * 
177
 * Returns the value of the given @param
178
 * 
179
 * Return value: a string or NULL if no parameter found
180
 **/
181
GPtrArray *
182
lasso_query_get_value(const gchar   *query,
183
		      const xmlChar *param)
184
{
185
  guint i;
186
  GData *gd;
187
  GPtrArray *tmp_array, *array = NULL;
188
189
  gd = lasso_query_to_dict(query);
190
  tmp_array = (GPtrArray *)g_datalist_get_data(&gd, (gchar *)param);
191
  /* create a copy of tmp_array */
192
  if (tmp_array != NULL) {
193
    array = g_ptr_array_new();
194
    for(i=0; i<tmp_array->len; i++)
195
      g_ptr_array_add(array, g_strdup(g_ptr_array_index(tmp_array, i)));
196
  }
197
  g_datalist_clear(&gd);
198
  return array;
199
}
200
201
static void
202
gdata_query_to_dict_destroy_notify(gpointer data)
203
{
204
  guint i;
205
  GPtrArray *array = data;
206
207
  for (i=0; i<array->len; i++) {
208
    g_free(array->pdata[i]);
209
  }
210
  g_ptr_array_free(array, TRUE);
211
}
212
213
/**
214
 * lasso_query_to_dict:
215
 * @query: the query (an url-encoded node)
216
 * 
217
 * Explodes query to build a dictonary.
218
 * Dictionary values are stored in GPtrArray.
219
 * The caller is responsible for freeing returned object by calling
220
 * g_datalist_clear() function.
221
 *
222
 * Return value: a dictonary
223
 **/
224
GData *
225
lasso_query_to_dict(const gchar *query)
226
{
227
  GData *gd = NULL;
228
  gchar **sa1, **sa2, **sa3;
229
  xmlChar *str_unescaped;
230
  GPtrArray *gpa;
231
  guint i, j;
232
  
233
  g_datalist_init(&gd);
234
  
235
  i = 0;
236
  sa1 = g_strsplit(query, "&", 0);
237
238
  while (sa1[i++] != NULL) {
239
    /* split of key=value to get (key, value) sub-strings */
240
    sa2 = g_strsplit(sa1[i-1], "=", 0);
241
    /* if no key / value found, then continue */
242
    if (sa2 == NULL) {
243
      continue;
244
    }
245
    /* if only a key but no value, then continue */
246
    if (sa2[1] == NULL) {
247
      continue;
248
    }
249
250
    /* printf("%s => ", sa2[0]); */
251
    /* split of value to get mutli values sub-strings separated by SPACE char */
252
    str_unescaped = lasso_str_unescape(sa2[1]);
253
    sa3 = g_strsplit(str_unescaped, " ", 0);
254
    if (sa3 == NULL) {
255
      g_strfreev(sa2);
256
      continue;
257
    }
258
259
    xmlFree(str_unescaped);
260
    gpa = g_ptr_array_new();
261
    j = 0;
262
    while (sa3[j++] != NULL) {
263
      g_ptr_array_add(gpa, g_strdup(sa3[j-1]));
264
      /* printf("%s, ", sa3[j-1]); */
265
    }
266
    /* printf("\n"); */
267
    /* add key => values in dict */
268
    g_datalist_set_data_full(&gd, sa2[0], gpa,
269
			     gdata_query_to_dict_destroy_notify);
270
    g_strfreev(sa3);
271
    g_strfreev(sa2);
272
  }  
273
  g_strfreev(sa1);
274
275
  return gd;
276
}
277
278
/**
279
 * lasso_query_verify_signature:
280
 * @query: a query  (an url-encoded and signed node)
281
 * @sender_public_key_file: the sender public key
282
 * @recipient_private_key_file: the recipient private key
283
 * 
284
 * Verifys the query's signature.
285
 * 
286
 * Return value: 1 if signature is valid, 0 if invalid, 2 if no signature found
287
 * and -1 if an error occurs.
288
 **/
289
int
290
lasso_query_verify_signature(const gchar   *query,
291
			     const xmlChar *sender_public_key_file,
292
			     const xmlChar *recipient_private_key_file)
293
{
294
  xmlDocPtr doc;
295
  xmlNodePtr sigNode, sigValNode;
296
  xmlSecDSigCtxPtr dsigCtx;
297
  xmlChar *str_unescaped;
298
  gchar **str_split;
299
  /*
300
     0: signature invalid
301
     1: signature ok
302
     2: signature not found
303
    -1: error during verification
304
  */
305
  gint ret = -1;
306
307
  /* split query, signature (must be last param) */
308
  str_split = g_strsplit(query, "&Signature=", 0);
309
  if (str_split[1] == NULL)
310
    return 2;
311
  /* re-create doc to verify (signed + enrypted) */
312
  doc = lasso_str_sign(str_split[0],
313
		       lassoSignatureMethodRsaSha1,
314
		       recipient_private_key_file);
315
  sigValNode = xmlSecFindNode(xmlDocGetRootElement(doc),
316
				          xmlSecNodeSignatureValue,
317
					  xmlSecDSigNs);
318
  /* set SignatureValue content */
319
  str_unescaped = lasso_str_unescape(str_split[1]);
320
  xmlNodeSetContent(sigValNode, str_unescaped);
321
  g_free(str_unescaped);
322
323
  g_strfreev(str_split);
324
  /*xmlDocDump(stdout, doc);*/
325
326
  /* find start node */
327
  sigNode = xmlSecFindNode(xmlDocGetRootElement(doc),
328
			   xmlSecNodeSignature, xmlSecDSigNs);
329
  
330
  /* create signature context */
331
  dsigCtx = xmlSecDSigCtxCreate(NULL);
332
  if(dsigCtx == NULL) {
333
    message(G_LOG_LEVEL_CRITICAL, "Failed to create signature context\n");
334
    goto done;
335
  }
336
  
337
  /* load public key */
338
  dsigCtx->signKey = xmlSecCryptoAppKeyLoad(sender_public_key_file,
339
					    xmlSecKeyDataFormatPem,
340
					    NULL, NULL, NULL);
341
  if(dsigCtx->signKey == NULL) {
342
    message(G_LOG_LEVEL_CRITICAL, "Failed to load public pem key from \"%s\"\n",
343
	    sender_public_key_file);
344
    goto done;
345
  }
346
  
347
  /* Verify signature */
348
  if(xmlSecDSigCtxVerify(dsigCtx, sigNode) < 0) {
349
    message(G_LOG_LEVEL_CRITICAL, "Signature verify failed\n");
350
    ret = 0;
351
    goto done;
352
  }
353
  
354
  /* print verification result to stdout and return */
355
  if(dsigCtx->status == xmlSecDSigStatusSucceeded) {
356
    ret = 1;
357
  }
358
  else {
359
    ret = 0;
360
  }
361
  
362
 done:
363
  /* cleanup */
364
  if(dsigCtx != NULL) {
365
    xmlSecDSigCtxDestroy(dsigCtx);
366
  }
367
  
368
  if(doc != NULL) {
369
    xmlFreeDoc(doc);
370
  }
371
  return ret;
372
}
373
374
/**
375
 * lasso_sha1:
376
 * @str: a string
377
 * 
378
 * Builds the SHA-1 message digest (cryptographic hash) of @str
379
 * 
380
 * Return value: a 20 bytes length string
381
 **/
382
xmlChar*
383
lasso_sha1(xmlChar *str)
384
{
385
  unsigned char *md;
386
387
  if (str != NULL) {
388
    md = xmlMalloc(20);
389
    return SHA1(str, strlen(str), md);
390
  }
391
  
392
  return NULL;
393
}
394
395
/**
396
 * lasso_str_escape:
397
 * @str: a string
398
 * 
399
 * Escapes the given string @str.
400
 * 
401
 * Return value: a new escaped string or NULL in case of error.
402
 **/
403
xmlChar *
404
lasso_str_escape(xmlChar *str)
405
{
406
  /* value returned must be xmlFree() */
407
  return xmlURIEscapeStr((const xmlChar *)str, NULL);
408
}
409
410
xmlChar *
411
lasso_str_hash(xmlChar    *str,
412
	       const char *private_key_file)
413
{
414
  xmlDocPtr doc;
415
  xmlChar *b64_digest, *digest = g_new0(xmlChar, 21);
416
  gint i;
417
418
  doc = lasso_str_sign(str,
419
		       lassoSignatureMethodRsaSha1,
420
		       private_key_file);
421
  b64_digest = xmlNodeGetContent(xmlSecFindNode(
422
			  	 	xmlDocGetRootElement(doc),
423
					xmlSecNodeDigestValue,
424
					xmlSecDSigNs));
425
  i = xmlSecBase64Decode(b64_digest, digest, 21);
426
  /* printf("Decoded string %s length is %d\n", digest, i); */
427
  xmlFree(b64_digest);
428
  xmlFreeDoc(doc);
429
  /* value returned must be xmlFree() */
430
  return digest;
431
}
432
433
/**
434
 * lasso_str_sign:
435
 * @str: 
436
 * @sign_method: 
437
 * @private_key_file: 
438
 * 
439
 * 
440
 * 
441
 * Return value: 
442
 **/
443
xmlDocPtr
444
lasso_str_sign(xmlChar              *str,
445
	       lassoSignatureMethod  sign_method,
446
	       const char           *private_key_file)
447
{
448
  /* FIXME : renamed fct into lasso_query_add_signature
449
     SHOULD returned a query (xmlChar) instead of xmlDoc */
450
  xmlDocPtr  doc = xmlNewDoc("1.0");
451
  xmlNodePtr envelope = xmlNewNode(NULL, "Envelope");
452
  xmlNodePtr cdata, data = xmlNewNode(NULL, "Data");
453
  xmlNodePtr signNode = NULL;
454
  xmlNodePtr refNode = NULL;
455
  xmlNodePtr keyInfoNode = NULL;
456
  xmlSecDSigCtxPtr dsigCtx = NULL;
457
458
  /* create doc */
459
  xmlNewNs(envelope, "urn:envelope", NULL);
460
  cdata = xmlNewCDataBlock(doc, str, strlen(str));
461
  xmlAddChild(envelope, data);
462
  xmlAddChild(data, cdata);
463
  xmlAddChild((xmlNodePtr)doc, envelope);
464
465
  /* create signature template for enveloped signature */
466
  switch (sign_method) {
467
  case lassoSignatureMethodRsaSha1:
468
    signNode = xmlSecTmplSignatureCreate(doc, xmlSecTransformExclC14NId,
469
					 xmlSecTransformRsaSha1Id, NULL);
470
    break;
471
  case lassoSignatureMethodDsaSha1:
472
    signNode = xmlSecTmplSignatureCreate(doc, xmlSecTransformExclC14NId,
473
					 xmlSecTransformDsaSha1Id, NULL);
474
    break;
475
  }
476
477
  if (signNode == NULL) {
478
    message(G_LOG_LEVEL_CRITICAL, "Failed to create signature template\n");
479
    goto done;		
480
  }
481
  
482
  /* add <dsig:Signature/> node to the doc */
483
  xmlAddChild(xmlDocGetRootElement(doc), signNode);
484
  
485
  /* add reference */
486
  refNode = xmlSecTmplSignatureAddReference(signNode, xmlSecTransformSha1Id,
487
					    NULL, NULL, NULL);
488
  if (refNode == NULL) {
489
    message(G_LOG_LEVEL_CRITICAL, "Failed to add reference to signature template\n");
490
    goto done;		
491
  }
492
  
493
  /* add enveloped transform */
494
  if (xmlSecTmplReferenceAddTransform(refNode,
495
				      xmlSecTransformEnvelopedId) == NULL) {
496
    message(G_LOG_LEVEL_CRITICAL, "Failed to add enveloped transform to reference\n");
497
    goto done;		
498
  }
499
  
500
  /* add <dsig:KeyInfo/> and <dsig:KeyName/> nodes to put key name in the
501
     signed document */
502
  keyInfoNode = xmlSecTmplSignatureEnsureKeyInfo(signNode, NULL);
503
  if (keyInfoNode == NULL) {
504
    message(G_LOG_LEVEL_CRITICAL, "Failed to add key info\n");
505
    goto done;		
506
  }
507
  
508
  if (xmlSecTmplKeyInfoAddKeyName(keyInfoNode, NULL) == NULL) {
509
    message(G_LOG_LEVEL_CRITICAL, "Failed to add key name\n");
510
    goto done;		
511
  }
512
  
513
  /* create signature context */
514
  dsigCtx = xmlSecDSigCtxCreate(NULL);
515
  if (dsigCtx == NULL) {
516
    message(G_LOG_LEVEL_CRITICAL, "Failed to create signature context\n");
517
    goto done;
518
  }
519
520
  /* load private key */
521
  dsigCtx->signKey = xmlSecCryptoAppKeyLoad(private_key_file,
522
					    xmlSecKeyDataFormatPem,
523
					    NULL, NULL, NULL);
524
  if (dsigCtx->signKey == NULL) {
525
    message(G_LOG_LEVEL_CRITICAL, "Failed to load private pem key from \"%s\"\n",
526
	    private_key_file);
527
    goto done;
528
  }
529
530
  /* sign the template */
531
  if (xmlSecDSigCtxSign(dsigCtx, signNode) < 0) {
532
    message(G_LOG_LEVEL_CRITICAL, "Signature failed\n");
533
    goto done;
534
  }
535
  
536
  /* xmlDocDump(stdout, doc); */
537
  xmlSecDSigCtxDestroy(dsigCtx);
538
  /* doc must be freed be caller */
539
  return doc;
540
541
 done:    
542
  /* cleanup */
543
  if (dsigCtx != NULL) {
544
    xmlSecDSigCtxDestroy(dsigCtx);
545
  }
546
  
547
  if (doc != NULL) {
548
    xmlFreeDoc(doc); 
549
  }
550
  return NULL;
551
}
552
553
/**
554
 * lasso_str_unescape:
555
 * @str: an escaped string
556
 * 
557
 * Unescapes the given string @str.
558
 * 
559
 * Return value: a new unescaped string or NULL in case of error.
560
 **/
561
xmlChar *
562
lasso_str_unescape(xmlChar *str)
563
{
564
  xmlChar *ret;
565
566
  ret = g_malloc(strlen(str) * 2); /* XXX why *2?  strlen(str) should be enough */
567
  xmlURIUnescapeString((const char *)str, 0, ret);
568
  return ret;
569
}