/*
* 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 "gkm-attributes.h"
#include "gkm-certificate.h"
#include "gkm-certificate-key.h"
#include "gkm-crypto.h"
#include "gkm-data-asn1.h"
#include "gkm-data-der.h"
#define DEBUG_FLAG GKM_DEBUG_STORAGE
#include "gkm-debug.h"
#include "gkm-factory.h"
#include "gkm-sexp-key.h"
#include "gkm-manager.h"
#include "gkm-session.h"
#include "gkm-sexp.h"
#include "gkm-serializable.h"
#include "gkm-transaction.h"
#include "gkm-util.h"
#include "egg/egg-dn.h"
#include "egg/egg-asn1x.h"
#include "pkcs11/pkcs11.h"
#include "pkcs11/pkcs11i.h"
#include
enum {
PROP_0,
PROP_LABEL,
PROP_PUBLIC_KEY
};
struct _GkmCertificatePrivate {
GkmCertificateKey *key;
GNode *asn1;
GBytes *der;
gchar *label;
};
static GQuark OID_BASIC_CONSTRAINTS;
static GQuark OID_ENHANCED_USAGE;
static void gkm_certificate_serializable (GkmSerializableIface *iface);
G_DEFINE_TYPE_EXTENDED (GkmCertificate, gkm_certificate, GKM_TYPE_OBJECT, 0,
G_ADD_PRIVATE (GkmCertificate)
G_IMPLEMENT_INTERFACE (GKM_TYPE_SERIALIZABLE, gkm_certificate_serializable));
/* -----------------------------------------------------------------------------
* INTERNAL
*/
static void
init_quarks (void)
{
static gsize quarks_inited = 0;
if (g_once_init_enter (&quarks_inited)) {
#define QUARK(name, value) \
name = g_quark_from_static_string(value)
QUARK (OID_BASIC_CONSTRAINTS, "2.5.29.19");
QUARK (OID_ENHANCED_USAGE, "2.5.29.37");
#undef QUARK
g_once_init_leave (&quarks_inited, 1);
}
}
static gint
find_certificate_extension (GkmCertificate *self, GQuark oid)
{
GQuark exoid;
GNode *node;
guint index;
g_assert (oid);
g_assert (GKM_IS_CERTIFICATE (self));
g_assert (self->pv->asn1);
for (index = 1; TRUE; ++index) {
/* Make sure it is present */
node = egg_asn1x_node (self->pv->asn1, "tbsCertificate", "extensions", index, NULL);
if (node == NULL)
break;
/* See if it's the same */
exoid = egg_asn1x_get_oid_as_quark (egg_asn1x_node (node, "extnID", NULL));
if(exoid == oid)
return index;
}
return 0;
}
static GkmObject*
factory_create_certificate (GkmSession *session, GkmTransaction *transaction,
CK_ATTRIBUTE_PTR attrs, CK_ULONG n_attrs)
{
CK_ATTRIBUTE_PTR attr;
GkmCertificate *cert;
GBytes *bytes;
gboolean ret;
g_return_val_if_fail (GKM_IS_TRANSACTION (transaction), NULL);
g_return_val_if_fail (attrs || !n_attrs, NULL);
/* Dig out the value */
attr = gkm_attributes_find (attrs, n_attrs, CKA_VALUE);
if (attr == NULL) {
gkm_transaction_fail (transaction, CKR_TEMPLATE_INCOMPLETE);
return NULL;
}
cert = g_object_new (GKM_TYPE_CERTIFICATE,
"module", gkm_session_get_module (session),
"manager", gkm_manager_for_template (attrs, n_attrs, session),
NULL);
/* Load the certificate from the data specified */
bytes = g_bytes_new (attr->pValue, attr->ulValueLen);
ret = gkm_serializable_load (GKM_SERIALIZABLE (cert), NULL, bytes);
g_bytes_unref (bytes);
if(!ret) {
gkm_transaction_fail (transaction, CKR_ATTRIBUTE_VALUE_INVALID);
g_object_unref (cert);
return NULL;
}
/* We calculate these attributes automatically */
gkm_attributes_consume (attrs, n_attrs,
CKA_VALUE, CKA_SUBJECT, CKA_SERIAL_NUMBER, CKA_ID,
G_MAXULONG);
gkm_session_complete_object_creation (session, transaction, GKM_OBJECT (cert),
TRUE, attrs, n_attrs);
return GKM_OBJECT (cert);
}
/* -----------------------------------------------------------------------------
* KEY
*/
static CK_RV
gkm_certificate_real_get_attribute (GkmObject *base, GkmSession *session, CK_ATTRIBUTE* attr)
{
GkmCertificate *self = GKM_CERTIFICATE (base);
CK_ULONG category;
GBytes *cdata;
guchar *data;
gsize n_data;
time_t when;
CK_RV rv;
switch (attr->type) {
case CKA_CLASS:
return gkm_attribute_set_ulong (attr, CKO_CERTIFICATE);
case CKA_PRIVATE:
return gkm_attribute_set_bool (attr, FALSE);
case CKA_LABEL:
return gkm_attribute_set_string (attr, gkm_certificate_get_label (self));
case CKA_CERTIFICATE_TYPE:
return gkm_attribute_set_ulong (attr, CKC_X_509);
case CKA_TRUSTED:
return gkm_attribute_set_bool (attr, FALSE);
case CKA_CERTIFICATE_CATEGORY:
if (!gkm_certificate_calc_category (self, session, &category))
return CKR_FUNCTION_FAILED;
return gkm_attribute_set_ulong (attr, category);
case CKA_CHECK_VALUE:
g_return_val_if_fail (self->pv->der != NULL, CKR_GENERAL_ERROR);
n_data = gcry_md_get_algo_dlen (GCRY_MD_SHA1);
g_return_val_if_fail (n_data && n_data > 3, CKR_GENERAL_ERROR);
data = g_new0 (guchar, n_data);
gcry_md_hash_buffer (GCRY_MD_SHA1, data,
g_bytes_get_data (self->pv->der, NULL),
g_bytes_get_size (self->pv->der));
rv = gkm_attribute_set_data (attr, data, 3);
g_free (data);
return rv;
case CKA_START_DATE:
case CKA_END_DATE:
g_return_val_if_fail (self->pv->asn1, CKR_GENERAL_ERROR);
when = egg_asn1x_get_time_as_long (egg_asn1x_node (self->pv->asn1,
"tbsCertificate", "validity",
attr->type == CKA_START_DATE ? "notBefore" : "notAfter",
NULL));
if (when < 0)
return CKR_FUNCTION_FAILED;
return gkm_attribute_set_date (attr, when);
case CKA_SUBJECT:
g_return_val_if_fail (self->pv->asn1, CKR_GENERAL_ERROR);
cdata = egg_asn1x_get_element_raw (egg_asn1x_node (self->pv->asn1, "tbsCertificate", "subject", NULL));
g_return_val_if_fail (cdata, CKR_GENERAL_ERROR);
rv = gkm_attribute_set_bytes (attr, cdata);
g_bytes_unref (cdata);
return rv;
case CKA_ID:
if (!self->pv->key)
return gkm_attribute_set_data (attr, NULL, 0);
return gkm_object_get_attribute (GKM_OBJECT (self->pv->key), session, attr);
case CKA_ISSUER:
g_return_val_if_fail (self->pv->asn1, CKR_GENERAL_ERROR);
cdata = egg_asn1x_get_element_raw (egg_asn1x_node (self->pv->asn1, "tbsCertificate", "issuer", NULL));
g_return_val_if_fail (cdata, CKR_GENERAL_ERROR);
rv = gkm_attribute_set_bytes (attr, cdata);
g_bytes_unref (cdata);
return rv;
case CKA_SERIAL_NUMBER:
g_return_val_if_fail (self->pv->asn1, CKR_GENERAL_ERROR);
cdata = egg_asn1x_get_element_raw (egg_asn1x_node (self->pv->asn1, "tbsCertificate", "serialNumber", NULL));
g_return_val_if_fail (cdata, CKR_GENERAL_ERROR);
rv = gkm_attribute_set_bytes (attr, cdata);
g_bytes_unref (cdata);
return rv;
case CKA_VALUE:
g_return_val_if_fail (self->pv->der != NULL, CKR_GENERAL_ERROR);
return gkm_attribute_set_bytes (attr, self->pv->der);
/* These are only used for strange online certificates which we don't support */
case CKA_URL:
case CKA_HASH_OF_SUBJECT_PUBLIC_KEY:
case CKA_HASH_OF_ISSUER_PUBLIC_KEY:
return gkm_attribute_set_data (attr, "", 0);
/* What in the world is this doing in the spec? */
case CKA_JAVA_MIDP_SECURITY_DOMAIN:
return gkm_attribute_set_ulong (attr, 0); /* 0 = unspecified */
};
return GKM_OBJECT_CLASS (gkm_certificate_parent_class)->get_attribute (base, session, attr);
}
static GObject*
gkm_certificate_constructor (GType type, guint n_props, GObjectConstructParam *props)
{
GkmCertificate *self = GKM_CERTIFICATE (G_OBJECT_CLASS (gkm_certificate_parent_class)->constructor(type, n_props, props));
g_return_val_if_fail (self, NULL);
return G_OBJECT (self);
}
static void
gkm_certificate_init (GkmCertificate *self)
{
self->pv = gkm_certificate_get_instance_private (self);
}
static void
gkm_certificate_dispose (GObject *obj)
{
GkmCertificate *self = GKM_CERTIFICATE (obj);
if (self->pv->key)
g_object_unref (self->pv->key);
self->pv->key = NULL;
G_OBJECT_CLASS (gkm_certificate_parent_class)->dispose (obj);
}
static void
gkm_certificate_finalize (GObject *obj)
{
GkmCertificate *self = GKM_CERTIFICATE (obj);
g_assert (!self->pv->key);
if (self->pv->der)
g_bytes_unref (self->pv->der);
g_free (self->pv->label);
egg_asn1x_destroy (self->pv->asn1);
G_OBJECT_CLASS (gkm_certificate_parent_class)->finalize (obj);
}
static void
gkm_certificate_set_property (GObject *obj, guint prop_id, const GValue *value,
GParamSpec *pspec)
{
GkmCertificate *self = GKM_CERTIFICATE (obj);
switch (prop_id) {
case PROP_LABEL:
gkm_certificate_set_label (self, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
break;
}
}
static void
gkm_certificate_get_property (GObject *obj, guint prop_id, GValue *value,
GParamSpec *pspec)
{
GkmCertificate *self = GKM_CERTIFICATE (obj);
switch (prop_id) {
case PROP_LABEL:
g_value_set_string (value, gkm_certificate_get_label (self));
break;
case PROP_PUBLIC_KEY:
g_value_set_object (value, gkm_certificate_get_public_key (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
break;
}
}
static void
gkm_certificate_class_init (GkmCertificateClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GkmObjectClass *gkm_class = GKM_OBJECT_CLASS (klass);
gobject_class->constructor = gkm_certificate_constructor;
gobject_class->dispose = gkm_certificate_dispose;
gobject_class->finalize = gkm_certificate_finalize;
gobject_class->set_property = gkm_certificate_set_property;
gobject_class->get_property = gkm_certificate_get_property;
gkm_class->get_attribute = gkm_certificate_real_get_attribute;
g_object_class_install_property (gobject_class, PROP_PUBLIC_KEY,
g_param_spec_object ("public-key", "Public Key", "Public key contained in certificate",
GKM_TYPE_CERTIFICATE_KEY, G_PARAM_READABLE));
g_object_class_install_property (gobject_class, PROP_PUBLIC_KEY,
g_param_spec_string ("label", "Label", "Label of the certificate",
"", G_PARAM_READWRITE));
init_quarks ();
}
static gboolean
gkm_certificate_real_load (GkmSerializable *base,
GkmSecret *login,
GBytes *data)
{
GkmCertificate *self = GKM_CERTIFICATE (base);
GNode *asn1 = NULL;
GkmDataResult res;
GBytes *keydata;
gcry_sexp_t sexp;
GkmSexp *wrapper;
if (g_bytes_get_size (data) == 0) {
gkm_debug ("cannot load empty certificate file");
return FALSE;
}
/* Parse the ASN1 data */
res = gkm_data_der_read_certificate (data, &asn1);
if (res != GKM_DATA_SUCCESS) {
gkm_debug ("couldn't parse certificate data");
return FALSE;
}
/* Generate a raw public key from our certificate */
keydata = egg_asn1x_encode (egg_asn1x_node (asn1, "tbsCertificate", "subjectPublicKeyInfo", NULL), NULL);
g_return_val_if_fail (keydata, FALSE);
/* Now create us a nice public key with that identifier */
res = gkm_data_der_read_public_key_info (keydata, &sexp);
g_bytes_unref (keydata);
switch (res) {
/* Create ourselves a public key with that */
case GKM_DATA_SUCCESS:
wrapper = gkm_sexp_new (sexp);
if (!self->pv->key)
self->pv->key = gkm_certificate_key_new (gkm_object_get_module (GKM_OBJECT (self)),
gkm_object_get_manager (GKM_OBJECT (self)),
self);
gkm_sexp_key_set_base (GKM_SEXP_KEY (self->pv->key), wrapper);
gkm_sexp_unref (wrapper);
break;
/* Unknown type of public key for this certificate, just ignore */
case GKM_DATA_UNRECOGNIZED:
if (self->pv->key)
g_object_unref (self->pv->key);
self->pv->key = NULL;
break;
/* Bad key, drop certificate */
case GKM_DATA_FAILURE:
case GKM_DATA_LOCKED:
g_warning ("couldn't parse certificate key data");
egg_asn1x_destroy (asn1);
return FALSE;
default:
g_assert_not_reached ();
break;
}
g_bytes_ref (data);
if (self->pv->der)
g_bytes_unref (self->pv->der);
self->pv->der = data;
egg_asn1x_destroy (self->pv->asn1);
self->pv->asn1 = asn1;
return TRUE;
}
static GBytes *
gkm_certificate_real_save (GkmSerializable *base,
GkmSecret *login)
{
GkmCertificate *self = GKM_CERTIFICATE (base);
g_return_val_if_fail (GKM_IS_CERTIFICATE (self), FALSE);
return g_bytes_ref (self->pv->der);
}
static void
gkm_certificate_serializable (GkmSerializableIface *iface)
{
iface->extension = ".cer";
iface->load = gkm_certificate_real_load;
iface->save = gkm_certificate_real_save;
}
/* -----------------------------------------------------------------------------
* PUBLIC
*/
gboolean
gkm_certificate_calc_category (GkmCertificate *self, GkmSession *session, CK_ULONG* category)
{
GBytes *extension;
GkmManager *manager;
GkmDataResult res;
gboolean is_ca;
GkmObject *object;
g_return_val_if_fail (GKM_IS_CERTIFICATE (self), CKR_GENERAL_ERROR);
g_return_val_if_fail (category, CKR_GENERAL_ERROR);
/* First see if we have a private key for this certificate */
manager = gkm_object_get_manager (GKM_OBJECT (self));
if (manager != NULL) {
object = gkm_manager_find_related (manager, session, CKO_PRIVATE_KEY, GKM_OBJECT (self));
if (object != NULL) {
*category = 1; /* token user */
return TRUE;
}
}
/* Read in the Basic Constraints section */
extension = gkm_certificate_get_extension (self, OID_BASIC_CONSTRAINTS, NULL);
if (extension != NULL) {
res = gkm_data_der_read_basic_constraints (extension, &is_ca, NULL);
if (res != GKM_DATA_SUCCESS)
return FALSE;
if (is_ca)
*category = 2; /* authority */
else
*category = 3; /* other entity */
} else {
*category = 0; /* unspecified */
}
return TRUE;
}
GkmCertificateKey*
gkm_certificate_get_public_key (GkmCertificate *self)
{
g_return_val_if_fail (GKM_IS_CERTIFICATE (self), NULL);
return self->pv->key;
}
GBytes *
gkm_certificate_get_extension (GkmCertificate *self, GQuark oid,
gboolean *critical)
{
guchar *val;
gsize n_val;
gint index;
g_return_val_if_fail (GKM_IS_CERTIFICATE (self), NULL);
g_return_val_if_fail (self->pv->asn1, NULL);
g_return_val_if_fail (oid, NULL);
index = find_certificate_extension (self, oid);
if (index <= 0)
return NULL;
/* Read the critical status */
if (critical) {
val = egg_asn1x_get_string_as_raw (egg_asn1x_node (self->pv->asn1, "tbsCertificate",
"extensions", index, "critical", NULL), NULL, &n_val);
/*
* We're pretty liberal in what we accept as critical. The goal
* here is not to accidentally mark as non-critical what some
* other x509 implementation meant to say critical.
*/
if (!val || n_val < 1 || g_ascii_toupper (val[0]) != 'T')
*critical = FALSE;
else
*critical = TRUE;
g_free (val);
}
/* And the extension value */
return egg_asn1x_get_string_as_bytes (egg_asn1x_node (self->pv->asn1, "tbsCertificate",
"extensions", index, "extnValue", NULL));
}
const gchar*
gkm_certificate_get_label (GkmCertificate *self)
{
gchar *label;
g_return_val_if_fail (GKM_IS_CERTIFICATE (self), "");
if (!self->pv->label) {
g_return_val_if_fail (self->pv->asn1, "");
/* Look for the CN in the certificate */
label = egg_dn_read_part (egg_asn1x_node (self->pv->asn1, "tbsCertificate", "subject", "rdnSequence", NULL), "cn");
/* Otherwise use the full DN */
if (!label)
label = egg_dn_read (egg_asn1x_node (self->pv->asn1, "tbsCertificate", "subject", "rdnSequence", NULL));
if (!label)
label = g_strdup (_("Unnamed Certificate"));
self->pv->label = label;
}
return self->pv->label;
}
void
gkm_certificate_set_label (GkmCertificate *self, const gchar *label)
{
g_return_if_fail (GKM_IS_CERTIFICATE (self));
g_free (self->pv->label);
self->pv->label = g_strdup (label);
g_object_notify (G_OBJECT (self), "label");
}
guchar*
gkm_certificate_hash (GkmCertificate *self, int hash_algo, gsize *n_hash)
{
guchar *hash;
g_return_val_if_fail (GKM_IS_CERTIFICATE (self), NULL);
g_return_val_if_fail (self->pv->der != NULL, NULL);
g_return_val_if_fail (n_hash, NULL);
*n_hash = gcry_md_get_algo_dlen (hash_algo);
g_return_val_if_fail (*n_hash > 0, NULL);
hash = g_malloc0 (*n_hash);
gcry_md_hash_buffer (hash_algo, hash, g_bytes_get_data (self->pv->der, NULL),
g_bytes_get_size (self->pv->der));
return hash;
}
gconstpointer
gkm_certificate_der_data (GkmCertificate *self, gsize *n_data)
{
g_return_val_if_fail (GKM_IS_CERTIFICATE (self), NULL);
g_return_val_if_fail (self->pv->der != NULL, NULL);
g_return_val_if_fail (n_data, NULL);
*n_data = g_bytes_get_size (self->pv->der);
return g_bytes_get_data (self->pv->der, NULL);
}
GkmFactory*
gkm_certificate_get_factory (void)
{
static CK_OBJECT_CLASS klass = CKO_CERTIFICATE;
static CK_CERTIFICATE_TYPE type = CKC_X_509;
static CK_ATTRIBUTE attributes[] = {
{ CKA_CLASS, &klass, sizeof (klass) },
{ CKA_CERTIFICATE_TYPE, &type, sizeof (type) },
};
static GkmFactory factory = {
attributes,
G_N_ELEMENTS (attributes),
factory_create_certificate
};
return &factory;
}