3275 lines
91 KiB
C
3275 lines
91 KiB
C
/* $Id$
|
|
*
|
|
* Lasso - A free implementation of the Liberty Alliance specifications.
|
|
*
|
|
* Copyright (C) 2004-2007 Entr'ouvert
|
|
* http://lasso.entrouvert.org
|
|
*
|
|
* Authors: See AUTHORS file in top-level directory.
|
|
*
|
|
* 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* The effect of defining the _DEFAULT_SOURCE macro is equivalent to
|
|
* the effect of explicitly defining three macros in earlier glibc
|
|
* versions: -D_BSD_SOURCE -D_SVID_SOURCE -D_POSIX_C_SOURCE=200809C
|
|
*/
|
|
#define _DEFAULT_SOURCE
|
|
/* permit importation of strptime for glibc2 */
|
|
#if !defined(__sun)
|
|
#define _XOPEN_SOURCE
|
|
#endif
|
|
/* permit importation of timegm for glibc2, wait for people to complain it does not work on their
|
|
* system. */
|
|
#define _BSD_SOURCE
|
|
#include "private.h"
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <time.h>
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
|
|
#include <libxml/uri.h>
|
|
#include <libxml/parser.h>
|
|
#include <libxml/parserInternals.h>
|
|
#include <libxml/xmlIO.h>
|
|
|
|
#include <openssl/pem.h>
|
|
#include <openssl/sha.h>
|
|
#include <openssl/engine.h>
|
|
#include <openssl/hmac.h>
|
|
#include <openssl/evp.h>
|
|
|
|
#include <xmlsec/base64.h>
|
|
#include <xmlsec/crypto.h>
|
|
#include <xmlsec/templates.h>
|
|
#include <xmlsec/xmldsig.h>
|
|
#include <xmlsec/xmltree.h>
|
|
#include <xmlsec/errors.h>
|
|
#include <xmlsec/openssl/x509.h>
|
|
#include <xmlsec/openssl/crypto.h>
|
|
|
|
#include <zlib.h>
|
|
|
|
#include <glib.h>
|
|
#include "xml.h"
|
|
#include "xml_enc.h"
|
|
#include "saml-2.0/saml2_assertion.h"
|
|
#include <unistd.h>
|
|
#include "../debug.h"
|
|
#include "../utils.h"
|
|
#include <stdarg.h>
|
|
#include <ctype.h>
|
|
#include "../lasso_config.h"
|
|
|
|
/**
|
|
* SECTION:saml2_utils
|
|
* @short_description: Misc functions used inside Lasso
|
|
* @stability: Internal
|
|
*/
|
|
|
|
/* A query string can be 3 times larger than the byte string value, because of the octet encoding
|
|
* %xx */
|
|
const int query_string_attribute_length_limit = 8192 * 3;
|
|
static xmlSecKeyPtr lasso_get_public_key_from_private_key_file(const char *private_key_file);
|
|
static gboolean is_base64(const char *message);
|
|
static void xmlDetectSAX2(xmlParserCtxtPtr ctxt);
|
|
|
|
/**
|
|
* lasso_build_random_sequence:
|
|
* @buffer: buffer to fill with random sequence
|
|
* @size: the sequence size in byte (character)
|
|
*
|
|
* Builds a random sequence of [0-9A-F] characters of size @size.
|
|
*
|
|
* Return value: None
|
|
**/
|
|
void
|
|
lasso_build_random_sequence(char *buffer, unsigned int size)
|
|
{
|
|
char *t;
|
|
unsigned int rnd, i;
|
|
|
|
t = buffer;
|
|
while (t-buffer < (int)size) {
|
|
rnd = g_random_int();
|
|
for (i=0; i<sizeof(int); i++) {
|
|
*(t++) = '0' + ((rnd>>i*4)&0xf);
|
|
if (*(t-1) > '9') *(t-1) += 7;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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:(transfer full): a "unique" ID (begin always with _ character)
|
|
**/
|
|
char*
|
|
lasso_build_unique_id(unsigned int size)
|
|
{
|
|
/*
|
|
* When generating one-time-use identifiers for Principals, in the
|
|
* case that a pseudorandom technique is employed, the probability
|
|
* of two randomly chosen identifiers being identical MUST be less
|
|
* than or equal to 2-128 and SHOULD be less than or equal to 2-160.
|
|
* These levels correspond, respectively, to use of strong 128-bit
|
|
* and 160-bit hash functions, in conjunction with sufficient input
|
|
* entropy.
|
|
* -- 3.1.4 Name Identifier Construction
|
|
* in « Liberty ID-FF Protocols and Schema Specification »
|
|
*/
|
|
char *result;
|
|
|
|
g_assert(size >= 32);
|
|
|
|
result = g_malloc(size+2); /* trailing \0 and leading _ */
|
|
result[0] = '_';
|
|
lasso_build_random_sequence(result+1, size);
|
|
result[size+1] = 0;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* lasso_time_to_iso_8601_gmt:
|
|
* @now: a #time_t value
|
|
*
|
|
* Format the given time as an ISO 8601 date-time value in UTC.
|
|
*
|
|
* Return value:(transfer full): an ISO 9601 formatted string.
|
|
*/
|
|
char*
|
|
lasso_time_to_iso_8601_gmt(time_t now)
|
|
{
|
|
struct tm *tm;
|
|
char *ret;
|
|
|
|
ret = g_malloc(21);
|
|
tm = gmtime(&now);
|
|
strftime(ret, 21, "%Y-%m-%dT%H:%M:%SZ", tm);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* lasso_get_current_time:
|
|
*
|
|
* Returns the current time, format is "yyyy-mm-ddThh:mm:ssZ".
|
|
*
|
|
* Return value: a string
|
|
**/
|
|
char*
|
|
lasso_get_current_time()
|
|
{
|
|
return lasso_time_to_iso_8601_gmt(time(NULL));
|
|
}
|
|
|
|
static const char xsdtime_format1[] = "dddd-dd-ddTdd:dd:ddZ";
|
|
static const char xsdtime_format2[] = "dddd-dd-ddTdd:dd:dd.?Z";
|
|
|
|
static gboolean
|
|
xsdtime_match_format(const char *xsdtime, const char *format)
|
|
{
|
|
while (*format && *xsdtime) {
|
|
if (*format == 'd' && isdigit(*xsdtime)) {
|
|
++format;
|
|
++xsdtime;
|
|
} else if (*format == '?') {
|
|
while (isdigit(*xsdtime))
|
|
++xsdtime;
|
|
++format;
|
|
} else if (*format == *xsdtime) {
|
|
++format;
|
|
++xsdtime;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (*format == '\0' && *xsdtime == '\0') {
|
|
return TRUE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* lasso_iso_8601_gmt_to_time_t:
|
|
* @xsdtime: an xsd time value
|
|
*
|
|
* Return value: a corresponding time_t value if possible.
|
|
*/
|
|
time_t
|
|
lasso_iso_8601_gmt_to_time_t(const char *xsdtime)
|
|
{
|
|
struct tm tm;
|
|
char *strptime_ret;
|
|
|
|
if (xsdtime == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (xsdtime_match_format(xsdtime, xsdtime_format1)) {
|
|
strptime_ret = strptime (xsdtime, "%Y-%m-%dT%H:%M:%SZ", &tm);
|
|
if (strptime_ret == NULL) {
|
|
return -1;
|
|
}
|
|
} else if (xsdtime_match_format(xsdtime, xsdtime_format2)) {
|
|
strptime_ret = strptime (xsdtime, "%Y-%m-%dT%H:%M:%S.", &tm);
|
|
if (strptime_ret == NULL) {
|
|
return -1;
|
|
}
|
|
} else {
|
|
return -1;
|
|
}
|
|
return timegm(&tm);
|
|
}
|
|
|
|
/**
|
|
* lasso_get_pem_file_type:
|
|
* @pem_file: a pem file
|
|
*
|
|
* Gets the type of a pem file.
|
|
*
|
|
* Return value: the pem file type
|
|
**/
|
|
LassoPemFileType
|
|
lasso_get_pem_file_type(const char *pem_file)
|
|
{
|
|
BIO* bio;
|
|
EVP_PKEY *pkey;
|
|
X509 *cert;
|
|
LassoPemFileType type = LASSO_PEM_FILE_TYPE_UNKNOWN;
|
|
|
|
g_return_val_if_fail(pem_file != NULL, LASSO_PARAM_ERROR_INVALID_VALUE);
|
|
|
|
bio = BIO_new_file(pem_file, "rb");
|
|
if (bio == NULL) {
|
|
message(G_LOG_LEVEL_CRITICAL, "Failed to open %s pem file", pem_file);
|
|
return LASSO_PEM_FILE_TYPE_UNKNOWN;
|
|
}
|
|
|
|
pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
|
|
if (pkey != NULL) {
|
|
type = LASSO_PEM_FILE_TYPE_PUB_KEY;
|
|
EVP_PKEY_free(pkey);
|
|
} else {
|
|
if (BIO_reset(bio) == 0) {
|
|
pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
|
|
if (pkey != NULL) {
|
|
type = LASSO_PEM_FILE_TYPE_PRIVATE_KEY;
|
|
EVP_PKEY_free(pkey);
|
|
} else {
|
|
if (BIO_reset(bio) == 0) {
|
|
cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
|
if (cert != NULL) {
|
|
type = LASSO_PEM_FILE_TYPE_CERT;
|
|
X509_free(cert);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
BIO_free(bio);
|
|
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* lasso_get_public_key_from_pem_file:
|
|
* @file: the name of a file containing a public key
|
|
*
|
|
* Load a public key from a file in the PEM format.
|
|
*
|
|
* Returns: a #xmlSecKey if one is found, NULL otherwise.
|
|
*/
|
|
xmlSecKeyPtr lasso_get_public_key_from_pem_file(const char *file) {
|
|
LassoPemFileType file_type;
|
|
xmlSecKeyPtr pub_key = NULL;
|
|
|
|
file_type = lasso_get_pem_file_type(file);
|
|
switch (file_type) {
|
|
case LASSO_PEM_FILE_TYPE_UNKNOWN:
|
|
message(G_LOG_LEVEL_CRITICAL, "PEM file type unknown: %s", file);
|
|
break; /* with a warning ? */
|
|
case LASSO_PEM_FILE_TYPE_CERT:
|
|
pub_key = lasso_get_public_key_from_pem_cert_file(file);
|
|
break;
|
|
case LASSO_PEM_FILE_TYPE_PUB_KEY:
|
|
pub_key = xmlSecCryptoAppKeyLoad(file,
|
|
xmlSecKeyDataFormatPem, NULL, NULL, NULL);
|
|
break;
|
|
case LASSO_PEM_FILE_TYPE_PRIVATE_KEY:
|
|
pub_key = lasso_get_public_key_from_private_key_file(file);
|
|
|
|
break; /* with a warning ? */
|
|
}
|
|
return pub_key;
|
|
}
|
|
/**
|
|
* lasso_get_public_key_from_pem_cert_file:
|
|
* @pem_cert_file: an X509 pem certificate file
|
|
*
|
|
* Gets the public key in an X509 pem certificate file.
|
|
*
|
|
* Return value: a public key or NULL if an error occurs.
|
|
**/
|
|
xmlSecKeyPtr
|
|
lasso_get_public_key_from_pem_cert_file(const char *pem_cert_file)
|
|
{
|
|
FILE *fd;
|
|
X509 *pem_cert;
|
|
xmlSecKeyDataPtr data;
|
|
xmlSecKeyPtr key = NULL;
|
|
|
|
g_return_val_if_fail(pem_cert_file != NULL, NULL);
|
|
|
|
/* load pem certificate from file */
|
|
fd = fopen(pem_cert_file, "r");
|
|
if (fd == NULL) {
|
|
message(G_LOG_LEVEL_CRITICAL, "Failed to open %s pem certificate file",
|
|
pem_cert_file);
|
|
return NULL;
|
|
}
|
|
/* read the pem X509 certificate */
|
|
pem_cert = PEM_read_X509(fd, NULL, NULL, NULL);
|
|
fclose(fd);
|
|
if (pem_cert == NULL) {
|
|
message(G_LOG_LEVEL_CRITICAL, "Failed to read X509 certificate");
|
|
return NULL;
|
|
}
|
|
|
|
/* get public key value in certificate */
|
|
data = xmlSecOpenSSLX509CertGetKey(pem_cert);
|
|
if (data != NULL) {
|
|
/* create key and set key value */
|
|
key = xmlSecKeyCreate();
|
|
xmlSecKeySetValue(key, data);
|
|
} else {
|
|
message(G_LOG_LEVEL_CRITICAL,
|
|
"Failed to get the public key in the X509 certificate");
|
|
}
|
|
X509_free(pem_cert);
|
|
|
|
return key;
|
|
}
|
|
|
|
/**
|
|
* lasso_get_public_key_from_private_key_file:
|
|
* @private_key_file: the name of a file containing a private key in PEM format
|
|
*
|
|
* Load a public key from a private key.
|
|
*
|
|
* Returns: a new $xmlSecKey containing the private key
|
|
*/
|
|
static xmlSecKeyPtr
|
|
lasso_get_public_key_from_private_key_file(const char *private_key_file)
|
|
{
|
|
return xmlSecCryptoAppKeyLoad(private_key_file,
|
|
xmlSecKeyDataFormatPem, NULL, NULL, NULL);
|
|
}
|
|
|
|
/**
|
|
* lasso_load_certs_from_pem_certs_chain_file:
|
|
* @pem_certs_chain_file: a CA certificate chain file
|
|
*
|
|
* Creates a keys manager and loads inside all the CA certificates of
|
|
* @pem_certs_chain_file. Caller is responsible for freeing it with
|
|
* xmlSecKeysMngrDestroy() function.
|
|
*
|
|
* Return value: a newly allocated keys manager or NULL if an error occurs.
|
|
**/
|
|
xmlSecKeysMngrPtr
|
|
lasso_load_certs_from_pem_certs_chain_file(const char* pem_certs_chain_file)
|
|
{
|
|
xmlSecKeysMngrPtr keys_mngr = NULL;
|
|
GIOChannel *gioc = NULL;
|
|
gchar *line = NULL;
|
|
gsize len, pos;
|
|
GString *cert = NULL;
|
|
gint ret;
|
|
gint certificates = 0;
|
|
|
|
/* No file just return NULL */
|
|
goto_cleanup_if_fail (pem_certs_chain_file && strlen(pem_certs_chain_file) != 0);
|
|
gioc = g_io_channel_new_file(pem_certs_chain_file, "r", NULL);
|
|
if (! gioc) {
|
|
message(G_LOG_LEVEL_CRITICAL, "Cannot open chain file %s", pem_certs_chain_file);
|
|
goto cleanup;
|
|
}
|
|
|
|
keys_mngr = xmlSecKeysMngrCreate();
|
|
if (keys_mngr == NULL) {
|
|
message(G_LOG_LEVEL_CRITICAL,
|
|
lasso_strerror(LASSO_DS_ERROR_KEYS_MNGR_CREATION_FAILED));
|
|
goto cleanup;
|
|
}
|
|
|
|
/* initialize keys manager */
|
|
if (xmlSecCryptoAppDefaultKeysMngrInit(keys_mngr) < 0) {
|
|
message(G_LOG_LEVEL_CRITICAL,
|
|
lasso_strerror(LASSO_DS_ERROR_KEYS_MNGR_INIT_FAILED));
|
|
xmlSecKeysMngrDestroy(keys_mngr);
|
|
goto cleanup;
|
|
}
|
|
|
|
while (g_io_channel_read_line(gioc, &line, &len, &pos, NULL) == G_IO_STATUS_NORMAL) {
|
|
if (line != NULL && g_strstr_len(line, 64, "BEGIN CERTIFICATE") != NULL) {
|
|
cert = g_string_new(line);
|
|
} else if (cert != NULL && line != NULL && g_strstr_len(line, 64, "END CERTIFICATE") != NULL) {
|
|
g_string_append(cert, line);
|
|
/* load the new certificate found in the keys manager */
|
|
/* create keys manager */
|
|
ret = xmlSecCryptoAppKeysMngrCertLoadMemory(keys_mngr,
|
|
(const xmlSecByte*) cert->str,
|
|
(xmlSecSize) cert->len,
|
|
xmlSecKeyDataFormatPem,
|
|
xmlSecKeyDataTypeTrusted);
|
|
if (ret < 0) {
|
|
goto cleanup;
|
|
}
|
|
certificates++;
|
|
lasso_release_gstring(cert, TRUE);
|
|
cert = NULL;
|
|
} else if (cert != NULL && line != NULL && line[0] != '\0') {
|
|
g_string_append(cert, line);
|
|
}
|
|
/* free last line read */
|
|
lasso_release_string(line);
|
|
}
|
|
|
|
cleanup:
|
|
if (gioc) {
|
|
g_io_channel_shutdown(gioc, TRUE, NULL);
|
|
g_io_channel_unref(gioc);
|
|
}
|
|
if (cert)
|
|
lasso_release_gstring(cert, TRUE);
|
|
if (certificates == 0)
|
|
lasso_release_key_manager(keys_mngr);
|
|
lasso_release_string(line);
|
|
|
|
return keys_mngr;
|
|
}
|
|
|
|
/*
|
|
* lasso_query_sign:
|
|
* @query: a query (an url-encoded node)
|
|
* @sign_method: the Signature transform method
|
|
* @private_key_file: the private key
|
|
* @private_key_file_password: the private key password
|
|
*
|
|
* Signs a query (url-encoded message).
|
|
*
|
|
* Return value: a newly allocated query signed or NULL if an error occurs.
|
|
**/
|
|
char*
|
|
lasso_query_sign(char *query, LassoSignatureContext context)
|
|
{
|
|
char *digest = NULL; /* 160 bit buffer */
|
|
RSA *rsa = NULL;
|
|
DSA *dsa = NULL;
|
|
unsigned char *sigret = NULL;
|
|
unsigned int siglen = 0;
|
|
xmlChar *b64_sigret = NULL, *e_b64_sigret = NULL;
|
|
char *new_query = NULL, *s_new_query = NULL;
|
|
int status = 0;
|
|
const xmlChar *algo_href = NULL;
|
|
char *hmac_key;
|
|
size_t hmac_key_length;
|
|
const EVP_MD *md = NULL;
|
|
xmlSecKey *key;
|
|
xmlSecKeyData *key_data;
|
|
unsigned int sigret_size = 0;
|
|
LassoSignatureMethod sign_method;
|
|
lasso_error_t rc = 0;
|
|
|
|
g_return_val_if_fail(query != NULL, NULL);
|
|
g_return_val_if_fail(lasso_ok_signature_method(context.signature_method), NULL);
|
|
|
|
key = context.signature_key;
|
|
sign_method = context.signature_method;
|
|
key_data = xmlSecKeyGetValue(key);
|
|
|
|
|
|
/* add SigAlg */
|
|
switch (sign_method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
algo_href = xmlSecHrefRsaSha1;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
algo_href = xmlSecHrefDsaSha1;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA1:
|
|
algo_href = xmlSecHrefHmacSha1;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
algo_href = xmlSecHrefRsaSha256;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA256:
|
|
algo_href = xmlSecHrefHmacSha256;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
algo_href = xmlSecHrefRsaSha384;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA384:
|
|
algo_href = xmlSecHrefHmacSha384;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
algo_href = xmlSecHrefRsaSha512;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA512:
|
|
algo_href = xmlSecHrefHmacSha512;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_NONE:
|
|
case LASSO_SIGNATURE_METHOD_LAST:
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
{
|
|
const char *t = (char*)lasso_xmlURIEscapeStr(algo_href, NULL);
|
|
new_query = g_strdup_printf("%s&SigAlg=%s", query, t);
|
|
xmlFree(BAD_CAST t);
|
|
}
|
|
|
|
/* build buffer digest */
|
|
switch (sign_method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
digest = lasso_sha1(new_query);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
digest = lasso_sha256(new_query);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
digest = lasso_sha384(new_query);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
digest = lasso_sha512(new_query);
|
|
default:
|
|
break;
|
|
}
|
|
switch (sign_method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
if (digest == NULL) {
|
|
message(G_LOG_LEVEL_CRITICAL, "Failed to build the buffer digest");
|
|
goto done;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
/* extract the OpenSSL key */
|
|
switch (sign_method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
rsa = xmlSecOpenSSLKeyDataRsaGetRsa(key_data);
|
|
g_assert(rsa);
|
|
/* alloc memory for sigret */
|
|
sigret_size = RSA_size(rsa);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
dsa = xmlSecOpenSSLKeyDataDsaGetDsa(key_data);
|
|
g_assert(dsa);
|
|
/* alloc memory for sigret */
|
|
sigret_size = DSA_size(dsa);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA1:
|
|
md = EVP_sha1();
|
|
sigret_size = EVP_MD_size(md);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA256:
|
|
md = EVP_sha256();
|
|
sigret_size = EVP_MD_size(md);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA384:
|
|
md = EVP_sha384();
|
|
sigret_size = EVP_MD_size(md);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA512:
|
|
md = EVP_sha512();
|
|
sigret_size = EVP_MD_size(md);
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
sigret = (unsigned char *)g_malloc (sigret_size);
|
|
|
|
switch (sign_method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
/* sign digest message */
|
|
status = RSA_sign(NID_sha1, (unsigned char*)digest, SHA_DIGEST_LENGTH, sigret,
|
|
&siglen, rsa);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
/* sign digest message */
|
|
status = RSA_sign(NID_sha256, (unsigned char*)digest, SHA256_DIGEST_LENGTH, sigret,
|
|
&siglen, rsa);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
/* sign digest message */
|
|
status = RSA_sign(NID_sha384, (unsigned char*)digest, SHA384_DIGEST_LENGTH, sigret,
|
|
&siglen, rsa);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
/* sign digest message */
|
|
status = RSA_sign(NID_sha512, (unsigned char*)digest, SHA512_DIGEST_LENGTH, sigret,
|
|
&siglen, rsa);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
status = DSA_sign(NID_sha1, (unsigned char*)digest, SHA_DIGEST_LENGTH, sigret,
|
|
&siglen, dsa);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA512:
|
|
if ((rc = lasso_get_hmac_key(key, (void**)&hmac_key,
|
|
&hmac_key_length))) {
|
|
message(G_LOG_LEVEL_CRITICAL, "Failed to get hmac key (%s)", lasso_strerror(rc));
|
|
goto done;
|
|
}
|
|
g_assert(hmac_key);
|
|
|
|
/* key should be at least 128 bits long */
|
|
if (hmac_key_length < 16) {
|
|
critical("HMAC key should be at least 128 bits long");
|
|
goto done;
|
|
}
|
|
|
|
HMAC(md, hmac_key, hmac_key_length, (unsigned char *)new_query,
|
|
strlen(new_query), sigret, &siglen);
|
|
status = 1;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_LAST:
|
|
case LASSO_SIGNATURE_METHOD_NONE:
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
g_assert(siglen == sigret_size);
|
|
|
|
if (status == 0) {
|
|
goto done;
|
|
}
|
|
|
|
/* Base64 encode the signature value */
|
|
b64_sigret = xmlSecBase64Encode(sigret, sigret_size, 0);
|
|
/* escape b64_sigret */
|
|
e_b64_sigret = lasso_xmlURIEscapeStr((xmlChar*)b64_sigret, NULL);
|
|
|
|
/* add signature */
|
|
switch (sign_method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA512:
|
|
s_new_query = g_strdup_printf("%s&Signature=%s", new_query, (char*)
|
|
e_b64_sigret);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_NONE:
|
|
case LASSO_SIGNATURE_METHOD_LAST:
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
done:
|
|
lasso_release(new_query);
|
|
lasso_release_string(digest);
|
|
lasso_release(sigret);
|
|
lasso_release_xml_string(b64_sigret);
|
|
lasso_release_xml_string(e_b64_sigret);
|
|
|
|
return s_new_query;
|
|
}
|
|
|
|
LassoNode*
|
|
lasso_assertion_encrypt(LassoSaml2Assertion *assertion, char *recipient)
|
|
{
|
|
xmlSecKey *encryption_public_key = NULL;
|
|
LassoEncryptionSymKeyType encryption_sym_key_type;
|
|
LassoKeyEncryptionMethod key_encryption_method;
|
|
LassoNode *ret = NULL;
|
|
|
|
lasso_node_get_encryption((LassoNode*)assertion, &encryption_public_key,
|
|
&encryption_sym_key_type, &key_encryption_method);
|
|
if (! encryption_public_key) {
|
|
return NULL;
|
|
}
|
|
|
|
ret = LASSO_NODE(lasso_node_encrypt(LASSO_NODE(assertion),
|
|
encryption_public_key, encryption_sym_key_type, key_encryption_method, recipient));
|
|
lasso_release_sec_key(encryption_public_key);
|
|
return ret;
|
|
|
|
}
|
|
|
|
static lasso_error_t
|
|
lasso_query_verify_helper(const char *signed_content, const char *b64_signature, const char *algorithm,
|
|
const xmlSecKey *key)
|
|
{
|
|
RSA *rsa = NULL;
|
|
DSA *dsa = NULL;
|
|
char *digest = NULL;
|
|
xmlSecByte *signature = NULL;
|
|
int key_size = 0;
|
|
unsigned char *hmac_key = NULL;
|
|
size_t hmac_key_length = 0;
|
|
const EVP_MD *md = NULL;
|
|
lasso_error_t rc = 0;
|
|
LassoSignatureMethod method = LASSO_SIGNATURE_METHOD_NONE;
|
|
size_t digest_size = 1;
|
|
int type = -1;
|
|
|
|
if (lasso_strisequal(algorithm, (char*)xmlSecHrefRsaSha1)) {
|
|
goto_cleanup_if_fail_with_rc(key->value->id == xmlSecOpenSSLKeyDataRsaId,
|
|
LASSO_DS_ERROR_INVALID_SIGALG)
|
|
rsa = xmlSecOpenSSLKeyDataRsaGetRsa(key->value);
|
|
key_size = RSA_size(rsa);
|
|
method = LASSO_SIGNATURE_METHOD_RSA_SHA1;
|
|
digest_size = SHA_DIGEST_LENGTH;
|
|
type = NID_sha1;
|
|
} else if (lasso_strisequal(algorithm, (char*)xmlSecHrefDsaSha1)) {
|
|
goto_cleanup_if_fail_with_rc(key->value->id == xmlSecOpenSSLKeyDataDsaId, LASSO_DS_ERROR_INVALID_SIGALG);
|
|
dsa = xmlSecOpenSSLKeyDataDsaGetDsa(key->value);
|
|
key_size = DSA_size(dsa);
|
|
method = LASSO_SIGNATURE_METHOD_DSA_SHA1;
|
|
digest_size = SHA_DIGEST_LENGTH;
|
|
type = NID_sha1;
|
|
} else if (lasso_strisequal(algorithm, (char*)xmlSecHrefRsaSha256)) {
|
|
goto_cleanup_if_fail_with_rc(key->value->id == xmlSecOpenSSLKeyDataRsaId,
|
|
LASSO_DS_ERROR_INVALID_SIGALG)
|
|
rsa = xmlSecOpenSSLKeyDataRsaGetRsa(key->value);
|
|
key_size = RSA_size(rsa);
|
|
method = LASSO_SIGNATURE_METHOD_RSA_SHA256;
|
|
digest_size = SHA256_DIGEST_LENGTH;
|
|
type = NID_sha256;
|
|
} else if (lasso_strisequal(algorithm, (char*)xmlSecHrefRsaSha384)) {
|
|
goto_cleanup_if_fail_with_rc(key->value->id == xmlSecOpenSSLKeyDataRsaId,
|
|
LASSO_DS_ERROR_INVALID_SIGALG)
|
|
rsa = xmlSecOpenSSLKeyDataRsaGetRsa(key->value);
|
|
key_size = RSA_size(rsa);
|
|
method = LASSO_SIGNATURE_METHOD_RSA_SHA384;
|
|
digest_size = SHA384_DIGEST_LENGTH;
|
|
type = NID_sha384;
|
|
} else if (lasso_strisequal(algorithm, (char*)xmlSecHrefRsaSha512)) {
|
|
goto_cleanup_if_fail_with_rc(key->value->id == xmlSecOpenSSLKeyDataRsaId,
|
|
LASSO_DS_ERROR_INVALID_SIGALG)
|
|
rsa = xmlSecOpenSSLKeyDataRsaGetRsa(key->value);
|
|
key_size = RSA_size(rsa);
|
|
method = LASSO_SIGNATURE_METHOD_RSA_SHA512;
|
|
digest_size = SHA512_DIGEST_LENGTH;
|
|
type = NID_sha512;
|
|
} else if (lasso_strisequal(algorithm, (char*)xmlSecHrefHmacSha1)) {
|
|
lasso_check_good_rc(lasso_get_hmac_key(key, (void**)&hmac_key, &hmac_key_length));
|
|
md = EVP_sha1();
|
|
key_size = EVP_MD_size(md);
|
|
method = LASSO_SIGNATURE_METHOD_HMAC_SHA1;
|
|
} else if (lasso_strisequal(algorithm, (char*)xmlSecHrefHmacSha256)) {
|
|
lasso_check_good_rc(lasso_get_hmac_key(key, (void**)&hmac_key, &hmac_key_length));
|
|
md = EVP_sha256();
|
|
key_size = EVP_MD_size(md);
|
|
method = LASSO_SIGNATURE_METHOD_HMAC_SHA256;
|
|
} else if (lasso_strisequal(algorithm, (char*)xmlSecHrefHmacSha384)) {
|
|
lasso_check_good_rc(lasso_get_hmac_key(key, (void**)&hmac_key, &hmac_key_length));
|
|
md = EVP_sha384();
|
|
key_size = EVP_MD_size(md);
|
|
method = LASSO_SIGNATURE_METHOD_HMAC_SHA384;
|
|
} else if (lasso_strisequal(algorithm, (char*)xmlSecHrefHmacSha512)) {
|
|
lasso_check_good_rc(lasso_get_hmac_key(key, (void**)&hmac_key, &hmac_key_length));
|
|
md = EVP_sha512();
|
|
key_size = EVP_MD_size(md);
|
|
method = LASSO_SIGNATURE_METHOD_HMAC_SHA512;
|
|
} else {
|
|
goto_cleanup_with_rc(LASSO_DS_ERROR_INVALID_SIGALG);
|
|
}
|
|
|
|
/* is the signature algo allowed */
|
|
goto_cleanup_if_fail_with_rc(
|
|
lasso_allowed_signature_method(method),
|
|
LASSO_DS_ERROR_INVALID_SIGALG);
|
|
|
|
/* decode signature */
|
|
signature = g_malloc(key_size+1);
|
|
goto_cleanup_if_fail_with_rc(
|
|
xmlSecBase64Decode((xmlChar*)b64_signature, signature, key_size+1) != 0,
|
|
LASSO_DS_ERROR_INVALID_SIGNATURE);
|
|
/* digest */
|
|
switch (method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
digest = lasso_sha1(signed_content);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
digest = lasso_sha256(signed_content);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
digest = lasso_sha384(signed_content);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
digest = lasso_sha512(signed_content);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA512:
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
/* verify signature */
|
|
switch (method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
goto_cleanup_if_fail_with_rc(
|
|
RSA_verify(
|
|
type,
|
|
(unsigned char*)digest,
|
|
digest_size,
|
|
signature,
|
|
key_size, rsa) == 1,
|
|
LASSO_DS_ERROR_INVALID_SIGNATURE);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
goto_cleanup_if_fail_with_rc(
|
|
DSA_verify(
|
|
type,
|
|
(unsigned char*)digest,
|
|
digest_size,
|
|
signature,
|
|
key_size, dsa) == 1,
|
|
LASSO_DS_ERROR_INVALID_SIGNATURE);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA512:
|
|
digest = g_malloc(key_size);
|
|
HMAC(md, hmac_key, hmac_key_length, (unsigned char*)signed_content,
|
|
strlen(signed_content), (unsigned char*)digest, NULL);
|
|
|
|
goto_cleanup_if_fail_with_rc(lasso_crypto_memequal(digest, signature,
|
|
key_size),
|
|
LASSO_DS_ERROR_INVALID_SIGNATURE);
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_NONE:
|
|
case LASSO_SIGNATURE_METHOD_LAST:
|
|
g_assert_not_reached();
|
|
}
|
|
cleanup:
|
|
lasso_release_string(digest);
|
|
lasso_release_string(signature);
|
|
return rc;
|
|
|
|
}
|
|
|
|
/**
|
|
* lasso_query_verify_signature:
|
|
* @query: a query (an url-encoded message)
|
|
* @sender_public_key: the query sender public key
|
|
*
|
|
* Verifies the query signature.
|
|
*
|
|
* Return value: 0 if signature is valid
|
|
* a positive value if signature was not found or is invalid
|
|
* a negative value if an error occurs during verification
|
|
**/
|
|
lasso_error_t
|
|
lasso_query_verify_signature(const char *query, const xmlSecKey *sender_public_key)
|
|
{
|
|
gchar **str_split = NULL;
|
|
char *b64_signature = NULL;
|
|
char *sig_alg = NULL;
|
|
char *usig_alg = NULL;
|
|
lasso_error_t rc = 0;
|
|
|
|
g_return_val_if_fail(query != NULL, LASSO_PARAM_ERROR_INVALID_VALUE);
|
|
|
|
if (lasso_flag_verify_signature == FALSE) {
|
|
return 0;
|
|
}
|
|
|
|
g_return_val_if_fail(sender_public_key != NULL, LASSO_PARAM_ERROR_INVALID_VALUE);
|
|
g_return_val_if_fail(sender_public_key->value != NULL, LASSO_PARAM_ERROR_INVALID_VALUE);
|
|
|
|
/* split query, the signature MUST be the last param of the query
|
|
* actually there could be more params in the URL; but they wouldn't be
|
|
* covered by the signature */
|
|
|
|
str_split = g_strsplit(query, "&Signature=", 0);
|
|
if (str_split[0] == NULL || str_split[1] == NULL)
|
|
goto_cleanup_with_rc(LASSO_DS_ERROR_SIGNATURE_NOT_FOUND);
|
|
sig_alg = strstr(str_split[0], "&SigAlg=");
|
|
if (sig_alg == NULL)
|
|
goto_cleanup_with_rc(LASSO_DS_ERROR_INVALID_SIGALG);
|
|
sig_alg = strchr(sig_alg, '=')+1;
|
|
usig_alg = xmlURIUnescapeString(sig_alg, 0, NULL);
|
|
/* insure there is only the signature in str_split[1] */
|
|
if (strchr(str_split[1], '&')) {
|
|
strchr(str_split[1], '&')[0] = 0;
|
|
}
|
|
|
|
/* get signature (unescape + base64 decode) */
|
|
b64_signature = (char*)xmlURIUnescapeString(str_split[1], 0, NULL);
|
|
lasso_check_good_rc(lasso_query_verify_helper(str_split[0],
|
|
b64_signature, usig_alg, sender_public_key));
|
|
|
|
|
|
cleanup:
|
|
if (b64_signature)
|
|
xmlFree(b64_signature);
|
|
if (usig_alg)
|
|
xmlFree(usig_alg);
|
|
g_strfreev(str_split);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* lasso_saml2_query_verify_signature:
|
|
* @query: a query string
|
|
* @sender_public_key: the #xmlSecKey for the sender
|
|
*
|
|
* Verify a query signature following SAML 2.0 semantic.
|
|
*
|
|
* Return value: 0 if signature is validated, an error code otherwise.
|
|
*/
|
|
int
|
|
lasso_saml2_query_verify_signature(const char *query, const xmlSecKey *sender_public_key)
|
|
{
|
|
char *b64_signature = NULL;
|
|
char *query_copy = NULL;
|
|
char *signed_query = NULL;
|
|
char *i = NULL;
|
|
char **components = NULL, **j = NULL;
|
|
int n = 0;
|
|
char *saml_request_response = NULL;
|
|
char *relaystate = NULL;
|
|
char *sig_alg = NULL, *usig_alg = NULL;
|
|
lasso_error_t rc = 0;
|
|
|
|
lasso_return_val_if_fail(query != NULL, LASSO_PARAM_ERROR_INVALID_VALUE);
|
|
lasso_return_val_if_fail(lasso_flag_verify_signature, 0);
|
|
lasso_return_val_if_fail(sender_public_key != NULL, LASSO_PARAM_ERROR_INVALID_VALUE);
|
|
lasso_return_val_if_fail(sender_public_key->value != NULL, LASSO_PARAM_ERROR_INVALID_VALUE);
|
|
|
|
/* extract fields */
|
|
i = query_copy = g_strdup(query);
|
|
n = 1;
|
|
while (*i) {
|
|
if (*i == '&' || *i == ';')
|
|
n++;
|
|
i++;
|
|
}
|
|
components = g_new0(char*, n+1);
|
|
components[n] = NULL;
|
|
n = 0;
|
|
i = query_copy;
|
|
components[n] = query_copy;
|
|
n += 1;
|
|
while (*i) {
|
|
if (*i == '&' || *i == ';') {
|
|
*i = '\0';
|
|
components[n] = i + 1;
|
|
n++;
|
|
}
|
|
i++;
|
|
}
|
|
/* extract specific fields */
|
|
j = components;
|
|
#define match_field(x) \
|
|
(strncmp(x "=", *j, sizeof(x)) == 0)
|
|
#define value strchr(*j, '=') + 1
|
|
while (*j) {
|
|
if (match_field(LASSO_SAML2_FIELD_RESPONSE)
|
|
|| match_field(LASSO_SAML2_FIELD_REQUEST)) {
|
|
saml_request_response = *j;
|
|
} else if (match_field(LASSO_SAML2_FIELD_RELAYSTATE)) {
|
|
relaystate = *j;
|
|
} else if (match_field(LASSO_SAML2_FIELD_SIGALG)) {
|
|
sig_alg = *j;
|
|
} else if (match_field(LASSO_SAML2_FIELD_SIGNATURE)) {
|
|
b64_signature = value;
|
|
b64_signature = xmlURIUnescapeString(b64_signature, 0, NULL);
|
|
}
|
|
++j;
|
|
}
|
|
#undef match_field
|
|
#undef value
|
|
|
|
if (! saml_request_response) {
|
|
message(G_LOG_LEVEL_CRITICAL, "SAMLRequest or SAMLResponse missing in query");
|
|
goto_cleanup_with_rc(LASSO_PROFILE_ERROR_INVALID_QUERY);
|
|
}
|
|
|
|
if (! sig_alg) {
|
|
goto_cleanup_with_rc(LASSO_DS_ERROR_INVALID_SIGALG);
|
|
}
|
|
if (! b64_signature) {
|
|
goto_cleanup_with_rc(LASSO_DS_ERROR_SIGNATURE_NOT_FOUND);
|
|
}
|
|
/* build the signed query */
|
|
if (relaystate) {
|
|
signed_query = g_strconcat(saml_request_response, "&", relaystate, "&", sig_alg, NULL);
|
|
} else {
|
|
signed_query = g_strconcat(saml_request_response, "&", sig_alg, NULL);
|
|
}
|
|
|
|
sig_alg = strchr(sig_alg, '=')+1;
|
|
if (! sig_alg) {
|
|
goto_cleanup_with_rc(LASSO_DS_ERROR_INVALID_SIGALG);
|
|
}
|
|
usig_alg = xmlURIUnescapeString(sig_alg, 0, NULL);
|
|
lasso_check_good_rc(lasso_query_verify_helper(signed_query, b64_signature, usig_alg,
|
|
sender_public_key));
|
|
|
|
|
|
cleanup:
|
|
if (b64_signature)
|
|
xmlFree(b64_signature);
|
|
if (usig_alg)
|
|
xmlFree(usig_alg);
|
|
lasso_release(components);
|
|
lasso_release(query_copy);
|
|
lasso_release(signed_query);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* lasso_sha1:
|
|
* @str: a string
|
|
*
|
|
* Builds the SHA-1 message digest (cryptographic hash) of @str
|
|
*
|
|
* Return value: 20-bytes buffer allocated with g_malloc
|
|
**/
|
|
char*
|
|
lasso_sha1(const char *str)
|
|
{
|
|
xmlChar *md;
|
|
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
md = g_malloc(20);
|
|
return (char*)SHA1((unsigned char*)str, strlen(str), md);
|
|
}
|
|
|
|
/**
|
|
* lasso_sha256:
|
|
* @str: a string
|
|
*
|
|
* Builds the SHA-256 message digest (cryptographic hash) of @str
|
|
*
|
|
* Return value: 32-bytes buffer allocated with g_malloc
|
|
**/
|
|
char*
|
|
lasso_sha256(const char *str)
|
|
{
|
|
xmlChar *md;
|
|
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
md = g_malloc(32);
|
|
return (char*)SHA256((unsigned char*)str, strlen(str), md);
|
|
}
|
|
|
|
/**
|
|
* lasso_sha384:
|
|
* @str: a string
|
|
*
|
|
* Builds the SHA-384 message digest (cryptographic hash) of @str
|
|
*
|
|
* Return value: 48-bytes buffer allocated with g_malloc
|
|
**/
|
|
char*
|
|
lasso_sha384(const char *str)
|
|
{
|
|
xmlChar *md;
|
|
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
md = g_malloc(48);
|
|
return (char*)SHA384((unsigned char*)str, strlen(str), md);
|
|
}
|
|
|
|
/**
|
|
* lasso_sha512:
|
|
* @str: a string
|
|
*
|
|
* Builds the SHA-512 message digest (cryptographic hash) of @str
|
|
*
|
|
* Return value: 64-bytes buffer allocated with g_malloc
|
|
**/
|
|
char*
|
|
lasso_sha512(const char *str)
|
|
{
|
|
xmlChar *md;
|
|
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
md = g_malloc(64);
|
|
return (char*)SHA512((unsigned char*)str, strlen(str), md);
|
|
}
|
|
|
|
|
|
/**
|
|
* lasso_urlencoded_to_strings:
|
|
* @str: a query string
|
|
*
|
|
* Parse a query string and separate it into an char* array of its components.
|
|
*
|
|
* The returned array must be deallocated.
|
|
*/
|
|
xmlChar**
|
|
lasso_urlencoded_to_strings(const char *str)
|
|
{
|
|
int i, n=1;
|
|
char *st, *st2;
|
|
xmlChar **result;
|
|
|
|
g_assert(str);
|
|
/* count components */
|
|
st = (char*)str;
|
|
while (*st) {
|
|
if (*st == '&' || *st == ';')
|
|
n++;
|
|
st++;
|
|
}
|
|
|
|
/* allocate result array */
|
|
result = g_new0(xmlChar*, n+1);
|
|
result[n] = NULL;
|
|
|
|
/* tokenize */
|
|
st = st2 = (char*)str;
|
|
i = 0;
|
|
while(1) {
|
|
if (*st == '&' || *st == ';' || *st == '\0') {
|
|
ptrdiff_t len = st - st2;
|
|
|
|
g_assert(i < n+1);
|
|
if (len) {
|
|
result[i] = (xmlChar*)xmlURIUnescapeString(st2, len, NULL);
|
|
} else {
|
|
result[i] = g_malloc0(1);
|
|
}
|
|
i++;
|
|
if (*st == '\0')
|
|
break;
|
|
st2 = st + 1;
|
|
}
|
|
st++;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void _lasso_xmlsec_password_callback() {
|
|
}
|
|
|
|
/**
|
|
* lasso_sign_node:
|
|
* @xmlnode: the xmlnode to sign
|
|
* @id_attr_name: (allow-none): an ID attribute to reference the xmlnode in the signature
|
|
* @id_value: (allow-none): value of the ID attribute
|
|
* @private_key_file: the path to a key file, or the key itself PEM encoded.
|
|
* @certificate_file: (allow-none): the path to a certificate file to place in the KeyInfo, or the certificate
|
|
* itself PEM encoded.
|
|
*
|
|
* Sign an xmlnode, use the given attribute to reference or create an envelopped signature,
|
|
* eventually place a certificate in the KeyInfo node. The signature template must already be
|
|
* present on the xmlnode.
|
|
*
|
|
* Return value: 0 if successful, an error code otherwise.
|
|
*/
|
|
int
|
|
lasso_sign_node(xmlNode *xmlnode, LassoSignatureContext context, const char *id_attr_name,
|
|
const char *id_value)
|
|
{
|
|
xmlDoc *doc = NULL;
|
|
xmlNode *sign_tmpl = NULL, *old_parent = NULL;
|
|
xmlSecDSigCtx *dsig_ctx = NULL;
|
|
xmlAttr *id_attr = NULL;
|
|
lasso_error_t rc = 0;
|
|
|
|
g_return_val_if_fail(context.signature_method, LASSO_DS_ERROR_INVALID_SIGALG);
|
|
g_return_val_if_fail(context.signature_key, LASSO_DS_ERROR_PRIVATE_KEY_LOAD_FAILED);
|
|
|
|
sign_tmpl = xmlSecFindNode(xmlnode, xmlSecNodeSignature, xmlSecDSigNs);
|
|
goto_cleanup_if_fail_with_rc(sign_tmpl != NULL,
|
|
LASSO_DS_ERROR_SIGNATURE_TEMPLATE_NOT_FOUND);
|
|
|
|
doc = xmlNewDoc((xmlChar*)"1.0");
|
|
old_parent = xmlnode->parent;
|
|
xmlnode->parent = NULL;
|
|
xmlDocSetRootElement(doc, xmlnode);
|
|
xmlSetTreeDoc(sign_tmpl, doc);
|
|
if (id_attr_name && id_value) {
|
|
id_attr = xmlHasProp(xmlnode, (xmlChar*)id_attr_name);
|
|
xmlAddID(NULL, doc, (xmlChar*)id_value, id_attr);
|
|
}
|
|
|
|
dsig_ctx = xmlSecDSigCtxCreate(NULL);
|
|
lasso_assign_sec_key(dsig_ctx->signKey, context.signature_key);
|
|
if (xmlSecDSigCtxSign(dsig_ctx, sign_tmpl) < 0) {
|
|
goto_cleanup_with_rc(LASSO_DS_ERROR_SIGNATURE_FAILED);
|
|
}
|
|
|
|
cleanup:
|
|
if (doc) {
|
|
xmlRemoveID(doc, id_attr);
|
|
xmlUnlinkNode(xmlnode);
|
|
lasso_release_doc(doc);
|
|
xmlnode->parent = old_parent;
|
|
xmlSetTreeDoc(xmlnode, NULL);
|
|
}
|
|
lasso_release_signature_context(dsig_ctx);
|
|
return rc;
|
|
}
|
|
|
|
gchar*
|
|
lasso_node_build_deflated_query(LassoNode *node)
|
|
{
|
|
/* actually deflated and b64'ed and url-escaped */
|
|
xmlNode *xmlnode;
|
|
gchar *result;
|
|
|
|
xmlnode = lasso_node_get_xmlNode(node, FALSE);
|
|
result = lasso_xmlnode_build_deflated_query(xmlnode);
|
|
xmlFreeNode(xmlnode);
|
|
return result;
|
|
}
|
|
|
|
gchar*
|
|
lasso_xmlnode_build_deflated_query(xmlNode *xmlnode)
|
|
{
|
|
xmlOutputBuffer *output_buffer;
|
|
xmlBuffer *buffer;
|
|
xmlCharEncodingHandlerPtr handler = NULL;
|
|
xmlChar *ret, *b64_ret;
|
|
char *rret;
|
|
unsigned long in_len;
|
|
int rc = 0;
|
|
z_stream stream;
|
|
|
|
handler = xmlFindCharEncodingHandler("utf-8");
|
|
buffer = xmlBufferCreate();
|
|
output_buffer = xmlOutputBufferCreateBuffer(buffer, handler);
|
|
xmlNodeDumpOutput(output_buffer, NULL, xmlnode, 0, 0, NULL);
|
|
xmlOutputBufferClose(output_buffer);
|
|
xmlBufferAdd(buffer, BAD_CAST "", 1);
|
|
|
|
in_len = strlen((char*)xmlBufferContent(buffer));
|
|
ret = g_malloc(in_len * 2);
|
|
/* deflating should never increase the required size but we are
|
|
* more conservative than that. Twice the size should be
|
|
* enough. */
|
|
|
|
stream.next_in = (xmlChar*)xmlBufferContent(buffer);
|
|
stream.avail_in = in_len;
|
|
stream.next_out = ret;
|
|
stream.avail_out = in_len * 2;
|
|
|
|
stream.zalloc = NULL;
|
|
stream.zfree = NULL;
|
|
stream.opaque = NULL;
|
|
|
|
/* -MAX_WBITS to disable zib headers */
|
|
rc = deflateInit2(&stream, Z_DEFAULT_COMPRESSION,
|
|
Z_DEFLATED, -MAX_WBITS, 5, 0);
|
|
if (rc == Z_OK) {
|
|
rc = deflate(&stream, Z_FINISH);
|
|
if (rc != Z_STREAM_END) {
|
|
deflateEnd(&stream);
|
|
if (rc == Z_OK) {
|
|
rc = Z_BUF_ERROR;
|
|
}
|
|
} else {
|
|
rc = deflateEnd(&stream);
|
|
}
|
|
}
|
|
xmlBufferFree(buffer);
|
|
if (rc != Z_OK) {
|
|
lasso_release(ret);
|
|
message(G_LOG_LEVEL_CRITICAL, "Failed to deflate");
|
|
return NULL;
|
|
}
|
|
|
|
b64_ret = xmlSecBase64Encode(ret, stream.total_out, 0);
|
|
lasso_release(ret);
|
|
|
|
ret = lasso_xmlURIEscapeStr(b64_ret, NULL);
|
|
rret = g_strdup((char*)ret);
|
|
xmlFree(b64_ret);
|
|
xmlFree(ret);
|
|
|
|
return rret;
|
|
}
|
|
|
|
void
|
|
lasso_get_query_string_param_value(const char *qs, const char *param_key, const char **value,
|
|
size_t *length)
|
|
{
|
|
size_t key_size = strlen(param_key);
|
|
|
|
*value = NULL;
|
|
*length = 0;
|
|
while (qs) {
|
|
if (strncmp(qs, param_key, key_size) == 0 &&
|
|
qs[key_size] == '=')
|
|
{
|
|
char *end;
|
|
*value = &qs[key_size+1];
|
|
end = strchr(*value, '&');
|
|
if (! end) {
|
|
end = strchr(*value, ';');
|
|
}
|
|
if (end) {
|
|
*length = (ptrdiff_t)(end - *value);
|
|
} else {
|
|
*length = strlen(*value);
|
|
}
|
|
return;
|
|
}
|
|
qs = strchr(qs, '&');
|
|
}
|
|
}
|
|
|
|
unsigned char*
|
|
lasso_inflate(unsigned char *input, size_t len)
|
|
{
|
|
z_stream zstr;
|
|
unsigned char *output;
|
|
int z_err;
|
|
|
|
zstr.zalloc = NULL;
|
|
zstr.zfree = NULL;
|
|
zstr.opaque = NULL;
|
|
|
|
output = g_malloc(len*20);
|
|
zstr.avail_in = len;
|
|
zstr.next_in = (unsigned char*)input;
|
|
zstr.total_in = 0;
|
|
zstr.avail_out = len*20;
|
|
zstr.total_out = 0;
|
|
zstr.next_out = output;
|
|
|
|
z_err = inflateInit2(&zstr, -MAX_WBITS);
|
|
if (z_err != Z_OK) {
|
|
message(G_LOG_LEVEL_CRITICAL, "Failed to inflateInit");
|
|
lasso_release_string(output);
|
|
return FALSE;
|
|
}
|
|
|
|
z_err = inflate(&zstr, Z_FINISH);
|
|
if (z_err != Z_STREAM_END) {
|
|
message(G_LOG_LEVEL_CRITICAL, "Failed to inflate");
|
|
inflateEnd(&zstr);
|
|
lasso_release_string(output);
|
|
return NULL;
|
|
}
|
|
output[zstr.total_out] = 0;
|
|
inflateEnd(&zstr);
|
|
|
|
return output;
|
|
}
|
|
|
|
|
|
gboolean
|
|
lasso_node_init_from_deflated_query_part(LassoNode *node, char *deflate_string)
|
|
{
|
|
int len;
|
|
xmlChar *b64_zre, *zre, *re;
|
|
xmlDoc *doc;
|
|
xmlNode *root;
|
|
|
|
b64_zre = (xmlChar*)xmlURIUnescapeString(deflate_string, 0, NULL);
|
|
len = strlen((char*)b64_zre);
|
|
zre = xmlMalloc(len*4);
|
|
len = xmlSecBase64Decode(b64_zre, zre, len*4);
|
|
xmlFree(b64_zre);
|
|
if (len == -1) {
|
|
message(G_LOG_LEVEL_CRITICAL, "Failed to base64-decode query");
|
|
xmlFree(zre);
|
|
return FALSE;
|
|
}
|
|
|
|
re = lasso_inflate(zre, len);
|
|
xmlFree(zre);
|
|
|
|
if (! re)
|
|
return FALSE;
|
|
|
|
doc = lasso_xml_parse_memory((char*)re, strlen((char*)re));
|
|
lasso_release_string(re);
|
|
|
|
root = xmlDocGetRootElement(doc);
|
|
lasso_node_init_from_xml(node, root);
|
|
lasso_release_doc(doc);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
char*
|
|
lasso_concat_url_query(const char *url, const char *query)
|
|
{
|
|
if (strchr(url, '?')) {
|
|
return g_strdup_printf("%s&%s", url, query);
|
|
} else {
|
|
return g_strdup_printf("%s?%s", url, query);
|
|
}
|
|
}
|
|
|
|
static void structuredErrorFunc (void *userData, xmlErrorPtr error) {
|
|
*(int*)userData = error->code;
|
|
}
|
|
|
|
/**
|
|
* lasso_eval_xpath_expression:
|
|
* @xpath_ctx: the XPath context object
|
|
* @expression: a string containg the XPath expression to evaluate
|
|
* @xpath_object_ptr: pointer to an output variable to store the resulting XPath object, can be
|
|
* NULL.
|
|
* @xpath_error_code: pointer to an output variable to store an eventual XPath error code, can be
|
|
* NULL.
|
|
*
|
|
* Evaluates a given XPath expression in the given XPath context. Eventually return an XPath object
|
|
* and/or an error code.
|
|
*
|
|
* Return value: TRUE if no error occurred during evaluation, FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
lasso_eval_xpath_expression(xmlXPathContextPtr xpath_ctx, const char *expression,
|
|
xmlXPathObjectPtr *xpath_object_ptr, int *xpath_error_code)
|
|
{
|
|
xmlXPathObject *xpath_object = NULL;
|
|
int errorCode = 0;
|
|
xmlStructuredErrorFunc oldStructuredErrorFunc;
|
|
gboolean rc = TRUE;
|
|
|
|
g_return_val_if_fail(xpath_ctx != NULL && expression != NULL, FALSE);
|
|
|
|
if (xpath_error_code) { /* reset */
|
|
*xpath_error_code = 0;
|
|
}
|
|
oldStructuredErrorFunc = xpath_ctx->error;
|
|
xpath_ctx->error = structuredErrorFunc;
|
|
xpath_ctx->userData = &errorCode;
|
|
xpath_object = xmlXPathEvalExpression((xmlChar*)expression, xpath_ctx);
|
|
xpath_ctx->error = oldStructuredErrorFunc;
|
|
xpath_ctx->userData = NULL;
|
|
|
|
if (xpath_object) {
|
|
if (xpath_object_ptr) {
|
|
lasso_transfer_xpath_object(*xpath_object_ptr, xpath_object);
|
|
}
|
|
} else {
|
|
rc = FALSE;
|
|
}
|
|
|
|
if (xpath_error_code && errorCode) {
|
|
*xpath_error_code = errorCode;
|
|
}
|
|
lasso_release_xpath_object(xpath_object);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static gboolean
|
|
lasso_saml_constrain_dsigctxt(xmlSecDSigCtxPtr dsigCtx) {
|
|
/* Limit allowed transforms for signature and reference processing */
|
|
if((xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformInclC14NId) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformExclC14NId) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformInclC14NWithCommentsId) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformExclC14NWithCommentsId) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformInclC14N11Id) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformInclC14N11WithCommentsId) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformSha512Id) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformHmacSha512Id) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformRsaSha512Id) < 0)
|
|
) {
|
|
|
|
message(G_LOG_LEVEL_CRITICAL, "Error: failed to limit allowed signature transforms");
|
|
return FALSE;
|
|
}
|
|
|
|
if (lasso_get_min_signature_method() <= LASSO_SIGNATURE_METHOD_RSA_SHA384) {
|
|
if ((xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformSha384Id) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformHmacSha384Id) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformRsaSha384Id) < 0)) {
|
|
|
|
message(G_LOG_LEVEL_CRITICAL, "Error: failed to limit allowed sha384 signature transforms");
|
|
return FALSE;
|
|
}
|
|
|
|
if (xmlSecDSigCtxEnableReferenceTransform(dsigCtx, xmlSecTransformSha384Id) < 0) {
|
|
|
|
message(G_LOG_LEVEL_CRITICAL, "Error: failed to limit allowed sha384 reference transforms");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (lasso_get_min_signature_method() <= LASSO_SIGNATURE_METHOD_RSA_SHA256) {
|
|
if ((xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformSha256Id) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformHmacSha256Id) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformRsaSha256Id) < 0)) {
|
|
|
|
message(G_LOG_LEVEL_CRITICAL, "Error: failed to limit allowed sha256 signature transforms");
|
|
return FALSE;
|
|
}
|
|
|
|
if (xmlSecDSigCtxEnableReferenceTransform(dsigCtx, xmlSecTransformSha256Id) < 0) {
|
|
|
|
message(G_LOG_LEVEL_CRITICAL, "Error: failed to limit allowed sha256 reference transforms");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (lasso_get_min_signature_method() <= LASSO_SIGNATURE_METHOD_RSA_SHA1) {
|
|
if ((xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformSha1Id) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformHmacSha1Id) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformDsaSha1Id) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformRsaSha1Id) < 0)) {
|
|
|
|
message(G_LOG_LEVEL_CRITICAL, "Error: failed to limit allowed sha1 signature transforms");
|
|
return FALSE;
|
|
}
|
|
|
|
if (xmlSecDSigCtxEnableReferenceTransform(dsigCtx, xmlSecTransformSha1Id) < 0) {
|
|
|
|
message(G_LOG_LEVEL_CRITICAL, "Error: failed to limit allowed sha1 reference transforms");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if((xmlSecDSigCtxEnableReferenceTransform(dsigCtx, xmlSecTransformInclC14NId) < 0) ||
|
|
(xmlSecDSigCtxEnableReferenceTransform(dsigCtx, xmlSecTransformExclC14NId) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformInclC14NWithCommentsId) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformExclC14NWithCommentsId) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformInclC14N11Id) < 0) ||
|
|
(xmlSecDSigCtxEnableSignatureTransform(dsigCtx, xmlSecTransformInclC14N11WithCommentsId) < 0) ||
|
|
(xmlSecDSigCtxEnableReferenceTransform(dsigCtx, xmlSecTransformSha512Id) < 0) ||
|
|
(xmlSecDSigCtxEnableReferenceTransform(dsigCtx, xmlSecTransformEnvelopedId) < 0)) {
|
|
|
|
message(G_LOG_LEVEL_CRITICAL, "Error: failed to limit allowed reference transforms");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Limit possible key info to X509, RSA and DSA */
|
|
if((xmlSecPtrListAdd(&(dsigCtx->keyInfoReadCtx.enabledKeyData), BAD_CAST xmlSecKeyDataX509Id) < 0) ||
|
|
(xmlSecPtrListAdd(&(dsigCtx->keyInfoReadCtx.enabledKeyData), BAD_CAST xmlSecKeyDataHmacId) < 0) ||
|
|
(xmlSecPtrListAdd(&(dsigCtx->keyInfoReadCtx.enabledKeyData), BAD_CAST xmlSecKeyDataRsaId) < 0) ||
|
|
(xmlSecPtrListAdd(&(dsigCtx->keyInfoReadCtx.enabledKeyData), BAD_CAST xmlSecKeyDataDsaId) < 0)) {
|
|
message(G_LOG_LEVEL_CRITICAL, "Error: failed to limit allowed key data");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* lasso_verify_signature:
|
|
* @signed_node: an #xmlNode containing an enveloped xmlDSig signature
|
|
* @doc: (allow-none): the eventual #xmlDoc from which the node is extracted, if none is given then it will be
|
|
* created
|
|
* @id_attr_name: the id attribune name for this node
|
|
* @keys_manager: (allow-none): an #xmlSecKeysMnr containing the CA cert chain, to validate the key in the
|
|
* signature if there is one.
|
|
* @public_key: (allow-none): a public key to validate the signature, if present the function ignore the key
|
|
* contained in the signature.
|
|
* @signature_verification_option: flag to specify option about signature validation, see
|
|
* #SignatureVerificationOption.
|
|
* @uri_references: if the signature references multiple nodes, return them as a list of node IDs.
|
|
*
|
|
* This function validate a signature on an xmlNode following the instructions given in the document
|
|
* Assertions and Protocol or the OASIS Security Markup Language (SAML) V1.1.
|
|
*
|
|
* The only kind of references that are accepted in thoses signatures are node ID references,
|
|
* looking like #xxx;.
|
|
*
|
|
* Beware that it does not validate every needed properties for a SAML assertion, request or
|
|
* response to be acceptable.
|
|
*
|
|
* Return: 0 if signature was validated, and error code otherwise.
|
|
*/
|
|
|
|
int
|
|
lasso_verify_signature(xmlNode *signed_node, xmlDoc *doc, const char *id_attr_name,
|
|
xmlSecKeysMngr *keys_manager, xmlSecKey *public_key,
|
|
SignatureVerificationOption signature_verification_option,
|
|
GList **uri_references)
|
|
{
|
|
int rc = LASSO_DS_ERROR_SIGNATURE_VERIFICATION_FAILED;
|
|
xmlNodePtr signature = NULL;
|
|
xmlSecDSigCtx *dsigCtx = NULL;
|
|
xmlChar *id = NULL;
|
|
char *reference_uri = NULL;
|
|
xmlSecDSigReferenceCtx *dsig_reference_ctx = NULL;
|
|
gboolean free_the_doc = FALSE;
|
|
|
|
g_return_val_if_fail(signed_node && (keys_manager || public_key),
|
|
LASSO_PARAM_ERROR_INVALID_VALUE);
|
|
|
|
if (lasso_flag_verify_signature == FALSE) {
|
|
return 0;
|
|
}
|
|
/* Find signature as direct child. */
|
|
signature = xmlSecFindChild(signed_node, xmlSecNodeSignature, xmlSecDSigNs);
|
|
goto_cleanup_if_fail_with_rc (signature, LASSO_DS_ERROR_SIGNATURE_NOT_FOUND);
|
|
|
|
/* Create a temporary doc, if needed */
|
|
if (doc == NULL) {
|
|
doc = xmlNewDoc((xmlChar*)XML_DEFAULT_VERSION);
|
|
goto_cleanup_if_fail_with_rc(doc, LASSO_ERROR_OUT_OF_MEMORY);
|
|
xmlDocSetRootElement(doc, signed_node);
|
|
free_the_doc = TRUE;
|
|
}
|
|
|
|
/* Find ID */
|
|
if (id_attr_name) {
|
|
id = xmlGetProp(signed_node, (xmlChar*)id_attr_name);
|
|
if (id && (xmlGetID(doc, id) == NULL)) {
|
|
xmlAddID(NULL, doc, id, xmlHasProp(signed_node, (xmlChar*)id_attr_name));
|
|
}
|
|
}
|
|
|
|
/* Create DSig context */
|
|
dsigCtx = xmlSecDSigCtxCreate(keys_manager);
|
|
goto_cleanup_if_fail_with_rc(doc, LASSO_DS_ERROR_CONTEXT_CREATION_FAILED);
|
|
/* XXX: Is xmlSecTransformUriTypeSameEmpty permitted ?
|
|
* I would say yes only if signed_node == signature->parent. */
|
|
dsigCtx->enabledReferenceUris = 0;
|
|
dsigCtx->enabledReferenceUris |= xmlSecTransformUriTypeSameDocument;
|
|
if (signature_verification_option & EMPTY_URI) {
|
|
dsigCtx->enabledReferenceUris |= xmlSecTransformUriTypeEmpty;
|
|
}
|
|
|
|
goto_cleanup_if_fail_with_rc(lasso_saml_constrain_dsigctxt(dsigCtx),
|
|
LASSO_DS_ERROR_SIGNATURE_VERIFICATION_FAILED);
|
|
/* Given a public key use it to validate the signature ! */
|
|
if (public_key) {
|
|
dsigCtx->signKey = xmlSecKeyDuplicate(public_key);
|
|
}
|
|
|
|
/* Verify signature */
|
|
goto_cleanup_if_fail_with_rc(xmlSecDSigCtxVerify(dsigCtx, signature) >= 0,
|
|
LASSO_DS_ERROR_SIGNATURE_VERIFICATION_FAILED);
|
|
goto_cleanup_if_fail_with_rc(dsigCtx->status == xmlSecDSigStatusSucceeded,
|
|
LASSO_DS_ERROR_SIGNATURE_VERIFICATION_FAILED);
|
|
|
|
/* There should be only one reference */
|
|
goto_cleanup_if_fail_with_rc(((signature_verification_option & NO_SINGLE_REFERENCE) == 0) ||
|
|
xmlSecPtrListGetSize(&(dsigCtx->signedInfoReferences)) == 1, LASSO_DS_ERROR_TOO_MUCH_REFERENCES);
|
|
/* The reference should be to the signed node */
|
|
{
|
|
gboolean ok = FALSE;
|
|
reference_uri = g_strdup_printf("#%s", id);
|
|
dsig_reference_ctx = (xmlSecDSigReferenceCtx*)
|
|
xmlSecPtrListGetItem(&(dsigCtx->signedInfoReferences), 0);
|
|
ok |= dsig_reference_ctx != 0 &&
|
|
lasso_strisequal((char*)dsig_reference_ctx->uri, reference_uri);
|
|
ok |= (signature_verification_option & EMPTY_URI)
|
|
&& xmlDocGetRootElement(doc) == signed_node
|
|
&& dsig_reference_ctx != NULL
|
|
&& lasso_strisequal((char*)dsig_reference_ctx->uri, "");
|
|
goto_cleanup_if_fail_with_rc(ok,
|
|
LASSO_DS_ERROR_INVALID_REFERENCE_FOR_SAML);
|
|
}
|
|
/* Keep URI of all nodes signed if asked */
|
|
if (uri_references) {
|
|
gint size = xmlSecPtrListGetSize(&(dsigCtx->signedInfoReferences));
|
|
int i;
|
|
for (i = 0; i < size; ++i) {
|
|
|
|
dsig_reference_ctx = (xmlSecDSigReferenceCtx*)xmlSecPtrListGetItem(&(dsigCtx->signedInfoReferences), i);
|
|
if (dsig_reference_ctx == NULL ||
|
|
dsig_reference_ctx->uri == NULL) {
|
|
message(G_LOG_LEVEL_CRITICAL, "dsig_reference_ctx->uri cannot be null");
|
|
continue;
|
|
}
|
|
lasso_list_add_xml_string(*uri_references, dsig_reference_ctx->uri);
|
|
}
|
|
}
|
|
|
|
if (dsigCtx->status == xmlSecDSigStatusSucceeded) {
|
|
rc = 0;
|
|
}
|
|
|
|
cleanup:
|
|
lasso_release_string(reference_uri);
|
|
lasso_release_signature_context(dsigCtx);
|
|
if (free_the_doc) {
|
|
xmlUnlinkNode(signed_node);
|
|
xmlSetTreeDoc(signed_node, NULL);
|
|
lasso_release_doc(doc);
|
|
}
|
|
lasso_release_string(id);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* lasso_xml_next_element_node:
|
|
* @node: the pointer to an XML node.
|
|
*
|
|
* Seraches for the next element node.
|
|
*
|
|
* Returns: the pointer to next element node or NULL if it is not found.
|
|
*/
|
|
xmlNodePtr
|
|
lasso_xml_next_element_node(xmlNodePtr node)
|
|
{
|
|
|
|
for (; node != NULL && node->type != XML_ELEMENT_NODE; node = node->next);
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* lasso_xml_get_node_ns_href:
|
|
* @node: the pointer to node.
|
|
*
|
|
* Get's node's namespace href.
|
|
*
|
|
* Returns: node's namespace href.
|
|
*/
|
|
const xmlChar*
|
|
lasso_xml_get_node_ns_href(const xmlNodePtr node)
|
|
{
|
|
xmlNsPtr ns;
|
|
|
|
if (node == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* do we have a namespace in the node? */
|
|
if (node->ns != NULL) {
|
|
return node->ns->href;
|
|
}
|
|
|
|
/* search for default namespace */
|
|
ns = xmlSearchNs(node->doc, node, NULL);
|
|
if (ns != NULL) {
|
|
return ns->href;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* lasso_xml_is_element_node:
|
|
* @node: the pointer to an XML node.
|
|
* @name: the name,
|
|
* @ns: the namespace href.
|
|
*
|
|
* Checks that the node has a given name and a given namespace href.
|
|
*
|
|
* Returns: true if the node matches false otherwise.
|
|
*/
|
|
gboolean
|
|
lasso_xml_is_element_node(const xmlNodePtr node,
|
|
const xmlChar *name, const xmlChar *ns)
|
|
{
|
|
if (node == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
return (node->type == XML_ELEMENT_NODE &&
|
|
xmlStrEqual(node->name, name) &&
|
|
xmlStrEqual(lasso_xml_get_node_ns_href(node), ns));
|
|
}
|
|
|
|
gboolean
|
|
lasso_xml_is_soap(xmlNode *root)
|
|
{
|
|
return lasso_xml_is_element_node(root, BAD_CAST "Envelope",
|
|
BAD_CAST LASSO_SOAP_ENV_HREF);
|
|
}
|
|
|
|
/**
|
|
* lasso_xml_soap11_get_header:
|
|
* @envelope_node: the pointer to <soap:Envelope> node.
|
|
*
|
|
* Gets pointer to the <soap:Header> node.
|
|
*
|
|
* Returns: pointer to <soap:Header> node or NULL if an error occurs.
|
|
*/
|
|
xmlNodePtr
|
|
lasso_xml_soap11_get_header(xmlNodePtr envelope_node)
|
|
{
|
|
xmlNodePtr node;
|
|
|
|
if (envelope_node == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* optional Header node is first */
|
|
node = lasso_xml_next_element_node(envelope_node->children);
|
|
if (lasso_xml_is_element_node(node, BAD_CAST "Header",
|
|
BAD_CAST LASSO_SOAP_ENV_HREF)) {
|
|
return node;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* lasso_xml_soap11_get_body:
|
|
* @envelope_node: the pointer to <soap:Envelope> node.
|
|
*
|
|
* Gets pointer to the <soap:Body> node.
|
|
*
|
|
* Returns: pointer to <soap:Body> node or NULL if an error occurs.
|
|
*/
|
|
xmlNodePtr
|
|
lasso_xml_soap11_get_body(xmlNodePtr envelope_node)
|
|
{
|
|
xmlNodePtr node;
|
|
|
|
if (envelope_node == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* optional Header node first */
|
|
node = lasso_xml_next_element_node(envelope_node->children);
|
|
if (lasso_xml_is_element_node(node, BAD_CAST "Header",
|
|
BAD_CAST LASSO_SOAP_ENV_HREF)) {
|
|
node = lasso_xml_next_element_node(node->next);
|
|
}
|
|
|
|
/* Body node is next */
|
|
if (!lasso_xml_is_element_node(node, BAD_CAST "Body",
|
|
BAD_CAST LASSO_SOAP_ENV_HREF)) {
|
|
return NULL;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
xmlNode*
|
|
lasso_xml_get_soap_content(xmlNode *root)
|
|
{
|
|
gboolean is_soap11 = FALSE;
|
|
xmlNode *content = NULL;
|
|
|
|
is_soap11 = lasso_xml_is_element_node(root, BAD_CAST "Envelope",
|
|
BAD_CAST LASSO_SOAP_ENV_HREF);
|
|
if (is_soap11) {
|
|
xmlNode *body;
|
|
|
|
if (is_soap11) {
|
|
body = lasso_xml_soap11_get_body(root);
|
|
}
|
|
if (body) {
|
|
content = xmlSecGetNextElementNode(body->children);
|
|
}
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
LassoMessageFormat
|
|
lasso_xml_parse_message(const char *message, LassoMessageFormat constraint, xmlDoc **doc_out, xmlNode **root_out)
|
|
{
|
|
char *msg = NULL;
|
|
gboolean b64 = FALSE;
|
|
LassoMessageFormat rc = LASSO_MESSAGE_FORMAT_UNKNOWN;
|
|
xmlDoc *doc = NULL;
|
|
xmlNode *root = NULL;
|
|
gboolean any = constraint == LASSO_MESSAGE_FORMAT_UNKNOWN;
|
|
|
|
msg = (char*)message;
|
|
|
|
/* BASE64 case */
|
|
if (any || constraint == LASSO_MESSAGE_FORMAT_BASE64) {
|
|
if (message[0] != 0 && is_base64(message)) {
|
|
msg = g_malloc(strlen(message));
|
|
rc = xmlSecBase64Decode((xmlChar*)message, (xmlChar*)msg, strlen(message));
|
|
if (rc >= 0) {
|
|
b64 = TRUE;
|
|
} else {
|
|
lasso_release(msg);
|
|
msg = (char*)message;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* XML case */
|
|
if (any || constraint == LASSO_MESSAGE_FORMAT_BASE64 ||
|
|
constraint == LASSO_MESSAGE_FORMAT_XML ||
|
|
constraint == LASSO_MESSAGE_FORMAT_SOAP) {
|
|
if (strchr(msg, '<')) {
|
|
doc = lasso_xml_parse_memory(msg, strlen(msg));
|
|
if (doc == NULL) {
|
|
rc = LASSO_MESSAGE_FORMAT_UNKNOWN;
|
|
goto cleanup;
|
|
}
|
|
root = xmlDocGetRootElement(doc);
|
|
|
|
if (any || constraint == LASSO_MESSAGE_FORMAT_SOAP) {
|
|
gboolean is_soap = FALSE;
|
|
|
|
is_soap = lasso_xml_is_soap(root);
|
|
if (is_soap) {
|
|
root = lasso_xml_get_soap_content(root);
|
|
}
|
|
if (! root) {
|
|
rc = LASSO_MESSAGE_FORMAT_ERROR;
|
|
goto cleanup;
|
|
}
|
|
if (is_soap) {
|
|
rc = LASSO_MESSAGE_FORMAT_SOAP;
|
|
goto cleanup;
|
|
}
|
|
if (b64) {
|
|
lasso_release(msg);
|
|
rc = LASSO_MESSAGE_FORMAT_BASE64;
|
|
goto cleanup;
|
|
}
|
|
rc = LASSO_MESSAGE_FORMAT_XML;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
if (doc_out) {
|
|
*doc_out = doc;
|
|
if (root_out) {
|
|
*root_out = root;
|
|
}
|
|
} else {
|
|
lasso_release_doc(doc);
|
|
lasso_release_xml_node(root);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static gboolean
|
|
is_base64(const char *message)
|
|
{
|
|
const char *c;
|
|
|
|
c = message;
|
|
while (*c != 0 && (isalnum((int)*c) || *c == '+' || *c == '/' || *c == '\n' || *c == '\r')) c++;
|
|
while (*c == '=' || *c == '\n' || *c == '\r') c++; /* trailing = */
|
|
|
|
if (*c == 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* lasso_node_decrypt_xmlnode
|
|
* @encrypted_element: an EncrytpedData #xmlNode
|
|
* @encrypted_keys: a #GList of EncrytpedKey #xmlNode
|
|
* @encryption_private_key : a private key to decrypt the node
|
|
* @output: a pointer a #LassoNode variable to store the decrypted element
|
|
*
|
|
* Try to decrypt an encrypted element.
|
|
*
|
|
* Return value: 0 if successful,
|
|
* LASSO_DS_ERROR_DECRYPTION_FAILED if decrypted failed,
|
|
* LASSO_XML_ERROR_OBJECT_CONSTRUCTION_FAILED if construction of a #LassoNode from the decrypted
|
|
* content failed,
|
|
* LASSO_DS_ERROR_CONTEXT_CREATION_FAILED if some context initialization failed.
|
|
**/
|
|
int
|
|
lasso_node_decrypt_xmlnode(xmlNode* encrypted_element,
|
|
GList *encrypted_keys,
|
|
xmlSecKey *encryption_private_key,
|
|
LassoNode **output)
|
|
{
|
|
xmlDocPtr doc = NULL;
|
|
xmlDocPtr doc2 = NULL;
|
|
xmlSecEncCtxPtr encCtx = NULL;
|
|
xmlSecKeyPtr sym_key = NULL;
|
|
xmlSecBufferPtr key_buffer = NULL;
|
|
LassoNode *decrypted_node = NULL;
|
|
xmlNodePtr encrypted_data_node = NULL;
|
|
xmlNodePtr encrypted_key_node = NULL;
|
|
xmlNodePtr encryption_method_node = NULL;
|
|
xmlChar *algorithm = NULL;
|
|
xmlSecKeyDataId key_type;
|
|
GList *i = NULL;
|
|
int rc = LASSO_XMLENC_ERROR_INVALID_ENCRYPTED_DATA;
|
|
|
|
if (encryption_private_key == NULL || !xmlSecKeyIsValid(encryption_private_key)) {
|
|
message(G_LOG_LEVEL_WARNING, "Invalid decryption key");
|
|
rc = LASSO_PROFILE_ERROR_MISSING_ENCRYPTION_PRIVATE_KEY;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Need to duplicate it because xmlSecEncCtxDestroy(encCtx); will destroy it */
|
|
encryption_private_key = xmlSecKeyDuplicate(encryption_private_key);
|
|
|
|
encrypted_data_node = xmlCopyNode(encrypted_element, 1);
|
|
|
|
/* Get the encryption algorithm for EncryptedData in its EncryptionMethod node */
|
|
encryption_method_node = xmlSecTmplEncDataGetEncMethodNode(encrypted_data_node);
|
|
if (encryption_method_node == NULL) {
|
|
message(G_LOG_LEVEL_WARNING, "No EncryptionMethod node in EncryptedData");
|
|
goto cleanup;
|
|
}
|
|
algorithm = xmlGetProp(encryption_method_node, (xmlChar *)"Algorithm");
|
|
if (algorithm == NULL) {
|
|
message(G_LOG_LEVEL_WARNING, "No EncryptionMethod");
|
|
goto cleanup;
|
|
}
|
|
if (strstr((char*)algorithm , "#aes")) {
|
|
key_type = xmlSecKeyDataAesId;
|
|
} else if (strstr((char*)algorithm , "des")) {
|
|
key_type = xmlSecKeyDataDesId;
|
|
} else {
|
|
message(G_LOG_LEVEL_WARNING, "Unknown EncryptionMethod");
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Get the EncryptedKey */
|
|
if (encrypted_keys != NULL) {
|
|
for (i = encrypted_keys; i; i = g_list_next(i)) {
|
|
if (i->data == NULL)
|
|
continue;
|
|
if (strcmp((char*)((xmlNode*)i->data)->name, "EncryptedKey") == 0) {
|
|
encrypted_key_node = xmlCopyNode((xmlNode*)(i->data), 1);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
/* Look an EncryptedKey inside the EncryptedData */
|
|
xmlNodePtr key_info;
|
|
do {
|
|
key_info = xmlSecFindChild(encrypted_data_node, xmlSecNodeKeyInfo, xmlSecDSigNs);
|
|
if (! key_info)
|
|
break;
|
|
encrypted_key_node = xmlSecFindChild(key_info, xmlSecNodeEncryptedKey, xmlSecEncNs);
|
|
} while (0);
|
|
}
|
|
|
|
if (encrypted_key_node == NULL) {
|
|
message(G_LOG_LEVEL_WARNING, "No EncryptedKey node");
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Create a document to contain the node to decrypt */
|
|
doc = xmlNewDoc((xmlChar*)"1.0");
|
|
xmlDocSetRootElement(doc, encrypted_data_node);
|
|
|
|
doc2 = xmlNewDoc((xmlChar*)"1.0");
|
|
xmlDocSetRootElement(doc2, encrypted_key_node);
|
|
|
|
/* create encryption context to decrypt EncryptedKey */
|
|
encCtx = xmlSecEncCtxCreate(NULL);
|
|
if (encCtx == NULL) {
|
|
message(G_LOG_LEVEL_WARNING, "Failed to create encryption context");
|
|
rc = LASSO_DS_ERROR_CONTEXT_CREATION_FAILED;
|
|
goto cleanup;
|
|
}
|
|
encCtx->encKey = encryption_private_key;
|
|
encCtx->mode = xmlEncCtxModeEncryptedKey;
|
|
|
|
/* decrypt the EncryptedKey */
|
|
key_buffer = xmlSecEncCtxDecryptToBuffer(encCtx, encrypted_key_node);
|
|
if (key_buffer != NULL) {
|
|
sym_key = xmlSecKeyReadBuffer(key_type, key_buffer);
|
|
}
|
|
rc = LASSO_DS_ERROR_ENCRYPTION_FAILED;
|
|
if (sym_key == NULL) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* create encryption context to decrypt EncryptedData */
|
|
xmlSecEncCtxDestroy(encCtx);
|
|
encCtx = xmlSecEncCtxCreate(NULL);
|
|
if (encCtx == NULL) {
|
|
message(G_LOG_LEVEL_WARNING, "Failed to create encryption context");
|
|
rc = LASSO_DS_ERROR_CONTEXT_CREATION_FAILED;
|
|
goto cleanup;
|
|
}
|
|
encCtx->encKey = sym_key;
|
|
encCtx->mode = xmlEncCtxModeEncryptedData;
|
|
|
|
/* decrypt the EncryptedData */
|
|
if ((xmlSecEncCtxDecrypt(encCtx, encrypted_data_node) < 0) || (encCtx->result == NULL)) {
|
|
rc = LASSO_XMLENC_ERROR_INVALID_ENCRYPTED_DATA;
|
|
message(G_LOG_LEVEL_WARNING, "EncryptedData decryption failed");
|
|
goto cleanup;
|
|
}
|
|
|
|
decrypted_node = lasso_node_new_from_xmlNode(doc->children);
|
|
if (decrypted_node) {
|
|
rc = 0;
|
|
} else {
|
|
rc = LASSO_XML_ERROR_OBJECT_CONSTRUCTION_FAILED;
|
|
}
|
|
if (output) {
|
|
lasso_assign_gobject(*output, decrypted_node);
|
|
}
|
|
|
|
cleanup:
|
|
if (doc == NULL && encrypted_data_node) {
|
|
xmlFreeNode(encrypted_data_node);
|
|
}
|
|
if (doc2 == NULL && encrypted_key_node) {
|
|
xmlFreeNode(encrypted_key_node);
|
|
}
|
|
if (encCtx) {
|
|
xmlSecEncCtxDestroy(encCtx);
|
|
}
|
|
lasso_release_doc(doc);
|
|
lasso_release_doc(doc2);
|
|
lasso_release_gobject(decrypted_node);
|
|
lasso_release_xml_string(algorithm);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void xml_logv(int log_level, const char *msg, va_list arg_ptr) {
|
|
char buffer[512], *escaped;
|
|
|
|
vsnprintf(buffer, 512, msg, arg_ptr);
|
|
escaped = g_strescape(buffer, NULL);
|
|
g_log(LASSO_LOG_DOMAIN, log_level, "libxml2: %s", escaped);
|
|
lasso_release_string(escaped);
|
|
}
|
|
|
|
static void __xmlWarningFunc(G_GNUC_UNUSED void *userData, const char *msg, ...) {
|
|
va_list arg_ptr;
|
|
|
|
va_start(arg_ptr, msg);
|
|
xml_logv(G_LOG_LEVEL_WARNING, msg, arg_ptr);
|
|
va_end(arg_ptr);
|
|
}
|
|
|
|
static void __xmlErrorFunc(G_GNUC_UNUSED void *userData, const char *msg, ...) {
|
|
va_list arg_ptr;
|
|
|
|
va_start(arg_ptr, msg);
|
|
xml_logv(G_LOG_LEVEL_CRITICAL, msg, arg_ptr);
|
|
va_end(arg_ptr);
|
|
}
|
|
|
|
/**
|
|
* lasso_xml_parse_memory:
|
|
* @buffer: an pointer to a char array
|
|
* @size: the size of the array
|
|
*
|
|
* Parse an XML in-memory block and build a tree; exactly like xmlParseMemory
|
|
* safe two exceptions:
|
|
* <itemizedlist>
|
|
* <listitem><para>
|
|
* it won't download anything from the network (XML_PARSE_NONET)
|
|
* </listitem></para>
|
|
* <listitem><para>
|
|
* it will refuse documents with a DTD (for security reason)
|
|
* </para></listitem>
|
|
* </itemizedlist>
|
|
*
|
|
* Return value: the resulting document tree
|
|
**/
|
|
xmlDocPtr
|
|
lasso_xml_parse_memory(const char *buffer, int size) {
|
|
return lasso_xml_parse_memory_with_error(buffer, size, NULL);
|
|
}
|
|
|
|
xmlDocPtr
|
|
lasso_xml_parse_memory_with_error(const char *buffer, int size, xmlError *error) {
|
|
xmlDocPtr ret;
|
|
xmlParserCtxtPtr ctxt;
|
|
|
|
ctxt = xmlCreateMemoryParserCtxt(buffer, size);
|
|
if (ctxt == NULL) {
|
|
return NULL;
|
|
}
|
|
xmlDetectSAX2(ctxt);
|
|
if (ctxt->errNo == XML_ERR_NO_MEMORY) {
|
|
return NULL;
|
|
}
|
|
ctxt->recovery = 0;
|
|
xmlCtxtUseOptions(ctxt, XML_PARSE_NONET);
|
|
if (error) {
|
|
ctxt->sax->warning = NULL;
|
|
ctxt->sax->error = NULL;
|
|
ctxt->sax->fatalError = NULL;
|
|
} else {
|
|
/* reroute errors through GLib logger */
|
|
ctxt->sax->warning = __xmlWarningFunc;
|
|
ctxt->sax->error = __xmlErrorFunc;
|
|
}
|
|
|
|
xmlParseDocument(ctxt);
|
|
|
|
if (error) {
|
|
xmlCopyError(&ctxt->lastError, error);
|
|
}
|
|
|
|
if (ctxt->wellFormed && ctxt->myDoc->intSubset != NULL) {
|
|
message(G_LOG_LEVEL_WARNING, "Denied message with DTD content");
|
|
ctxt->wellFormed = 0;
|
|
}
|
|
|
|
if (ctxt->wellFormed) {
|
|
ret = ctxt->myDoc;
|
|
} else {
|
|
ret = NULL;
|
|
lasso_release_doc(ctxt->myDoc);
|
|
ctxt->myDoc = NULL;
|
|
}
|
|
xmlFreeParserCtxt(ctxt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* lasso_xml_parse_file:
|
|
* @filepath: the file path
|
|
*
|
|
* Parse an XML file, report errors through GLib logger with the Lasso domain
|
|
*
|
|
* Return value: a newly create #xmlDoc object if successful, NULL otherwise.
|
|
*/
|
|
xmlDocPtr
|
|
lasso_xml_parse_file(const char *filepath)
|
|
{
|
|
char *file_content;
|
|
size_t file_length;
|
|
GError *error = NULL;
|
|
|
|
if (g_file_get_contents(filepath, &file_content, &file_length, &error)) {
|
|
xmlDocPtr ret;
|
|
|
|
ret = lasso_xml_parse_memory(file_content, file_length);
|
|
lasso_release(file_content);
|
|
return ret;
|
|
} else {
|
|
message(G_LOG_LEVEL_CRITICAL, "Cannot read XML file %s: %s", filepath, error->message);
|
|
g_error_free(error);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* (almost) straight from libxml2 internal API */
|
|
static void
|
|
xmlDetectSAX2(xmlParserCtxtPtr ctxt) {
|
|
if (ctxt == NULL) return;
|
|
#ifdef LIBXML_SAX1_ENABLED
|
|
if ((ctxt->sax != NULL) && (ctxt->sax->initialized == XML_SAX2_MAGIC) &&
|
|
((ctxt->sax->startElementNs != NULL) ||
|
|
(ctxt->sax->endElementNs != NULL)))
|
|
ctxt->sax2 = 1;
|
|
#else
|
|
ctxt->sax2 = 1;
|
|
#endif /* LIBXML_SAX1_ENABLED */
|
|
|
|
ctxt->str_xml = xmlDictLookup(ctxt->dict, BAD_CAST "xml", 3);
|
|
ctxt->str_xmlns = xmlDictLookup(ctxt->dict, BAD_CAST "xmlns", 5);
|
|
ctxt->str_xml_ns = xmlDictLookup(ctxt->dict, XML_XML_NAMESPACE, 36);
|
|
if ((ctxt->str_xml==NULL) || (ctxt->str_xmlns==NULL) ||
|
|
(ctxt->str_xml_ns == NULL)) {
|
|
ctxt->errNo = XML_ERR_NO_MEMORY;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* lasso_get_relaystate_from_query:
|
|
* @query: a C-string containing the query part of an URL
|
|
*
|
|
* Extracts the relaystate argument contained in an URL query string.
|
|
*
|
|
* Return value: NULL if not relaystate is present in the URL, the RelayState decoded value
|
|
* otherwise.
|
|
*/
|
|
char *
|
|
lasso_get_relaystate_from_query(const char *query) {
|
|
const char *start = NULL, *end = NULL;
|
|
char *result = NULL;
|
|
|
|
if (query == NULL)
|
|
return NULL;
|
|
if (strncmp(query, LASSO_SAML2_FIELD_RELAYSTATE "=", sizeof(LASSO_SAML2_FIELD_RELAYSTATE
|
|
"=") - 1) == 0) {
|
|
start = query + sizeof(LASSO_SAML2_FIELD_RELAYSTATE);
|
|
}
|
|
if (! start) {
|
|
if (! start) {
|
|
start = strstr(query, "&RelayState=");
|
|
}
|
|
if (! start) {
|
|
start = strstr(query, ";RelayState=");
|
|
}
|
|
if (start) {
|
|
start += sizeof(LASSO_SAML2_FIELD_RELAYSTATE "=");
|
|
}
|
|
}
|
|
if (start) {
|
|
ptrdiff_t length;
|
|
const char *end2;
|
|
|
|
end = strchr(start, '&');
|
|
end2 = strchr(start, ';');
|
|
if ((end2 != NULL) && ((end == NULL) || (end2 < end))) {
|
|
end = end2;
|
|
}
|
|
if (end) {
|
|
length = end-start;
|
|
} else {
|
|
length = strlen(start);
|
|
}
|
|
if (length > query_string_attribute_length_limit) {
|
|
message(G_LOG_LEVEL_WARNING, "Received a RelayState of size %ti > %u",
|
|
length, query_string_attribute_length_limit);
|
|
}
|
|
if (length) {
|
|
result = xmlURIUnescapeString(start, length, NULL);
|
|
} else {
|
|
result = g_malloc0(1);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* lasso_url_add_parameters:
|
|
* @url: the original URL
|
|
* @free: whether to free the URL parameter
|
|
* @...: pairs of strings, key, value, followed by NULL
|
|
*
|
|
* Iterate over all pairs of key,value, and concatenate them to @url encoded as "&key=value", where
|
|
* key and value are url-encoded.
|
|
* If free is true and at least one pair was given, url is freed. If url is NULL, the first
|
|
* ampersand is omitted.
|
|
*
|
|
* Return value: a newly allocated string, or url.
|
|
*/
|
|
char*
|
|
lasso_url_add_parameters(char *url,
|
|
gboolean free, ...)
|
|
{
|
|
char *old_url = url, *new_url = NULL;
|
|
xmlChar *encoded_key = NULL, *encoded_value;
|
|
va_list ap;
|
|
|
|
va_start(ap, free);
|
|
|
|
while (1) {
|
|
char *key;
|
|
char *value;
|
|
|
|
key = va_arg(ap, char*);
|
|
if (! key) {
|
|
break;
|
|
}
|
|
encoded_key = lasso_xmlURIEscapeStr((xmlChar*)key, NULL);
|
|
goto_cleanup_if_fail(encoded_key);
|
|
|
|
value = va_arg(ap, char*);
|
|
if (! value) {
|
|
message(G_LOG_LEVEL_CRITICAL, "lasso_url_add_parameter: key without a value !!");
|
|
break;
|
|
}
|
|
encoded_value = lasso_xmlURIEscapeStr((xmlChar*)value, NULL);
|
|
goto_cleanup_if_fail(encoded_value);
|
|
|
|
if (old_url) {
|
|
new_url = g_strdup_printf("%s&%s=%s", old_url, (char*)encoded_key, (char*)encoded_value);
|
|
} else {
|
|
new_url = g_strdup_printf("%s=%s", (char*)encoded_key, (char*)encoded_value);
|
|
}
|
|
if (old_url != url) {
|
|
lasso_release_string(old_url);
|
|
}
|
|
old_url = new_url;
|
|
|
|
lasso_release_xml_string(encoded_key);
|
|
lasso_release_xml_string(encoded_value);
|
|
}
|
|
cleanup:
|
|
va_end(ap);
|
|
if (free && new_url != url) {
|
|
lasso_release(url);
|
|
}
|
|
lasso_release_xml_string(encoded_key);
|
|
|
|
return new_url;
|
|
}
|
|
|
|
xmlSecKey*
|
|
_lasso_xmlsec_load_key_from_buffer(const char *buffer, size_t length, const char *password,
|
|
LassoSignatureMethod signature_method, const char *certificate)
|
|
{
|
|
int i = 0;
|
|
xmlSecKeyDataFormat key_formats[] = {
|
|
xmlSecKeyDataFormatPem,
|
|
xmlSecKeyDataFormatCertPem,
|
|
xmlSecKeyDataFormatDer,
|
|
xmlSecKeyDataFormatBinary,
|
|
xmlSecKeyDataFormatCertDer,
|
|
xmlSecKeyDataFormatPkcs8Der,
|
|
xmlSecKeyDataFormatPkcs8Pem,
|
|
0
|
|
};
|
|
xmlSecKeyDataFormat cert_formats[] = {
|
|
xmlSecKeyDataFormatCertPem,
|
|
xmlSecKeyDataFormatCertDer,
|
|
0
|
|
};
|
|
xmlSecKey *private_key = NULL;
|
|
|
|
/* is the signature algo allowed */
|
|
goto_cleanup_if_fail(lasso_allowed_signature_method(signature_method));
|
|
|
|
xmlSecErrorsDefaultCallbackEnableOutput(FALSE);
|
|
switch (signature_method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
for (i = 0; key_formats[i] && private_key == NULL; i++) {
|
|
private_key = xmlSecCryptoAppKeyLoadMemory((xmlSecByte*)buffer, length,
|
|
key_formats[i], password, NULL, NULL);
|
|
}
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA512:
|
|
private_key = xmlSecKeyReadMemory(xmlSecKeyDataHmacId, (xmlSecByte*)buffer, length);
|
|
if (private_key) {
|
|
xmlSecKeySetName(private_key, BAD_CAST "shared");
|
|
}
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_LAST:
|
|
case LASSO_SIGNATURE_METHOD_NONE:
|
|
g_assert_not_reached();
|
|
}
|
|
goto_cleanup_if_fail(private_key != NULL);
|
|
if (certificate) {
|
|
int done = 0;
|
|
|
|
switch (signature_method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
|
|
for (i=0; cert_formats[i]; i++) {
|
|
if (xmlSecCryptoAppKeyCertLoad(private_key, certificate, cert_formats[i])
|
|
== 0) {
|
|
done = 1;
|
|
break;
|
|
}
|
|
if (xmlSecCryptoAppKeyCertLoadMemory(private_key, BAD_CAST certificate,
|
|
strlen(certificate), cert_formats[i]) == 0) {
|
|
done = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (done == 0) {
|
|
warning("Unable to load certificate: %s", certificate);
|
|
}
|
|
break;
|
|
default:
|
|
warning("Attaching a certificate for signature only "
|
|
"works with DSA and RSA algorithms.");
|
|
}
|
|
}
|
|
cleanup:
|
|
xmlSecErrorsDefaultCallbackEnableOutput(TRUE);
|
|
return private_key;
|
|
}
|
|
/**
|
|
* lasso_base64_decode:
|
|
* @from: the source base64 encoded string
|
|
* @buffer: an output argument to place the resulting buffer pointer
|
|
* @buffer_len: an output argument to place the resulting buffer length
|
|
*
|
|
* Decode the given string as Base64 and allocate a buffer for the decoded content, place the
|
|
* pointer to the buffer in @buffer and the length in @buffer_len
|
|
*
|
|
* Return value: TRUE if successful, FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
lasso_base64_decode(const char *from, char **buffer, int *buffer_len)
|
|
{
|
|
size_t len = strlen(from);
|
|
int ret;
|
|
|
|
/* base64 map 4 bytes to 3 */
|
|
len = len / 4 + (len % 4 ? 1 : 0);
|
|
len *= 3;
|
|
len += 1; /* zero byte */
|
|
*buffer = g_malloc0(len);
|
|
|
|
xmlSecErrorsDefaultCallbackEnableOutput(FALSE);
|
|
ret = xmlSecBase64Decode(BAD_CAST from, BAD_CAST *buffer, len);
|
|
xmlSecErrorsDefaultCallbackEnableOutput(TRUE);
|
|
if (ret <= 0) {
|
|
lasso_release_string(*buffer);
|
|
return FALSE;
|
|
}
|
|
*buffer_len = ret;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* lasso_xmlURIEscapeStr:
|
|
* @from: the source URI string
|
|
* @list: optional list of characters not to escape
|
|
*
|
|
* Drop-in replacement for libxml2 xmlURIEscapeStr(), but encoding
|
|
* everything but [A-Za-z0-9._~-] which are the unreserved chartacters
|
|
* for RFC3986 section 2.3
|
|
*
|
|
* Return value: a buffer containing the URL-encoded string or NULL on error
|
|
*/
|
|
xmlChar *
|
|
lasso_xmlURIEscapeStr(const xmlChar *from, const xmlChar *list)
|
|
{
|
|
size_t len = 0;
|
|
const xmlChar *fp;
|
|
xmlChar *result;
|
|
int ri;
|
|
|
|
if (list == NULL)
|
|
list = "";
|
|
|
|
for (fp = from; *fp; fp++) {
|
|
if (isalnum(*fp) || strchr("._~-", *fp) || strchr(list, *fp))
|
|
len++;
|
|
else
|
|
len += 3;
|
|
}
|
|
|
|
result = g_malloc0(len + 1);
|
|
ri = 0;
|
|
|
|
for (fp = from; *fp; fp++) {
|
|
if (isalnum(*fp) || strchr("._~-", *fp) || strchr(list, *fp)) {
|
|
result[ri++] = *fp;
|
|
} else {
|
|
int msb = (*fp & 0xf0) >> 4;
|
|
int lsb = *fp & 0x0f;
|
|
|
|
result[ri++] = '%';
|
|
result[ri++] = (msb > 9) ? 'A' + msb - 10 : '0' + msb;
|
|
result[ri++] = (lsb > 9) ? 'A' + lsb - 10 : '0' + lsb;
|
|
}
|
|
}
|
|
|
|
result[ri++] = '\0';
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* lasso_xmlsec_load_private_key_from_buffer:
|
|
* @buffer: a buffer containing a key in any format
|
|
* @length: length of the buffer
|
|
* @password: eventually a password
|
|
*/
|
|
xmlSecKey*
|
|
lasso_xmlsec_load_private_key_from_buffer(const char *buffer, size_t length, const char *password,
|
|
LassoSignatureMethod signature_method, const char *certificate) {
|
|
xmlSecKey *private_key = NULL;
|
|
|
|
private_key = _lasso_xmlsec_load_key_from_buffer(buffer, length, password, signature_method, certificate);
|
|
|
|
/* special lasso metadata hack */
|
|
if (! private_key) {
|
|
char *out = NULL;
|
|
int len;
|
|
|
|
if (lasso_base64_decode(buffer, &out, &len)) {
|
|
private_key = _lasso_xmlsec_load_key_from_buffer((char*)out, len, password,
|
|
signature_method, certificate);
|
|
}
|
|
lasso_release_string(out);
|
|
}
|
|
|
|
return private_key;
|
|
}
|
|
|
|
xmlSecKey*
|
|
lasso_xmlsec_load_private_key(const char *filename_or_buffer, const char *password, LassoSignatureMethod signature_method, const char *certificate) {
|
|
char *buffer = NULL;
|
|
size_t length;
|
|
xmlSecKey *ret;
|
|
|
|
if (! filename_or_buffer)
|
|
return NULL;
|
|
|
|
if (g_file_get_contents(filename_or_buffer, &buffer, &length, NULL)) {
|
|
ret = lasso_xmlsec_load_private_key_from_buffer(buffer, length, password, signature_method, certificate);
|
|
} else {
|
|
ret = lasso_xmlsec_load_private_key_from_buffer(filename_or_buffer,
|
|
strlen(filename_or_buffer), password, signature_method,
|
|
certificate);
|
|
}
|
|
lasso_release_string(buffer);
|
|
return ret;
|
|
|
|
}
|
|
|
|
gboolean
|
|
lasso_get_base64_content(xmlNode *node, char **content, size_t *length) {
|
|
xmlChar *base64, *stripped_base64;
|
|
xmlChar *result;
|
|
int base64_length;
|
|
int rc = 0;
|
|
|
|
if (! node || ! content || ! length)
|
|
return FALSE;
|
|
|
|
base64 = xmlNodeGetContent(node);
|
|
if (! base64)
|
|
return FALSE;
|
|
stripped_base64 = base64;
|
|
/* skip spaces */
|
|
while (*stripped_base64 && isspace(*stripped_base64))
|
|
stripped_base64++;
|
|
|
|
base64_length = strlen((char*)stripped_base64);
|
|
result = g_new(xmlChar, base64_length);
|
|
xmlSecErrorsDefaultCallbackEnableOutput(FALSE);
|
|
rc = xmlSecBase64Decode(stripped_base64, result, base64_length);
|
|
xmlSecErrorsDefaultCallbackEnableOutput(TRUE);
|
|
xmlFree(base64);
|
|
if (rc < 0) {
|
|
return FALSE;
|
|
} else {
|
|
*content = (char*)g_memdup(result, rc);
|
|
xmlFree(result);
|
|
*length = rc;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
xmlSecKeyPtr
|
|
lasso_xmlsec_load_key_info(xmlNode *key_descriptor)
|
|
{
|
|
xmlSecKeyPtr key, result = NULL;
|
|
xmlNodePtr key_info = NULL;
|
|
xmlSecKeyInfoCtx ctx = {0};
|
|
xmlSecKeysMngr *keys_mngr = NULL;
|
|
xmlNodePtr key_value = NULL;
|
|
int rc = 0;
|
|
xmlChar *content = NULL;
|
|
X509 *cert = NULL;
|
|
xmlSecKeyDataPtr cert_data = NULL;
|
|
xmlSecKeyDataPtr cert_key = NULL;
|
|
|
|
if (! key_descriptor)
|
|
return NULL;
|
|
|
|
key_info = xmlSecFindChild(key_descriptor, xmlSecNodeKeyInfo, xmlSecDSigNs);
|
|
if (! key_info)
|
|
return NULL;
|
|
keys_mngr = xmlSecKeysMngrCreate();
|
|
rc = xmlSecCryptoAppDefaultKeysMngrInit(keys_mngr);
|
|
if (rc < 0) {
|
|
goto next;
|
|
}
|
|
rc = xmlSecKeyInfoCtxInitialize(&ctx, keys_mngr);
|
|
if (rc < 0) {
|
|
goto next;
|
|
}
|
|
ctx.flags = XMLSEC_KEYINFO_FLAGS_DONT_STOP_ON_KEY_FOUND
|
|
| XMLSEC_KEYINFO_FLAGS_X509DATA_DONT_VERIFY_CERTS;
|
|
ctx.mode = xmlSecKeyInfoModeRead;
|
|
ctx.keyReq.keyId = xmlSecKeyDataIdUnknown;
|
|
ctx.keyReq.keyType = xmlSecKeyDataTypePublic;
|
|
ctx.keyReq.keyUsage = xmlSecKeyDataUsageAny;
|
|
ctx.certsVerificationDepth = 0;
|
|
|
|
key = xmlSecKeyCreate();
|
|
if (lasso_flag_pem_public_key) {
|
|
xmlSecErrorsDefaultCallbackEnableOutput(FALSE);
|
|
}
|
|
rc = xmlSecKeyInfoNodeRead(key_info, key, &ctx);
|
|
if (lasso_flag_pem_public_key) {
|
|
xmlSecErrorsDefaultCallbackEnableOutput(TRUE);
|
|
}
|
|
xmlSecKeyInfoCtxFinalize(&ctx);
|
|
|
|
if (rc != 0) {
|
|
goto next;
|
|
}
|
|
/* reference, do not free */
|
|
cert_data = xmlSecKeyGetData(key, xmlSecOpenSSLKeyDataX509Id);
|
|
|
|
if (! cert_data) {
|
|
goto next;
|
|
}
|
|
/* reference, do not free */
|
|
cert = xmlSecOpenSSLKeyDataX509GetCert(cert_data, 0);
|
|
if (! cert) {
|
|
goto next;
|
|
}
|
|
/* new value, free */
|
|
cert_key = xmlSecOpenSSLX509CertGetKey(cert);
|
|
rc = xmlSecKeySetValue(key, cert_key);
|
|
if (rc < 0) {
|
|
xmlSecKeyDataDestroy(cert_key);
|
|
}
|
|
next:
|
|
/* We found a key, exit */
|
|
if (xmlSecKeyIsValid(key)) {
|
|
result = key;
|
|
key = NULL;
|
|
goto cleanup;
|
|
}
|
|
xmlSecKeyDestroy(key);
|
|
if (lasso_flag_pem_public_key) {
|
|
if (! (key_value = xmlSecFindChild(key_info, xmlSecNodeKeyValue, xmlSecDSigNs)) &&
|
|
! (key_value = xmlSecFindNode(key_info, xmlSecNodeX509Certificate, xmlSecDSigNs))) {
|
|
goto cleanup;
|
|
}
|
|
|
|
content = xmlNodeGetContent(key_value);
|
|
if (content) {
|
|
result = lasso_xmlsec_load_private_key_from_buffer((char*)content,
|
|
strlen((char*)content), NULL, lasso_get_default_signature_method(), NULL);
|
|
xmlFree(content);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
lasso_release_key_manager(keys_mngr);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* lasso_xmlnode_to_string:
|
|
* @xmlnode: an #xmlNode structure
|
|
* @format: whether to allow formatting (it break XML signatures)
|
|
*
|
|
* Transform an XML node to a C string
|
|
*
|
|
* Return value: a newly allocated C string
|
|
*/
|
|
char*
|
|
lasso_xmlnode_to_string(xmlNode *node, gboolean format, int level)
|
|
{
|
|
xmlOutputBufferPtr output_buffer;
|
|
xmlBuffer *buffer;
|
|
char *str;
|
|
|
|
if (! node)
|
|
return NULL;
|
|
|
|
buffer = xmlBufferCreate();
|
|
output_buffer = xmlOutputBufferCreateBuffer(buffer, NULL);
|
|
xmlNodeDumpOutput(output_buffer, NULL, node, level, format ? 1 : 0, NULL);
|
|
xmlOutputBufferClose(output_buffer);
|
|
xmlBufferAdd(buffer, BAD_CAST "", 1);
|
|
/* do not mix XML and GLib strings, so we must copy */
|
|
str = g_strdup((char*)xmlBufferContent(buffer));
|
|
xmlBufferFree(buffer);
|
|
|
|
return str;
|
|
}
|
|
|
|
/**
|
|
* lasso_string_to_xsd_integer:
|
|
* @saml2_assertion: a #LassoSaml2Assertion object
|
|
* @integer: a long int variable to store the result
|
|
*
|
|
* Parse a string using the xsd:integer schema.
|
|
*
|
|
* Return value: TRUE if successful, FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
lasso_string_to_xsd_integer(const char *str, long int *integer)
|
|
{
|
|
const char *save = str;
|
|
|
|
if (! str)
|
|
return FALSE;
|
|
while (isspace(*str))
|
|
str++;
|
|
if (*str == '+' || *str == '-')
|
|
str++;
|
|
while (isdigit(*str))
|
|
str++;
|
|
while (isspace(*str))
|
|
str++;
|
|
if (*str)
|
|
return FALSE;
|
|
*integer = strtol(save, NULL, 10);
|
|
if ((*integer == LONG_MAX || *integer == LONG_MIN) && errno == ERANGE)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
lasso_set_string_from_prop(char **str, xmlNode *node, xmlChar *name, xmlChar *ns)
|
|
{
|
|
xmlChar *value;
|
|
|
|
g_assert(str);
|
|
g_assert(node);
|
|
value = xmlGetNsProp(node, name, ns);
|
|
if (value) {
|
|
lasso_assign_string(*str, (char*)value);
|
|
}
|
|
lasso_release_xml_string(value);
|
|
}
|
|
|
|
|
|
/**
|
|
* lasso_log_set_handler:
|
|
* @log_levels: the log levels to apply the log handler for. To handle fatal
|
|
* and recursive messages as well, combine the log levels with the
|
|
* #G_LOG_FLAG_FATAL and #G_LOG_FLAG_RECURSION bit flags.
|
|
* @log_func: the log handler function.
|
|
* @user_data: data passed to the log handler.
|
|
*
|
|
* Sets the log handler for a domain and a set of log levels. To handle fatal
|
|
* and recursive messages the @log_levels parameter must be combined with the
|
|
* #G_LOG_FLAG_FATAL and #G_LOG_FLAG_RECURSION bit flags.
|
|
*
|
|
* Note that since the #G_LOG_LEVEL_ERROR log level is always fatal, if you
|
|
* want to set a handler for this log level you must combine it with
|
|
* #G_LOG_FLAG_FATAL.
|
|
*
|
|
* Returns: the id of the new handler.
|
|
**/
|
|
guint
|
|
lasso_log_set_handler(GLogLevelFlags log_levels, GLogFunc log_func, gpointer user_data)
|
|
{
|
|
return g_log_set_handler(LASSO_LOG_DOMAIN, log_levels, log_func, user_data);
|
|
}
|
|
|
|
/**
|
|
* lasso_log_remove_handler:
|
|
* @handler_id: the id of the handler, which was returned in
|
|
* lasso_log_set_handler().
|
|
*
|
|
* Removes the log handler.
|
|
**/
|
|
void
|
|
lasso_log_remove_handler(guint handler_id)
|
|
{
|
|
g_log_remove_handler(LASSO_LOG_DOMAIN, handler_id);
|
|
}
|
|
|
|
/**
|
|
* lasso_get_hmac_key:
|
|
* @key: an #xmlSecKey object
|
|
* @buffer: a byte buffer of size @size
|
|
* @size: the size of @buffer as bytes
|
|
*
|
|
* Extract the symetric HMAC key from the #xmlSecKey structure and place a pointer to i into the
|
|
* buffer variable.
|
|
*
|
|
* Return value: 0 if successful, an error code otherwise.
|
|
*/
|
|
lasso_error_t
|
|
lasso_get_hmac_key(const xmlSecKey *key, void **buffer, size_t *size)
|
|
{
|
|
xmlSecKeyDataPtr key_data;
|
|
xmlSecBufferPtr key_data_buffer;
|
|
|
|
lasso_null_param(key);
|
|
lasso_null_param(buffer);
|
|
lasso_null_param(size);
|
|
|
|
if (key->value->id != xmlSecKeyDataHmacId) {
|
|
return LASSO_PARAM_ERROR_INVALID_VALUE;
|
|
}
|
|
key_data = xmlSecKeyGetValue((xmlSecKeyPtr)key);
|
|
g_return_val_if_fail(key_data, LASSO_PARAM_ERROR_INVALID_VALUE);
|
|
key_data_buffer = xmlSecKeyDataBinaryValueGetBuffer(key_data);
|
|
g_return_val_if_fail(key_data_buffer, LASSO_PARAM_ERROR_INVALID_VALUE);
|
|
*buffer = xmlSecBufferGetData(key_data_buffer);
|
|
*size = xmlSecBufferGetSize(key_data_buffer);
|
|
g_return_val_if_fail(*buffer && *size, LASSO_PARAM_ERROR_INVALID_VALUE);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* lasso_make_signature_context_from_buffer:
|
|
* @buffer: a byte buffer of size @length
|
|
* @length: the size of @buffer as bytes
|
|
* @password: an eventual password to decoded the private key contained in @buffer
|
|
* @signature_method: the signature method to associate to this key
|
|
* @certificate: a certificate as a file path or PEM encoded in a NULL-terminated string, to
|
|
* associate with the key, it will be used to fill the KeyInfo node in an eventual signature.
|
|
*
|
|
* Load a signature key and return an initialized #LassoSignatureContext structure. If the structure
|
|
* contains a new #xmlSecKey it must be freed by the caller. If your must store it. use
|
|
* lasso_assign_new_signature_context and not lasso_assign_signature_context which is gonna
|
|
* duplicate the key and so make a leak.
|
|
*
|
|
* Return value: an initialized LassoSignatureContext containing a freshly created @xmlSecKey object
|
|
* successful, LASSO_SIGNATURE_CONTEXT_NONE otherwise. The caller must free the #xmlSecKey.
|
|
*/
|
|
LassoSignatureContext
|
|
lasso_make_signature_context_from_buffer(const void *buffer, size_t length, const char *password,
|
|
LassoSignatureMethod signature_method, const char *certificate) {
|
|
LassoSignatureContext context = LASSO_SIGNATURE_CONTEXT_NONE;
|
|
|
|
context.signature_key = lasso_xmlsec_load_private_key_from_buffer(buffer, length, password,
|
|
signature_method, certificate);
|
|
if (context.signature_key) {
|
|
context.signature_method = signature_method;
|
|
}
|
|
return context;
|
|
}
|
|
|
|
/**
|
|
* lasso_make_signature_context_from_path_or_string:
|
|
* @filename_or_buffer: a file path of a string containing the key PEM or Base64 encoded
|
|
* @password: an eventual password to decoded the private key contained in @buffer
|
|
* @signature_method: the signature method to associate to this key
|
|
* @certificate: a certificate as a file path or PEM encoded in a NULL-terminated string, to
|
|
* associate with the key, it will be used to fill the KeyInfo node in an eventual signature.
|
|
*
|
|
* Load a signature key and return an initialized #LassoSignatureContext structure. If the structure
|
|
* contains a new #xmlSecKey it must be freed by the caller. If your must store it. use
|
|
* lasso_assign_new_signature_context and not lasso_assign_signature_context which is gonna
|
|
* duplicate the key and so make a leak.
|
|
*
|
|
* Return value: an initialized LassoSignatureContext containing a freshly created @xmlSecKey object
|
|
* successful, LASSO_SIGNATURE_CONTEXT_NONE otherwise.
|
|
*/
|
|
LassoSignatureContext
|
|
lasso_make_signature_context_from_path_or_string(char *filename_or_buffer, const char *password,
|
|
LassoSignatureMethod signature_method, const char *certificate) {
|
|
LassoSignatureContext context = LASSO_SIGNATURE_CONTEXT_NONE;
|
|
|
|
context.signature_key = lasso_xmlsec_load_private_key(filename_or_buffer, password,
|
|
signature_method, certificate);
|
|
if (context.signature_key) {
|
|
context.signature_method = signature_method;
|
|
}
|
|
return context;
|
|
}
|
|
|
|
xmlNs *
|
|
get_or_define_ns(xmlNode *xmlnode, const xmlChar *ns_uri, const xmlChar *advised_prefix) {
|
|
xmlNs *ns;
|
|
char prefix[20];
|
|
int i = 1;
|
|
|
|
ns = xmlSearchNsByHref(NULL, xmlnode, ns_uri);
|
|
if (ns)
|
|
return ns;
|
|
/* Try with the advised prefix */
|
|
if (advised_prefix) {
|
|
ns = xmlSearchNs(NULL, xmlnode, BAD_CAST advised_prefix);
|
|
if (! ns) { /* If not taken, use it */
|
|
return xmlNewNs(xmlnode, ns_uri, BAD_CAST advised_prefix);
|
|
}
|
|
}
|
|
/* Create a prefix from scratch */
|
|
do {
|
|
sprintf(prefix, "ns%u", i);
|
|
i++;
|
|
ns = xmlSearchNs(NULL, xmlnode, BAD_CAST prefix);
|
|
} while (ns);
|
|
return xmlNewNs(xmlnode, ns_uri, BAD_CAST prefix);
|
|
}
|
|
|
|
|
|
void
|
|
set_qname_attribute(xmlNode *node,
|
|
const xmlChar *attribute_ns_prefix,
|
|
const xmlChar *attribute_ns_href,
|
|
const xmlChar *attribute_name,
|
|
const xmlChar *prefix,
|
|
const xmlChar *href,
|
|
const xmlChar *name) {
|
|
xmlNs *type_ns;
|
|
xmlNs *xsi_ns;
|
|
xmlChar *value;
|
|
|
|
xsi_ns = get_or_define_ns(node, attribute_ns_href, attribute_ns_prefix);
|
|
type_ns = get_or_define_ns(node, href, prefix);
|
|
value = BAD_CAST g_strdup_printf("%s:%s", type_ns->prefix, name);
|
|
xmlSetNsProp(node, xsi_ns, attribute_name, value);
|
|
lasso_release_string(value);
|
|
}
|
|
|
|
void
|
|
set_xsi_type(xmlNode *node,
|
|
const xmlChar *type_ns_prefix,
|
|
const xmlChar *type_ns_href,
|
|
const xmlChar *type_name) {
|
|
set_qname_attribute(node,
|
|
BAD_CAST LASSO_XSI_PREFIX,
|
|
BAD_CAST LASSO_XSI_HREF,
|
|
BAD_CAST "type",
|
|
type_ns_prefix,
|
|
type_ns_href,
|
|
type_name);
|
|
}
|
|
|
|
void
|
|
lasso_xmlnode_add_saml2_signature_template(xmlNode *node, LassoSignatureContext context,
|
|
const char *id) {
|
|
xmlSecTransformId transform_id;
|
|
xmlNode *existing_signature = NULL, *signature = NULL, *reference, *key_info;
|
|
char *uri;
|
|
|
|
g_assert(id);
|
|
|
|
if (! lasso_validate_signature_context(context) || ! node)
|
|
return;
|
|
|
|
switch (context.signature_method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
transform_id = xmlSecTransformRsaSha1Id;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
transform_id = xmlSecTransformDsaSha1Id;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA1:
|
|
transform_id = xmlSecTransformHmacSha1Id;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
transform_id = xmlSecTransformRsaSha256Id;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA256:
|
|
transform_id = xmlSecTransformHmacSha256Id;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
transform_id = xmlSecTransformRsaSha384Id;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA384:
|
|
transform_id = xmlSecTransformHmacSha384Id;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
transform_id = xmlSecTransformRsaSha512Id;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA512:
|
|
transform_id = xmlSecTransformHmacSha512Id;
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
existing_signature = xmlSecFindChild(node, xmlSecNodeSignature, xmlSecDSigNs);
|
|
signature = xmlSecTmplSignatureCreate(NULL,
|
|
xmlSecTransformExclC14NId,
|
|
transform_id, NULL);
|
|
if (existing_signature) {
|
|
xmlSecReplaceNode(existing_signature, signature);
|
|
} else {
|
|
xmlAddChild(node, signature);
|
|
}
|
|
|
|
/* choose a digest for handling references based on the chosen signature algorithm */
|
|
{
|
|
xmlSecTransformId digest_method_id;
|
|
switch (context.signature_method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA1:
|
|
digest_method_id = xmlSecTransformSha1Id;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA256:
|
|
digest_method_id = xmlSecTransformSha256Id;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA384:
|
|
digest_method_id = xmlSecTransformSha384Id;
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA512:
|
|
digest_method_id = xmlSecTransformSha384Id;
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
/* Normally the signature is son of the signed node, which holds an Id attribute, but in
|
|
* other cases, set snippet->offset to 0 and use xmlSecTmpSignatureAddReference from another
|
|
* node get_xmlNode virtual method to add the needed reference.
|
|
*/
|
|
uri = g_strdup_printf("#%s", id);
|
|
reference = xmlSecTmplSignatureAddReference(signature, digest_method_id, NULL,
|
|
(xmlChar*)uri, NULL);
|
|
lasso_release(uri);
|
|
}
|
|
|
|
/* add enveloped transform */
|
|
xmlSecTmplReferenceAddTransform(reference, xmlSecTransformEnvelopedId);
|
|
/* add exclusive C14N transform */
|
|
xmlSecTmplReferenceAddTransform(reference, xmlSecTransformExclC14NId);
|
|
/* if the key is the public part of an asymetric key, add its certificate or the key itself */
|
|
switch (context.signature_method) {
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_DSA_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_RSA_SHA512:
|
|
/* asymetric cryptography methods */
|
|
key_info = xmlSecTmplSignatureEnsureKeyInfo(signature, NULL);
|
|
if (xmlSecKeyGetData(context.signature_key, xmlSecOpenSSLKeyDataX509Id)) {
|
|
/* add <dsig:KeyInfo/> */
|
|
xmlSecTmplKeyInfoAddX509Data(key_info);
|
|
} else {
|
|
xmlSecTmplKeyInfoAddKeyValue(key_info);
|
|
}
|
|
break;
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA1:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA256:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA384:
|
|
case LASSO_SIGNATURE_METHOD_HMAC_SHA512:
|
|
if (context.signature_key->name) {
|
|
key_info = xmlSecTmplSignatureEnsureKeyInfo(signature, NULL);
|
|
xmlSecTmplKeyInfoAddKeyName(key_info, NULL);
|
|
|
|
}
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* lasso_get_saml_message:
|
|
* @query_fields: a NULL terminated array of char* representing a parsed query string.
|
|
*
|
|
* Return the first SAMLRequest or SAMLResponse value in the query string array.
|
|
*/
|
|
static char*
|
|
lasso_get_saml_message(xmlChar **query_fields) {
|
|
int i = 0;
|
|
char *enc = NULL;
|
|
char *message = NULL;
|
|
char *saml_message = NULL;
|
|
char *decoded_message = NULL;
|
|
xmlChar *field = NULL;
|
|
char *t = NULL;
|
|
int rc = 0;
|
|
int len = 0;
|
|
|
|
for (i=0; (field=query_fields[i]); i++) {
|
|
t = strchr((char*)field, '=');
|
|
if (t == NULL)
|
|
continue;
|
|
*t = 0;
|
|
if (strcmp((char*)field, LASSO_SAML2_FIELD_ENCODING) == 0) {
|
|
enc = t+1;
|
|
continue;
|
|
}
|
|
if (strcmp((char*)field, LASSO_SAML2_FIELD_REQUEST) == 0 || strcmp((char*)field, LASSO_SAML2_FIELD_RESPONSE) == 0) {
|
|
message = t+1;
|
|
continue;
|
|
}
|
|
}
|
|
if (message == NULL) {
|
|
return NULL;
|
|
}
|
|
if (enc && strcmp(enc, LASSO_SAML2_DEFLATE_ENCODING) != 0) {
|
|
/* unknown encoding */
|
|
debug("Unknown URL encoding: %64s", enc);
|
|
return NULL;
|
|
}
|
|
len = strlen(message);
|
|
decoded_message = g_malloc(len);
|
|
if (! is_base64(message)) {
|
|
debug("message is not base64");
|
|
goto cleanup;
|
|
}
|
|
rc = xmlSecBase64Decode((xmlChar*)message, (xmlChar*)decoded_message, len);
|
|
if (rc < 0) {
|
|
debug("could not decode redirect SAML message");
|
|
goto cleanup;
|
|
}
|
|
/* rc contains the length of the result */
|
|
saml_message = (char*)lasso_inflate((unsigned char*) decoded_message, rc);
|
|
cleanup:
|
|
if (decoded_message) {
|
|
lasso_release(decoded_message);
|
|
}
|
|
return saml_message;
|
|
}
|
|
|
|
/**
|
|
* lasso_xmltextreader_from_message:
|
|
* @message: the HTTP query, POST content or SOAP message
|
|
*
|
|
* Try to parse the passed message and create an xmlTextReader from it.
|
|
*/
|
|
xmlTextReader *
|
|
lasso_xmltextreader_from_message(const char *message, char **to_free) {
|
|
size_t len = strlen(message);
|
|
char *needle;
|
|
xmlChar **query_fields = NULL;
|
|
char *decoded_message = NULL;
|
|
xmlTextReader *reader = NULL;
|
|
|
|
g_assert(to_free);
|
|
/* Differentiate SOAP from others */
|
|
if (message[0] != '<') {
|
|
needle = strchr(message, '=');
|
|
/* Differentiate redirect binding from POST */
|
|
if (needle && message[len-1] != '=') {
|
|
query_fields = lasso_urlencoded_to_strings(message);
|
|
message = *to_free = lasso_get_saml_message(query_fields);
|
|
if (! message) {
|
|
goto cleanup;
|
|
}
|
|
len = strlen(message);
|
|
} else { /* POST */
|
|
int rc = 0;
|
|
|
|
if (! is_base64(message)) {
|
|
debug("POST message is not base64");
|
|
goto cleanup;
|
|
}
|
|
decoded_message = g_malloc(len);
|
|
rc = xmlSecBase64Decode((xmlChar*)message, (xmlChar*)decoded_message, len);
|
|
if (rc < 0) {
|
|
debug("could not decode POST SAML message");
|
|
goto cleanup;
|
|
}
|
|
len = rc;
|
|
decoded_message[len] = '\0';
|
|
message = *to_free = decoded_message;
|
|
decoded_message = NULL;
|
|
}
|
|
}
|
|
|
|
if (message[0] == '<') // XML case
|
|
reader = xmlReaderForMemory(message, len, "", NULL, XML_PARSE_NONET);
|
|
|
|
cleanup:
|
|
if (query_fields)
|
|
lasso_release_array_of_xml_strings(query_fields);
|
|
if (decoded_message)
|
|
lasso_release_string(decoded_message);
|
|
return reader;
|
|
}
|