/*
* gnome-keyring
*
* Copyright (C) 2008 Stefan Walter
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, see .
*/
#include "config.h"
#include "gck/gck.h"
#include "gcr-internal.h"
#include "gcr-openpgp.h"
#include "gcr-openssh.h"
#include "gcr-parser.h"
#include "gcr-record.h"
#include "gcr-types.h"
#include "gcr/gcr-marshal.h"
#include "gcr/gcr-oids.h"
#include "egg/egg-armor.h"
#include "egg/egg-asn1x.h"
#include "egg/egg-asn1-defs.h"
#include "egg/egg-dn.h"
#include "egg/egg-openssl.h"
#include "egg/egg-secure-memory.h"
#include "egg/egg-symkey.h"
#include
#include
#include
/**
* SECTION:gcr-parser
* @title: GcrParser
* @short_description: Parser for certificate and key files
*
* A #GcrParser can parse various certificate and key files such as OpenSSL
* PEM files, DER encoded certifictes, PKCS\#8 keys and so on. Each various
* format is identified by a value in the #GcrDataFormat enumeration.
*
* In order to parse data, a new parser is created with gcr_parser_new() and
* then the #GcrParser::authenticate and #GcrParser::parsed signals should be
* connected to. Data is then fed to the parser via gcr_parser_parse_data()
* or gcr_parser_parse_stream().
*
* During the #GcrParser::parsed signal the attributes that make up the currently
* parsed item can be retrieved using the gcr_parser_get_parsed_attributes()
* function.
*/
/**
* GcrParser:
*
* A parser for parsing various types of files or data.
*/
/**
* GcrParsed:
*
* A parsed item parsed by a #GcrParser.
*/
/**
* GcrParserClass:
* @parent_class: The parent class
* @authenticate: The default handler for the authenticate signal.
* @parsed: The default handler for the parsed signal.
*
* The class for #GcrParser
*/
/**
* GCR_DATA_ERROR:
*
* A domain for data errors with codes from #GcrDataError
*/
/**
* GcrDataError:
* @GCR_ERROR_FAILURE: Failed to parse or serialize the data
* @GCR_ERROR_UNRECOGNIZED: The data was unrecognized or unsupported
* @GCR_ERROR_CANCELLED: The operation was cancelled
* @GCR_ERROR_LOCKED: The data was encrypted or locked and could not be unlocked.
*
* Values responding to error codes for parsing and serializing data.
*/
enum {
PROP_0,
PROP_PARSED_LABEL,
PROP_PARSED_ATTRIBUTES,
PROP_PARSED_DESCRIPTION
};
enum {
AUTHENTICATE,
PARSED,
LAST_SIGNAL
};
#define SUCCESS 0
static guint signals[LAST_SIGNAL] = { 0 };
struct _GcrParsed {
gint refs;
GckBuilder builder;
GckAttributes *attrs;
const gchar *description;
gchar *label;
GBytes *data;
gboolean sensitive;
GcrDataFormat format;
gchar *filename;
struct _GcrParsed *next;
};
struct _GcrParserPrivate {
GTree *specific_formats;
gboolean normal_formats;
GPtrArray *passwords;
GcrParsed *parsed;
gchar *filename;
};
G_DEFINE_TYPE_WITH_PRIVATE (GcrParser, gcr_parser, G_TYPE_OBJECT);
typedef struct {
gint ask_state;
gint seen;
} PasswordState;
#define PASSWORD_STATE_INIT { 0, 0 }
typedef struct _ParserFormat {
gint format_id;
gint (*function) (GcrParser *self, GBytes *data);
} ParserFormat;
/* Forward declarations */
static const ParserFormat parser_normal[];
static const ParserFormat parser_formats[];
static ParserFormat* parser_format_lookup (gint format_id);
EGG_SECURE_DECLARE (parser);
/* -----------------------------------------------------------------------------
* QUARK DEFINITIONS
*/
/*
* PEM STRINGS
* The xxxxx in: ----- BEGIN xxxxx ------
*/
static GQuark PEM_CERTIFICATE;
static GQuark PEM_RSA_PRIVATE_KEY;
static GQuark PEM_DSA_PRIVATE_KEY;
static GQuark PEM_EC_PRIVATE_KEY;
static GQuark PEM_ANY_PRIVATE_KEY;
static GQuark PEM_ENCRYPTED_PRIVATE_KEY;
static GQuark PEM_PRIVATE_KEY;
static GQuark PEM_PKCS7;
static GQuark PEM_PKCS12;
static GQuark PEM_CERTIFICATE_REQUEST;
static GQuark PEM_NEW_CERTIFICATE_REQUEST;
static GQuark PEM_PUBLIC_KEY;
static GQuark ARMOR_PGP_PUBLIC_KEY_BLOCK;
static GQuark ARMOR_PGP_PRIVATE_KEY_BLOCK;
static void
init_quarks (void)
{
static size_t quarks_inited = 0;
if (g_once_init_enter (&quarks_inited)) {
#define QUARK(name, value) \
name = g_quark_from_static_string(value)
QUARK (PEM_CERTIFICATE, "CERTIFICATE");
QUARK (PEM_PRIVATE_KEY, "PRIVATE KEY");
QUARK (PEM_RSA_PRIVATE_KEY, "RSA PRIVATE KEY");
QUARK (PEM_DSA_PRIVATE_KEY, "DSA PRIVATE KEY");
QUARK (PEM_EC_PRIVATE_KEY, "EC PRIVATE KEY");
QUARK (PEM_ANY_PRIVATE_KEY, "ANY PRIVATE KEY");
QUARK (PEM_ENCRYPTED_PRIVATE_KEY, "ENCRYPTED PRIVATE KEY");
QUARK (PEM_PKCS7, "PKCS7");
QUARK (PEM_PKCS12, "PKCS12");
QUARK (PEM_CERTIFICATE_REQUEST, "CERTIFICATE REQUEST");
QUARK (PEM_NEW_CERTIFICATE_REQUEST, "NEW CERTIFICATE REQUEST");
QUARK (PEM_PUBLIC_KEY, "PUBLIC KEY");
QUARK (ARMOR_PGP_PRIVATE_KEY_BLOCK, "PGP PRIVATE KEY BLOCK");
QUARK (ARMOR_PGP_PUBLIC_KEY_BLOCK, "PGP PUBLIC KEY BLOCK");
#undef QUARK
g_once_init_leave (&quarks_inited, 1);
}
}
/* -----------------------------------------------------------------------------
* INTERNAL
*/
static void
parsed_attribute (GcrParsed *parsed,
CK_ATTRIBUTE_TYPE type,
gconstpointer data,
gsize n_data)
{
g_assert (parsed != NULL);
gck_builder_add_data (&parsed->builder, type, data, n_data);
}
static void
parsed_attribute_bytes (GcrParsed *parsed,
CK_ATTRIBUTE_TYPE type,
GBytes *data)
{
g_assert (parsed != NULL);
gck_builder_add_data (&parsed->builder, type,
g_bytes_get_data (data, NULL),
g_bytes_get_size (data));
}
static gboolean
parsed_asn1_number (GcrParsed *parsed,
GNode *asn,
const gchar *part,
CK_ATTRIBUTE_TYPE type)
{
GBytes *value;
g_assert (asn);
g_assert (parsed);
value = egg_asn1x_get_integer_as_usg (egg_asn1x_node (asn, part, NULL));
if (value == NULL)
return FALSE;
parsed_attribute_bytes (parsed, type, value);
g_bytes_unref (value);
return TRUE;
}
static gboolean
parsed_asn1_element (GcrParsed *parsed,
GNode *asn,
const gchar *part,
CK_ATTRIBUTE_TYPE type)
{
GBytes *value;
g_assert (asn);
g_assert (parsed);
value = egg_asn1x_get_element_raw (egg_asn1x_node (asn, part, NULL));
if (value == NULL)
return FALSE;
parsed_attribute_bytes (parsed, type, value);
g_bytes_unref (value);
return TRUE;
}
static gboolean
parsed_asn1_structure (GcrParsed *parsed,
GNode *asn,
CK_ATTRIBUTE_TYPE type)
{
GBytes *value;
g_assert (asn);
g_assert (parsed);
value = egg_asn1x_encode (asn, g_realloc);
if (value == NULL)
return FALSE;
parsed_attribute_bytes (parsed, type, value);
g_bytes_unref (value);
return TRUE;
}
static void
parsed_ulong_attribute (GcrParsed *parsed,
CK_ATTRIBUTE_TYPE type,
gulong value)
{
g_assert (parsed != NULL);
gck_builder_add_ulong (&parsed->builder, type, value);
}
static void
parsed_boolean_attribute (GcrParsed *parsed,
CK_ATTRIBUTE_TYPE type,
gboolean value)
{
g_assert (parsed != NULL);
gck_builder_add_boolean (&parsed->builder, type, value);
}
static void
parsing_block (GcrParsed *parsed,
gint format,
GBytes *data)
{
g_assert (parsed != NULL);
g_assert (data != NULL);
g_assert (format != 0);
g_assert (parsed->data == NULL);
parsed->format = format;
parsed->data = g_bytes_ref (data);
}
static void
parsed_description (GcrParsed *parsed,
CK_OBJECT_CLASS klass)
{
g_assert (parsed != NULL);
switch (klass) {
case CKO_PRIVATE_KEY:
parsed->description = _("Private Key");
break;
case CKO_CERTIFICATE:
parsed->description = _("Certificate");
break;
case CKO_PUBLIC_KEY:
parsed->description = _("Public Key");
break;
case CKO_GCR_GNUPG_RECORDS:
parsed->description = _("PGP Key");
break;
case CKO_GCR_CERTIFICATE_REQUEST:
parsed->description = _("Certificate Request");
break;
default:
parsed->description = NULL;
break;
}
}
static void
parsing_object (GcrParsed *parsed,
CK_OBJECT_CLASS klass)
{
g_assert (parsed != NULL);
gck_builder_clear (&parsed->builder);
if (parsed->sensitive)
gck_builder_init_full (&parsed->builder, GCK_BUILDER_SECURE_MEMORY);
else
gck_builder_init_full (&parsed->builder, GCK_BUILDER_NONE);
gck_builder_add_ulong (&parsed->builder, CKA_CLASS, klass);
parsed_description (parsed, klass);
}
static void
parsed_attributes (GcrParsed *parsed,
GckAttributes *attrs)
{
gulong klass;
g_assert (parsed != NULL);
g_assert (attrs != NULL);
if (gck_attributes_find_ulong (attrs, CKA_CLASS, &klass))
parsed_description (parsed, klass);
gck_builder_add_all (&parsed->builder, attrs);
}
static void
parsed_label (GcrParsed *parsed,
const gchar *label)
{
g_assert (parsed != NULL);
g_assert (parsed->label == NULL);
parsed->label = g_strdup (label);
}
static GcrParsed *
push_parsed (GcrParser *self,
gboolean sensitive)
{
GcrParsed *parsed = g_new0 (GcrParsed, 1);
parsed->refs = 0;
parsed->sensitive = sensitive;
parsed->next = self->pv->parsed;
parsed->filename = g_strdup (gcr_parser_get_filename (self));
self->pv->parsed = parsed;
return parsed;
}
static void
_gcr_parsed_free (GcrParsed *parsed)
{
gck_builder_clear (&parsed->builder);
if (parsed->attrs)
gck_attributes_unref (parsed->attrs);
if (parsed->data)
g_bytes_unref (parsed->data);
g_free (parsed->label);
g_free (parsed->filename);
g_free (parsed);
}
static void
pop_parsed (GcrParser *self,
GcrParsed *parsed)
{
g_assert (parsed == self->pv->parsed);
self->pv->parsed = parsed->next;
_gcr_parsed_free (parsed);
}
static gint
enum_next_password (GcrParser *self, PasswordState *state, const gchar **password)
{
gboolean result;
/*
* Next passes we look through all the passwords that the parser
* has seen so far. This is because different parts of a encrypted
* container (such as PKCS#12) often use the same password even
* if with different algorithms.
*
* If we didn't do this and the user chooses enters a password,
* but doesn't save it, they would get prompted for the same thing
* over and over, dumb.
*/
/* Look in our list of passwords */
if (state->seen < self->pv->passwords->len) {
g_assert (state->seen >= 0);
*password = g_ptr_array_index (self->pv->passwords, state->seen);
++state->seen;
return SUCCESS;
}
/* Fire off all the parsed property signals so anyone watching can update their state */
g_object_notify (G_OBJECT (self), "parsed-description");
g_object_notify (G_OBJECT (self), "parsed-attributes");
g_object_notify (G_OBJECT (self), "parsed-label");
g_signal_emit (self, signals[AUTHENTICATE], 0, state->ask_state, &result);
++state->ask_state;
if (!result)
return GCR_ERROR_CANCELLED;
/* Return any passwords added */
if (state->seen < self->pv->passwords->len) {
g_assert (state->seen >= 0);
*password = g_ptr_array_index (self->pv->passwords, state->seen);
++state->seen;
return SUCCESS;
}
return GCR_ERROR_LOCKED;
}
static void
parsed_fire (GcrParser *self,
GcrParsed *parsed)
{
g_assert (GCR_IS_PARSER (self));
g_assert (parsed != NULL);
g_assert (parsed == self->pv->parsed);
g_assert (parsed->attrs == NULL);
parsed->attrs = gck_attributes_ref_sink (gck_builder_end (&parsed->builder));
g_object_notify (G_OBJECT (self), "parsed-description");
g_object_notify (G_OBJECT (self), "parsed-attributes");
g_object_notify (G_OBJECT (self), "parsed-label");
g_signal_emit (self, signals[PARSED], 0);
}
/* -----------------------------------------------------------------------------
* RSA PRIVATE KEY
*/
static gint
parse_der_private_key_rsa (GcrParser *self,
GBytes *data)
{
gint res = GCR_ERROR_UNRECOGNIZED;
GNode *asn = NULL;
gulong version;
GcrParsed *parsed;
parsed = push_parsed (self, TRUE);
asn = egg_asn1x_create_and_decode (pk_asn1_tab, "RSAPrivateKey", data);
if (!asn)
goto done;
parsing_block (parsed, GCR_FORMAT_DER_PRIVATE_KEY_RSA, data);
parsing_object (parsed, CKO_PRIVATE_KEY);
parsed_ulong_attribute (parsed, CKA_KEY_TYPE, CKK_RSA);
parsed_boolean_attribute (parsed, CKA_PRIVATE, CK_TRUE);
res = GCR_ERROR_FAILURE;
if (!egg_asn1x_get_integer_as_ulong (egg_asn1x_node (asn, "version", NULL), &version))
goto done;
/* We only support simple version */
if (version != 0) {
res = GCR_ERROR_UNRECOGNIZED;
g_message ("unsupported version of RSA key: %lu", version);
goto done;
}
if (!parsed_asn1_number (parsed, asn, "modulus", CKA_MODULUS) ||
!parsed_asn1_number (parsed, asn, "publicExponent", CKA_PUBLIC_EXPONENT) ||
!parsed_asn1_number (parsed, asn, "privateExponent", CKA_PRIVATE_EXPONENT) ||
!parsed_asn1_number (parsed, asn, "prime1", CKA_PRIME_1) ||
!parsed_asn1_number (parsed, asn, "prime2", CKA_PRIME_2) ||
!parsed_asn1_number (parsed, asn, "coefficient", CKA_COEFFICIENT))
goto done;
parsed_fire (self, parsed);
res = SUCCESS;
done:
egg_asn1x_destroy (asn);
if (res == GCR_ERROR_FAILURE)
g_message ("invalid RSA key");
pop_parsed (self, parsed);
return res;
}
/* -----------------------------------------------------------------------------
* DSA PRIVATE KEY
*/
static gint
parse_der_private_key_dsa (GcrParser *self,
GBytes *data)
{
gint ret = GCR_ERROR_UNRECOGNIZED;
GNode *asn = NULL;
GcrParsed *parsed;
parsed = push_parsed (self, TRUE);
asn = egg_asn1x_create_and_decode (pk_asn1_tab, "DSAPrivateKey", data);
if (!asn)
goto done;
parsing_block (parsed, GCR_FORMAT_DER_PRIVATE_KEY_DSA, data);
parsing_object (parsed, CKO_PRIVATE_KEY);
parsed_ulong_attribute (parsed, CKA_KEY_TYPE, CKK_DSA);
parsed_boolean_attribute (parsed, CKA_PRIVATE, CK_TRUE);
ret = GCR_ERROR_FAILURE;
if (!parsed_asn1_number (parsed, asn, "p", CKA_PRIME) ||
!parsed_asn1_number (parsed, asn, "q", CKA_SUBPRIME) ||
!parsed_asn1_number (parsed, asn, "g", CKA_BASE) ||
!parsed_asn1_number (parsed, asn, "priv", CKA_VALUE))
goto done;
parsed_fire (self, parsed);
ret = SUCCESS;
done:
egg_asn1x_destroy (asn);
if (ret == GCR_ERROR_FAILURE)
g_message ("invalid DSA key");
pop_parsed (self, parsed);
return ret;
}
static gint
parse_der_private_key_dsa_parts (GcrParser *self,
GBytes *keydata,
GNode *params)
{
gint ret = GCR_ERROR_UNRECOGNIZED;
GNode *asn_params = NULL;
GNode *asn_key = NULL;
GcrParsed *parsed;
parsed = push_parsed (self, TRUE);
asn_params = egg_asn1x_get_any_as (params, pk_asn1_tab, "DSAParameters");
asn_key = egg_asn1x_create_and_decode (pk_asn1_tab, "DSAPrivatePart", keydata);
if (!asn_params || !asn_key)
goto done;
parsing_object (parsed, CKO_PRIVATE_KEY);
parsed_ulong_attribute (parsed, CKA_KEY_TYPE, CKK_DSA);
parsed_boolean_attribute (parsed, CKA_PRIVATE, CK_TRUE);
ret = GCR_ERROR_FAILURE;
if (!parsed_asn1_number (parsed, asn_params, "p", CKA_PRIME) ||
!parsed_asn1_number (parsed, asn_params, "q", CKA_SUBPRIME) ||
!parsed_asn1_number (parsed, asn_params, "g", CKA_BASE) ||
!parsed_asn1_number (parsed, asn_key, NULL, CKA_VALUE))
goto done;
parsed_fire (self, parsed);
ret = SUCCESS;
done:
egg_asn1x_destroy (asn_key);
egg_asn1x_destroy (asn_params);
if (ret == GCR_ERROR_FAILURE)
g_message ("invalid DSA key");
pop_parsed (self, parsed);
return ret;
}
/* -----------------------------------------------------------------------------
* EC PRIVATE KEY
*/
static gint
parse_der_private_key_ec (GcrParser *self,
GBytes *data)
{
gint ret = GCR_ERROR_UNRECOGNIZED;
GNode *asn = NULL;
GBytes *value = NULL;
GBytes *pub = NULL;
GNode *asn_q = NULL;
GcrParsed *parsed;
guint bits;
gulong version;
parsed = push_parsed (self, TRUE);
asn = egg_asn1x_create_and_decode (pk_asn1_tab, "ECPrivateKey", data);
if (!asn)
goto done;
if (!egg_asn1x_get_integer_as_ulong (egg_asn1x_node (asn, "version", NULL), &version))
goto done;
/* We only support simple version */
if (version != 1) {
g_message ("unsupported version of EC key: %lu", version);
goto done;
}
parsing_block (parsed, GCR_FORMAT_DER_PRIVATE_KEY_EC, data);
parsing_object (parsed, CKO_PRIVATE_KEY);
parsed_ulong_attribute (parsed, CKA_KEY_TYPE, CKK_EC);
parsed_boolean_attribute (parsed, CKA_PRIVATE, CK_TRUE);
ret = GCR_ERROR_FAILURE;
if (!parsed_asn1_element (parsed, asn, "parameters", CKA_EC_PARAMS))
goto done;
value = egg_asn1x_get_string_as_usg (egg_asn1x_node (asn, "privateKey", NULL), egg_secure_realloc);
if (!value)
goto done;
parsed_attribute_bytes (parsed, CKA_VALUE, value);
pub = egg_asn1x_get_bits_as_raw (egg_asn1x_node (asn, "publicKey", NULL), &bits);
if (!pub || bits != 8 * g_bytes_get_size (pub))
goto done;
asn_q = egg_asn1x_create (pk_asn1_tab, "ECPoint");
if (!asn_q)
goto done;
egg_asn1x_set_string_as_bytes (asn_q, pub);
if (!parsed_asn1_structure (parsed, asn_q, CKA_EC_POINT))
goto done;
parsed_fire (self, parsed);
ret = SUCCESS;
done:
if (pub)
g_bytes_unref (pub);
if (value)
g_bytes_unref (value);
egg_asn1x_destroy (asn);
egg_asn1x_destroy (asn_q);
if (ret == GCR_ERROR_FAILURE)
g_message ("invalid EC key");
pop_parsed (self, parsed);
return ret;
}
/* -----------------------------------------------------------------------------
* PRIVATE KEY
*/
static gint
parse_der_private_key (GcrParser *self,
GBytes *data)
{
gint res;
res = parse_der_private_key_rsa (self, data);
if (res == GCR_ERROR_UNRECOGNIZED)
res = parse_der_private_key_dsa (self, data);
if (res == GCR_ERROR_UNRECOGNIZED)
res = parse_der_private_key_ec (self, data);
return res;
}
/* -----------------------------------------------------------------------------
* SUBJECT PUBLIC KEY
*/
static gint
handle_subject_public_key_rsa (GcrParser *self,
GcrParsed *parsed,
GBytes *key,
GNode *params)
{
gint res = GCR_ERROR_FAILURE;
GNode *asn = NULL;
asn = egg_asn1x_create_and_decode (pk_asn1_tab, "RSAPublicKey", key);
if (!asn)
goto done;
parsing_object (parsed, CKO_PUBLIC_KEY);
parsed_ulong_attribute (parsed, CKA_KEY_TYPE, CKK_RSA);
if (!parsed_asn1_number (parsed, asn, "modulus", CKA_MODULUS) ||
!parsed_asn1_number (parsed, asn, "publicExponent", CKA_PUBLIC_EXPONENT))
goto done;
res = SUCCESS;
done:
egg_asn1x_destroy (asn);
return res;
}
static gint
handle_subject_public_key_dsa (GcrParser *self,
GcrParsed *parsed,
GBytes *key,
GNode *params)
{
gint res = GCR_ERROR_FAILURE;
GNode *key_asn = NULL;
GNode *param_asn = NULL;
key_asn = egg_asn1x_create_and_decode (pk_asn1_tab, "DSAPublicPart", key);
param_asn = egg_asn1x_get_any_as (params, pk_asn1_tab, "DSAParameters");
if (!key_asn || !param_asn)
goto done;
parsing_object (parsed, CKO_PUBLIC_KEY);
parsed_ulong_attribute (parsed, CKA_KEY_TYPE, CKK_DSA);
if (!parsed_asn1_number (parsed, param_asn, "p", CKA_PRIME) ||
!parsed_asn1_number (parsed, param_asn, "q", CKA_SUBPRIME) ||
!parsed_asn1_number (parsed, param_asn, "g", CKA_BASE) ||
!parsed_asn1_number (parsed, key_asn, NULL, CKA_VALUE))
goto done;
res = SUCCESS;
done:
egg_asn1x_destroy (key_asn);
egg_asn1x_destroy (param_asn);
return res;
}
static gint
handle_subject_public_key_ec (GcrParser *self,
GcrParsed *parsed,
GBytes *key,
GNode *params)
{
gint ret = GCR_ERROR_FAILURE;
GBytes *bytes = NULL;
GNode *asn = NULL;
parsing_object (parsed, CKO_PUBLIC_KEY);
parsed_ulong_attribute (parsed, CKA_KEY_TYPE, CKK_EC);
bytes = egg_asn1x_encode (params, g_realloc);
parsed_attribute_bytes (parsed, CKA_EC_PARAMS, bytes);
g_bytes_unref (bytes);
asn = egg_asn1x_create (pk_asn1_tab, "ECPoint");
if (!asn)
goto done;
egg_asn1x_set_string_as_bytes (asn, key);
parsed_asn1_structure (parsed, asn, CKA_EC_POINT);
ret = SUCCESS;
done:
egg_asn1x_destroy (asn);
return ret;
}
static gint
parse_der_subject_public_key (GcrParser *self,
GBytes *data)
{
GcrParsed *parsed;
GNode *params;
GBytes *key;
GNode *asn = NULL;
GNode *node;
GQuark oid;
guint bits;
gint ret;
asn = egg_asn1x_create_and_decode (pkix_asn1_tab, "SubjectPublicKeyInfo", data);
if (asn == NULL)
return GCR_ERROR_UNRECOGNIZED;
parsed = push_parsed (self, TRUE);
parsing_block (parsed, GCR_FORMAT_DER_SUBJECT_PUBLIC_KEY, data);
node = egg_asn1x_node (asn, "algorithm", "algorithm", NULL);
oid = egg_asn1x_get_oid_as_quark (node);
params = egg_asn1x_node (asn, "algorithm", "parameters", NULL);
node = egg_asn1x_node (asn, "subjectPublicKey", NULL);
key = egg_asn1x_get_bits_as_raw (node, &bits);
if (oid == GCR_OID_PKIX1_RSA)
ret = handle_subject_public_key_rsa (self, parsed, key, params);
else if (oid == GCR_OID_PKIX1_DSA)
ret = handle_subject_public_key_dsa (self, parsed, key, params);
else if (oid == GCR_OID_PKIX1_EC)
ret = handle_subject_public_key_ec (self, parsed, key, params);
else
ret = GCR_ERROR_UNRECOGNIZED;
g_bytes_unref (key);
if (ret == SUCCESS)
parsed_fire (self, parsed);
pop_parsed (self, parsed);
egg_asn1x_destroy (asn);
return ret;
}
/* -----------------------------------------------------------------------------
* PKCS8
*/
static gint
parse_der_pkcs8_plain (GcrParser *self,
GBytes *data)
{
gint ret;
CK_KEY_TYPE key_type;
GQuark key_algo;
GBytes *keydata = NULL;
GNode *params = NULL;
GNode *asn = NULL;
GcrParsed *parsed;
parsed = push_parsed (self, TRUE);
ret = GCR_ERROR_UNRECOGNIZED;
asn = egg_asn1x_create_and_decode (pkix_asn1_tab, "pkcs-8-PrivateKeyInfo", data);
if (!asn)
goto done;
parsing_block (parsed, GCR_FORMAT_DER_PKCS8_PLAIN, data);
ret = GCR_ERROR_FAILURE;
key_type = GCK_INVALID;
key_algo = egg_asn1x_get_oid_as_quark (egg_asn1x_node (asn, "privateKeyAlgorithm", "algorithm", NULL));
if (!key_algo)
goto done;
else if (key_algo == GCR_OID_PKIX1_RSA)
key_type = CKK_RSA;
else if (key_algo == GCR_OID_PKIX1_DSA)
key_type = CKK_DSA;
else if (key_algo == GCR_OID_PKIX1_EC)
key_type = CKK_EC;
if (key_type == GCK_INVALID) {
ret = GCR_ERROR_UNRECOGNIZED;
goto done;
}
keydata = egg_asn1x_get_string_as_bytes (egg_asn1x_node (asn, "privateKey", NULL));
if (!keydata)
goto done;
params = egg_asn1x_node (asn, "privateKeyAlgorithm", "parameters", NULL);
ret = SUCCESS;
done:
if (ret == SUCCESS) {
switch (key_type) {
case CKK_RSA:
ret = parse_der_private_key_rsa (self, keydata);
break;
case CKK_DSA:
/* Try the normal sane format */
ret = parse_der_private_key_dsa (self, keydata);
/* Otherwise try the two part format that everyone seems to like */
if (ret == GCR_ERROR_UNRECOGNIZED && params)
ret = parse_der_private_key_dsa_parts (self, keydata, params);
break;
case CKK_EC:
ret = parse_der_private_key_ec (self, keydata);
break;
default:
g_message ("invalid or unsupported key type in PKCS#8 key");
ret = GCR_ERROR_UNRECOGNIZED;
break;
};
} else if (ret == GCR_ERROR_FAILURE) {
g_message ("invalid PKCS#8 key");
}
if (keydata)
g_bytes_unref (keydata);
egg_asn1x_destroy (asn);
pop_parsed (self, parsed);
return ret;
}
static gint
parse_der_pkcs8_encrypted (GcrParser *self,
GBytes *data)
{
PasswordState pstate = PASSWORD_STATE_INIT;
GNode *asn = NULL;
gcry_cipher_hd_t cih = NULL;
gcry_error_t gcry;
gint ret, r;
GQuark scheme;
guchar *crypted = NULL;
GNode *params = NULL;
GBytes *cbytes;
gsize n_crypted;
const gchar *password;
GcrParsed *parsed;
gint l;
parsed = push_parsed (self, FALSE);
ret = GCR_ERROR_UNRECOGNIZED;
asn = egg_asn1x_create_and_decode (pkix_asn1_tab, "pkcs-8-EncryptedPrivateKeyInfo", data);
if (!asn)
goto done;
parsing_block (parsed, GCR_FORMAT_DER_PKCS8_ENCRYPTED, data);
ret = GCR_ERROR_FAILURE;
/* Figure out the type of encryption */
scheme = egg_asn1x_get_oid_as_quark (egg_asn1x_node (asn, "encryptionAlgorithm", "algorithm", NULL));
if (!scheme)
goto done;
params = egg_asn1x_node (asn, "encryptionAlgorithm", "parameters", NULL);
/* Loop to try different passwords */
for (;;) {
g_assert (cih == NULL);
r = enum_next_password (self, &pstate, &password);
if (r != SUCCESS) {
ret = r;
break;
}
/* Parse the encryption stuff into a cipher. */
if (!egg_symkey_read_cipher (scheme, password, -1, params, &cih))
break;
crypted = egg_asn1x_get_string_as_raw (egg_asn1x_node (asn, "encryptedData", NULL), egg_secure_realloc, &n_crypted);
if (!crypted)
break;
gcry = gcry_cipher_decrypt (cih, crypted, n_crypted, NULL, 0);
gcry_cipher_close (cih);
cih = NULL;
if (gcry != 0) {
g_warning ("couldn't decrypt pkcs8 data: %s", gcry_strerror (gcry));
break;
}
/* Unpad the DER data */
l = egg_asn1x_element_length (crypted, n_crypted);
if (l > 0)
n_crypted = l;
cbytes = g_bytes_new_with_free_func (crypted, n_crypted,
egg_secure_free, crypted);
crypted = NULL;
/* Try to parse the resulting key */
r = parse_der_pkcs8_plain (self, cbytes);
g_bytes_unref (cbytes);
if (r != GCR_ERROR_UNRECOGNIZED) {
ret = r;
break;
}
/* We assume unrecognized data, is a bad encryption key */
}
done:
if (cih)
gcry_cipher_close (cih);
egg_asn1x_destroy (asn);
egg_secure_free (crypted);
pop_parsed (self, parsed);
return ret;
}
static gint
parse_der_pkcs8 (GcrParser *self,
GBytes *data)
{
gint ret;
ret = parse_der_pkcs8_plain (self, data);
if (ret == GCR_ERROR_UNRECOGNIZED)
ret = parse_der_pkcs8_encrypted (self, data);
return ret;
}
/* -----------------------------------------------------------------------------
* CERTIFICATE
*/
static gint
parse_der_certificate (GcrParser *self,
GBytes *data)
{
gchar *name = NULL;
GcrParsed *parsed;
GNode *node;
GNode *asn;
asn = egg_asn1x_create_and_decode (pkix_asn1_tab, "Certificate", data);
if (asn == NULL)
return GCR_ERROR_UNRECOGNIZED;
parsed = push_parsed (self, FALSE);
parsing_block (parsed, GCR_FORMAT_DER_CERTIFICATE_X509, data);
parsing_object (parsed, CKO_CERTIFICATE);
parsed_ulong_attribute (parsed, CKA_CERTIFICATE_TYPE, CKC_X_509);
node = egg_asn1x_node (asn, "tbsCertificate", NULL);
g_return_val_if_fail (node != NULL, GCR_ERROR_FAILURE);
if (gcr_parser_get_parsed_label (self) == NULL)
name = egg_dn_read_part (egg_asn1x_node (node, "subject", "rdnSequence", NULL), "CN");
if (name != NULL) {
parsed_label (parsed, name);
g_free (name);
}
parsed_attribute_bytes (parsed, CKA_VALUE, data);
parsed_asn1_element (parsed, node, "subject", CKA_SUBJECT);
parsed_asn1_element (parsed, node, "issuer", CKA_ISSUER);
parsed_asn1_number (parsed, node, "serialNumber", CKA_SERIAL_NUMBER);
parsed_fire (self, parsed);
egg_asn1x_destroy (asn);
pop_parsed (self, parsed);
return SUCCESS;
}
/* -----------------------------------------------------------------------------
* PKCS7
*/
static gint
handle_pkcs7_signed_data (GcrParser *self,
GNode *content)
{
GNode *asn = NULL;
GNode *node;
gint ret;
GBytes *certificate;
int i;
ret = GCR_ERROR_UNRECOGNIZED;
asn = egg_asn1x_get_any_as (content, pkix_asn1_tab, "pkcs-7-SignedData");
if (!asn)
goto done;
for (i = 0; TRUE; ++i) {
node = egg_asn1x_node (asn, "certificates", i + 1, NULL);
/* No more certificates? */
if (node == NULL)
break;
certificate = egg_asn1x_get_element_raw (node);
ret = parse_der_certificate (self, certificate);
g_bytes_unref (certificate);
if (ret != SUCCESS)
goto done;
}
/* TODO: Parse out all the CRLs */
ret = SUCCESS;
done:
egg_asn1x_destroy (asn);
return ret;
}
static gint
parse_der_pkcs7 (GcrParser *self,
GBytes *data)
{
GNode *asn = NULL;
GNode *node;
gint ret;
GNode *content = NULL;
GQuark oid;
GcrParsed *parsed;
parsed = push_parsed (self, FALSE);
ret = GCR_ERROR_UNRECOGNIZED;
asn = egg_asn1x_create_and_decode (pkix_asn1_tab, "pkcs-7-ContentInfo", data);
if (!asn)
goto done;
parsing_block (parsed, GCR_FORMAT_DER_PKCS7, data);
ret = GCR_ERROR_FAILURE;
node = egg_asn1x_node (asn, "contentType", NULL);
if (!node)
goto done;
oid = egg_asn1x_get_oid_as_quark (node);
g_return_val_if_fail (oid, GCR_ERROR_FAILURE);
/* Outer most one must just be plain data */
if (oid != GCR_OID_PKCS7_SIGNED_DATA) {
g_message ("unsupported outer content type in pkcs7: %s", g_quark_to_string (oid));
goto done;
}
content = egg_asn1x_node (asn, "content", NULL);
if (!content)
goto done;
ret = handle_pkcs7_signed_data (self, content);
done:
egg_asn1x_destroy (asn);
pop_parsed (self, parsed);
return ret;
}
/* -----------------------------------------------------------------------------
* PKCS12
*/
static gint
handle_pkcs12_cert_bag (GcrParser *self,
GBytes *data)
{
GNode *asn = NULL;
GNode *asn_content = NULL;
guchar *certificate = NULL;
GNode *element = NULL;
gsize n_certificate;
GBytes *bytes;
gint ret;
ret = GCR_ERROR_UNRECOGNIZED;
asn = egg_asn1x_create_and_decode_full (pkix_asn1_tab, "pkcs-12-CertBag",
data, EGG_ASN1X_NO_STRICT);
if (!asn)
goto done;
ret = GCR_ERROR_FAILURE;
element = egg_asn1x_node (asn, "certValue", NULL);
if (!element)
goto done;
asn_content = egg_asn1x_get_any_as (element, pkix_asn1_tab, "pkcs-7-Data");
if (!asn_content)
goto done;
certificate = egg_asn1x_get_string_as_raw (asn_content, NULL, &n_certificate);
if (!certificate)
goto done;
bytes = g_bytes_new_take (certificate, n_certificate);
ret = parse_der_certificate (self, bytes);
g_bytes_unref (bytes);
done:
egg_asn1x_destroy (asn_content);
egg_asn1x_destroy (asn);
return ret;
}
static gchar *
parse_pkcs12_bag_friendly_name (GNode *asn)
{
guint count, i;
GQuark oid;
GNode *node;
GNode *asn_str;
gchar *result;
if (asn == NULL)
return NULL;
count = egg_asn1x_count (asn);
for (i = 1; i <= count; i++) {
oid = egg_asn1x_get_oid_as_quark (egg_asn1x_node (asn, i, "type", NULL));
if (oid == GCR_OID_PKCS9_ATTRIBUTE_FRIENDLY) {
node = egg_asn1x_node (asn, i, "values", 1, NULL);
if (node != NULL) {
asn_str = egg_asn1x_get_any_as_string (node, EGG_ASN1X_BMP_STRING);
if (asn_str) {
result = egg_asn1x_get_bmpstring_as_utf8 (asn_str);
egg_asn1x_destroy (asn_str);
return result;
}
}
}
}
return NULL;
}
static gint
handle_pkcs12_bag (GcrParser *self,
GBytes *data)
{
GNode *asn = NULL;
gint ret, r;
guint count = 0;
GQuark oid;
GNode *value;
GBytes *element = NULL;
gchar *friendly;
guint i;
GcrParsed *parsed;
ret = GCR_ERROR_UNRECOGNIZED;
asn = egg_asn1x_create_and_decode_full (pkix_asn1_tab, "pkcs-12-SafeContents",
data, EGG_ASN1X_NO_STRICT);
if (!asn)
goto done;
ret = GCR_ERROR_FAILURE;
/* Get the number of elements in this bag */
count = egg_asn1x_count (asn);
/*
* Now inside each bag are multiple elements. Who comes up
* with this stuff?
*/
for (i = 1; i <= count; i++) {
oid = egg_asn1x_get_oid_as_quark (egg_asn1x_node (asn, i, "bagId", NULL));
if (!oid)
goto done;
value = egg_asn1x_node (asn, i, "bagValue", NULL);
if (!value)
goto done;
element = egg_asn1x_get_element_raw (value);
parsed = push_parsed (self, FALSE);
friendly = parse_pkcs12_bag_friendly_name (egg_asn1x_node (asn, i, "bagAttributes", NULL));
if (friendly != NULL) {
parsed_label (parsed, friendly);
g_free (friendly);
}
/* A normal unencrypted key */
if (oid == GCR_OID_PKCS12_BAG_PKCS8_KEY) {
r = parse_der_pkcs8_plain (self, element);
/* A properly encrypted key */
} else if (oid == GCR_OID_PKCS12_BAG_PKCS8_ENCRYPTED_KEY) {
r = parse_der_pkcs8_encrypted (self, element);
/* A certificate */
} else if (oid == GCR_OID_PKCS12_BAG_CERTIFICATE) {
r = handle_pkcs12_cert_bag (self, element);
/* TODO: GCR_OID_PKCS12_BAG_CRL */
} else {
r = GCR_ERROR_UNRECOGNIZED;
}
if (element != NULL)
g_bytes_unref (element);
pop_parsed (self, parsed);
if (r == GCR_ERROR_FAILURE ||
r == GCR_ERROR_CANCELLED ||
r == GCR_ERROR_LOCKED) {
ret = r;
goto done;
}
}
ret = SUCCESS;
done:
egg_asn1x_destroy (asn);
return ret;
}
static gint
handle_pkcs12_encrypted_bag (GcrParser *self,
GNode *bag)
{
PasswordState pstate = PASSWORD_STATE_INIT;
GNode *asn = NULL;
gcry_cipher_hd_t cih = NULL;
gcry_error_t gcry;
guchar *crypted = NULL;
GNode *params = NULL;
gsize n_crypted;
const gchar *password;
GBytes *cbytes;
GQuark scheme;
gint ret, r;
gint l;
ret = GCR_ERROR_UNRECOGNIZED;
asn = egg_asn1x_get_any_as_full (bag, pkix_asn1_tab, "pkcs-7-EncryptedData",
EGG_ASN1X_NO_STRICT);
if (!asn)
goto done;
ret = GCR_ERROR_FAILURE;
/* Check the encryption schema OID */
scheme = egg_asn1x_get_oid_as_quark (egg_asn1x_node (asn, "encryptedContentInfo", "contentEncryptionAlgorithm", "algorithm", NULL));
if (!scheme)
goto done;
params = egg_asn1x_node (asn, "encryptedContentInfo", "contentEncryptionAlgorithm", "parameters", NULL);
if (!params)
goto done;
/* Loop to try different passwords */
for (;;) {
g_assert (cih == NULL);
r = enum_next_password (self, &pstate, &password);
if (r != SUCCESS) {
ret = r;
goto done;
}
/* Parse the encryption stuff into a cipher. */
if (!egg_symkey_read_cipher (scheme, password, -1, params, &cih)) {
ret = GCR_ERROR_FAILURE;
goto done;
}
crypted = egg_asn1x_get_string_as_raw (egg_asn1x_node (asn, "encryptedContentInfo", "encryptedContent", NULL),
egg_secure_realloc, &n_crypted);
if (!crypted)
goto done;
gcry = gcry_cipher_decrypt (cih, crypted, n_crypted, NULL, 0);
gcry_cipher_close (cih);
cih = NULL;
if (gcry != 0) {
g_warning ("couldn't decrypt pkcs12 data: %s", gcry_strerror (gcry));
goto done;
}
/* Unpad the DER data */
l = egg_asn1x_element_length (crypted, n_crypted);
if (l > 0)
n_crypted = l;
cbytes = g_bytes_new_with_free_func (crypted, n_crypted, egg_secure_free, crypted);
crypted = NULL;
/* Try to parse the resulting key */
r = handle_pkcs12_bag (self, cbytes);
g_bytes_unref (cbytes);
if (r != GCR_ERROR_UNRECOGNIZED) {
ret = r;
break;
}
/* We assume unrecognized data is a bad encryption key */
}
done:
if (cih)
gcry_cipher_close (cih);
egg_asn1x_destroy (asn);
egg_secure_free (crypted);
return ret;
}
static gint
handle_pkcs12_safe (GcrParser *self,
GBytes *data)
{
GNode *asn = NULL;
GNode *asn_content = NULL;
gint ret, r;
GNode *bag;
GBytes *content;
GQuark oid;
guint i;
GNode *node;
ret = GCR_ERROR_UNRECOGNIZED;
asn = egg_asn1x_create_and_decode_full (pkix_asn1_tab, "pkcs-12-AuthenticatedSafe",
data, EGG_ASN1X_NO_STRICT);
if (!asn)
goto done;
ret = GCR_ERROR_FAILURE;
/*
* Inside each PKCS12 safe there are multiple bags.
*/
for (i = 0; TRUE; ++i) {
node = egg_asn1x_node (asn, i + 1, "contentType", NULL);
/* All done? no more bags */
if (!node)
break;
oid = egg_asn1x_get_oid_as_quark (node);
bag = egg_asn1x_node (asn, i + 1, "content", NULL);
if (!bag)
goto done;
/* A non encrypted bag, just parse */
if (oid == GCR_OID_PKCS7_DATA) {
egg_asn1x_destroy (asn_content);
asn_content = egg_asn1x_get_any_as_full (bag, pkix_asn1_tab,
"pkcs-7-Data", EGG_ASN1X_NO_STRICT);
if (!asn_content)
goto done;
content = egg_asn1x_get_string_as_bytes (asn_content);
if (!content)
goto done;
r = handle_pkcs12_bag (self, content);
g_bytes_unref (content);
/* Encrypted data first needs decryption */
} else if (oid == GCR_OID_PKCS7_ENCRYPTED_DATA) {
r = handle_pkcs12_encrypted_bag (self, bag);
/* Hmmmm, not sure what this is */
} else {
g_warning ("unrecognized type of safe content in pkcs12: %s", g_quark_to_string (oid));
r = GCR_ERROR_UNRECOGNIZED;
}
if (r == GCR_ERROR_FAILURE ||
r == GCR_ERROR_CANCELLED ||
r == GCR_ERROR_LOCKED) {
ret = r;
goto done;
}
}
ret = SUCCESS;
done:
egg_asn1x_destroy (asn);
egg_asn1x_destroy (asn_content);
return ret;
}
static gint
verify_pkcs12_safe (GcrParser *self,
GNode *asn,
GBytes *content)
{
PasswordState pstate = PASSWORD_STATE_INIT;
const gchar *password;
gcry_md_hd_t mdh = NULL;
const guchar *mac_digest;
gsize mac_len;
guchar *digest = NULL;
gsize n_digest;
GQuark algorithm;
GNode *mac_data;
int ret, r;
ret = GCR_ERROR_FAILURE;
/*
* The MAC is optional (and outside the encryption no less). I wonder
* what the designers (ha) of PKCS#12 were trying to achieve
*/
mac_data = egg_asn1x_node (asn, "macData", NULL);
if (mac_data == NULL)
return SUCCESS;
algorithm = egg_asn1x_get_oid_as_quark (egg_asn1x_node (mac_data, "mac",
"digestAlgorithm", "algorithm", NULL));
if (!algorithm)
goto done;
digest = egg_asn1x_get_string_as_raw (egg_asn1x_node (mac_data, "mac", "digest", NULL), NULL, &n_digest);
if (!digest)
goto done;
/* Loop to try different passwords */
for (;;) {
g_assert (mdh == NULL);
r = enum_next_password (self, &pstate, &password);
if (r != SUCCESS) {
ret = r;
goto done;
}
/* Parse the encryption stuff into a cipher. */
if (!egg_symkey_read_mac (algorithm, password, -1, mac_data, &mdh, &mac_len)) {
ret = GCR_ERROR_FAILURE;
goto done;
}
/* If not the right length, then that's really broken */
if (mac_len != n_digest) {
r = GCR_ERROR_FAILURE;
} else {
gcry_md_write (mdh, g_bytes_get_data (content, NULL), g_bytes_get_size (content));
mac_digest = gcry_md_read (mdh, 0);
g_return_val_if_fail (mac_digest, GCR_ERROR_FAILURE);
r = memcmp (mac_digest, digest, n_digest) == 0 ? SUCCESS : GCR_ERROR_LOCKED;
}
gcry_md_close (mdh);
mdh = NULL;
if (r != GCR_ERROR_LOCKED) {
ret = r;
break;
}
}
done:
if (mdh)
gcry_md_close (mdh);
g_free (digest);
return ret;
}
static gint
parse_der_pkcs12 (GcrParser *self,
GBytes *data)
{
GNode *asn = NULL;
gint ret;
GNode *content = NULL;
GBytes *string = NULL;
GQuark oid;
GcrParsed *parsed;
parsed = push_parsed (self, FALSE);
ret = GCR_ERROR_UNRECOGNIZED;
/*
* Because PKCS#12 files, the bags specifically, are notorious for
* being crappily constructed and are often break rules such as DER
* sorting order etc.. we parse the DER in a non-strict fashion.
*
* The rules in DER are designed for X.509 certificates, so there is
* only one way to represent a given certificate (although they fail
* at that as well). But with PKCS#12 we don't have such high
* requirements, and we can slack off on our validation.
*/
asn = egg_asn1x_create_and_decode_full (pkix_asn1_tab, "pkcs-12-PFX",
data, EGG_ASN1X_NO_STRICT);
if (!asn)
goto done;
parsing_block (parsed, GCR_FORMAT_DER_PKCS12, data);
oid = egg_asn1x_get_oid_as_quark (egg_asn1x_node (asn, "authSafe", "contentType", NULL));
if (!oid)
goto done;
/* Outer most one must just be plain data */
if (oid != GCR_OID_PKCS7_DATA) {
g_message ("unsupported safe content type in pkcs12: %s", g_quark_to_string (oid));
goto done;
}
content = egg_asn1x_get_any_as (egg_asn1x_node (asn, "authSafe", "content", NULL),
pkix_asn1_tab, "pkcs-7-Data");
if (!content)
goto done;
string = egg_asn1x_get_string_as_bytes (content);
if (!string)
goto done;
ret = verify_pkcs12_safe (self, asn, string);
if (ret == SUCCESS)
ret = handle_pkcs12_safe (self, string);
done:
if (content)
egg_asn1x_destroy (content);
if (string)
g_bytes_unref (string);
egg_asn1x_destroy (asn);
pop_parsed (self, parsed);
return ret;
}
/* -----------------------------------------------------------------------------
* CERTIFICATE REQUESTS
*/
static gint
parse_der_pkcs10 (GcrParser *self,
GBytes *data)
{
GNode *asn = NULL;
GNode *node;
GcrParsed *parsed;
gchar *name = NULL;
asn = egg_asn1x_create_and_decode (pkix_asn1_tab, "pkcs-10-CertificationRequest", data);
if (!asn)
return GCR_ERROR_UNRECOGNIZED;
parsed = push_parsed (self, FALSE);
parsing_block (parsed, GCR_FORMAT_DER_PKCS10, data);
parsing_object (parsed, CKO_GCR_CERTIFICATE_REQUEST);
parsed_ulong_attribute (parsed, CKA_GCR_CERTIFICATE_REQUEST_TYPE, CKQ_GCR_PKCS10);
node = egg_asn1x_node (asn, "certificationRequestInfo", NULL);
g_return_val_if_fail (node != NULL, GCR_ERROR_FAILURE);
if (gcr_parser_get_parsed_label (self) == NULL)
name = egg_dn_read_part (egg_asn1x_node (node, "subject", "rdnSequence", NULL), "CN");
if (name != NULL) {
parsed_label (parsed, name);
g_free (name);
}
parsed_attribute_bytes (parsed, CKA_VALUE, data);
parsed_asn1_element (parsed, node, "subject", CKA_SUBJECT);
parsed_fire (self, parsed);
egg_asn1x_destroy (asn);
pop_parsed (self, parsed);
return SUCCESS;
}
static gint
parse_der_spkac (GcrParser *self,
GBytes *data)
{
GNode *asn = NULL;
GcrParsed *parsed;
asn = egg_asn1x_create_and_decode (pkix_asn1_tab, "SignedPublicKeyAndChallenge", data);
if (!asn)
return GCR_ERROR_UNRECOGNIZED;
parsed = push_parsed (self, FALSE);
parsing_block (parsed, GCR_FORMAT_DER_SPKAC, data);
parsing_object (parsed, CKO_GCR_CERTIFICATE_REQUEST);
parsed_ulong_attribute (parsed, CKA_GCR_CERTIFICATE_REQUEST_TYPE, CKQ_GCR_SPKAC);
parsed_attribute_bytes (parsed, CKA_VALUE, data);
parsed_fire (self, parsed);
egg_asn1x_destroy (asn);
pop_parsed (self, parsed);
return SUCCESS;
}
static gint
parse_base64_spkac (GcrParser *self,
GBytes *dat)
{
const gchar *PREFIX = "SPKAC=";
const gsize PREFIX_LEN = 6;
GcrParsed *parsed;
guchar *spkac;
gsize n_spkac;
const guchar *data;
GBytes *bytes;
gsize n_data;
gint ret;
data = g_bytes_get_data (dat, &n_data);
if (n_data > PREFIX_LEN && memcmp (PREFIX, data, PREFIX_LEN))
return GCR_ERROR_UNRECOGNIZED;
parsed = push_parsed (self, FALSE);
parsing_block (parsed, GCR_FORMAT_DER_SPKAC, dat);
data += PREFIX_LEN;
n_data -= PREFIX_LEN;
spkac = g_base64_decode ((const gchar *)data, &n_spkac);
if (spkac != NULL) {
bytes = g_bytes_new_take (spkac, n_spkac);
ret = parse_der_spkac (self, bytes);
g_bytes_unref (bytes);
} else {
ret = GCR_ERROR_FAILURE;
}
pop_parsed (self, parsed);
return ret;
}
/* -----------------------------------------------------------------------------
* OPENPGP
*/
static void
on_openpgp_packet (GPtrArray *records,
GBytes *outer,
gpointer user_data)
{
GcrParser *self = GCR_PARSER (user_data);
GcrParsed *parsed;
gchar *string;
/*
* If it's an openpgp packet that doesn't contain a key, then
* just ignore it here.
*/
if (records->len == 0)
return;
parsed = push_parsed (self, FALSE);
/* All we can do is the packet bounds */
parsing_block (parsed, GCR_FORMAT_OPENPGP_PACKET, outer);
parsing_object (parsed, CKO_GCR_GNUPG_RECORDS);
string = _gcr_records_format (records);
parsed_attribute (parsed, CKA_VALUE, string, strlen (string));
parsed_fire (self, parsed);
pop_parsed (self, parsed);
g_free (string);
}
static gint
parse_openpgp_packets (GcrParser *self,
GBytes *data)
{
gint num_parsed;
num_parsed = _gcr_openpgp_parse (data,
GCR_OPENPGP_PARSE_KEYS |
GCR_OPENPGP_PARSE_ATTRIBUTES |
GCR_OPENPGP_PARSE_SIGNATURES,
on_openpgp_packet, self);
if (num_parsed == 0)
return GCR_ERROR_UNRECOGNIZED;
return SUCCESS;
}
/* -----------------------------------------------------------------------------
* ARMOR PARSING
*/
static gboolean
formats_for_armor_type (GQuark armor_type,
gint *inner_format,
gint *outer_format)
{
gint dummy;
if (!inner_format)
inner_format = &dummy;
if (!outer_format)
outer_format = &dummy;
if (armor_type == PEM_RSA_PRIVATE_KEY) {
*inner_format = GCR_FORMAT_DER_PRIVATE_KEY_RSA;
*outer_format = GCR_FORMAT_PEM_PRIVATE_KEY_RSA;
} else if (armor_type == PEM_DSA_PRIVATE_KEY) {
*inner_format = GCR_FORMAT_DER_PRIVATE_KEY_DSA;
*outer_format = GCR_FORMAT_PEM_PRIVATE_KEY_DSA;
} else if (armor_type == PEM_EC_PRIVATE_KEY) {
*inner_format = GCR_FORMAT_DER_PRIVATE_KEY_EC;
*outer_format = GCR_FORMAT_PEM_PRIVATE_KEY_EC;
} else if (armor_type == PEM_ANY_PRIVATE_KEY) {
*inner_format = GCR_FORMAT_DER_PRIVATE_KEY;
*outer_format = GCR_FORMAT_PEM_PRIVATE_KEY;
} else if (armor_type == PEM_PRIVATE_KEY) {
*inner_format = GCR_FORMAT_DER_PKCS8_PLAIN;
*outer_format = GCR_FORMAT_PEM_PKCS8_PLAIN;
} else if (armor_type == PEM_ENCRYPTED_PRIVATE_KEY) {
*inner_format = GCR_FORMAT_DER_PKCS8_ENCRYPTED;
*outer_format = GCR_FORMAT_PEM_PKCS8_ENCRYPTED;
} else if (armor_type == PEM_CERTIFICATE) {
*inner_format = GCR_FORMAT_DER_CERTIFICATE_X509;
*outer_format = GCR_FORMAT_PEM_CERTIFICATE_X509;
} else if (armor_type == PEM_PKCS7) {
*inner_format = GCR_FORMAT_DER_PKCS7;
*outer_format = GCR_FORMAT_PEM_PKCS7;
} else if (armor_type == PEM_CERTIFICATE_REQUEST) {
*inner_format = GCR_FORMAT_DER_PKCS10;
*outer_format = GCR_FORMAT_PEM_PKCS10;
} else if (armor_type == PEM_NEW_CERTIFICATE_REQUEST) {
*inner_format = GCR_FORMAT_DER_PKCS10;
*outer_format = GCR_FORMAT_PEM_PKCS10;
} else if (armor_type == PEM_PKCS12) {
*inner_format = GCR_FORMAT_DER_PKCS12;
*outer_format = GCR_FORMAT_PEM_PKCS12;
} else if (armor_type == PEM_PUBLIC_KEY) {
*inner_format = GCR_FORMAT_DER_SUBJECT_PUBLIC_KEY;
*outer_format = GCR_FORMAT_PEM_PUBLIC_KEY;
} else if (armor_type == ARMOR_PGP_PRIVATE_KEY_BLOCK) {
*inner_format = GCR_FORMAT_OPENPGP_PACKET;
*outer_format = GCR_FORMAT_OPENPGP_ARMOR;
} else if (armor_type == ARMOR_PGP_PUBLIC_KEY_BLOCK) {
*inner_format = GCR_FORMAT_OPENPGP_PACKET;
*outer_format = GCR_FORMAT_OPENPGP_ARMOR;
} else {
return FALSE;
}
return TRUE;
}
static gint
handle_plain_pem (GcrParser *self,
gint format_id,
gint want_format,
GBytes *data)
{
ParserFormat *format;
if (want_format != 0 && want_format != format_id)
return GCR_ERROR_UNRECOGNIZED;
format = parser_format_lookup (format_id);
if (format == NULL)
return GCR_ERROR_UNRECOGNIZED;
return (format->function) (self, data);
}
static gint
handle_encrypted_pem (GcrParser *self,
gint format_id,
gint want_format,
GHashTable *headers,
GBytes *data)
{
PasswordState pstate = PASSWORD_STATE_INIT;
const gchar *password;
guchar *decrypted;
gsize n_decrypted;
const gchar *val;
GBytes *dbytes;
gint res;
gint l;
g_assert (GCR_IS_PARSER (self));
g_assert (headers);
val = g_hash_table_lookup (headers, "DEK-Info");
if (!val) {
g_message ("missing encryption header");
return GCR_ERROR_FAILURE;
}
for (;;) {
res = enum_next_password (self, &pstate, &password);
if (res != SUCCESS)
break;
/* Decrypt, this will result in garble if invalid password */
decrypted = egg_openssl_decrypt_block (val, password, -1, data, &n_decrypted);
if (!decrypted) {
res = GCR_ERROR_FAILURE;
break;
}
/* Unpad the DER data */
l = egg_asn1x_element_length (decrypted, n_decrypted);
if (l > 0)
n_decrypted = l;
dbytes = g_bytes_new_with_free_func (decrypted, n_decrypted,
egg_secure_free, decrypted);
decrypted = NULL;
/* Try to parse */
res = handle_plain_pem (self, format_id, want_format, dbytes);
g_bytes_unref (dbytes);
/* Unrecognized is a bad password */
if (res != GCR_ERROR_UNRECOGNIZED)
break;
}
return res;
}
typedef struct {
GcrParser *parser;
gint result;
gint want_format;
} HandlePemArgs;
static void
handle_pem_data (GQuark type,
GBytes *data,
GBytes *outer,
GHashTable *headers,
gpointer user_data)
{
HandlePemArgs *args = (HandlePemArgs*)user_data;
gint res = GCR_ERROR_FAILURE;
gboolean encrypted = FALSE;
const gchar *val;
gint inner_format;
gint outer_format;
GcrParsed *parsed;
/* Something already failed to parse */
if (args->result == GCR_ERROR_FAILURE)
return;
if (!formats_for_armor_type (type, &inner_format, &outer_format))
return;
parsed = push_parsed (args->parser, FALSE);
/* Fill in information necessary for prompting */
parsing_block (parsed, outer_format, outer);
/* See if it's encrypted PEM all openssl like*/
if (headers) {
val = g_hash_table_lookup (headers, "Proc-Type");
if (val && strcmp (val, "4,ENCRYPTED") == 0)
encrypted = TRUE;
}
if (encrypted)
res = handle_encrypted_pem (args->parser, inner_format,
args->want_format, headers,
data);
else
res = handle_plain_pem (args->parser, inner_format,
args->want_format, data);
pop_parsed (args->parser, parsed);
if (res != GCR_ERROR_UNRECOGNIZED) {
if (args->result == GCR_ERROR_UNRECOGNIZED)
args->result = res;
else if (res > args->result)
args->result = res;
}
}
static gint
handle_pem_format (GcrParser *self,
gint subformat,
GBytes *data)
{
HandlePemArgs ctx = { self, GCR_ERROR_UNRECOGNIZED, subformat };
guint found;
if (g_bytes_get_size (data) == 0)
return GCR_ERROR_UNRECOGNIZED;
found = egg_armor_parse (data, handle_pem_data, &ctx);
if (found == 0)
return GCR_ERROR_UNRECOGNIZED;
return ctx.result;
}
static gint
parse_pem (GcrParser *self,
GBytes *data)
{
return handle_pem_format (self, 0, data);
}
static gint
parse_pem_private_key_rsa (GcrParser *self,
GBytes *data)
{
return handle_pem_format (self, GCR_FORMAT_DER_PRIVATE_KEY_RSA, data);
}
static gint
parse_pem_private_key_dsa (GcrParser *self,
GBytes *data)
{
return handle_pem_format (self, GCR_FORMAT_DER_PRIVATE_KEY_DSA, data);
}
static gint
parse_pem_private_key_ec (GcrParser *self,
GBytes *data)
{
return handle_pem_format (self, GCR_FORMAT_DER_PRIVATE_KEY_EC, data);
}
static gint
parse_pem_public_key (GcrParser *self,
GBytes *data)
{
return handle_pem_format (self, GCR_FORMAT_DER_SUBJECT_PUBLIC_KEY, data);
}
static gint
parse_pem_certificate (GcrParser *self,
GBytes *data)
{
return handle_pem_format (self, GCR_FORMAT_DER_CERTIFICATE_X509, data);
}
static gint
parse_pem_pkcs8_plain (GcrParser *self,
GBytes *data)
{
return handle_pem_format (self, GCR_FORMAT_DER_PKCS8_PLAIN, data);
}
static gint
parse_pem_pkcs8_encrypted (GcrParser *self,
GBytes *data)
{
return handle_pem_format (self, GCR_FORMAT_DER_PKCS8_ENCRYPTED, data);
}
static gint
parse_pem_pkcs7 (GcrParser *self,
GBytes *data)
{
return handle_pem_format (self, GCR_FORMAT_DER_PKCS7, data);
}
static gint
parse_pem_pkcs10 (GcrParser *self,
GBytes *data)
{
return handle_pem_format (self, GCR_FORMAT_DER_PKCS10, data);
}
static gint
parse_pem_pkcs12 (GcrParser *self,
GBytes *data)
{
return handle_pem_format (self, GCR_FORMAT_DER_PKCS12, data);
}
static gint
parse_openpgp_armor (GcrParser *self,
GBytes *data)
{
return handle_pem_format (self, GCR_FORMAT_OPENPGP_PACKET, data);
}
/* -----------------------------------------------------------------------------
* OPENSSH
*/
static void
on_openssh_public_key_parsed (GckAttributes *attrs,
const gchar *label,
const gchar *options,
GBytes *outer,
gpointer user_data)
{
GcrParser *self = GCR_PARSER (user_data);
GcrParsed *parsed;
parsed = push_parsed (self, FALSE);
parsing_block (parsed, GCR_FORMAT_OPENSSH_PUBLIC, outer);
parsed_attributes (parsed, attrs);
parsed_label (parsed, label);
parsed_fire (self, parsed);
pop_parsed (self, parsed);
}
static gint
parse_openssh_public (GcrParser *self,
GBytes *data)
{
guint num_parsed;
num_parsed = _gcr_openssh_pub_parse (data, on_openssh_public_key_parsed, self);
if (num_parsed == 0)
return GCR_ERROR_UNRECOGNIZED;
return SUCCESS;
}
/* -----------------------------------------------------------------------------
* FORMATS
*/
/**
* GcrDataFormat:
* @GCR_FORMAT_ALL: Represents all the formats, when enabling or disabling
* @GCR_FORMAT_INVALID: Not a valid format
* @GCR_FORMAT_DER_PRIVATE_KEY: DER encoded private key
* @GCR_FORMAT_DER_PRIVATE_KEY_RSA: DER encoded RSA private key
* @GCR_FORMAT_DER_PRIVATE_KEY_DSA: DER encoded DSA private key
* @GCR_FORMAT_DER_PRIVATE_KEY_EC: DER encoded EC private key
* @GCR_FORMAT_DER_SUBJECT_PUBLIC_KEY: DER encoded SubjectPublicKeyInfo
* @GCR_FORMAT_DER_CERTIFICATE_X509: DER encoded X.509 certificate
* @GCR_FORMAT_DER_PKCS7: DER encoded PKCS\#7 container file which can contain certificates
* @GCR_FORMAT_DER_PKCS8: DER encoded PKCS\#8 file which can contain a key
* @GCR_FORMAT_DER_PKCS8_PLAIN: Unencrypted DER encoded PKCS\#8 file which can contain a key
* @GCR_FORMAT_DER_PKCS8_ENCRYPTED: Encrypted DER encoded PKCS\#8 file which can contain a key
* @GCR_FORMAT_DER_PKCS10: DER encoded PKCS\#10 certificate request file
* @GCR_FORMAT_DER_PKCS12: DER encoded PKCS\#12 file which can contain certificates and/or keys
* @GCR_FORMAT_OPENSSH_PUBLIC: OpenSSH v1 or v2 public key
* @GCR_FORMAT_OPENPGP_PACKET: OpenPGP key packet(s)
* @GCR_FORMAT_OPENPGP_ARMOR: OpenPGP public or private key armor encoded data
* @GCR_FORMAT_PEM: An OpenSSL style PEM file with unspecified contents
* @GCR_FORMAT_PEM_PRIVATE_KEY: An OpenSSL style PEM file with a private key
* @GCR_FORMAT_PEM_PRIVATE_KEY_RSA: An OpenSSL style PEM file with a private RSA key
* @GCR_FORMAT_PEM_PRIVATE_KEY_DSA: An OpenSSL style PEM file with a private DSA key
* @GCR_FORMAT_PEM_PRIVATE_KEY_EC: An OpenSSL style PEM file with a private EC key
* @GCR_FORMAT_PEM_CERTIFICATE_X509: An OpenSSL style PEM file with an X.509 certificate
* @GCR_FORMAT_PEM_PKCS7: An OpenSSL style PEM file containing PKCS\#7
* @GCR_FORMAT_PEM_PKCS8_PLAIN: Unencrypted OpenSSL style PEM file containing PKCS\#8
* @GCR_FORMAT_PEM_PKCS8_ENCRYPTED: Encrypted OpenSSL style PEM file containing PKCS\#8
* @GCR_FORMAT_PEM_PKCS10: An OpenSSL style PEM file containing PKCS\#10
* @GCR_FORMAT_PEM_PKCS12: An OpenSSL style PEM file containing PKCS\#12
* @GCR_FORMAT_PEM_PUBLIC_KEY: An OpenSSL style PEM file containing a SubjectPublicKeyInfo
* @GCR_FORMAT_DER_SPKAC: DER encoded SPKAC as generated by HTML5 keygen element
* @GCR_FORMAT_BASE64_SPKAC: OpenSSL style SPKAC data
*
* The various format identifiers.
*/
/*
* In order of parsing when no formats specified. We put formats earlier
* if the parser can quickly detect whether GCR_ERROR_UNRECOGNIZED or not
*/
static const ParserFormat parser_normal[] = {
{ GCR_FORMAT_PEM, parse_pem },
{ GCR_FORMAT_BASE64_SPKAC, parse_base64_spkac },
{ GCR_FORMAT_DER_PRIVATE_KEY_RSA, parse_der_private_key_rsa },
{ GCR_FORMAT_DER_PRIVATE_KEY_DSA, parse_der_private_key_dsa },
{ GCR_FORMAT_DER_PRIVATE_KEY_EC, parse_der_private_key_ec },
{ GCR_FORMAT_DER_SUBJECT_PUBLIC_KEY, parse_der_subject_public_key },
{ GCR_FORMAT_DER_CERTIFICATE_X509, parse_der_certificate },
{ GCR_FORMAT_DER_PKCS7, parse_der_pkcs7 },
{ GCR_FORMAT_DER_PKCS8_PLAIN, parse_der_pkcs8_plain },
{ GCR_FORMAT_DER_PKCS8_ENCRYPTED, parse_der_pkcs8_encrypted },
{ GCR_FORMAT_DER_PKCS12, parse_der_pkcs12 },
{ GCR_FORMAT_OPENSSH_PUBLIC, parse_openssh_public },
{ GCR_FORMAT_OPENPGP_PACKET, parse_openpgp_packets },
{ GCR_FORMAT_OPENPGP_ARMOR, parse_openpgp_armor },
{ GCR_FORMAT_DER_PKCS10, parse_der_pkcs10 },
{ GCR_FORMAT_DER_SPKAC, parse_der_spkac },
};
/* Must be in format_id numeric order */
static const ParserFormat parser_formats[] = {
{ GCR_FORMAT_DER_PRIVATE_KEY, parse_der_private_key },
{ GCR_FORMAT_DER_PRIVATE_KEY_RSA, parse_der_private_key_rsa },
{ GCR_FORMAT_DER_PRIVATE_KEY_DSA, parse_der_private_key_dsa },
{ GCR_FORMAT_DER_PRIVATE_KEY_EC, parse_der_private_key_ec },
{ GCR_FORMAT_DER_SUBJECT_PUBLIC_KEY, parse_der_subject_public_key },
{ GCR_FORMAT_DER_CERTIFICATE_X509, parse_der_certificate },
{ GCR_FORMAT_DER_PKCS7, parse_der_pkcs7 },
{ GCR_FORMAT_DER_PKCS8, parse_der_pkcs8 },
{ GCR_FORMAT_DER_PKCS8_PLAIN, parse_der_pkcs8_plain },
{ GCR_FORMAT_DER_PKCS8_ENCRYPTED, parse_der_pkcs8_encrypted },
{ GCR_FORMAT_DER_PKCS10, parse_der_pkcs10 },
{ GCR_FORMAT_DER_SPKAC, parse_der_spkac },
{ GCR_FORMAT_BASE64_SPKAC, parse_base64_spkac },
{ GCR_FORMAT_DER_PKCS12, parse_der_pkcs12 },
{ GCR_FORMAT_OPENSSH_PUBLIC, parse_openssh_public },
{ GCR_FORMAT_OPENPGP_PACKET, parse_openpgp_packets },
{ GCR_FORMAT_OPENPGP_ARMOR, parse_openpgp_armor },
{ GCR_FORMAT_PEM, parse_pem },
{ GCR_FORMAT_PEM_PRIVATE_KEY_RSA, parse_pem_private_key_rsa },
{ GCR_FORMAT_PEM_PRIVATE_KEY_DSA, parse_pem_private_key_dsa },
{ GCR_FORMAT_PEM_CERTIFICATE_X509, parse_pem_certificate },
{ GCR_FORMAT_PEM_PKCS7, parse_pem_pkcs7 },
{ GCR_FORMAT_PEM_PKCS8_PLAIN, parse_pem_pkcs8_plain },
{ GCR_FORMAT_PEM_PKCS8_ENCRYPTED, parse_pem_pkcs8_encrypted },
{ GCR_FORMAT_PEM_PKCS12, parse_pem_pkcs12 },
{ GCR_FORMAT_PEM_PKCS10, parse_pem_pkcs10 },
{ GCR_FORMAT_PEM_PRIVATE_KEY_EC, parse_pem_private_key_ec },
{ GCR_FORMAT_PEM_PUBLIC_KEY, parse_pem_public_key },
};
static int
compar_id_to_parser_format (const void *a, const void *b)
{
const gint *format_id = a;
const ParserFormat *format = b;
g_assert (format_id);
g_assert (format);
if (format->format_id == *format_id)
return 0;
return (*format_id < format->format_id) ? -1 : 1;
}
static ParserFormat*
parser_format_lookup (gint format_id)
{
return bsearch (&format_id, parser_formats, G_N_ELEMENTS (parser_formats),
sizeof (parser_formats[0]), compar_id_to_parser_format);
}
static gint
compare_pointers (gconstpointer a, gconstpointer b)
{
if (a == b)
return 0;
return a < b ? -1 : 1;
}
typedef struct _ForeachArgs {
GcrParser *parser;
GBytes *data;
gint result;
} ForeachArgs;
static gboolean
parser_format_foreach (gpointer key, gpointer value, gpointer data)
{
ForeachArgs *args = data;
ParserFormat *format = key;
gint result;
g_assert (format);
g_assert (format->function);
g_assert (GCR_IS_PARSER (args->parser));
result = (format->function) (args->parser, args->data);
if (result != GCR_ERROR_UNRECOGNIZED) {
args->result = result;
return TRUE;
}
/* Keep going */
return FALSE;
}
/* -----------------------------------------------------------------------------
* OBJECT
*/
static GObject*
gcr_parser_constructor (GType type, guint n_props, GObjectConstructParam *props)
{
GcrParser *self = GCR_PARSER (G_OBJECT_CLASS (gcr_parser_parent_class)->constructor(type, n_props, props));
g_return_val_if_fail (self, NULL);
/* Always try to parse with NULL and empty passwords first */
gcr_parser_add_password (self, NULL);
gcr_parser_add_password (self, "");
return G_OBJECT (self);
}
static void
gcr_parser_init (GcrParser *self)
{
self->pv = gcr_parser_get_instance_private (self);
self->pv->passwords = g_ptr_array_new ();
self->pv->normal_formats = TRUE;
}
static void
gcr_parser_dispose (GObject *obj)
{
GcrParser *self = GCR_PARSER (obj);
gsize i;
g_assert (!self->pv->parsed);
if (self->pv->specific_formats)
g_tree_destroy (self->pv->specific_formats);
self->pv->specific_formats = NULL;
for (i = 0; i < self->pv->passwords->len; ++i)
egg_secure_strfree (g_ptr_array_index (self->pv->passwords, i));
g_ptr_array_set_size (self->pv->passwords, 0);
G_OBJECT_CLASS (gcr_parser_parent_class)->dispose (obj);
}
static void
gcr_parser_finalize (GObject *obj)
{
GcrParser *self = GCR_PARSER (obj);
g_assert (!self->pv->parsed);
g_ptr_array_free (self->pv->passwords, TRUE);
self->pv->passwords = NULL;
g_free (self->pv->filename);
self->pv->filename = NULL;
G_OBJECT_CLASS (gcr_parser_parent_class)->finalize (obj);
}
static void
gcr_parser_set_property (GObject *obj, guint prop_id, const GValue *value,
GParamSpec *pspec)
{
switch (prop_id) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
break;
}
}
static void
gcr_parser_get_property (GObject *obj, guint prop_id, GValue *value,
GParamSpec *pspec)
{
GcrParser *self = GCR_PARSER (obj);
switch (prop_id) {
case PROP_PARSED_ATTRIBUTES:
g_value_set_boxed (value, gcr_parser_get_parsed_attributes (self));
break;
case PROP_PARSED_LABEL:
g_value_set_string (value, gcr_parser_get_parsed_label (self));
break;
case PROP_PARSED_DESCRIPTION:
g_value_set_string (value, gcr_parser_get_parsed_description (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
break;
}
}
static void
gcr_parser_class_init (GcrParserClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gint i;
gobject_class->constructor = gcr_parser_constructor;
gobject_class->dispose = gcr_parser_dispose;
gobject_class->finalize = gcr_parser_finalize;
gobject_class->set_property = gcr_parser_set_property;
gobject_class->get_property = gcr_parser_get_property;
/**
* GcrParser:parsed-attributes:
*
* Get the attributes that make up the currently parsed item. This is
* generally only valid during a #GcrParser::parsed signal.
*/
g_object_class_install_property (gobject_class, PROP_PARSED_ATTRIBUTES,
g_param_spec_boxed ("parsed-attributes", "Parsed Attributes", "Parsed PKCS#11 attributes",
GCK_TYPE_ATTRIBUTES,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* GcrParser:parsed-label:
*
* The label of the currently parsed item. This is generally
* only valid during a #GcrParser::parsed signal.
*/
g_object_class_install_property (gobject_class, PROP_PARSED_LABEL,
g_param_spec_string ("parsed-label", "Parsed Label", "Parsed item label",
"",
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* GcrParser:parsed-description:
*
* The description of the type of the currently parsed item. This is generally
* only valid during a #GcrParser::parsed signal.
*/
g_object_class_install_property (gobject_class, PROP_PARSED_DESCRIPTION,
g_param_spec_string ("parsed-description", "Parsed Description", "Parsed item description",
"",
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* GcrParser::authenticate:
* @self: the parser
* @count: the number of times this item has been authenticated
*
* This signal is emitted when an item needs to be unlocked or decrypted before
* it can be parsed. The @count argument specifies the number of times
* the signal has been emitted for a given item. This can be used to
* display a message saying the previous password was incorrect.
*
* Typically the gcr_parser_add_password() function is called in
* response to this signal.
*
* If %FALSE is returned, then the authentication was not handled. If
* no handlers return %TRUE then the item is not parsed and an error
* with the code %GCR_ERROR_CANCELLED will be raised.
*
* Returns: Whether the authentication was handled.
*/
signals[AUTHENTICATE] = g_signal_new ("authenticate", GCR_TYPE_PARSER,
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GcrParserClass, authenticate),
g_signal_accumulator_true_handled, NULL, _gcr_marshal_BOOLEAN__INT,
G_TYPE_BOOLEAN, 1, G_TYPE_INT);
/**
* GcrParser::parsed:
* @self: the parser
*
* This signal is emitted when an item is sucessfully parsed. To access
* the information about the item use the gcr_parser_get_parsed_label(),
* gcr_parser_get_parsed_attributes() and gcr_parser_get_parsed_description()
* functions.
*/
signals[PARSED] = g_signal_new ("parsed", GCR_TYPE_PARSER,
G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GcrParserClass, parsed),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
init_quarks ();
_gcr_initialize_library ();
/* Check that the format tables are in order */
for (i = 1; i < G_N_ELEMENTS (parser_formats); ++i)
g_assert (parser_formats[i].format_id >= parser_formats[i - 1].format_id);
}
/* -----------------------------------------------------------------------------
* PUBLIC
*/
/**
* gcr_parser_new:
*
* Create a new #GcrParser
*
* Returns: (transfer full): a newly allocated #GcrParser
*/
GcrParser *
gcr_parser_new (void)
{
return g_object_new (GCR_TYPE_PARSER, NULL);
}
/**
* gcr_parser_add_password:
* @self: The parser
* @password: (nullable): a password to try
*
* Add a password to the set of passwords to try when parsing locked or encrypted
* items. This is usually called from the #GcrParser::authenticate signal.
*/
void
gcr_parser_add_password (GcrParser *self, const gchar *password)
{
g_return_if_fail (GCR_IS_PARSER (self));
g_ptr_array_add (self->pv->passwords, egg_secure_strdup (password));
}
/**
* gcr_parser_parse_bytes:
* @self: The parser
* @data: the data to parse
* @error: A location to raise an error on failure.
*
* Parse the data. The #GcrParser::parsed and #GcrParser::authenticate signals
* may fire during the parsing.
*
* Returns: Whether the data was parsed successfully or not.
*/
gboolean
gcr_parser_parse_bytes (GcrParser *self,
GBytes *data,
GError **error)
{
ForeachArgs args = { self, NULL, GCR_ERROR_UNRECOGNIZED };
const gchar *message = NULL;
gint i;
g_return_val_if_fail (GCR_IS_PARSER (self), FALSE);
g_return_val_if_fail (data != NULL, FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
if (g_bytes_get_size (data) > 0) {
args.data = g_bytes_ref (data);
/* Just the specific formats requested */
if (self->pv->specific_formats) {
g_tree_foreach (self->pv->specific_formats, parser_format_foreach, &args);
/* All the 'normal' formats */
} else if (self->pv->normal_formats) {
for (i = 0; i < G_N_ELEMENTS (parser_normal); ++i) {
if (parser_format_foreach ((gpointer)(parser_normal + i),
(gpointer)(parser_normal + i), &args))
break;
}
}
g_bytes_unref (args.data);
}
switch (args.result) {
case SUCCESS:
return TRUE;
case GCR_ERROR_CANCELLED:
message = _("The operation was cancelled");
break;
case GCR_ERROR_UNRECOGNIZED:
message = _("Unrecognized or unsupported data.");
break;
case GCR_ERROR_FAILURE:
message = _("Could not parse invalid or corrupted data.");
break;
case GCR_ERROR_LOCKED:
message = _("The data is locked");
break;
default:
g_assert_not_reached ();
break;
};
g_set_error_literal (error, GCR_DATA_ERROR, args.result, message);
return FALSE;
}
/**
* gcr_parser_parse_data:
* @self: The parser
* @data: (array length=n_data): the data to parse
* @n_data: The length of the data
* @error: A location to raise an error on failure.
*
* Parse the data. The #GcrParser::parsed and #GcrParser::authenticate signals
* may fire during the parsing.
*
* A copy of the data will be made. Use gcr_parser_parse_bytes() to avoid this.
*
* Returns: Whether the data was parsed successfully or not.
*/
gboolean
gcr_parser_parse_data (GcrParser *self,
const guchar *data,
gsize n_data,
GError **error)
{
GBytes *bytes;
gboolean ret;
g_return_val_if_fail (GCR_IS_PARSER (self), FALSE);
g_return_val_if_fail (data || !n_data, FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
bytes = g_bytes_new (data, n_data);
ret = gcr_parser_parse_bytes (self, bytes, error);
g_bytes_unref (bytes);
return ret;
}
/**
* gcr_parser_format_enable:
* @self: The parser
* @format: The format identifier
*
* Enable parsing of the given format. Use %GCR_FORMAT_ALL to enable all the formats.
*/
void
gcr_parser_format_enable (GcrParser *self,
GcrDataFormat format)
{
const ParserFormat *form;
guint i;
g_return_if_fail (GCR_IS_PARSER (self));
if (!self->pv->specific_formats)
self->pv->specific_formats = g_tree_new (compare_pointers);
if (format != -1) {
form = parser_format_lookup (format);
g_return_if_fail (form);
g_tree_insert (self->pv->specific_formats,
(gpointer)form, (gpointer)form);
} else {
for (i = 0; i < G_N_ELEMENTS (parser_formats); i++) {
form = &parser_formats[i];
g_tree_insert (self->pv->specific_formats, (gpointer)form,
(gpointer)form);
}
}
}
/**
* gcr_parser_format_disable:
* @self: The parser
* @format: The format identifier
*
* Disable parsing of the given format. Use %GCR_FORMAT_ALL to disable all the formats.
*/
void
gcr_parser_format_disable (GcrParser *self,
GcrDataFormat format)
{
ParserFormat *form;
g_return_if_fail (GCR_IS_PARSER (self));
if (format == -1) {
if (self->pv->specific_formats)
g_tree_destroy (self->pv->specific_formats);
self->pv->specific_formats = NULL;
self->pv->normal_formats = FALSE;
}
if (!self->pv->specific_formats)
return;
form = parser_format_lookup (format);
g_return_if_fail (form);
g_tree_remove (self->pv->specific_formats, form);
}
/**
* gcr_parser_format_supported:
* @self: The parser
* @format: The format identifier
*
* Check whether the given format is supported by the parser.
*
* Returns: Whether the format is supported.
*/
gboolean
gcr_parser_format_supported (GcrParser *self,
GcrDataFormat format)
{
g_return_val_if_fail (GCR_IS_PARSER (self), FALSE);
g_return_val_if_fail (format != GCR_FORMAT_ALL, FALSE);
g_return_val_if_fail (format != GCR_FORMAT_INVALID, FALSE);
return parser_format_lookup (format) ? TRUE : FALSE;
}
/**
* gcr_parser_get_parsed:
* @self: a parser
*
* Get the currently parsed item
*
* Returns: (transfer none): the currently parsed item
*/
GcrParsed *
gcr_parser_get_parsed (GcrParser *self)
{
g_return_val_if_fail (GCR_IS_PARSER (self), NULL);
return self->pv->parsed;
}
G_DEFINE_BOXED_TYPE (GcrParsed, gcr_parsed, gcr_parsed_ref, gcr_parsed_unref)
/**
* gcr_parser_get_filename:
* @self: a parser item
*
* Get the filename of the parser item.
*
* Returns: the filename set on the parser, or %NULL
*/
const gchar *
gcr_parser_get_filename (GcrParser *self)
{
g_return_val_if_fail (GCR_IS_PARSER (self), NULL);
return self->pv->filename;
}
/**
* gcr_parser_set_filename:
* @self: a parser item
* @filename: (nullable): a string of the filename of the parser item
*
* Sets the filename of the parser item.
*/
void
gcr_parser_set_filename (GcrParser *self,
const gchar *filename)
{
g_return_if_fail (GCR_IS_PARSER (self));
g_free (self->pv->filename);
self->pv->filename = g_strdup (filename);
}
/**
* gcr_parsed_ref:
* @parsed: a parsed item
*
* Add a reference to a parsed item. An item may not be shared across threads
* until it has been referenced at least once.
*
* Returns: (transfer full): the parsed item
*/
GcrParsed *
gcr_parsed_ref (GcrParsed *parsed)
{
GcrParsed *copy;
g_return_val_if_fail (parsed != NULL, NULL);
/* Already had a reference */
if (g_atomic_int_add (&parsed->refs, 1) >= 1)
return parsed;
/* If this is the first reference, flatten the stack of parsed */
copy = g_new0 (GcrParsed, 1);
copy->refs = 1;
copy->label = g_strdup (gcr_parsed_get_label (parsed));
copy->filename = g_strdup (gcr_parsed_get_filename (parsed));
copy->attrs = gcr_parsed_get_attributes (parsed);
copy->format = gcr_parsed_get_format (parsed);
if (copy->attrs)
gck_attributes_ref (copy->attrs);
copy->description = gcr_parsed_get_description (parsed);
copy->next = NULL;
/* Find the block of data to copy */
while (parsed != NULL) {
if (parsed->data != NULL) {
copy->data = g_bytes_ref (parsed->data);
copy->sensitive = parsed->sensitive;
break;
}
parsed = parsed->next;
}
return copy;
}
/**
* gcr_parsed_unref:
* @parsed: a parsed item
*
* Unreferences a parsed item which was referenced with gcr_parsed_ref()
*/
void
gcr_parsed_unref (gpointer parsed)
{
GcrParsed *par = parsed;
g_return_if_fail (parsed != NULL);
if (g_atomic_int_dec_and_test (&par->refs)) {
_gcr_parsed_free (parsed);
}
}
/**
* gcr_parser_get_parsed_description:
* @self: The parser
*
* Get a description for the type of the currently parsed item. This is generally
* only valid during the #GcrParser::parsed signal.
*
* Returns: (nullable): the description for the current item; this is owned by
* the parser and should not be freed
*/
const gchar*
gcr_parser_get_parsed_description (GcrParser *self)
{
g_return_val_if_fail (GCR_IS_PARSER (self), NULL);
g_return_val_if_fail (self->pv->parsed != NULL, NULL);
return gcr_parsed_get_description (self->pv->parsed);
}
/**
* gcr_parsed_get_description:
* @parsed: a parsed item
*
* Get the descirption for a parsed item.
*
* Returns: (nullable): the description
*/
const gchar*
gcr_parsed_get_description (GcrParsed *parsed)
{
while (parsed != NULL) {
if (parsed->description != NULL)
return parsed->description;
parsed = parsed->next;
}
return NULL;
}
/**
* gcr_parser_get_parsed_attributes:
* @self: The parser
*
* Get the attributes which make up the currently parsed item. This is generally
* only valid during the #GcrParser::parsed signal.
*
* Returns: (transfer none) (nullable): the attributes for the current item,
* which are owned by the parser and should not be freed
*/
GckAttributes *
gcr_parser_get_parsed_attributes (GcrParser *self)
{
g_return_val_if_fail (GCR_IS_PARSER (self), NULL);
g_return_val_if_fail (self->pv->parsed != NULL, NULL);
return gcr_parsed_get_attributes (self->pv->parsed);
}
/**
* gcr_parsed_get_attributes:
* @parsed: a parsed item
*
* Get the attributes which make up the parsed item.
*
* Returns: (transfer none) (nullable): the attributes for the item; these
* are owned by the parsed item and should not be freed
*/
GckAttributes *
gcr_parsed_get_attributes (GcrParsed *parsed)
{
while (parsed != NULL) {
if (parsed->attrs != NULL)
return parsed->attrs;
parsed = parsed->next;
}
return NULL;
}
/**
* gcr_parser_get_parsed_label:
* @self: The parser
*
* Get the label of the currently parsed item. This is generally only valid
* during the #GcrParser::parsed signal.
*
* Returns: (nullable): the label of the currently parsed item. The value is
* owned by the parser and should not be freed.
*/
const gchar*
gcr_parser_get_parsed_label (GcrParser *self)
{
g_return_val_if_fail (GCR_IS_PARSER (self), NULL);
g_return_val_if_fail (self->pv->parsed != NULL, NULL);
return gcr_parsed_get_label (self->pv->parsed);
}
/**
* gcr_parsed_get_label:
* @parsed: a parsed item
*
* Get the label for the parsed item.
*
* Returns: (nullable): the label for the item
*/
const gchar*
gcr_parsed_get_label (GcrParsed *parsed)
{
while (parsed != NULL) {
if (parsed->label != NULL)
return parsed->label;
parsed = parsed->next;
}
return NULL;
}
/**
* gcr_parser_get_parsed_block:
* @self: a parser
* @n_block: a location to place the size of the block
*
* Get the raw data block that represents this parsed object. This is only
* valid during the #GcrParser::parsed signal.
*
* Returns: (transfer none) (array length=n_block) (nullable): the raw data
* block of the currently parsed item; the value is owned by the parser
* and should not be freed
*/
const guchar *
gcr_parser_get_parsed_block (GcrParser *self,
gsize *n_block)
{
g_return_val_if_fail (GCR_IS_PARSER (self), NULL);
g_return_val_if_fail (n_block != NULL, NULL);
g_return_val_if_fail (self->pv->parsed != NULL, NULL);
return gcr_parsed_get_data (self->pv->parsed, n_block);
}
/**
* gcr_parser_get_parsed_bytes:
* @self: a parser
*
* Get the raw data block that represents this parsed object. This is only
* valid during the #GcrParser::parsed signal.
*
* Returns: (transfer none): the raw data block of the currently parsed item
*/
GBytes *
gcr_parser_get_parsed_bytes (GcrParser *self)
{
return gcr_parsed_get_bytes (self->pv->parsed);
}
/**
* gcr_parsed_get_data:
* @parsed: a parsed item
* @n_data: location to store size of returned data
*
* Get the raw data block for the parsed item.
*
* Returns: (transfer none) (array length=n_data) (nullable): the raw data of
* the parsed item, or %NULL
*/
const guchar *
gcr_parsed_get_data (GcrParsed *parsed,
gsize *n_data)
{
GBytes *bytes;
g_return_val_if_fail (n_data != NULL, NULL);
bytes = gcr_parsed_get_bytes (parsed);
if (bytes == NULL) {
*n_data = 0;
return NULL;
}
return g_bytes_get_data (bytes, n_data);
}
/**
* gcr_parsed_get_bytes:
* @parsed: a parsed item
*
* Get the raw data block for the parsed item.
*
* Returns: (transfer none): the raw data of the parsed item, or %NULL
*/
GBytes *
gcr_parsed_get_bytes (GcrParsed *parsed)
{
while (parsed != NULL) {
if (parsed->data != NULL)
return parsed->data;
parsed = parsed->next;
}
return NULL;
}
/**
* gcr_parser_get_parsed_format:
* @self: a parser
*
* Get the format of the raw data block that represents this parsed object.
* This corresponds with the data returned from gcr_parser_get_parsed_block().
*
* This is only valid during the #GcrParser::parsed signal.
*
* Returns: the data format of the currently parsed item
*/
GcrDataFormat
gcr_parser_get_parsed_format (GcrParser *self)
{
g_return_val_if_fail (GCR_IS_PARSER (self), 0);
g_return_val_if_fail (self->pv->parsed != NULL, 0);
return gcr_parsed_get_format (self->pv->parsed);
}
/**
* gcr_parsed_get_format:
* @parsed: a parsed item
*
* Get the format of the parsed item.
*
* Returns: the data format of the item
*/
GcrDataFormat
gcr_parsed_get_format (GcrParsed *parsed)
{
while (parsed != NULL) {
if (parsed->data != NULL)
return parsed->format;
parsed = parsed->next;
}
return 0;
}
/**
* gcr_parsed_get_filename:
* @parsed: a parsed item
*
* Get the filename of the parsed item.
*
* Returns: (transfer none): the filename of
* the parsed item, or %NULL
*/
const gchar *
gcr_parsed_get_filename (GcrParsed *parsed)
{
g_return_val_if_fail (parsed != NULL, NULL);
return parsed->filename;
}
/* ---------------------------------------------------------------------------------
* STREAM PARSING
*/
#define GCR_TYPE_PARSING (gcr_parsing_get_type ())
#define GCR_PARSING(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GCR_TYPE_PARSING, GcrParsing))
#define GCR_IS_PARSING(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GCR_TYPE_PARSING))
typedef struct _GcrParsing {
GObjectClass parent;
GcrParser *parser;
gboolean async;
GCancellable *cancel;
/* Failure information */
GError *error;
gboolean complete;
/* Operation state */
GInputStream *input;
GByteArray *buffer;
/* Async callback stuff */
GAsyncReadyCallback callback;
gpointer user_data;
} GcrParsing;
typedef struct _GcrParsingClass {
GObjectClass parent_class;
} GcrParsingClass;
/* State forward declarations */
static void state_cancelled (GcrParsing *self, gboolean async);
static void state_failure (GcrParsing *self, gboolean async);
static void state_complete (GcrParsing *self, gboolean async);
static void state_parse_buffer (GcrParsing *self, gboolean async);
static void state_read_buffer (GcrParsing *self, gboolean async);
/* Other forward declarations */
static GType gcr_parsing_get_type (void) G_GNUC_CONST;
static void gcr_parsing_async_result_init (GAsyncResultIface *iface);
G_DEFINE_TYPE_WITH_CODE (GcrParsing, gcr_parsing, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_RESULT, gcr_parsing_async_result_init));
#define BLOCK 4096
static void
next_state (GcrParsing *self, void (*state) (GcrParsing*, gboolean))
{
g_assert (GCR_IS_PARSING (self));
g_assert (state);
if (self->cancel && g_cancellable_is_cancelled (self->cancel))
state = state_cancelled;
(state) (self, self->async);
}
static void
state_complete (GcrParsing *self, gboolean async)
{
g_assert (GCR_IS_PARSING (self));
g_assert (!self->complete);
self->complete = TRUE;
if (async && self->callback != NULL)
(self->callback) (G_OBJECT (self->parser), G_ASYNC_RESULT (self), self->user_data);
}
static void
state_failure (GcrParsing *self, gboolean async)
{
g_assert (GCR_IS_PARSING (self));
g_assert (self->error);
next_state (self, state_complete);
}
static void
state_cancelled (GcrParsing *self, gboolean async)
{
g_assert (GCR_IS_PARSING (self));
if (self->cancel && g_cancellable_is_cancelled (self->cancel))
g_cancellable_cancel (self->cancel);
if (self->error)
g_error_free (self->error);
self->error = g_error_new_literal (GCR_DATA_ERROR, GCR_ERROR_CANCELLED, _("The operation was cancelled"));
next_state (self, state_failure);
}
static void
state_parse_buffer (GcrParsing *self, gboolean async)
{
GError *error = NULL;
GBytes *bytes;
gboolean ret;
g_assert (GCR_IS_PARSING (self));
g_assert (self->buffer);
bytes = g_byte_array_free_to_bytes (self->buffer);
self->buffer = NULL;
ret = gcr_parser_parse_bytes (self->parser, bytes, &error);
g_bytes_unref (bytes);
if (ret == TRUE) {
next_state (self, state_complete);
} else {
g_propagate_error (&self->error, error);
next_state (self, state_failure);
}
}
static void
complete_read_buffer (GcrParsing *self, gssize count, GError *error)
{
g_assert (GCR_IS_PARSING (self));
g_assert (self->buffer);
/* A failure */
if (count == -1) {
g_propagate_error (&self->error, error);
next_state (self, state_failure);
} else {
g_return_if_fail (count >= 0 && count <= BLOCK);
g_byte_array_set_size (self->buffer, self->buffer->len - (BLOCK - count));
/* Finished reading */
if (count == 0)
next_state (self, state_parse_buffer);
/* Read the next block */
else
next_state (self, state_read_buffer);
}
}
static void
on_read_buffer (GObject *obj, GAsyncResult *res, gpointer user_data)
{
GError *error = NULL;
gssize count;
count = g_input_stream_read_finish (G_INPUT_STREAM (obj), res, &error);
complete_read_buffer (user_data, count, error);
}
static void
state_read_buffer (GcrParsing *self, gboolean async)
{
GError *error = NULL;
gssize count;
gsize at;
g_assert (GCR_IS_PARSING (self));
g_assert (G_IS_INPUT_STREAM (self->input));
if (!self->buffer)
self->buffer = g_byte_array_sized_new (BLOCK);
at = self->buffer->len;
g_byte_array_set_size (self->buffer, at + BLOCK);
if (async) {
g_input_stream_read_async (self->input, self->buffer->data + at,
BLOCK, G_PRIORITY_DEFAULT, self->cancel,
on_read_buffer, self);
} else {
count = g_input_stream_read (self->input, self->buffer->data + at,
BLOCK, self->cancel, &error);
complete_read_buffer (self, count, error);
}
}
static void
gcr_parsing_init (GcrParsing *self)
{
}
static void
gcr_parsing_finalize (GObject *obj)
{
GcrParsing *self = GCR_PARSING (obj);
g_object_unref (self->parser);
self->parser = NULL;
g_object_unref (self->input);
self->input = NULL;
if (self->cancel)
g_object_unref (self->cancel);
self->cancel = NULL;
g_clear_error (&self->error);
if (self->buffer)
g_byte_array_free (self->buffer, TRUE);
self->buffer = NULL;
G_OBJECT_CLASS (gcr_parsing_parent_class)->finalize (obj);
}
static void
gcr_parsing_class_init (GcrParsingClass *klass)
{
G_OBJECT_CLASS (klass)->finalize = gcr_parsing_finalize;
}
static gpointer
gcr_parsing_real_get_user_data (GAsyncResult *base)
{
g_return_val_if_fail (GCR_IS_PARSING (base), NULL);
return GCR_PARSING (base)->user_data;
}
static GObject*
gcr_parsing_real_get_source_object (GAsyncResult *base)
{
g_return_val_if_fail (GCR_IS_PARSING (base), NULL);
return G_OBJECT (GCR_PARSING (base)->parser);
}
static void
gcr_parsing_async_result_init (GAsyncResultIface *iface)
{
iface->get_source_object = gcr_parsing_real_get_source_object;
iface->get_user_data = gcr_parsing_real_get_user_data;
}
static GcrParsing*
gcr_parsing_new (GcrParser *parser, GInputStream *input, GCancellable *cancel)
{
GcrParsing *self;
g_assert (GCR_IS_PARSER (parser));
g_assert (G_IS_INPUT_STREAM (input));
self = g_object_new (GCR_TYPE_PARSING, NULL);
self->parser = g_object_ref (parser);
self->input = g_object_ref (input);
if (cancel)
self->cancel = g_object_ref (cancel);
return self;
}
/**
* gcr_parser_parse_stream:
* @self: The parser
* @input: The input stream
* @cancellable: An optional cancellation object
* @error: A location to raise an error on failure
*
* Parse items from the data in a #GInputStream. This function may block while
* reading from the input stream. Use gcr_parser_parse_stream_async() for
* a non-blocking variant.
*
* The #GcrParser::parsed and #GcrParser::authenticate signals
* may fire during the parsing.
*
* Returns: Whether the parsing completed successfully or not.
*/
gboolean
gcr_parser_parse_stream (GcrParser *self, GInputStream *input, GCancellable *cancellable,
GError **error)
{
GcrParsing *parsing;
gboolean result;
g_return_val_if_fail (GCR_IS_PARSER (self), FALSE);
g_return_val_if_fail (G_IS_INPUT_STREAM (input), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
parsing = gcr_parsing_new (self, input, cancellable);
parsing->async = FALSE;
next_state (parsing, state_read_buffer);
g_assert (parsing->complete);
result = gcr_parser_parse_stream_finish (self, G_ASYNC_RESULT (parsing), error);
g_object_unref (parsing);
return result;
}
/**
* gcr_parser_parse_stream_async:
* @self: The parser
* @input: The input stream
* @cancellable: An optional cancellation object
* @callback: Called when the operation result is ready.
* @user_data: Data to pass to callback
*
* Parse items from the data in a #GInputStream. This function completes
* asyncronously and doesn't block.
*
* The #GcrParser::parsed and #GcrParser::authenticate signals
* may fire during the parsing.
*/
void
gcr_parser_parse_stream_async (GcrParser *self, GInputStream *input, GCancellable *cancellable,
GAsyncReadyCallback callback, gpointer user_data)
{
GcrParsing *parsing;
g_return_if_fail (GCR_IS_PARSER (self));
g_return_if_fail (G_IS_INPUT_STREAM (input));
parsing = gcr_parsing_new (self, input, cancellable);
parsing->async = TRUE;
parsing->callback = callback;
parsing->user_data = user_data;
next_state (parsing, state_read_buffer);
}
/**
* gcr_parser_parse_stream_finish:
* @self: The parser
* @result:The operation result
* @error: A location to raise an error on failure
*
* Complete an operation to parse a stream.
*
* Returns: Whether the parsing completed successfully or not.
*/
gboolean
gcr_parser_parse_stream_finish (GcrParser *self, GAsyncResult *result, GError **error)
{
GcrParsing *parsing;
g_return_val_if_fail (GCR_IS_PARSING (result), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
parsing = GCR_PARSING (result);
g_return_val_if_fail (parsing->complete, FALSE);
if (parsing->error) {
g_propagate_error (error, parsing->error);
return FALSE;
}
return TRUE;
}