1
/* $Id: tools.c,v 1.34 2004/09/01 09:59:53 fpeters Exp $
3
* Lasso - A free implementation of the Liberty Alliance specifications.
5
* Copyright (C) 2004 Entr'ouvert
6
* http://lasso.entrouvert.org
8
* Authors: Nicolas Clapies <nclapies@entrouvert.com>
9
* Valery Febvre <vfebvre@easter-eggs.com>
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.
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.
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
28
#include <libxml/uri.h>
30
#include <openssl/sha.h>
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>
38
#include <lasso/xml/tools.h>
41
lasso_build_random_sequence(guint8 size)
46
g_return_val_if_fail(size > 0, NULL);
48
seq = xmlMalloc(size+1);
50
for (i=0; i<size; i++) {
51
val = g_random_int_range(0, 16);
63
* lasso_build_unique_id:
64
* @size: the ID's length (between 32 and 40)
66
* Builds an ID which has an unicity probability of 2^(-size*4).
68
* Return value: a "unique" ID (begin always with _ character)
71
lasso_build_unique_id(guint8 size)
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
81
xmlChar *id; /* , *enc_id; */
83
g_return_val_if_fail((size >= 32 && size <= 40) || size == 0, NULL);
85
if (size == 0) size = 32;
86
id = g_malloc(size+1+1); /* one for _ and one for \0 */
88
/* build hex number (<= 2^exp-1) */
90
for (i=1; i<size+1; i++) {
91
val = g_random_int_range(0, 16);
99
/* base64 encoding of build string */
100
/* enc_id = xmlSecBase64Encode((const xmlChar *)id, size+1, 0); */
103
/* return (enc_id); */
108
* lasso_doc_get_node_content:
112
* Gets the value of the first node having given @name.
114
* Return value: a node value or NULL if no node found or if no content is
118
lasso_doc_get_node_content(xmlDocPtr doc, const xmlChar *name)
122
/* FIXME: bad namespace used */
123
node = xmlSecFindNode(xmlDocGetRootElement(doc), name, xmlSecDSigNs);
125
/* val returned must be xmlFree() */
126
return xmlNodeGetContent(node);
132
* lasso_g_ptr_array_index:
136
* Gets the pointer at the given index @i of the pointer array.
138
* Return value: the pointer at the given index.
141
lasso_g_ptr_array_index(GPtrArray *a, guint i)
144
return g_ptr_array_index(a, i);
152
* lasso_get_current_time:
154
* Returns the current time, format is "yyyy-mm-ddThh:mm:ssZ".
156
* Return value: a string
159
lasso_get_current_time()
163
gchar *ret = g_new0(gchar, 21);
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);
173
* lasso_query_get_value:
174
* @query: a query (an url-encoded node)
175
* @param: the parameter
177
* Returns the value of the given @param
179
* Return value: a string or NULL if no parameter found
182
lasso_query_get_value(const gchar *query,
183
const xmlChar *param)
187
GPtrArray *tmp_array, *array = NULL;
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)));
197
g_datalist_clear(&gd);
202
gdata_query_to_dict_destroy_notify(gpointer data)
205
GPtrArray *array = data;
207
for (i=0; i<array->len; i++) {
208
g_free(array->pdata[i]);
210
g_ptr_array_free(array, TRUE);
214
* lasso_query_to_dict:
215
* @query: the query (an url-encoded node)
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.
222
* Return value: a dictonary
225
lasso_query_to_dict(const gchar *query)
228
gchar **sa1, **sa2, **sa3;
229
xmlChar *str_unescaped;
233
g_datalist_init(&gd);
236
sa1 = g_strsplit(query, "&", 0);
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 */
245
/* if only a key but no value, then continue */
246
if (sa2[1] == NULL) {
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);
259
xmlFree(str_unescaped);
260
gpa = g_ptr_array_new();
262
while (sa3[j++] != NULL) {
263
g_ptr_array_add(gpa, g_strdup(sa3[j-1]));
264
/* printf("%s, ", sa3[j-1]); */
267
/* add key => values in dict */
268
g_datalist_set_data_full(&gd, sa2[0], gpa,
269
gdata_query_to_dict_destroy_notify);
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
284
* Verifys the query's signature.
286
* Return value: 1 if signature is valid, 0 if invalid, 2 if no signature found
287
* and -1 if an error occurs.
290
lasso_query_verify_signature(const gchar *query,
291
const xmlChar *sender_public_key_file,
292
const xmlChar *recipient_private_key_file)
295
xmlNodePtr sigNode, sigValNode;
296
xmlSecDSigCtxPtr dsigCtx;
297
xmlChar *str_unescaped;
302
2: signature not found
303
-1: error during verification
307
/* split query, signature (must be last param) */
308
str_split = g_strsplit(query, "&Signature=", 0);
309
if (str_split[1] == NULL)
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,
318
/* set SignatureValue content */
319
str_unescaped = lasso_str_unescape(str_split[1]);
320
xmlNodeSetContent(sigValNode, str_unescaped);
321
g_free(str_unescaped);
323
g_strfreev(str_split);
324
/*xmlDocDump(stdout, doc);*/
326
/* find start node */
327
sigNode = xmlSecFindNode(xmlDocGetRootElement(doc),
328
xmlSecNodeSignature, xmlSecDSigNs);
330
/* create signature context */
331
dsigCtx = xmlSecDSigCtxCreate(NULL);
332
if(dsigCtx == NULL) {
333
message(G_LOG_LEVEL_CRITICAL, "Failed to create signature context\n");
337
/* load public key */
338
dsigCtx->signKey = xmlSecCryptoAppKeyLoad(sender_public_key_file,
339
xmlSecKeyDataFormatPem,
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);
347
/* Verify signature */
348
if(xmlSecDSigCtxVerify(dsigCtx, sigNode) < 0) {
349
message(G_LOG_LEVEL_CRITICAL, "Signature verify failed\n");
354
/* print verification result to stdout and return */
355
if(dsigCtx->status == xmlSecDSigStatusSucceeded) {
364
if(dsigCtx != NULL) {
365
xmlSecDSigCtxDestroy(dsigCtx);
378
* Builds the SHA-1 message digest (cryptographic hash) of @str
380
* Return value: a 20 bytes length string
383
lasso_sha1(xmlChar *str)
389
return SHA1(str, strlen(str), md);
399
* Escapes the given string @str.
401
* Return value: a new escaped string or NULL in case of error.
404
lasso_str_escape(xmlChar *str)
406
/* value returned must be xmlFree() */
407
return xmlURIEscapeStr((const xmlChar *)str, NULL);
411
lasso_str_hash(xmlChar *str,
412
const char *private_key_file)
415
xmlChar *b64_digest, *digest = g_new0(xmlChar, 21);
418
doc = lasso_str_sign(str,
419
lassoSignatureMethodRsaSha1,
421
b64_digest = xmlNodeGetContent(xmlSecFindNode(
422
xmlDocGetRootElement(doc),
423
xmlSecNodeDigestValue,
425
i = xmlSecBase64Decode(b64_digest, digest, 21);
426
/* printf("Decoded string %s length is %d\n", digest, i); */
429
/* value returned must be xmlFree() */
444
lasso_str_sign(xmlChar *str,
445
lassoSignatureMethod sign_method,
446
const char *private_key_file)
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;
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);
465
/* create signature template for enveloped signature */
466
switch (sign_method) {
467
case lassoSignatureMethodRsaSha1:
468
signNode = xmlSecTmplSignatureCreate(doc, xmlSecTransformExclC14NId,
469
xmlSecTransformRsaSha1Id, NULL);
471
case lassoSignatureMethodDsaSha1:
472
signNode = xmlSecTmplSignatureCreate(doc, xmlSecTransformExclC14NId,
473
xmlSecTransformDsaSha1Id, NULL);
477
if (signNode == NULL) {
478
message(G_LOG_LEVEL_CRITICAL, "Failed to create signature template\n");
482
/* add <dsig:Signature/> node to the doc */
483
xmlAddChild(xmlDocGetRootElement(doc), signNode);
486
refNode = xmlSecTmplSignatureAddReference(signNode, xmlSecTransformSha1Id,
488
if (refNode == NULL) {
489
message(G_LOG_LEVEL_CRITICAL, "Failed to add reference to signature template\n");
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");
500
/* add <dsig:KeyInfo/> and <dsig:KeyName/> nodes to put key name in the
502
keyInfoNode = xmlSecTmplSignatureEnsureKeyInfo(signNode, NULL);
503
if (keyInfoNode == NULL) {
504
message(G_LOG_LEVEL_CRITICAL, "Failed to add key info\n");
508
if (xmlSecTmplKeyInfoAddKeyName(keyInfoNode, NULL) == NULL) {
509
message(G_LOG_LEVEL_CRITICAL, "Failed to add key name\n");
513
/* create signature context */
514
dsigCtx = xmlSecDSigCtxCreate(NULL);
515
if (dsigCtx == NULL) {
516
message(G_LOG_LEVEL_CRITICAL, "Failed to create signature context\n");
520
/* load private key */
521
dsigCtx->signKey = xmlSecCryptoAppKeyLoad(private_key_file,
522
xmlSecKeyDataFormatPem,
524
if (dsigCtx->signKey == NULL) {
525
message(G_LOG_LEVEL_CRITICAL, "Failed to load private pem key from \"%s\"\n",
530
/* sign the template */
531
if (xmlSecDSigCtxSign(dsigCtx, signNode) < 0) {
532
message(G_LOG_LEVEL_CRITICAL, "Signature failed\n");
536
/* xmlDocDump(stdout, doc); */
537
xmlSecDSigCtxDestroy(dsigCtx);
538
/* doc must be freed be caller */
543
if (dsigCtx != NULL) {
544
xmlSecDSigCtxDestroy(dsigCtx);
554
* lasso_str_unescape:
555
* @str: an escaped string
557
* Unescapes the given string @str.
559
* Return value: a new unescaped string or NULL in case of error.
562
lasso_str_unescape(xmlChar *str)
566
ret = g_malloc(strlen(str) * 2); /* XXX why *2? strlen(str) should be enough */
567
xmlURIUnescapeString((const char *)str, 0, ret);