lasso/lasso/xml/tools.c

570 lines
14 KiB
C

/* $Id$
*
* Lasso - A free implementation of the Liberty Alliance specifications.
*
* Copyright (C) 2004 Entr'ouvert
* http://lasso.entrouvert.org
*
* Authors: Nicolas Clapies <nclapies@entrouvert.com>
* Valery Febvre <vfebvre@easter-eggs.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <string.h>
#include <libxml/uri.h>
#include <openssl/sha.h>
#include <xmlsec/xmltree.h>
#include <xmlsec/base64.h>
#include <xmlsec/xmldsig.h>
#include <xmlsec/templates.h>
#include <xmlsec/crypto.h>
#include <lasso/xml/tools.h>
xmlChar *
lasso_build_random_sequence(guint8 size)
{
int i, val;
xmlChar *seq;
g_return_val_if_fail(size > 0, NULL);
seq = xmlMalloc(size+1);
for (i=0; i<size; i++) {
val = g_random_int_range(0, 16);
if (val < 10)
seq[i] = 48 + val;
else
seq[i] = 65 + val-10;
}
seq[size] = '\0';
return seq;
}
/**
* lasso_build_unique_id:
* @size: the ID's length (between 32 and 40)
*
* Builds an ID which has an unicity probability of 2^(-size*4).
*
* Return value: a "unique" ID (begin always with _ character)
**/
xmlChar *
lasso_build_unique_id(guint8 size)
{
/*
The probability of 2 randomly chosen identifiers being identical MUST be
less than 2^-128 and SHOULD be less than 2^-160.
so we must have 128 <= exp <= 160
we could build a 128-bit binary number but hexa system is shorter
32 <= hexa number size <= 40
*/
int i, val;
xmlChar *id; /* , *enc_id; */
g_return_val_if_fail((size >= 32 && size <= 40) || size == 0, NULL);
if (size == 0) size = 32;
id = g_malloc(size+1+1); /* one for _ and one for \0 */
/* build hex number (<= 2^exp-1) */
id[0] = '_';
for (i=1; i<size+1; i++) {
val = g_random_int_range(0, 16);
if (val < 10)
id[i] = 48 + val;
else
id[i] = 65 + val-10;
}
id[size+1] = '\0';
/* base64 encoding of build string */
/* enc_id = xmlSecBase64Encode((const xmlChar *)id, size+1, 0); */
/* g_free(id); */
/* return (enc_id); */
return id;
}
/**
* lasso_doc_get_node_content:
* @doc: a doc
* @name: the name
*
* Gets the value of the first node having given @name.
*
* Return value: a node value or NULL if no node found or if no content is
* available
**/
xmlChar *
lasso_doc_get_node_content(xmlDocPtr doc, const xmlChar *name)
{
xmlNodePtr node;
/* FIXME: bad namespace used */
node = xmlSecFindNode(xmlDocGetRootElement(doc), name, xmlSecDSigNs);
if (node != NULL)
/* val returned must be xmlFree() */
return xmlNodeGetContent(node);
else
return NULL;
}
/**
* lasso_g_ptr_array_index:
* @a: a GPtrArray
* @i: the index
*
* Gets the pointer at the given index @i of the pointer array.
*
* Return value: the pointer at the given index.
**/
xmlChar*
lasso_g_ptr_array_index(GPtrArray *a, guint i)
{
if (a != NULL) {
return g_ptr_array_index(a, i);
}
else {
return NULL;
}
}
/**
* lasso_get_current_time:
*
* Returns the current time, format is "yyyy-mm-ddThh:mm:ssZ".
*
* Return value: a string
**/
gchar *
lasso_get_current_time()
{
struct tm *tm;
GTimeVal time_val;
gchar *ret = g_new0(gchar, 21);
g_get_current_time(&time_val);
tm = localtime(&(time_val.tv_sec));
strftime((char *)ret, 21, "%Y-%m-%dT%H:%M:%SZ", tm);
return ret;
}
/**
* lasso_query_get_value:
* @query: a query (an url-encoded node)
* @param: the parameter
*
* Returns the value of the given @param
*
* Return value: a string or NULL if no parameter found
**/
GPtrArray *
lasso_query_get_value(const gchar *query,
const xmlChar *param)
{
guint i;
GData *gd;
GPtrArray *tmp_array, *array = NULL;
gd = lasso_query_to_dict(query);
tmp_array = (GPtrArray *)g_datalist_get_data(&gd, (gchar *)param);
/* create a copy of tmp_array */
if (tmp_array != NULL) {
array = g_ptr_array_new();
for(i=0; i<tmp_array->len; i++)
g_ptr_array_add(array, g_strdup(g_ptr_array_index(tmp_array, i)));
}
g_datalist_clear(&gd);
return array;
}
static void
gdata_query_to_dict_destroy_notify(gpointer data)
{
guint i;
GPtrArray *array = data;
for (i=0; i<array->len; i++) {
g_free(array->pdata[i]);
}
g_ptr_array_free(array, TRUE);
}
/**
* lasso_query_to_dict:
* @query: the query (an url-encoded node)
*
* Explodes query to build a dictonary.
* Dictionary values are stored in GPtrArray.
* The caller is responsible for freeing returned object by calling
* g_datalist_clear() function.
*
* Return value: a dictonary
**/
GData *
lasso_query_to_dict(const gchar *query)
{
GData *gd = NULL;
gchar **sa1, **sa2, **sa3;
xmlChar *str_unescaped;
GPtrArray *gpa;
guint i, j;
g_datalist_init(&gd);
i = 0;
sa1 = g_strsplit(query, "&", 0);
while (sa1[i++] != NULL) {
/* split of key=value to get (key, value) sub-strings */
sa2 = g_strsplit(sa1[i-1], "=", 0);
/* if no key / value found, then continue */
if (sa2 == NULL) {
continue;
}
/* if only a key but no value, then continue */
if (sa2[1] == NULL) {
continue;
}
/* printf("%s => ", sa2[0]); */
/* split of value to get mutli values sub-strings separated by SPACE char */
str_unescaped = lasso_str_unescape(sa2[1]);
sa3 = g_strsplit(str_unescaped, " ", 0);
if (sa3 == NULL) {
g_strfreev(sa2);
continue;
}
xmlFree(str_unescaped);
gpa = g_ptr_array_new();
j = 0;
while (sa3[j++] != NULL) {
g_ptr_array_add(gpa, g_strdup(sa3[j-1]));
/* printf("%s, ", sa3[j-1]); */
}
/* printf("\n"); */
/* add key => values in dict */
g_datalist_set_data_full(&gd, sa2[0], gpa,
gdata_query_to_dict_destroy_notify);
g_strfreev(sa3);
g_strfreev(sa2);
}
g_strfreev(sa1);
return gd;
}
/**
* lasso_query_verify_signature:
* @query: a query (an url-encoded and signed node)
* @sender_public_key_file: the sender public key
* @recipient_private_key_file: the recipient private key
*
* Verifys the query's signature.
*
* Return value: 1 if signature is valid, 0 if invalid, 2 if no signature found
* and -1 if an error occurs.
**/
int
lasso_query_verify_signature(const gchar *query,
const xmlChar *sender_public_key_file,
const xmlChar *recipient_private_key_file)
{
xmlDocPtr doc;
xmlNodePtr sigNode, sigValNode;
xmlSecDSigCtxPtr dsigCtx;
xmlChar *str_unescaped;
gchar **str_split;
/*
0: signature invalid
1: signature ok
2: signature not found
-1: error during verification
*/
gint ret = -1;
/* split query, signature (must be last param) */
str_split = g_strsplit(query, "&Signature=", 0);
if (str_split[1] == NULL)
return 2;
/* re-create doc to verify (signed + enrypted) */
doc = lasso_str_sign(str_split[0],
lassoSignatureMethodRsaSha1,
recipient_private_key_file);
sigValNode = xmlSecFindNode(xmlDocGetRootElement(doc),
xmlSecNodeSignatureValue,
xmlSecDSigNs);
/* set SignatureValue content */
str_unescaped = lasso_str_unescape(str_split[1]);
xmlNodeSetContent(sigValNode, str_unescaped);
g_free(str_unescaped);
g_strfreev(str_split);
/*xmlDocDump(stdout, doc);*/
/* find start node */
sigNode = xmlSecFindNode(xmlDocGetRootElement(doc),
xmlSecNodeSignature, xmlSecDSigNs);
/* create signature context */
dsigCtx = xmlSecDSigCtxCreate(NULL);
if(dsigCtx == NULL) {
message(G_LOG_LEVEL_CRITICAL, "Failed to create signature context\n");
goto done;
}
/* load public key */
dsigCtx->signKey = xmlSecCryptoAppKeyLoad(sender_public_key_file,
xmlSecKeyDataFormatPem,
NULL, NULL, NULL);
if(dsigCtx->signKey == NULL) {
message(G_LOG_LEVEL_CRITICAL, "Failed to load public pem key from \"%s\"\n",
sender_public_key_file);
goto done;
}
/* Verify signature */
if(xmlSecDSigCtxVerify(dsigCtx, sigNode) < 0) {
message(G_LOG_LEVEL_CRITICAL, "Signature verify failed\n");
ret = 0;
goto done;
}
/* print verification result to stdout and return */
if(dsigCtx->status == xmlSecDSigStatusSucceeded) {
ret = 1;
}
else {
ret = 0;
}
done:
/* cleanup */
if(dsigCtx != NULL) {
xmlSecDSigCtxDestroy(dsigCtx);
}
if(doc != NULL) {
xmlFreeDoc(doc);
}
return ret;
}
/**
* lasso_sha1:
* @str: a string
*
* Builds the SHA-1 message digest (cryptographic hash) of @str
*
* Return value: a 20 bytes length string
**/
xmlChar*
lasso_sha1(xmlChar *str)
{
unsigned char *md;
if (str != NULL) {
md = xmlMalloc(20);
return SHA1(str, strlen(str), md);
}
return NULL;
}
/**
* lasso_str_escape:
* @str: a string
*
* Escapes the given string @str.
*
* Return value: a new escaped string or NULL in case of error.
**/
xmlChar *
lasso_str_escape(xmlChar *str)
{
/* value returned must be xmlFree() */
return xmlURIEscapeStr((const xmlChar *)str, NULL);
}
xmlChar *
lasso_str_hash(xmlChar *str,
const char *private_key_file)
{
xmlDocPtr doc;
xmlChar *b64_digest, *digest = g_new0(xmlChar, 21);
gint i;
doc = lasso_str_sign(str,
lassoSignatureMethodRsaSha1,
private_key_file);
b64_digest = xmlNodeGetContent(xmlSecFindNode(
xmlDocGetRootElement(doc),
xmlSecNodeDigestValue,
xmlSecDSigNs));
i = xmlSecBase64Decode(b64_digest, digest, 21);
/* printf("Decoded string %s length is %d\n", digest, i); */
xmlFree(b64_digest);
xmlFreeDoc(doc);
/* value returned must be xmlFree() */
return digest;
}
/**
* lasso_str_sign:
* @str:
* @sign_method:
* @private_key_file:
*
*
*
* Return value:
**/
xmlDocPtr
lasso_str_sign(xmlChar *str,
lassoSignatureMethod sign_method,
const char *private_key_file)
{
/* FIXME : renamed fct into lasso_query_add_signature
SHOULD returned a query (xmlChar) instead of xmlDoc */
xmlDocPtr doc = xmlNewDoc("1.0");
xmlNodePtr envelope = xmlNewNode(NULL, "Envelope");
xmlNodePtr cdata, data = xmlNewNode(NULL, "Data");
xmlNodePtr signNode = NULL;
xmlNodePtr refNode = NULL;
xmlNodePtr keyInfoNode = NULL;
xmlSecDSigCtxPtr dsigCtx = NULL;
/* create doc */
xmlNewNs(envelope, "urn:envelope", NULL);
cdata = xmlNewCDataBlock(doc, str, strlen(str));
xmlAddChild(envelope, data);
xmlAddChild(data, cdata);
xmlAddChild((xmlNodePtr)doc, envelope);
/* create signature template for enveloped signature */
switch (sign_method) {
case lassoSignatureMethodRsaSha1:
signNode = xmlSecTmplSignatureCreate(doc, xmlSecTransformExclC14NId,
xmlSecTransformRsaSha1Id, NULL);
break;
case lassoSignatureMethodDsaSha1:
signNode = xmlSecTmplSignatureCreate(doc, xmlSecTransformExclC14NId,
xmlSecTransformDsaSha1Id, NULL);
break;
}
if (signNode == NULL) {
message(G_LOG_LEVEL_CRITICAL, "Failed to create signature template\n");
goto done;
}
/* add <dsig:Signature/> node to the doc */
xmlAddChild(xmlDocGetRootElement(doc), signNode);
/* add reference */
refNode = xmlSecTmplSignatureAddReference(signNode, xmlSecTransformSha1Id,
NULL, NULL, NULL);
if (refNode == NULL) {
message(G_LOG_LEVEL_CRITICAL, "Failed to add reference to signature template\n");
goto done;
}
/* add enveloped transform */
if (xmlSecTmplReferenceAddTransform(refNode,
xmlSecTransformEnvelopedId) == NULL) {
message(G_LOG_LEVEL_CRITICAL, "Failed to add enveloped transform to reference\n");
goto done;
}
/* add <dsig:KeyInfo/> and <dsig:KeyName/> nodes to put key name in the
signed document */
keyInfoNode = xmlSecTmplSignatureEnsureKeyInfo(signNode, NULL);
if (keyInfoNode == NULL) {
message(G_LOG_LEVEL_CRITICAL, "Failed to add key info\n");
goto done;
}
if (xmlSecTmplKeyInfoAddKeyName(keyInfoNode, NULL) == NULL) {
message(G_LOG_LEVEL_CRITICAL, "Failed to add key name\n");
goto done;
}
/* create signature context */
dsigCtx = xmlSecDSigCtxCreate(NULL);
if (dsigCtx == NULL) {
message(G_LOG_LEVEL_CRITICAL, "Failed to create signature context\n");
goto done;
}
/* load private key */
dsigCtx->signKey = xmlSecCryptoAppKeyLoad(private_key_file,
xmlSecKeyDataFormatPem,
NULL, NULL, NULL);
if (dsigCtx->signKey == NULL) {
message(G_LOG_LEVEL_CRITICAL, "Failed to load private pem key from \"%s\"\n",
private_key_file);
goto done;
}
/* sign the template */
if (xmlSecDSigCtxSign(dsigCtx, signNode) < 0) {
message(G_LOG_LEVEL_CRITICAL, "Signature failed\n");
goto done;
}
/* xmlDocDump(stdout, doc); */
xmlSecDSigCtxDestroy(dsigCtx);
/* doc must be freed be caller */
return doc;
done:
/* cleanup */
if (dsigCtx != NULL) {
xmlSecDSigCtxDestroy(dsigCtx);
}
if (doc != NULL) {
xmlFreeDoc(doc);
}
return NULL;
}
/**
* lasso_str_unescape:
* @str: an escaped string
*
* Unescapes the given string @str.
*
* Return value: a new unescaped string or NULL in case of error.
**/
xmlChar *
lasso_str_unescape(xmlChar *str)
{
xmlChar *ret;
ret = g_malloc(strlen(str) * 2); /* XXX why *2? strlen(str) should be enough */
xmlURIUnescapeString((const char *)str, 0, ret);
return ret;
}