/*
 * Copyright (C) 2011 Collabora Ltd.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * Author: Stef Walter <stefw@collabora.co.uk>
 */

#include "config.h"

#include "gcr-certificate-request.h"
#include "gcr-key-mechanisms.h"
#include "gcr-enum-types-base.h"
#include "gcr-oids.h"
#include "gcr-subject-public-key.h"

#include <egg/egg-armor.h>
#include <egg/egg-asn1x.h>
#include <egg/egg-asn1-defs.h>
#include <egg/egg-dn.h>

#include <glib/gi18n-lib.h>

/**
 * SECTION:gcr-certificate-request
 * @title: GcrCertificateRequest
 * @short_description: Represents a certificate request
 *
 * This is an object that allows creation of certificate requests. A
 * certificate request is sent to a certificate authority to request an
 * X.509 certificate.
 *
 * Use gcr_certificate_request_prepare() to create a blank certificate
 * request for a given private key. Set the common name on the certificate
 * request with gcr_certificate_request_set_cn(), and then sign the request
 * with gcr_certificate_request_complete_async().
 */

/**
 * GcrCertificateRequest:
 *
 * Represents a certificate request.
 */

/**
 * GcrCertificateRequestFormat:
 * @GCR_CERTIFICATE_REQUEST_PKCS10: certificate request is in PKCS\#10 format
 *
 * The format of a certificate request. Currently only PKCS\#10 is supported.
 */

#define GCR_CERTIFICATE_REQUEST_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GCR_TYPE_CERTIFICATE_REQUEST, GcrCertificateRequestClass))
#define GCR_IS_CERTIFICATE_REQUEST_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GCR_TYPE_CERTIFICATE_REQUEST))
#define GCR_CERTIFICATE_REQUEST_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GCR_TYPE_CERTIFICATE_REQUEST, GcrCertificateRequestClass))

typedef struct _GcrCertificateRequestClass GcrCertificateRequestClass;

struct _GcrCertificateRequest {
	GObject parent;

	GckObject *private_key;
	GNode *asn;
	gulong *mechanisms;
	gulong n_mechanisms;
};

struct _GcrCertificateRequestClass {
	GObjectClass parent_class;
};

enum {
	PROP_0,
	PROP_FORMAT,
	PROP_PRIVATE_KEY
};

/* Forward declarations */
G_DEFINE_TYPE (GcrCertificateRequest, gcr_certificate_request, G_TYPE_OBJECT);

/* When updating here, update prepare_to_be_signed() */
static const gulong RSA_MECHANISMS[] = {
	CKM_SHA1_RSA_PKCS,
	CKM_RSA_PKCS,
};

/* When updating here, update prepare_to_be_signed() */
static const gulong DSA_MECHANISMS[] = {
	CKM_DSA_SHA1,
	CKM_DSA,
};

static const gulong ALL_MECHANISMS[] = {
	CKM_SHA1_RSA_PKCS,
	CKM_DSA_SHA1,
	CKM_RSA_PKCS,
	CKM_DSA,
};

G_STATIC_ASSERT (sizeof (ALL_MECHANISMS) == sizeof (RSA_MECHANISMS) + sizeof (DSA_MECHANISMS));

static void
gcr_certificate_request_init (GcrCertificateRequest *self)
{

}

static void
gcr_certificate_request_constructed (GObject *obj)
{
	GcrCertificateRequest *self = GCR_CERTIFICATE_REQUEST (obj);
	GNode *version;

	G_OBJECT_CLASS (gcr_certificate_request_parent_class)->constructed (obj);

	self->asn = egg_asn1x_create (pkix_asn1_tab, "pkcs-10-CertificationRequest");
	g_return_if_fail (self->asn != NULL);

	/* Setup the version */
	version = egg_asn1x_node (self->asn, "certificationRequestInfo", "version", NULL);
	egg_asn1x_set_integer_as_ulong (version, 0);
}

static void
gcr_certificate_request_finalize (GObject *obj)
{
	GcrCertificateRequest *self = GCR_CERTIFICATE_REQUEST (obj);

	egg_asn1x_destroy (self->asn);
	g_free (self->mechanisms);

	G_OBJECT_CLASS (gcr_certificate_request_parent_class)->finalize (obj);
}

