/* * 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-ssh-openssh.h" #include "gkm-ssh-private-key.h" #include "gkm/gkm-attributes.h" #include "gkm/gkm-credential.h" #define DEBUG_FLAG GKM_DEBUG_OBJECT #include "gkm/gkm-debug.h" #include "gkm/gkm-manager.h" #include "gkm/gkm-module.h" #include "gkm/gkm-object.h" #include "gkm/gkm-sexp.h" #include "gkm/gkm-util.h" #include "pkcs11/pkcs11i.h" #include enum { PROP_0, PROP_LABEL, PROP_PUBLIC_KEY }; struct _GkmSshPrivateKey { GkmPrivateXsaKey parent; GkmSshPublicKey *pubkey; GBytes *private_bytes; gchar *label; gboolean is_encrypted; }; G_DEFINE_TYPE (GkmSshPrivateKey, gkm_ssh_private_key, GKM_TYPE_PRIVATE_XSA_KEY); /* ----------------------------------------------------------------------------- * INTERNAL */ static CK_RV unlock_private_key (GkmSshPrivateKey *self, const gchar *password, gssize n_password, GkmSexp **result) { GkmDataResult res; gcry_sexp_t sexp; GkmSexp *wrapper; g_assert (GKM_IS_SSH_PRIVATE_KEY (self)); res = gkm_ssh_openssh_parse_private_key (self->private_bytes, password, n_password, &sexp); switch (res) { case GKM_DATA_LOCKED: self->is_encrypted = TRUE; return CKR_PIN_INCORRECT; case GKM_DATA_FAILURE: g_message ("couldn't parse private SSH key: %s", self->label); return CKR_GENERAL_ERROR; case GKM_DATA_UNRECOGNIZED: g_message ("invalid or unrecognized private SSH key: %s", self->label); return CKR_FUNCTION_FAILED; case GKM_DATA_SUCCESS: break; default: g_assert_not_reached(); } if (!password || !password[0]) self->is_encrypted = FALSE; wrapper = gkm_sexp_new (sexp); *result = wrapper; return CKR_OK; } static void realize_and_take_data (GkmSshPrivateKey *self, gcry_sexp_t sexp, gchar *comment, GBytes *private_data) { GkmSexp *wrapper; g_assert (GKM_IS_SSH_PRIVATE_KEY (self)); /* The base public key gets setup. */ wrapper = gkm_sexp_new (sexp); gkm_sexp_key_set_base (GKM_SEXP_KEY (self), wrapper); gkm_sexp_key_set_base (GKM_SEXP_KEY (self->pubkey), wrapper); gkm_sexp_unref (wrapper); /* Own the comment */ gkm_ssh_public_key_set_label (self->pubkey, comment); gkm_ssh_private_key_set_label (self, comment); g_free (comment); /* Own the data */ if (self->private_bytes) g_bytes_unref (self->private_bytes); self->private_bytes = private_data; /* Try to parse the private data, and note if it's not actually encrypted */ self->is_encrypted = TRUE; if (unlock_private_key (self, "", 0, &wrapper) == CKR_OK) { self->is_encrypted = FALSE; gkm_private_xsa_key_set_unlocked_private (GKM_PRIVATE_XSA_KEY (self), wrapper); gkm_sexp_unref (wrapper); } } /* ----------------------------------------------------------------------------- * OBJECT */ static CK_RV gkm_ssh_private_key_get_attribute (GkmObject *base, GkmSession *session, CK_ATTRIBUTE_PTR attr) { GkmSshPrivateKey *self = GKM_SSH_PRIVATE_KEY (base); gchar *digest; CK_RV rv; switch (attr->type) { case CKA_LABEL: return gkm_attribute_set_string (attr, self->label); /* COMPAT: Previous versions of gnome-keyring used this to save unlock passwords */ case CKA_GNOME_INTERNAL_SHA1: if (!self->private_bytes) { gkm_debug ("CKR_ATTRIBUTE_TYPE_INVALID: no CKA_GNOME_INTERNAL_SHA1 attribute"); return CKR_ATTRIBUTE_TYPE_INVALID; } digest = gkm_ssh_openssh_digest_private_key (self->private_bytes); rv = gkm_attribute_set_string (attr, digest); g_free (digest); return rv; } return GKM_OBJECT_CLASS (gkm_ssh_private_key_parent_class)->get_attribute (base, session, attr); } static CK_RV gkm_ssh_private_key_unlock (GkmObject *base, GkmCredential *cred) { GkmSshPrivateKey *self = GKM_SSH_PRIVATE_KEY (base); const gchar *password; GkmSexp *wrapper; gsize n_password; CK_RV rv; if (!self->is_encrypted) return CKR_OK; password = gkm_credential_get_password (cred, &n_password); rv = unlock_private_key (self, password, n_password, &wrapper); if (rv == CKR_OK) { gkm_private_xsa_key_set_locked_private (GKM_PRIVATE_XSA_KEY (self), cred, wrapper); gkm_sexp_unref (wrapper); } return rv; } static void gkm_ssh_private_key_expose (GkmObject *base, gboolean expose) { GKM_OBJECT_CLASS (gkm_ssh_private_key_parent_class)->expose_object (base, expose); gkm_object_expose (GKM_OBJECT (GKM_SSH_PRIVATE_KEY (base)->pubkey), expose); } static GObject* gkm_ssh_private_key_constructor (GType type, guint n_props, GObjectConstructParam *props) { GkmSshPrivateKey *self = GKM_SSH_PRIVATE_KEY (G_OBJECT_CLASS (gkm_ssh_private_key_parent_class)->constructor(type, n_props, props)); GkmObject *object; gchar *unique; g_return_val_if_fail (self, NULL); object = GKM_OBJECT (self); unique = g_strdup_printf ("%s.pub", gkm_object_get_unique (object)); self->pubkey = gkm_ssh_public_key_new (gkm_object_get_module (object), unique); g_free (unique); return G_OBJECT (self); } static void gkm_ssh_private_key_init (GkmSshPrivateKey *self) { } static void gkm_ssh_private_key_dispose (GObject *obj) { GkmSshPrivateKey *self = GKM_SSH_PRIVATE_KEY (obj); if (self->pubkey) g_object_unref (self->pubkey); self->pubkey = NULL; G_OBJECT_CLASS (gkm_ssh_private_key_parent_class)->dispose (obj); } static void gkm_ssh_private_key_finalize (GObject *obj) { GkmSshPrivateKey *self = GKM_SSH_PRIVATE_KEY (obj); g_assert (self->pubkey == NULL); if (self->private_bytes) g_bytes_unref (self->private_bytes); g_free (self->label); G_OBJECT_CLASS (gkm_ssh_private_key_parent_class)->finalize (obj); } static void gkm_ssh_private_key_set_property (GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { GkmSshPrivateKey *self = GKM_SSH_PRIVATE_KEY (obj); switch (prop_id) { case PROP_LABEL: gkm_ssh_private_key_set_label (self, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } static void gkm_ssh_private_key_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) { GkmSshPrivateKey *self = GKM_SSH_PRIVATE_KEY (obj); switch (prop_id) { case PROP_LABEL: g_value_set_string (value, gkm_ssh_private_key_get_label (self)); break; case PROP_PUBLIC_KEY: g_value_set_object (value, gkm_ssh_private_key_get_public_key (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } static void gkm_ssh_private_key_class_init (GkmSshPrivateKeyClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GkmObjectClass *gkm_class = GKM_OBJECT_CLASS (klass); gobject_class->constructor = gkm_ssh_private_key_constructor; gobject_class->dispose = gkm_ssh_private_key_dispose; gobject_class->finalize = gkm_ssh_private_key_finalize; gobject_class->set_property = gkm_ssh_private_key_set_property; gobject_class->get_property = gkm_ssh_private_key_get_property; gkm_class->get_attribute = gkm_ssh_private_key_get_attribute; gkm_class->unlock = gkm_ssh_private_key_unlock; gkm_class->expose_object = gkm_ssh_private_key_expose; g_object_class_install_property (gobject_class, PROP_LABEL, g_param_spec_string ("label", "Label", "Object Label", "", G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_PUBLIC_KEY, g_param_spec_object ("public-key", "Public Key", "Public key belonging to this private key", GKM_TYPE_SSH_PUBLIC_KEY, G_PARAM_READABLE)); } /* ----------------------------------------------------------------------------- * PUBLIC */ GkmSshPrivateKey* gkm_ssh_private_key_new (GkmModule *module, const gchar *unique) { return g_object_new (GKM_TYPE_SSH_PRIVATE_KEY, "unique", unique, "module", module, "manager", gkm_module_get_manager (module), NULL); } gboolean gkm_ssh_private_key_parse (GkmSshPrivateKey *self, const gchar *public_path, const gchar *private_path, GError **error) { guchar *public_data, *private_data; gsize n_public_data, n_private_data; GkmDataResult res; gcry_sexp_t sexp; gchar *comment; g_return_val_if_fail (GKM_IS_SSH_PRIVATE_KEY (self), FALSE); g_return_val_if_fail (private_path, FALSE); g_return_val_if_fail (!error || !*error, FALSE); /* Read in the public key */ if (!g_file_get_contents (public_path, (gchar**)&public_data, &n_public_data, error)) return FALSE; /* Parse it */ res = gkm_ssh_openssh_parse_public_key (public_data, n_public_data, &sexp, &comment); g_free (public_data); if (res == GKM_DATA_UNRECOGNIZED) { return FALSE; } else if (res != GKM_DATA_SUCCESS) { g_set_error_literal (error, GKM_DATA_ERROR, res, _("Couldn’t parse public SSH key")); return FALSE; } /* Read in the private key */ if (!g_file_get_contents (private_path, (gchar**)&private_data, &n_private_data, error)) { g_free (comment); gcry_sexp_release (sexp); return FALSE; } if (comment == NULL) comment = g_path_get_basename (private_path); realize_and_take_data (self, sexp, comment, g_bytes_new_take (private_data, n_private_data)); return TRUE; } const gchar* gkm_ssh_private_key_get_label (GkmSshPrivateKey *self) { g_return_val_if_fail (GKM_IS_SSH_PRIVATE_KEY (self), NULL); return self->label; } void gkm_ssh_private_key_set_label (GkmSshPrivateKey *self, const gchar *label) { g_return_if_fail (GKM_IS_SSH_PRIVATE_KEY (self)); g_free (self->label); self->label = g_strdup (label); g_object_notify (G_OBJECT (self), "label"); } GkmSshPublicKey* gkm_ssh_private_key_get_public_key (GkmSshPrivateKey *self) { g_return_val_if_fail (GKM_IS_SSH_PRIVATE_KEY (self), NULL); return self->pubkey; }