/* Licensed to Stichting The Commons Conservancy (TCC) under one or more * contributor license agreements. See the AUTHORS file distributed with * this work for additional information regarding copyright ownership. * TCC licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "crypto.h" #include "message.h" #include #define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write) #ifndef CK_CERTIFICATE_CATEGORY_TOKEN_USER #define CK_CERTIFICATE_CATEGORY_TOKEN_USER 0x00000001UL #endif gboolean crypto_instance_append_ca(SignTextInstance *instance, const gchar *ca, GError **gerror) { GBytes *bytes; unsigned char *rder; size_t rder_len; gpg_error_t err; #if 0 g_printerr("crypto_instance_append_ca: %s\n", ca); #endif err = ksba_dn_str2der(ca, &rder, &rder_len); if (err) { *gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_INVALID_CA, "Requested issuer '%s' cannot be parsed.", ca); return FALSE; } #if 0 g_printerr("crypto_instance_append_ca: len %d: ", (int)rder_len); for (size_t i = 0; i < rder_len; i++) g_printerr("%02x", rder[i]); g_printerr("\n"); char *str; ksba_dn_der2str(rder, rder_len, &str); g_printerr("crypto_instance_append_ca: der2str %s\n", str); #endif bytes = g_bytes_new_with_free_func(rder, rder_len, (GDestroyNotify)g_free, rder); instance->cas = g_list_append(instance->cas, bytes); return TRUE; } #if 0 /* * This needs to be ported to GTK4 */ static gboolean crypto_filter_match (GObject *obj, gpointer data) { SignTextInstance *instance = data; GckAttributes *attrs; GcrCertificateChain *chain; gulong category; GcrCertificateChainStatus status; g_assert(GCR_IS_PKCS11_CERTIFICATE(obj)); #if 0 g_printerr("crypto_filter_match\n"); #endif chain = g_object_get_data(G_OBJECT (obj), "chain"); g_assert(GCR_IS_CERTIFICATE_CHAIN(chain)); status = gcr_certificate_chain_get_status(chain); switch (status) { case GCR_CERTIFICATE_CHAIN_ANCHORED: break; default: return FALSE; } attrs = gcr_pkcs11_certificate_get_attributes(GCR_PKCS11_CERTIFICATE(obj)); /* * Some smartcards have no CKA_CERTIFICATE_CATEGORY, and so silently fail. Keep * the filter active when the CKA_CERTIFICATE_CATEGORY is present, but let the * certificate be visible if CKA_CERTIFICATE_CATEGORY is absent. */ if (gck_attributes_find_ulong(attrs, CKA_CERTIFICATE_CATEGORY, &category)) { if (category != CK_CERTIFICATE_CATEGORY_TOKEN_USER) { return FALSE; } } if (instance->cas && g_list_length(instance->cas)) { int i, len; len = gcr_certificate_chain_get_length(chain); for (i = 0; i < len; i++) { GcrCertificate *certificate; guchar *der; gsize der_len; GBytes *dn; certificate = gcr_certificate_chain_get_certificate(chain, i); #if 0 g_printerr("crypto_filter_match: consider cert issuer %s\n", gcr_certificate_get_issuer_dn(certificate)); #endif der = gcr_certificate_get_issuer_raw(certificate, &der_len); dn = g_bytes_new_with_free_func(der, der_len, (GDestroyNotify)g_free, der); #if 0 g_printerr("crypto_filter_match: len %d: ", (int)der_len); for (size_t i = 0; i < der_len; i++) g_printerr("%02x", der[i]); g_printerr("\n"); char *str; ksba_dn_der2str(der, der_len, &str); g_printerr("crypto_filter_match: der2str %s\n", str); #endif if (g_list_find_custom(instance->cas, dn, g_bytes_compare)) { g_bytes_unref(dn); return TRUE; } g_bytes_unref(dn); } return FALSE; } return TRUE; } GListStore * crypto_filter(SignTextInstance *instance, GListStore *certificates) { GListStore *collection; #if 0 collection = gcr_filter_collection_new_with_callback (GCR_COLLECTION (certificates), crypto_filter_match, instance, NULL); #endif return collection; } #endif static void crypto_slots_count (gpointer data, gpointer user_data) { GckSlot *slot = data; guint *slots_len = user_data; GckSlotInfo *info; info = gck_slot_get_info (slot); if ((info->flags & CKF_TOKEN_PRESENT)) { (*slots_len)++; } gck_slot_info_free(info); } static gboolean crypto_slots_do ( gpointer user_data ); static gboolean crypto_slot_do (gpointer user_data); static gboolean crypto_chain_do (gpointer user_data); static gint crypto_certificate_compare (gconstpointer a, gconstpointer b, gpointer user_data) { GcrCertificate *cert_a = GCR_CERTIFICATE ((gpointer)a); GcrCertificate *cert_b = GCR_CERTIFICATE ((gpointer)b); const char *c_a = gcr_certificate_get_subject_name (cert_a); const char *c_b = gcr_certificate_get_subject_name (cert_b); return g_strcmp0 (c_a, c_b); } static void crypto_chain_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { SignTextData *signtext = user_data; GcrCertificateChain *chain = GCR_CERTIFICATE_CHAIN(source_object); GcrCertificate *certificate; GError *gerror = NULL; GcrCertificateChainStatus status; #if 0 g_printerr("crypto_chain_done\n"); #endif if (!gcr_certificate_chain_build_finish(chain, res, &gerror)) { goto fatal; } signtext->chains_len++; certificate = gcr_certificate_chain_get_endpoint(chain); g_object_set_data_full(G_OBJECT(certificate), "chain", g_object_ref(chain), (GDestroyNotify)g_object_unref); status = gcr_certificate_chain_get_status(chain); switch (status) { case GCR_CERTIFICATE_CHAIN_ANCHORED: signtext->anchored_len++; break; default: break; } signtext->incoming_len++; if (signtext->incoming_len < g_list_model_get_n_items(G_LIST_MODEL(signtext->incoming))) { g_idle_add((GSourceFunc)crypto_chain_do, signtext); } else { guint i; i = 0; while ((certificate = g_list_model_get_item (G_LIST_MODEL(signtext->incoming), i))) { if (!g_list_store_find(signtext->certificates, certificate, NULL)) { g_object_ref(certificate); g_list_store_insert_sorted(G_LIST_STORE(signtext->certificates), certificate, crypto_certificate_compare, NULL); } i++; } i = 0; while ((certificate = g_list_model_get_item (G_LIST_MODEL(signtext->certificates), i))) { if (!g_list_store_find(signtext->incoming, certificate, NULL)) { g_list_store_remove(signtext->certificates, i); g_object_unref(certificate); } else { i++; } } g_object_unref(signtext->incoming); signtext->incoming = g_list_store_new(GCR_TYPE_CERTIFICATE); g_printerr("Smartcard Summary: We scanned %u modules, in which we found %u slots with tokens, containing %u certificates, of which %u certificates had emailProtection, and %u certificates were anchored correctly.\n", signtext->modules_len, signtext->slots_len, signtext->certificates_len, signtext->chains_len, signtext->anchored_len); g_timeout_add_seconds(2, crypto_slots_do, signtext); } if (gerror) { fatal: signtext_error (NULL, gerror); g_error_free (gerror); } } static gboolean crypto_chain_do (gpointer user_data) { SignTextData *signtext = user_data; GcrCertificateChain *chain = gcr_certificate_chain_new(); #if 0 g_printerr("crypto_chain_do\n"); #endif gcr_certificate_chain_add (chain, g_list_model_get_item(G_LIST_MODEL(signtext->incoming), signtext->incoming_len)); gcr_certificate_chain_build_async(chain, GCR_PURPOSE_EMAIL, NULL, GCR_CERTIFICATE_CHAIN_NONE, signtext->cancellable, crypto_chain_done, signtext); return FALSE; } static void crypto_slot_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { SignTextToken *signtext_token = user_data; SignTextData *signtext = signtext_token->signtext; GList *incoming, *l; GError *gerror = NULL; guint certificates_len; #if 0 g_printerr("crypto_slots_done\n"); #endif incoming = gck_enumerator_next_finish(GCK_ENUMERATOR(source_object), res, &gerror); for (l = incoming, certificates_len = 0; l; l = g_list_next (l), certificates_len++) { gboolean is_ca; /* * Is our certificate an intermediate? Filter them out at this stage. */ if (gcr_certificate_get_basic_constraints(GCR_CERTIFICATE(l->data), &is_ca, NULL)) { if (is_ca) { continue; } } if (!g_list_store_find(signtext->incoming, l->data, NULL)) { g_object_ref(l->data); g_object_set_data_full(l->data, "slot", g_object_ref(signtext_token->slot), (GDestroyNotify)g_object_unref); g_object_set_data_full(l->data, "token-info", gck_token_info_copy(signtext_token->token_info), (GDestroyNotify)gck_token_info_free); g_list_store_append(G_LIST_STORE(signtext->incoming), l->data); } } g_list_free_full (g_steal_pointer (&incoming), g_object_unref); signtext->certificates_len += certificates_len; if (signtext_token->next) { g_idle_add((GSourceFunc)crypto_slot_do, signtext_token->next); } else { g_idle_add((GSourceFunc)crypto_chain_do, signtext); } signtext_token_free(signtext_token); } static gboolean crypto_slot_do (gpointer user_data) { SignTextToken *signtext_token = user_data; /* * The minumum you must request to get a certificate back that will display in a dropdown * is CKA_VALUE, CKA_CLASS, CKA_CERTIFICATE_TYPE. Any less than that and you get nothing. * * CKA_CERTIFICATE_CATEGORY tells us if the certificate is a user certificate, or an * intermediate/root. */ static const gulong CERTIFICATE_ATTRS[] = {CKA_VALUE, CKA_ID, CKA_LABEL, CKA_CLASS, CKA_CERTIFICATE_TYPE, CKA_CERTIFICATE_CATEGORY, CKA_MODIFIABLE}; GckEnumerator *cenum; GckBuilder builder = GCK_BUILDER_INIT; /* * Search for all certificates on a token (ie not in a session). */ gck_builder_add_boolean (&builder, CKA_TOKEN, TRUE); gck_builder_add_ulong(&builder, CKA_CLASS, CKO_CERTIFICATE); signtext_token->token_info = gck_slot_get_token_info (signtext_token->slot); cenum = gck_slot_enumerate_objects(signtext_token->slot, gck_builder_end (&builder), GCK_SESSION_READ_ONLY); gck_enumerator_set_object_type_full(cenum, GCR_TYPE_PKCS11_CERTIFICATE, CERTIFICATE_ATTRS, G_N_ELEMENTS (CERTIFICATE_ATTRS)); gck_enumerator_next_async(cenum, -1, signtext_token->signtext->cancellable, crypto_slot_done, signtext_token); return FALSE; } static gboolean crypto_slots_do ( gpointer user_data ) { SignTextData *signtext = user_data; GList *slots, *l; guint slots_len = 0; slots = gck_modules_get_slots(signtext->modules, TRUE); g_list_foreach(slots, crypto_slots_count, &slots_len); if (slots_len != signtext->slots_len) { SignTextToken *signtext_token = NULL, *st; #if 0 g_printerr("crypto_slots_do: %d/%d\n", slots_len, signtext->slots_len); #endif for (l = slots; l; l = g_list_next (l)) { GckSlot *slot = GCK_SLOT(l->data); st = signtext_token_new(); st->signtext = signtext; st->slot = slot; st->next = signtext_token; signtext_token = st; } g_idle_add((GSourceFunc)crypto_slot_do, signtext_token); signtext->slots_len = slots_len; signtext->certificates_len = 0; signtext->incoming_len = 0; signtext->chains_len = 0; signtext->anchored_len = 0; return FALSE; } else { g_list_free_full (g_steal_pointer (&slots), g_object_unref); } g_timeout_add_seconds(2, crypto_slots_do, signtext); return FALSE; } static void crypto_slots_start(SignTextData *signtext) { crypto_slots_do(signtext); } static void crypto_init_done (GObject *source_object, GAsyncResult *res, gpointer user_data) { SignTextData *signtext = user_data; GError *gerror = NULL; guint modules_len; #if 0 g_printerr("crypto_init_done\n"); #endif signtext->modules = gck_modules_initialize_registered_finish(res, &gerror); modules_len = g_list_length(signtext->modules); if (!modules_len) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_NO_MODULES, "No PKCS11 modules are available on this system. Text cannot be signed."); goto fatal; } else { /* * Start watching the slots for changes. */ crypto_slots_start(signtext); } if (gerror) { fatal: signtext_error (NULL, gerror); g_error_free (gerror); } signtext->modules_len = modules_len; } void crypto_start(SignTextData *signtext) { #if 0 g_printerr("crypto_start\n"); #endif #if 0 volatile int done = 0; while (!done) sleep(1); /* inside gdb: set var done = 1 */ #endif gck_modules_initialize_registered_async(signtext->cancellable, (GAsyncReadyCallback)crypto_init_done, signtext); } void crypto_selector_changed(AdwComboRow *selector, GParamSpec *pspec, gpointer user_data) { GObject *selected; SignTextInstance *instance = user_data; #if 0 g_printerr("crypto_selector_changed\n"); #endif selected = adw_combo_row_get_selected_item(selector); if (selected && GCR_IS_PKCS11_CERTIFICATE(selected)) { GcrPkcs11Certificate *certificate = GCR_PKCS11_CERTIFICATE(selected); GckSlot *slot = g_object_get_data(G_OBJECT (certificate), "slot"); GckTokenInfo *token_info = gck_slot_get_token_info (slot); if (token_info) { instance->can_sign = FALSE; instance->can_pin = FALSE; #if HAVE_ADW_ENTRY_ROW_SET_MAX_LENGTH adw_entry_row_set_max_length(ADW_ENTRY_ROW(instance->pinrow), token_info->max_pin_len); #endif if (token_info->flags & CKF_PROTECTED_AUTHENTICATION_PATH) { gtk_image_set_from_icon_name(GTK_IMAGE(instance->pinicon), "accessories-calculator-symbolic"); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(instance->pinrow), "Use pinpad to enter PIN"); gtk_editable_set_text(GTK_EDITABLE(instance->pinrow), ""); instance->can_sign = TRUE; instance->can_pin = FALSE; } else { gtk_image_set_from_icon_name(GTK_IMAGE(instance->pinicon), "dialog-password-symbolic"); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(instance->pinrow), "Enter your PIN"); gtk_editable_set_text(GTK_EDITABLE(instance->pinrow), ""); instance->can_sign = token_info->min_pin_len ? FALSE : TRUE; instance->can_pin = TRUE; } if (!(token_info->flags & CKF_USER_PIN_INITIALIZED)) { gtk_image_set_from_icon_name(GTK_IMAGE(instance->pinicon), "dialog-error-symbolic"); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(instance->pinrow), "PIN is not initialised"); instance->can_sign = FALSE; instance->can_pin = FALSE; } else if (token_info->flags & CKF_USER_PIN_LOCKED) { gtk_image_set_from_icon_name(GTK_IMAGE(instance->pinicon), "dialog-error-symbolic"); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(instance->pinrow), "PIN is locked"); instance->can_sign = FALSE; instance->can_pin = FALSE; } else if (token_info->flags & CKF_USER_PIN_TO_BE_CHANGED) { gtk_image_set_from_icon_name(GTK_IMAGE(instance->pinicon), "dialog-unavailable-symbolic"); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(instance->pinrow), "PIN must be changed before use"); instance->can_sign = FALSE; instance->can_pin = FALSE; } else if (token_info->flags & CKF_USER_PIN_FINAL_TRY) { gtk_image_set_from_icon_name(GTK_IMAGE(instance->pinicon), "dialog-warning-symbolic"); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(instance->pinrow), "Final try on PIN"); } else if (token_info->flags & CKF_USER_PIN_COUNT_LOW) { gtk_image_set_from_icon_name(GTK_IMAGE(instance->pinicon), "dialog-warning-symbolic"); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(instance->pinrow), "PIN retries are low"); } #if 0 else { g_printerr("crypto_selector_changed: %d\n", (int)token_info->flags); } #endif gtk_widget_set_sensitive(GTK_WIDGET(instance->signtext->sign), instance->can_sign); gtk_widget_set_sensitive(GTK_WIDGET(instance->pinrow), instance->can_pin); } } } void crypto_pin_changed(GtkEntry *pin, gpointer user_data) { GcrPkcs11Certificate *certificate; SignTextInstance *instance = user_data; #if 0 g_printerr("crypto_pin_changed\n"); #endif certificate = GCR_PKCS11_CERTIFICATE(adw_combo_row_get_selected_item(instance->selectorrow)); if (certificate) { GckSlot *slot = g_object_get_data(G_OBJECT (certificate), "slot"); GckTokenInfo *token_info = gck_slot_get_token_info (slot); if (token_info) { instance->can_sign = FALSE; if (adw_entry_row_get_text_length(ADW_ENTRY_ROW(instance->pinrow)) >= token_info->min_pin_len) { instance->can_sign = TRUE; } gtk_widget_set_sensitive(GTK_WIDGET(instance->signtext->sign), instance->can_sign); } } } void crypto_instance_start(SignTextInstance *instance, GListStore *certificates) { #if 0 g_printerr("crypto_instance_start\n"); #endif g_signal_connect(instance->selectorrow, "notify::selected", (GCallback)crypto_selector_changed, instance); g_signal_connect(instance->pinrow, "changed", (GCallback)crypto_pin_changed, instance); adw_combo_row_set_model(instance->selectorrow, G_LIST_MODEL(certificates)); } static void crypto_sign_loggedout (GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *gerror = NULL; SignTextInstance *instance = user_data; #if 0 g_printerr("crypto_sign_loggedout\n"); #endif if (gck_session_logout_finish(instance->session, res, &gerror)) { gtk_widget_set_visible(GTK_WIDGET(instance->progress), FALSE); } if (gerror) { signtext_error (instance, gerror); g_error_free (gerror); } /* * The instance is done at this point, clean it up. */ signtext_instance_free(instance); } static void crypto_sign_logout(SignTextInstance *instance) { #if 0 g_printerr("crypto_sign_logout\n"); #endif gck_session_logout_async (instance->session, instance->signtext->cancellable, crypto_sign_loggedout, instance); } /* * */ static gboolean crypto_sign_pulse (gpointer user_data) { GtkProgressBar *progress = user_data; if (gtk_widget_is_visible(GTK_WIDGET(progress))) { gtk_progress_bar_pulse (progress); return TRUE; } g_object_unref(progress); return FALSE; } /* * Handle the writing of the CMS structure. */ static int pem_writer_cb (void *cb_value, const void *buffer, size_t count) { SignTextInstance *instance = cb_value; gsize len; #if 0 g_printerr("pem_writer_cb: %d\n", (int)count); #endif len = instance->payload_len + count; instance->payload = g_realloc(instance->payload, len); memcpy(instance->payload + instance->payload_len, buffer, count); instance->payload_len = len; return 0; } /* * We're finished signing the CMS structure. * * Are we good to leave? */ gboolean crypto_sign_done (SignTextInstance *instance) { GError *gerror = NULL; #if 0 g_printerr("crypto_sign_done\n"); #endif crypto_sign_logout(instance); if (gerror) { signtext_error (instance, gerror); g_error_free (gerror); } return FALSE; } static void crypto_sign_send_payload(SignTextInstance *instance, gboolean done) { gchar *out = g_malloc( ((instance->payload_len / 3 + 1) * 4 + 4) + (((instance->payload_len / 3 + 1) * 4 + 4) / 76 + 1) + (done ? 5 : 0) + 1 ); gsize c1, c2; #if 0 g_printerr("crypto_sign_send_payload\n"); #endif instance->done = done; if (instance->payload) { c1 = g_base64_encode_step(instance->payload, instance->payload_len, TRUE, out, &instance->state, &instance->save); } else { c1 = 0; } if (done) { c2 = g_base64_encode_close(TRUE, out + c1, &instance->state, &instance->save); out[c1 + c2] = 0; } else { out[c1] = 0; } message_send_string_response(instance, out); /* out is freed by the message */ if (instance->payload) { instance->payload_len = 0; instance->payload = g_realloc(instance->payload, instance->payload_len); } } /* * Generate and sign the CMS structure in an idle loop. * * When done, jump to the completion step to send the message. */ static gboolean crypto_sign_do (gpointer user_data) { SignTextInstance *instance = user_data; GError *gerror = NULL; gpg_error_t err; int rc; #if 0 g_printerr("crypto_sign_do\n"); #endif /* Main building loop. */ err = ksba_cms_build (instance->cms, &instance->stopreason); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_build failed: %s", gpg_strerror (err)); goto fatal; } if (instance->stopreason == KSBA_SR_BEGIN_DATA) { #if 0 g_printerr("crypto_sign_do: begin data\n"); #endif } else if (instance->stopreason == KSBA_SR_NEED_SIG) { GckSlot *slot; GArray *mechanisms; gcry_md_hd_t md; unsigned char *sigval; size_t sigval_len; gcry_sexp_t sexp; unsigned char *digest; size_t digest_len; unsigned char *alg; size_t alg_len; unsigned char *sig; gsize sig_len; unsigned char *data; gsize data_len; #if 0 g_printerr("crypto_sign_do: need sig\n"); #endif slot = gck_session_get_slot(instance->session); mechanisms = gck_slot_get_mechanisms(slot); #if 0 //int i; //for (i = 0; i < gck_mechanisms_length(mechanisms); i++) { //g_printerr("crypto_sign_do: mechs: 0x%lxUL (%s)\n", gck_mechanisms_at(mechanisms, i), gcry_pk_algo_name(gck_mechanisms_at(mechanisms, i))); //} #endif if (!gck_mechanisms_check(mechanisms, CKM_RSA_PKCS, GCK_INVALID)) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "CKM_RSA_PKCS not supported by this card."); goto fatal; } rc = gcry_md_open (&md, 0, 0); if (rc) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "gcry_md_open failed: %s", gpg_strerror (rc)); goto fatal; } ksba_cms_set_hash_function (instance->cms, HASH_FNC, md); gcry_md_enable (md, instance->hash_alg); err = ksba_cms_hash_signed_attrs (instance->cms, 0); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_hash_signed_attrs failed: %s", gpg_strerror (err)); gcry_md_close (md); goto fatal; } digest = gcry_md_read (md, instance->hash_alg); digest_len = gcry_md_get_algo_dlen (instance->hash_alg); if (!digest || !digest_len) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "gcry_md_read / gcry_md_get_algo_dlen failed"); gcry_md_close (md); goto fatal; } if (gcry_md_algo_info(instance->hash_alg, GCRYCTL_GET_ASNOID, NULL, &alg_len)) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "gcry_md_algo_info failed."); gcry_md_close (md); goto fatal; } alg = g_malloc(alg_len); gcry_md_algo_info(instance->hash_alg, GCRYCTL_GET_ASNOID, alg, &alg_len); /* * Our signature is a DER of the digest algorithm, followed by the digest. */ data_len = alg_len + digest_len; data = g_malloc(data_len); memcpy(data, alg, alg_len); memcpy(data + alg_len, digest, digest_len); g_free(alg); gcry_md_close (md); /* * Sign on the line. */ sig = gck_session_sign(instance->session, instance->key, CKM_RSA_PKCS, data, data_len, &sig_len, instance->signtext->cancellable, &gerror); g_free(data); if (gerror) { goto fatal; } rc = gcry_sexp_build(&sexp, NULL, "(sig-val(rsa(s %b)))", sig_len, (char *)sig); g_free(sig); if (rc) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "gcry_sexp_build failed: %s", gcry_strerror (rc)); goto fatal; } sigval_len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0); sigval = g_malloc(sigval_len); gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, sigval, sigval_len); gcry_sexp_release(sexp); err = ksba_cms_set_sig_val (instance->cms, 0, sigval); g_free(sigval); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_set_sig_val failed: %s", gpg_strerror (err)); goto fatal; } } if (instance->stopreason == KSBA_SR_READY) { crypto_sign_send_payload(instance, TRUE); } else { if (instance->payload_len > 1000000) { crypto_sign_send_payload(instance, FALSE); } else { crypto_sign_continue(instance); } } if (gerror) { fatal: signtext_error (instance, gerror); g_error_free (gerror); } return FALSE; } /* * Continue the signing and data transfer process. * * If we're busy, we line up the next crypto_sign_do. * * If we're no longer busy, send the final eof and line up * the logout once we're done. */ void crypto_sign_continue (SignTextInstance *instance) { if (instance->stopreason == KSBA_SR_READY) { /* send eof, we're done */ message_send_boolean_response(instance, FALSE); g_idle_add((GSourceFunc)crypto_sign_done, instance); } else { g_idle_add((GSourceFunc)crypto_sign_do, instance); } } /* * Handle the results of the search for the private key. * * If we found the private key, get ready to trigger the sign. */ static void crypto_found_key (GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *gerror = NULL; SignTextInstance *instance = user_data; GList *keys; keys = gck_session_find_objects_finish(instance->session, res, &gerror); if (!keys) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "Private key was not found for this certificate."); goto fatal; } else if (g_list_length(keys) > 1) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "Multiple private keys were found for this certificate."); goto fatal; } else { instance->key = GCK_OBJECT(keys->data); /* next up, the signing loop */ crypto_sign_continue(instance); } if (gerror) { fatal: signtext_error (instance, gerror); g_error_free (gerror); } } /* * Handle the results of the attempt to login. * * If successful, start a search for our certificate's private key. */ static void crypto_find_key (GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *gerror = NULL; SignTextInstance *instance = user_data; if (gck_session_login_finish(instance->session, res, &gerror)) { GckAttributes *attrs; const GckAttribute *id; GckBuilder builder = GCK_BUILDER_INIT; attrs = gcr_pkcs11_certificate_get_attributes(GCR_PKCS11_CERTIFICATE(instance->certificate)); id = gck_attributes_find(attrs, CKA_ID); if (!id || !id->length) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "Certificate does not have an ID, cannot find the corresponding key."); goto fatal; } /* * Search for key on a token belonging to the certificate (ie not in a session). */ gck_builder_add_boolean (&builder, CKA_TOKEN, TRUE); gck_builder_add_ulong(&builder, CKA_CLASS, CKO_PRIVATE_KEY); gck_builder_add_data(&builder, CKA_ID, id->value, id->length); gck_session_find_objects_async(instance->session, gck_builder_end (&builder), instance->signtext->cancellable, crypto_found_key, instance); } if (gerror) { fatal: signtext_error (instance, gerror); g_error_free (gerror); } crypto_selector_changed(instance->selectorrow, NULL, instance); } /* * We have a newly opened session, try to log into the session. * * We either pass the PIN we captured, or we wait for the pinpad to have the PIN entered. */ static void crypto_login_session (GObject *source_object, GAsyncResult *res, gpointer user_data) { GError *gerror = NULL; SignTextInstance *instance = user_data; instance->session = gck_session_open_finish(res, &gerror); if (instance->session) { GckSlot *slot = gck_session_get_slot (instance->session); GckTokenInfo *token_info = gck_slot_get_token_info (slot); if (token_info->flags & CKF_PROTECTED_AUTHENTICATION_PATH) { gck_session_login_async(instance->session, CKU_USER, NULL, 0, instance->signtext->cancellable, crypto_find_key, instance); } else { const guchar *pin = (guchar *)gtk_editable_get_text(GTK_EDITABLE(instance->pinrow)); gsize n_pin = adw_entry_row_get_text_length(ADW_ENTRY_ROW(instance->pinrow)); gck_session_login_async(instance->session, CKU_USER, pin, n_pin, instance->signtext->cancellable, crypto_find_key, instance); } } if (gerror) { signtext_error (instance, gerror); g_error_free (gerror); } if (instance->certificate) { g_object_unref(instance->certificate); } } /* * Start the opening of the session. * * When the session has finished being opened, we jump to the session login above. */ static void crypto_open_session (SignTextInstance *instance) { GckSlot *slot = g_object_get_data(G_OBJECT (instance->certificate), "slot"); gck_session_open_async(slot, GCK_SESSION_READ_ONLY, NULL, instance->signtext->cancellable, crypto_login_session, instance); } /* * Receive the intermediate certs we found, and add them to the CMS object. * * Keep firing off requests for intermediates until we reach the root or we can't * find any more certs. * * Jump to the open session step above. */ static void crypto_sign_lookup (GObject *source_object, GAsyncResult *res, gpointer user_data) { SignTextInstance *instance = user_data; GcrPkcs11Certificate *certificate; GError *gerror = NULL; gpg_error_t err; #if 0 g_printerr("crypto_sign_lookup\n"); #endif certificate = GCR_PKCS11_CERTIFICATE(gcr_pkcs11_certificate_lookup_issuer_finish(res, &gerror)); /* * Certificate exists and is not self signed? Add the additional cert. */ if (certificate && !gcr_certificate_is_issuer(GCR_CERTIFICATE(certificate), GCR_CERTIFICATE(certificate))) { ksba_cert_t cert; const guint8 *der; gsize der_len; der = gcr_certificate_get_der_data(GCR_CERTIFICATE(certificate), &der_len); if (!der) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "Certificate could not be decoded"); goto fatal; } err = ksba_cert_new(&cert); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cert_new failed: %s", gpg_strerror (err)); goto fatal; } err = ksba_cert_init_from_mem(cert, der, der_len); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cert_init_from_mem failed: %s", gpg_strerror (err)); goto fatal; } err = ksba_cms_add_cert(instance->cms, cert); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_add_cert failed: %s", gpg_strerror (err)); goto fatal; } // ksba_cert_release (cert); gcr_pkcs11_certificate_lookup_issuer_async(GCR_CERTIFICATE(certificate), instance->signtext->cancellable, crypto_sign_lookup, instance); } else { GTimeZone *utc; GDateTime *now; ksba_isotime_t signed_time; err = ksba_writer_new (&instance->w); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_writer_new failed: %s", gpg_strerror (err)); goto fatal; } err = ksba_writer_set_cb (instance->w, pem_writer_cb, instance); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_writer_set_cb failed: %s", gpg_strerror (err)); goto fatal; } err = ksba_cms_set_reader_writer (instance->cms, NULL, instance->w); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_set_reader_writer failed: %s", gpg_strerror (err)); goto fatal; } err = ksba_cms_add_digest_algo (instance->cms, instance->hash_oid); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_add_digest_algo '%s' failed: %s", instance->hash_oid, gpg_strerror (err)); goto fatal; } err = gcry_md_open (&instance->data_md, 0, 0); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "gcry_md_open failed: %s", gpg_strerror (err)); goto fatal; } gcry_md_enable (instance->data_md, instance->hash_alg); if (instance->detached) { GtkTextIter start_iter, end_iter; char *text; unsigned char *digest; size_t digest_len; /* * Hash the content of the buffer in one go. */ gtk_text_buffer_get_start_iter( instance->buffer, &start_iter); gtk_text_buffer_get_end_iter( instance->buffer, &end_iter); text = gtk_text_buffer_get_text( instance->buffer, &start_iter, &end_iter, FALSE); gcry_md_write (instance->data_md, text, strlen(text)); g_free(text); digest = gcry_md_read (instance->data_md, instance->hash_alg); digest_len = gcry_md_get_algo_dlen (instance->hash_alg); if (!digest || !digest_len) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "gcry_md_read / gcry_md_get_algo_dlen failed"); goto fatal; } err = ksba_cms_set_message_digest (instance->cms, 0, digest, digest_len); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_set_message_digest failed: %s", gpg_strerror (err)); goto fatal; } } utc = g_time_zone_new_utc(); now = g_date_time_new_now(utc); snprintf (signed_time, 16, "%04d%02d%02dT%02d%02d%02d", g_date_time_get_year(now), g_date_time_get_month(now), g_date_time_get_day_of_month(now), g_date_time_get_hour(now), g_date_time_get_minute(now), g_date_time_get_second(now)); g_date_time_unref(now); g_time_zone_unref(utc); err = ksba_cms_set_signing_time (instance->cms, 0, signed_time); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_set_signing_time failed: %s", gpg_strerror (err)); goto fatal; } crypto_open_session (instance); } if (gerror) { fatal: signtext_error (instance, gerror); g_error_free (gerror); } if (certificate) { g_object_unref(certificate); } } /* * It's the main event! Start the signing process. * * Set up the CMS context. * * Identify the certificate that was selected, and set off the process to find the * issuer of that cert. */ void crypto_sign(SignTextData *signtext) { GError *gerror = NULL; const guint8 *der; gsize der_len; gpg_error_t err; const char *uuid = adw_view_stack_get_visible_child_name(signtext->stack); SignTextInstance *instance = g_object_get_data(G_OBJECT (signtext->stack), uuid); #if 0 g_printerr("crypto_sign\n"); #endif if (!instance) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "No views to sign"); goto fatal; } instance->certificate = GCR_PKCS11_CERTIFICATE(g_object_ref(adw_combo_row_get_selected_item(instance->selectorrow))); if (!instance->certificate) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "No certificate selected"); goto fatal; } der = gcr_certificate_get_der_data(GCR_CERTIFICATE(instance->certificate), &der_len); if (!der) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "Certificate could not be decoded"); goto fatal; } err = ksba_cms_new (&instance->cms); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_new failed: %s", gpg_strerror (err)); goto fatal; } err = ksba_cms_set_content_type (instance->cms, 0, KSBA_CT_SIGNED_DATA); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_set_content_type failed: %s", gpg_strerror (err)); goto fatal; } err = ksba_cms_set_content_type (instance->cms, 1, KSBA_CT_DATA); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_set_content_type failed: %s", gpg_strerror (err)); goto fatal; } err = ksba_cert_new(&instance->signer); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cert_new failed: %s", gpg_strerror (err)); goto fatal; } err = ksba_cert_init_from_mem(instance->signer, der, der_len); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cert_init_from_mem failed: %s", gpg_strerror (err)); goto fatal; } /* * By default we extract the digest from the certificate. * * The cert keeps track of the digest and the type of key in the same oid, * re-map so we just have the digest. */ instance->hash_oid = ksba_cert_get_digest_algo (instance->signer); instance->hash_alg = instance->hash_oid ? gcry_md_map_name (instance->hash_oid) : 0; switch (instance->hash_alg) { case GCRY_MD_SHA1: instance->hash_oid = "1.3.14.3.2.26"; break; case GCRY_MD_RMD160: instance->hash_oid = "1.3.36.3.2.1"; break; case GCRY_MD_SHA224: instance->hash_oid = "2.16.840.1.101.3.4.2.4"; break; case GCRY_MD_SHA256: instance->hash_oid = "2.16.840.1.101.3.4.2.1"; break; case GCRY_MD_SHA384: instance->hash_oid = "2.16.840.1.101.3.4.2.2"; break; case GCRY_MD_SHA512: instance->hash_oid = "2.16.840.1.101.3.4.2.3"; break; case GCRY_MD_MD5: /* Don't use MD5. */ case 0: /* No mapping existed for the cert. */ default: /* We don't have oids for these. */ /* if we don't recognise the algorithm, fall back to SHA256 */ instance->hash_oid = "2.16.840.1.101.3.4.2.1"; instance->hash_alg = GCRY_MD_SHA256; break; } #if 0 g_printerr("crypto_sign: %s %d\n", instance->hash_oid, instance->hash_alg); #endif err = ksba_cms_add_signer (instance->cms, instance->signer); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_add_signer failed: %s", gpg_strerror (err)); goto fatal; } err = ksba_cms_add_cert (instance->cms, instance->signer); if (err) { gerror = g_error_new(RST_CORE_ERROR, RST_CORE_ERROR_SIGN_ERROR, "ksba_cms_add_cert failed: %s", gpg_strerror (err)); goto fatal; } //ksba_cert_release (instance->signer); gcr_pkcs11_certificate_lookup_issuer_async(GCR_CERTIFICATE(instance->certificate), instance->signtext->cancellable, crypto_sign_lookup, instance); g_object_ref(instance->progress); g_timeout_add(250, crypto_sign_pulse, instance->progress); gtk_widget_set_visible(GTK_WIDGET(instance->progress), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(instance->selectorrow), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(instance->pinrow), FALSE); gtk_widget_set_sensitive(GTK_WIDGET(instance->signtext->sign), FALSE); if (gerror) { fatal: signtext_error (instance, gerror); g_error_free (gerror); } }