static void
gcr_certificate_request_set_property (GObject *obj,
                                      guint prop_id,
                                      const GValue *value,
                                      GParamSpec *pspec)
{
	GcrCertificateRequest *self = GCR_CERTIFICATE_REQUEST (obj);
	GcrCertificateRequestFormat format;

	switch (prop_id) {
	case PROP_PRIVATE_KEY:
		g_return_if_fail (self->private_key == NULL);
		self->private_key = g_value_dup_object (value);
		g_return_if_fail (GCK_IS_OBJECT (self->private_key));
		break;
	case PROP_FORMAT:
		format = g_value_get_enum (value);
		g_return_if_fail (format == GCR_CERTIFICATE_REQUEST_PKCS10);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
		break;
	}
}

static void
gcr_certificate_request_get_property (GObject *obj,
                                      guint prop_id,
                                      GValue *value,
                                      GParamSpec *pspec)
{
	GcrCertificateRequest *self = GCR_CERTIFICATE_REQUEST (obj);

	switch (prop_id) {
	case PROP_PRIVATE_KEY:
		g_value_set_object (value, gcr_certificate_request_get_private_key (self));
		break;
	case PROP_FORMAT:
		g_value_set_enum (value, gcr_certificate_request_get_format (self));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
		break;
	}
}

static void
gcr_certificate_request_class_init (GcrCertificateRequestClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

	_gcr_oids_init ();

	gobject_class->constructed = gcr_certificate_request_constructed;
	gobject_class->finalize = gcr_certificate_request_finalize;
	gobject_class->set_property = gcr_certificate_request_set_property;
	gobject_class->get_property = gcr_certificate_request_get_property;

	/**
	 * GcrCertificateRequest:private-key:
	 *
	 * The private key that this certificate request is for.
	 */
	g_object_class_install_property (gobject_class, PROP_PRIVATE_KEY,
	            g_param_spec_object ("private-key", "Private key", "Private key for request",
	                                 GCK_TYPE_OBJECT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

	/**
	 * GcrCertificateRequest:format:
	 *
	 * The format of the certificate request.
	 */
	g_object_class_install_property (gobject_class, PROP_FORMAT,
	              g_param_spec_enum ("format", "Format", "Format of certificate request",
	                                 GCR_TYPE_CERTIFICATE_REQUEST_FORMAT, GCR_CERTIFICATE_REQUEST_PKCS10,
	                                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}

/**
 * gcr_certificate_request_prepare:
 * @format: the format for the certificate request
 * @private_key: the private key the the certificate is being requested for
 *
 * Create a new certificate request, in the given format for the private key.
 *
 * Returns: (transfer full): a new #GcrCertificate request
 */
GcrCertificateRequest *
gcr_certificate_request_prepare (GcrCertificateRequestFormat format,
                                 GckObject *private_key)
{
	g_return_val_if_fail (format == GCR_CERTIFICATE_REQUEST_PKCS10, NULL);
	g_return_val_if_fail (GCK_IS_OBJECT (private_key), NULL);

	return g_object_new (GCR_TYPE_CERTIFICATE_REQUEST,
	                     "format", format,
	                     "private-key", private_key,
	                     NULL);
}

/**
 * gcr_certificate_request_get_private_key:
 * @self: the certificate request
 *
 * Get the private key this certificate request is for.
 *
 * Returns: (transfer none): the private key,
 */
GckObject *
gcr_certificate_request_get_private_key (GcrCertificateRequest *self)
{
	g_return_val_if_fail (GCR_IS_CERTIFICATE_REQUEST (self), NULL);
	return self->private_key;
}

/**
 * gcr_certificate_request_get_format:
 * @self: the certificate request
 *
 * Get the format of this certificate request.
 *
 * Returns: the format
 */
GcrCertificateRequestFormat
gcr_certificate_request_get_format (GcrCertificateRequest *self)
{
	g_return_val_if_fail (GCR_IS_CERTIFICATE_REQUEST (self), 0);
	return GCR_CERTIFICATE_REQUEST_PKCS10;
}

/**
 * gcr_certificate_request_set_cn:
 * @self: the certificate request
 * @cn: common name to set on the request
 *
 * Set the common name encoded in the certificate request.
 */
void
gcr_certificate_request_set_cn (GcrCertificateRequest *self,
                                const gchar *cn)
{
	GNode *subject;
	GNode *dn;

	g_return_if_fail (GCR_IS_CERTIFICATE_REQUEST (self));
	g_return_if_fail (cn != NULL);

	subject = egg_asn1x_node (self->asn, "certificationRequestInfo", "subject", NULL);
	dn = egg_asn1x_node (subject, "rdnSequence", NULL);

	/* TODO: we shouldn't really be clearing this, but replacing CN */
	egg_asn1x_set_choice (subject, dn);
	egg_asn1x_clear (dn);
	egg_dn_add_string_part (dn, GCR_OID_NAME_CN, cn);
}

static EggBytes *
hash_sha1_pkcs1 (EggBytes *data)
{
	const guchar SHA1_ASN[15] = /* Object ID is 1.3.14.3.2.26 */
		{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
		  0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };

	GChecksum *checksum;
	guchar *hash;
	gsize n_hash;
	gsize n_digest;

	n_digest = g_checksum_type_get_length (G_CHECKSUM_SHA1);
	n_hash = n_digest + sizeof (SHA1_ASN);
	hash = g_malloc (n_hash);
	memcpy (hash, SHA1_ASN, sizeof (SHA1_ASN));

	checksum = g_checksum_new (G_CHECKSUM_SHA1);
	g_checksum_update (checksum, egg_bytes_get_data (data), egg_bytes_get_size (data));
	g_checksum_get_digest (checksum, hash + sizeof (SHA1_ASN), &n_digest);
	g_checksum_free (checksum);

	return egg_bytes_new_take (hash, n_hash);
}

static EggBytes *
hash_sha1 (EggBytes *data)
{
	GChecksum *checksum;
	guchar *hash;
	gsize n_hash;

	n_hash = g_checksum_type_get_length (G_CHECKSUM_SHA1);
	hash = g_malloc (n_hash);

	checksum = g_checksum_new (G_CHECKSUM_SHA1);
	g_checksum_update (checksum, egg_bytes_get_data (data), egg_bytes_get_size (data));
	g_checksum_get_digest (checksum, hash, &n_hash);
	g_checksum_free (checksum);

	return egg_bytes_new_take (hash, n_hash);
}

static EggBytes *
prepare_to_be_signed (GcrCertificateRequest *self,
                      GckMechanism *mechanism)
{
	GNode *node;
	EggBytes *data;
	EggBytes *hash;

	g_assert (mechanism != NULL);

	node = egg_asn1x_node (self->asn, "certificationRequestInfo", NULL);
	data = egg_asn1x_encode (node, NULL);

	mechanism->parameter = NULL;
	mechanism->n_parameter = 0;

	switch (mechanism->type) {
	case CKM_SHA1_RSA_PKCS:
	case CKM_DSA_SHA1:
		return data;

	case CKM_RSA_PKCS:
		hash = hash_sha1_pkcs1 (data);
		egg_bytes_unref (data);
		return hash;

	case CKM_DSA:
		hash = hash_sha1 (data);
		egg_bytes_unref (data);
		return hash;

	default:
		g_assert_not_reached ();
		return NULL;
	}
}

static gboolean
prepare_subject_public_key_and_mechanisms (GcrCertificateRequest *self,
                                           GNode *subject_public_key,
                                           GQuark *algorithm,
                                           const gulong **mechanisms,
                                           gsize *n_mechanisms,
                                           GError **error)
{
	EggBytes *encoded;
	GNode *node;
	GQuark oid;

	g_assert (algorithm != NULL);
	g_assert (mechanisms != NULL);
	g_assert (n_mechanisms != NULL);

	encoded = egg_asn1x_encode (subject_public_key, NULL);
	g_return_val_if_fail (encoded != NULL, FALSE);

	node = egg_asn1x_node (subject_public_key, "algorithm", "algorithm", NULL);
	oid = egg_asn1x_get_oid_as_quark (node);

	if (oid == GCR_OID_PKIX1_RSA) {
		*mechanisms = RSA_MECHANISMS;
		*n_mechanisms = G_N_ELEMENTS (RSA_MECHANISMS);
		*algorithm = GCR_OID_PKIX1_SHA1_WITH_RSA;

	} else if (oid == GCR_OID_PKIX1_DSA) {
		*mechanisms = DSA_MECHANISMS;
		*n_mechanisms = G_N_ELEMENTS (DSA_MECHANISMS);
		*algorithm = GCR_OID_PKIX1_SHA1_WITH_DSA;

	} else {
		egg_bytes_unref (encoded);
		g_set_error (error, GCR_DATA_ERROR, GCR_ERROR_UNRECOGNIZED,
		             _("Unsupported key type for certificate request"));
		return FALSE;
	}

	node = egg_asn1x_node (self->asn, "certificationRequestInfo", "subjectPKInfo", NULL);
	if (!egg_asn1x_set_element_raw (node, encoded))
		g_return_val_if_reached (FALSE);

	egg_bytes_unref (encoded);
	return TRUE;
}

static void
encode_take_signature_into_request (GcrCertificateRequest *self,
                                    GQuark algorithm,
                                    GNode *subject_public_key,
                                    guchar *result,
                                    gsize n_result)
{
	EggBytes *data;
	GNode *params;
	GNode *node;

	node = egg_asn1x_node (self->asn, "signature", NULL);
	egg_asn1x_take_bits_as_raw (node, egg_bytes_new_take (result, n_result), n_result * 8);

	node = egg_asn1x_node (self->asn, "signatureAlgorithm", "algorithm", NULL);
	egg_asn1x_set_oid_as_quark (node, algorithm);

	node = egg_asn1x_node (self->asn, "signatureAlgorithm", "parameters", NULL);
	params = egg_asn1x_node (subject_public_key, "algorithm", "parameters", NULL);
	data = egg_asn1x_encode (params, NULL);
	egg_asn1x_set_element_raw (node, data);
	egg_bytes_unref (data);
}

/**
 * gcr_certificate_request_complete:
 * @self: a certificate request
 * @cancellable: a cancellation object
 * @error: location to place an error on failure
 *
 * Complete and sign a certificate request, so that it can be encoded
 * and sent to a certificate authority.
 *
 * This call may block as it signs the request using the private key.
 *
 * Returns: whether certificate request was successfully completed or not
 */
gboolean
gcr_certificate_request_complete (GcrCertificateRequest *self,
                                  GCancellable *cancellable,
                                  GError **error)
{
	GNode *subject_public_key;
	const gulong *mechanisms;
	gsize n_mechanisms;
	GckMechanism mechanism = { 0, };
	GQuark algorithm = 0;
	EggBytes *tbs;
	GckSession *session;
	guchar *signature;
	gsize n_signature;
	gboolean ret;

	g_return_val_if_fail (GCR_IS_CERTIFICATE_REQUEST (self), FALSE);
	g_return_val_if_fail (cancellable == NULL || G_CANCELLABLE (cancellable), FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	subject_public_key = _gcr_subject_public_key_load (self->private_key,
	                                                   cancellable, error);
	if (subject_public_key == NULL)
		return FALSE;

	ret = prepare_subject_public_key_and_mechanisms (self, subject_public_key,
	                                                 &algorithm, &mechanisms,
	                                                 &n_mechanisms, error);

	if (!ret) {
		egg_asn1x_destroy (subject_public_key);
		return FALSE;
	}

	/* Figure out which mechanism to use */
	mechanism.type = _gcr_key_mechanisms_check (self->private_key, mechanisms,
	                                            n_mechanisms, CKA_SIGN,
	                                            cancellable, NULL);
	if (mechanism.type == GCK_INVALID) {
		egg_asn1x_destroy (subject_public_key);
		g_set_error (error, GCK_ERROR, CKR_KEY_TYPE_INCONSISTENT,
		             _("The key cannot be used to sign the request"));
		return FALSE;
	}

	tbs = prepare_to_be_signed (self, &mechanism);
	session = gck_object_get_session (self->private_key);
	signature = gck_session_sign_full (session, self->private_key, &mechanism,
	                                   egg_bytes_get_data (tbs),
	                                   egg_bytes_get_size (tbs),
	                                   &n_signature, cancellable, error);
	g_object_unref (session);
	egg_bytes_unref (tbs);

	if (!signature) {
		egg_asn1x_destroy (subject_public_key);
		return FALSE;
	}

	encode_take_signature_into_request (self, algorithm, subject_public_key,
	                                    signature, n_signature);
	egg_asn1x_destroy (subject_public_key);
	return TRUE;
}

typedef struct {
	GcrCertificateRequest *request;
	GCancellable *cancellable;
	GQuark algorithm;
	GNode *subject_public_key;
	GckMechanism mechanism;
	GckSession *session;
	EggBytes *tbs;
} CompleteClosure;

static void
complete_closure_free (gpointer data)
{
	CompleteClosure *closure = data;
	egg_asn1x_destroy (closure->subject_public_key);
	g_clear_object (&closure->request);
	g_clear_object (&closure->cancellable);
	g_clear_object (&closure->session);
	if (closure->tbs)
		egg_bytes_unref (closure->tbs);
	g_free (closure);
}

static void
on_certificate_request_signed (GObject *source,
                               GAsyncResult *result,
                               gpointer user_data)
{
	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
	CompleteClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
	GError *error = NULL;
	guchar *signature;
	gsize n_signature;

	signature = gck_session_sign_finish (closure->session, result, &n_signature, &error);
	if (error == NULL) {
		encode_take_signature_into_request (closure->request,
		                                    closure->algorithm,
		                                    closure->subject_public_key,
		                                    signature, n_signature);

	} else {
		g_simple_async_result_take_error (res, error);
	}

	g_simple_async_result_complete (res);
	g_object_unref (res);
}

static void
on_mechanism_check (GObject *source,
                    GAsyncResult *result,
                    gpointer user_data)
{
	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
	CompleteClosure *closure = g_simple_async_result_get_op_res_gpointer (res);

	closure->mechanism.type =  _gcr_key_mechanisms_check_finish (closure->request->private_key,
	                                                             result, NULL);
	if (closure->mechanism.type == GCK_INVALID) {
		g_simple_async_result_set_error (res, GCK_ERROR, CKR_KEY_TYPE_INCONSISTENT,
		                                 _("The key cannot be used to sign the request"));
		g_simple_async_result_complete (res);

	} else {
		closure->tbs = prepare_to_be_signed (closure->request, &closure->mechanism);
		gck_session_sign_async (closure->session,
		                        closure->request->private_key,
		                        &closure->mechanism,
		                        egg_bytes_get_data (closure->tbs),
		                        egg_bytes_get_size (closure->tbs),
		                        closure->cancellable,
		                        on_certificate_request_signed,
		                        g_object_ref (res));
	}

	g_object_unref (res);
}

static void
on_subject_public_key_loaded (GObject *source,
                              GAsyncResult *result,
                              gpointer user_data)
{
	GSimpleAsyncResult *res = G_SIMPLE_ASYNC_RESULT (user_data);
	CompleteClosure *closure = g_simple_async_result_get_op_res_gpointer (res);
	const gulong *mechanisms;
	gsize n_mechanisms;
	GError *error = NULL;

	closure->subject_public_key = _gcr_subject_public_key_load_finish (result, &error);
	if (error == NULL) {
		prepare_subject_public_key_and_mechanisms (closure->request,
		                                           closure->subject_public_key,
		                                           &closure->algorithm,
		                                           &mechanisms,
		                                           &n_mechanisms,
		                                           &error);
	}

	if (error != NULL) {
		g_simple_async_result_take_error (res, error);
		g_simple_async_result_complete (res);

	} else {
		_gcr_key_mechanisms_check_async (closure->request->private_key,
		                                 mechanisms, n_mechanisms, CKA_SIGN,
		                                 closure->cancellable, on_mechanism_check,
		                                 g_object_ref (res));
	}

	g_object_unref (res);
}

/**
 * gcr_certificate_request_complete_async:
 * @self: a certificate request
 * @cancellable: a cancellation object
 * @callback: called when the operation completes
 * @user_data: data to pass to the callback
 *
 * Asynchronously complete and sign a certificate request, so that it can
 * be encoded and sent to a certificate authority.
 *
 * This call will return immediately and complete later.
 */
void
gcr_certificate_request_complete_async (GcrCertificateRequest *self,
                                        GCancellable *cancellable,
                                        GAsyncReadyCallback callback,
                                        gpointer user_data)
{
	GSimpleAsyncResult *res;
	CompleteClosure *closure;

	g_return_if_fail (GCR_IS_CERTIFICATE_REQUEST (self));
	g_return_if_fail (cancellable == NULL || G_CANCELLABLE (cancellable));

	res = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
	                                 gcr_certificate_request_complete_async);
	closure = g_new0 (CompleteClosure, 1);
	closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
	closure->session = gck_object_get_session (self->private_key);
	closure->request = g_object_ref (self);
	g_simple_async_result_set_op_res_gpointer (res, closure, complete_closure_free);

	_gcr_subject_public_key_load_async (self->private_key, cancellable,
	                                    on_subject_public_key_loaded,
	                                    g_object_ref (res));

	g_object_unref (res);
}

/**
 * gcr_certificate_request_complete_finish:
 * @self: a certificate request
 * @result: result of the asynchronous operation
 * @error: location to place an error on failure
 *
 * Finish an asynchronous operation to complete and sign a certificate
 * request.
 *
 * Returns: whether certificate request was successfully completed or not
 */
gboolean
gcr_certificate_request_complete_finish (GcrCertificateRequest *self,
                                         GAsyncResult *result,
                                         GError **error)
{
	g_return_val_if_fail (GCR_IS_CERTIFICATE_REQUEST (self), FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self),
	                      gcr_certificate_request_complete_async), FALSE);

	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
		return FALSE;

	return TRUE;
}

/**
 * gcr_certificate_request_encode:
 * @self: a certificate request
 * @textual: whether to encode output as text
 * @length: location to place length of returned data
 *
 * Encode the certificate request. It must have been completed with
 * gcr_certificate_request_complete() or gcr_certificate_request_complete_async()
 *
 * If @textual is %FALSE, the output is a DER encoded certificate request.
 *
 * If @textual is %TRUE, the output is encoded as text. For PKCS\#10 requests this
 * is done using the OpenSSL style PEM encoding.
 *
 * Returns: (transfer full) (array length=length): the encoded certificate request
 */
guchar *
gcr_certificate_request_encode (GcrCertificateRequest *self,
                                gboolean textual,
                                gsize *length)
{
	EggBytes *bytes;
	gpointer encoded;
	gpointer data;
	gsize size;

	g_return_val_if_fail (GCR_IS_CERTIFICATE_REQUEST (self), NULL);
	g_return_val_if_fail (length != NULL, NULL);

	bytes = egg_asn1x_encode (self->asn, NULL);
	if (bytes == NULL) {
		g_warning ("couldn't encode certificate request: %s",
		           egg_asn1x_message (self->asn));
		return NULL;
	}

	size = egg_bytes_get_size (bytes);
	encoded = g_byte_array_free (egg_bytes_unref_to_array (bytes), FALSE);

	if (textual) {
		data = egg_armor_write (encoded, size,
		                        g_quark_from_static_string ("CERTIFICATE REQUEST"),
		                        NULL, length);
		g_free (encoded);
		encoded = data;

	} else {
		*length = size;
	}

	return encoded;
}

/**
 * gcr_certificate_request_capable:
 * @private_key: a private key
 * @cancellable: cancellation object
 * @error: location to place an error
 *
 * Check whether #GcrCertificateRequest is capable of creating a request
 * for the given @private_key.
 *
 * Returns: whether a request can be created
 */
gboolean
gcr_certificate_request_capable (GckObject *private_key,
                                 GCancellable *cancellable,
                                 GError **error)
{
	g_return_val_if_fail (GCK_IS_OBJECT (private_key), FALSE);
	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

	return _gcr_key_mechanisms_check (private_key, ALL_MECHANISMS,
	                                  G_N_ELEMENTS (ALL_MECHANISMS),
	                                 CKA_SIGN, cancellable, error);
}

/**
 * gcr_certificate_request_capable_async:
 * @private_key: a private key
 * @cancellable: cancellation object
 * @callback: will be called when the operation completes
 * @user_data: data to be passed to callback
 *
 * Asynchronously check whether #GcrCertificateRequest is capable of creating
 * a request for the given @private_key.
 */
void
gcr_certificate_request_capable_async (GckObject *private_key,
                                       GCancellable *cancellable,
                                       GAsyncReadyCallback callback,
                                       gpointer user_data)
{
	g_return_if_fail (GCK_IS_OBJECT (private_key));
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));

	_gcr_key_mechanisms_check_async (private_key, ALL_MECHANISMS,
	                                 G_N_ELEMENTS (ALL_MECHANISMS),
	                                 CKA_SIGN, cancellable,
	                                 callback, user_data);
}

/**
 * gcr_certificate_request_capable_finish:
 * @result: asynchronous result
 * @error: location to place an error
 *
 * Get the result for asynchronously check whether #GcrCertificateRequest is
 * capable of creating a request for the given @private_key.
 *
 * Returns: whether a request can be created
 */
gboolean
gcr_certificate_request_capable_finish (GAsyncResult *result,
                                        GError **error)
{
	GObject *source;
	gulong mech;

	g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);

	source = g_async_result_get_source_object (result);
	mech = _gcr_key_mechanisms_check_finish (GCK_OBJECT (source), result, error);
	g_object_unref (source);

	return mech != GCK_INVALID;
}
