static gboolean checksum_start(int argc, char **argv) { GError *error = NULL; gboolean sign = FALSE; g_message("checksum start"); if (r_context()->certpath != NULL && r_context()->keypath != NULL) { sign = TRUE; } else if (r_context()->certpath != NULL || r_context()->keypath != NULL) { g_warning("Either both or none of cert and key files must be provided"); r_exit_status = 1; goto out; } if (argc != 3) { g_warning("A directory name must be provided"); r_exit_status = 1; goto out; } g_message("updating checksums for: %s", argv[2]); if (!update_manifest(argv[2], sign, &error)) { g_warning("Failed to update manifest: %s", error->message); g_clear_error(&error); r_exit_status = 1; } out: return TRUE; }
static gboolean grub_env_set(GPtrArray *pairs) { GSubprocess *sub; GError *error = NULL; gboolean res = FALSE; g_assert_cmpuint(pairs->len, >, 0); g_assert_nonnull(r_context()->config->grubenv_path); g_ptr_array_insert(pairs, 0, g_strdup("grub-editenv")); g_ptr_array_insert(pairs, 1, g_strdup(r_context()->config->grubenv_path)); g_ptr_array_insert(pairs, 2, g_strdup("set")); g_ptr_array_add(pairs, NULL); sub = g_subprocess_newv((const gchar * const *)pairs->pdata, G_SUBPROCESS_FLAGS_NONE, &error); if (!sub) { g_warning("starting grub-editenv failed: %s", error->message); g_clear_error(&error); goto out; } res = g_subprocess_wait_check(sub, NULL, &error); if (!res) { g_warning("grub-editenv failed: %s", error->message); g_clear_error(&error); goto out; } out: g_ptr_array_remove_index(pairs, pairs->len-1); g_ptr_array_remove_index(pairs, 2); g_ptr_array_remove_index(pairs, 1); g_ptr_array_remove_index(pairs, 0); return res; }
static gboolean save_slot_status_globally(GError **error) { g_autoptr(GKeyFile) key_file = g_key_file_new(); GError *ierror = NULL; GHashTableIter iter; RaucSlot *slot; gboolean res; g_return_val_if_fail(error == NULL || *error == NULL, FALSE); g_return_val_if_fail(r_context()->config->statusfile_path, FALSE); g_debug("Saving global slot status"); /* Save all slot status information */ g_hash_table_iter_init(&iter, r_context()->config->slots); while (g_hash_table_iter_next(&iter, NULL, (gpointer*) &slot)) { g_autofree gchar *group; if (!slot->status) { continue; } group = g_strdup_printf(RAUC_SLOT_PREFIX ".%s", slot->name); status_file_set_slot_status(key_file, group, slot->status); } res = g_key_file_save_to_file(key_file, r_context()->config->statusfile_path, &ierror); if (!res) g_propagate_error(error, ierror); return res; }
gboolean r_boot_set_primary(RaucSlot *slot) { if (g_strcmp0(r_context()->config->system_bootloader, "barebox") == 0) { return barebox_set_primary(slot); } else if (g_strcmp0(r_context()->config->system_bootloader, "grub") == 0) { return grub_set_primary(slot); } g_print("Warning: Your bootloader '%s' is not supported yet\n", r_context()->config->system_bootloader); return TRUE; }
int main(int argc, char *argv[]) { setlocale(LC_ALL, "C"); r_context_conf()->configpath = g_strdup("test/test.conf"); r_context_conf()->certpath = g_strdup("test/openssl-ca/rel/release-1.cert.pem"); r_context_conf()->keypath = g_strdup("test/openssl-ca/rel/private/release-1.pem"); r_context(); g_assert(test_prepare_dummy_file("test/", "random.dat", 256 * 1024, "/dev/urandom") == 0); g_assert(test_prepare_dummy_file("test/", "empty.dat", 0, "/dev/zero") == 0); g_test_init(&argc, &argv, NULL); g_test_add_func("/signature/sign", signature_sign); g_test_add_func("/signature/sign_file", signature_sign_file); g_test_add_func("/signature/verify", signature_verify); g_test_add_func("/signature/verify_file", signature_verify_file); g_test_add_func("/signature/loopback", signature_loopback); return g_test_run(); }
static void signature_loopback(void) { GBytes *content = read_file("test/openssl-ca/manifest", NULL); GBytes *sig = NULL; g_assert_nonnull(content); sig = cms_sign(content, r_context()->certpath, r_context()->keypath, NULL); g_assert_nonnull(sig); g_assert_true(cms_verify(content, sig, NULL)); ((char *)g_bytes_get_data(content, NULL))[0] = 0x00; g_assert_false(cms_verify(content, sig, NULL)); g_bytes_unref(content); g_bytes_unref(sig); }
gboolean save_slot_status(RaucSlot *dest_slot, GError **error) { g_return_val_if_fail(dest_slot, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); if (r_context()->config->statusfile_path) return save_slot_status_globally(error); else return save_slot_status_locally(dest_slot, error); }
static void load_slot_status_globally(void) { GError *ierror = NULL; GHashTable *slots = r_context()->config->slots; g_autoptr(GKeyFile) key_file = g_key_file_new(); g_auto(GStrv) groups = NULL; gchar **group, *slotname; GHashTableIter iter; RaucSlot *slot; g_return_if_fail(r_context()->config->statusfile_path); g_key_file_load_from_file(key_file, r_context()->config->statusfile_path, G_KEY_FILE_NONE, &ierror); if (ierror && !g_error_matches(ierror, G_FILE_ERROR, G_FILE_ERROR_NOENT)) g_message("load_slot_status_globally: %s.", ierror->message); g_clear_error(&ierror); /* Load all slot states included in the statusfile */ groups = g_key_file_get_groups(key_file, NULL); for (group = groups; *group != NULL; group++) { if (!g_str_has_prefix(*group, RAUC_SLOT_PREFIX ".")) continue; slotname = *group + strlen(RAUC_SLOT_PREFIX "."); slot = g_hash_table_lookup(slots, slotname); if (!slot || slot->status) continue; slot->status = g_new0(RaucSlotStatus, 1); g_debug("Load status for slot %s.", slot->name); status_file_get_slot_status(key_file, *group, slot->status); } /* Set all other slots to the default status */ g_hash_table_iter_init(&iter, slots); while (g_hash_table_iter_next(&iter, NULL, (gpointer*) &slot)) { if (slot->status) continue; g_debug("Set default status for slot %s.", slot->name); slot->status = g_new0(RaucSlotStatus, 1); } }
static gboolean bundle_start(int argc, char **argv) { GError *ierror = NULL; g_debug("bundle start"); if (r_context()->certpath == NULL || r_context()->keypath == NULL) { g_warning("cert and key files must be provided"); r_exit_status = 1; goto out; } if (argc < 3) { g_warning("an input directory name must be provided"); r_exit_status = 1; goto out; } if (argc != 4) { g_warning("an output bundle name must be provided"); r_exit_status = 1; goto out; } g_print("input directory: %s\n", argv[2]); g_print("output bundle: %s\n", argv[3]); if (!update_manifest(argv[2], FALSE, &ierror)) { g_warning("failed to update manifest: %s", ierror->message); r_exit_status = 1; goto out; } if (!create_bundle(argv[3], argv[2], &ierror)) { g_warning("failed to create bundle: %s", ierror->message); r_exit_status = 1; goto out; } out: return TRUE; }
void load_slot_status(RaucSlot *dest_slot) { g_return_if_fail(dest_slot); if (dest_slot->status) return; if (r_context()->config->statusfile_path) load_slot_status_globally(); else load_slot_status_locally(dest_slot); }
static void signature_sign(void) { GBytes *content = read_file("test/openssl-ca/manifest", NULL); GBytes *sig = NULL; GError *error = NULL; g_assert_nonnull(content); // Test valid signing sig = cms_sign(content, r_context()->certpath, r_context()->keypath, &error); g_assert_nonnull(sig); g_assert_null(error); g_bytes_unref(sig); // Test signing fail with invalid key sig = cms_sign(content, r_context()->certpath, "test/random.dat", &error); g_assert_null(sig); g_assert_nonnull(error); g_clear_error(&error); // Test signing fail with invalid cert sig = cms_sign(content, "test/random.dat", r_context()->keypath, &error); g_assert_null(sig); g_assert_nonnull(error); g_clear_error(&error); g_bytes_unref(content); }
static void signature_sign_file(void) { GBytes *sig = NULL; GError *error = NULL; // Test valid file sig = cms_sign_file("test/openssl-ca/manifest", r_context()->certpath, r_context()->keypath, &error); g_assert_nonnull(sig); g_assert_null(error); g_bytes_unref(sig); g_clear_error(&error); // Test non-existing file sig = cms_sign_file("path/to/nonexisting/file", r_context()->certpath, r_context()->keypath, &error); g_assert_null(sig); g_assert_nonnull(error); g_bytes_unref(sig); g_clear_error(&error); // Test invalid certificate sig = cms_sign_file("test/openssl-ca/manifest", NULL, r_context()->keypath, &error); g_assert_null(sig); g_assert_nonnull(error); g_bytes_unref(sig); g_clear_error(&error); }
/* Set slot as primary boot slot */ static gboolean barebox_set_primary(RaucSlot *slot) { GPtrArray *pairs = g_ptr_array_new_full(10, g_free); int prio1 = 20, prio2 = 10; gboolean res = FALSE; GList *slots; g_assert_nonnull(slot); /* Iterate over class members */ slots = g_hash_table_get_values(r_context()->config->slots); for (GList *l = slots; l != NULL; l = l->next) { RaucSlot *s = l->data; int prio; if (s->sclass != slot->sclass) continue; if (s == slot) { prio = prio1; } else { prio = prio2; } g_ptr_array_add(pairs, g_strdup_printf("%s.%s.priority=%i", BOOTSTATE_PREFIX, s->bootname, prio)); } g_ptr_array_add(pairs, g_strdup_printf("%s.%s.ok=%i", BOOTSTATE_PREFIX, slot->bootname, 1)); g_ptr_array_add(pairs, g_strdup_printf("%s.%s.remaining_attempts=%i", BOOTSTATE_PREFIX, slot->bootname, 3)); res = barebox_state_set(pairs); if (!res) { g_warning("failed marking as primary"); goto out; } res = TRUE; out: return res; }
/* Set slot as primary boot slot */ static gboolean grub_set_primary(RaucSlot *slot) { GPtrArray *pairs = g_ptr_array_new_full(10, g_free); GString *order = g_string_sized_new(10); gboolean res = FALSE; GList *slots; g_assert_nonnull(slot); g_string_append(order, slot->bootname); /* Iterate over class members */ slots = g_hash_table_get_values(r_context()->config->slots); for (GList *l = slots; l != NULL; l = l->next) { RaucSlot *s = l->data; if (s == slot) continue; if (s->sclass != slot->sclass) continue; g_string_append_c(order, ' '); g_string_append(order, s->bootname); } g_ptr_array_add(pairs, g_strdup_printf("%s_OK=%i", slot->bootname, 1)); g_ptr_array_add(pairs, g_strdup_printf("%s_TRY=%i", slot->bootname, 0)); g_ptr_array_add(pairs, g_strdup_printf("ORDER=%s", order->str)); res = grub_env_set(pairs); if (!res) { g_warning("failed marking as primary"); goto out; } res = TRUE; out: g_string_free(order, TRUE); g_ptr_array_unref(pairs); return res; }
/* Creates a mount subdir in mount path prefix */ gchar* r_create_mount_point(const gchar *name, GError **error) { gchar* prefix; gchar* mountpoint = NULL; prefix = r_context()->config->mount_prefix; if (!g_file_test (prefix, G_FILE_TEST_IS_DIR)) { g_set_error( error, G_FILE_ERROR, G_FILE_ERROR_NOTDIR, "mount prefix path %s does not exist", prefix); goto out; } mountpoint = g_build_filename(prefix, name, NULL); if (!g_file_test (mountpoint, G_FILE_TEST_IS_DIR)) { gint ret; ret = g_mkdir(mountpoint, 0777); if (ret != 0) { g_set_error( error, G_FILE_ERROR, G_FILE_ERROR_FAILED, "Failed creating mount path '%s'", mountpoint); g_free(mountpoint); mountpoint = NULL; goto out; } } out: return mountpoint; }
static gboolean status_start(int argc, char **argv) { GHashTableIter iter; gpointer key, value; gboolean res = FALSE; RaucSlot *booted = NULL; g_message("status start\n"); g_print("booted from: %s\n", get_bootname()); res = determine_slot_states(); if (!res) { g_warning("Failed to determine slot states"); r_exit_status = 1; goto out; } g_print("slot states:\n"); g_hash_table_iter_init(&iter, r_context()->config->slots); while (g_hash_table_iter_next(&iter, &key, &value)) { gchar *name = key; RaucSlot *slot = value; const gchar *state = NULL; switch (slot->state) { case ST_ACTIVE: state = "active"; break; case ST_INACTIVE: state = "inactive"; break; case ST_BOOTED: state = "booted"; booted = slot; break; case ST_UNKNOWN: default: g_error("invalid slot status"); r_exit_status = 1; break; } g_print(" %s: class=%s, device=%s, type=%s, bootname=%s\n", name, slot->sclass, slot->device, slot->type, slot->bootname); g_print(" state=%s", state); if (slot->parent) g_print(", parent=%s", slot->parent->name); else g_print(", parent=(none)"); if (slot->mountpoint) g_print(", mountpoint=%s", slot->mountpoint); else g_print(", mountpoint=(none)"); g_print("\n"); } if (argc < 3) { r_exit_status = 1; goto out; } if (!booted) { g_warning("Failed to determine booted slot"); r_exit_status = 1; goto out; } if (g_strcmp0(argv[2], "mark-good") == 0) { g_print("marking slot %s as good\n", booted->name); r_exit_status = r_boot_set_state(booted, TRUE) ? 0 : 1; } else if (g_strcmp0(argv[2], "mark-bad") == 0) { g_print("marking slot %s as bad\n", booted->name); r_exit_status = r_boot_set_state(booted, FALSE) ? 0 : 1; } else { g_message("unknown subcommand %s", argv[2]); r_exit_status = 1; } out: return TRUE; }
gboolean check_bundle(const gchar *bundlename, gsize *size, gboolean verify, GError **error) { GError *ierror = NULL; GBytes *sig = NULL; GFile *bundlefile = NULL; GFileInputStream *bundlestream = NULL; guint64 sigsize; goffset offset; gboolean res = FALSE; r_context_begin_step("check_bundle", "Checking bundle", verify); if (!r_context()->config->keyring_path) { g_set_error(error, G_FILE_ERROR, G_FILE_ERROR_EXIST, "No keyring file provided"); goto out; } g_message("Reading bundle: %s", bundlename); bundlefile = g_file_new_for_path(bundlename); bundlestream = g_file_read(bundlefile, NULL, &ierror); if (bundlestream == NULL) { g_propagate_prefixed_error( error, ierror, "failed to open bundle for reading: "); goto out; } offset = sizeof(sigsize); res = g_seekable_seek(G_SEEKABLE(bundlestream), -offset, G_SEEK_END, NULL, &ierror); if (!res) { g_propagate_prefixed_error( error, ierror, "failed to seek to end of bundle: "); goto out; } offset = g_seekable_tell((GSeekable *)bundlestream); res = input_stream_read_uint64_all(G_INPUT_STREAM(bundlestream), &sigsize, NULL, &ierror); if (!res) { g_propagate_prefixed_error( error, ierror, "failed to read signature size from bundle: "); goto out; } offset -= sigsize; if (size) *size = offset; res = g_seekable_seek(G_SEEKABLE(bundlestream), offset, G_SEEK_SET, NULL, &ierror); if (!res) { g_propagate_prefixed_error( error, ierror, "failed to seek to start of bundle signature: "); goto out; } res = input_stream_read_bytes_all(G_INPUT_STREAM(bundlestream), &sig, sigsize, NULL, &ierror); if (!res) { g_propagate_prefixed_error( error, ierror, "failed to read signature from bundle: "); goto out; } if (verify) { g_message("Verifying bundle... "); /* the squashfs image size is in offset */ res = cms_verify_file(bundlename, sig, offset, &ierror); if (!res) { g_propagate_error(error, ierror); goto out; } } res = TRUE; out: g_clear_object(&bundlestream); g_clear_object(&bundlefile); g_clear_pointer(&sig, g_bytes_unref); r_context_end_step("check_bundle", res); return res; }
gboolean create_bundle(const gchar *bundlename, const gchar *contentdir, GError **error) { GError *ierror = NULL; GBytes *sig = NULL; GFile *bundlefile = NULL; GFileOutputStream *bundlestream = NULL; gboolean res = FALSE; guint64 offset; g_assert_nonnull(r_context()->certpath); g_assert_nonnull(r_context()->keypath); res = mksquashfs(bundlename, contentdir, &ierror); if (!res) { g_propagate_error(error, ierror); goto out; } sig = cms_sign_file(bundlename, r_context()->certpath, r_context()->keypath, &ierror); if (sig == NULL) { g_propagate_prefixed_error( error, ierror, "failed signing bundle: "); goto out; } bundlefile = g_file_new_for_path(bundlename); bundlestream = g_file_append_to(bundlefile, G_FILE_CREATE_NONE, NULL, &ierror); if (bundlestream == NULL) { g_propagate_prefixed_error( error, ierror, "failed to open bundle for appending: "); goto out; } res = g_seekable_seek(G_SEEKABLE(bundlestream), 0, G_SEEK_END, NULL, &ierror); if (!res) { g_propagate_prefixed_error( error, ierror, "failed to seek to end of bundle: "); goto out; } offset = g_seekable_tell((GSeekable *)bundlestream); res = output_stream_write_bytes_all((GOutputStream *)bundlestream, sig, NULL, &ierror); if (!res) { g_propagate_prefixed_error( error, ierror, "failed to append signature to bundle: "); goto out; } offset = g_seekable_tell((GSeekable *)bundlestream) - offset; res = output_stream_write_uint64_all((GOutputStream *)bundlestream, offset, NULL, &ierror); if (!res) { g_propagate_prefixed_error( error, ierror, "failed to append signature size to bundle: "); goto out; } res = TRUE; out: g_clear_object(&bundlestream); g_clear_object(&bundlefile); g_clear_pointer(&sig, g_bytes_unref); return res; }
GBytes *cms_sign(GBytes *content, const gchar *certfile, const gchar *keyfile, gchar **interfiles, GError **error) { GError *ierror = NULL; BIO *incontent = BIO_new_mem_buf((void *)g_bytes_get_data(content, NULL), g_bytes_get_size(content)); BIO *outsig = BIO_new(BIO_s_mem()); X509 *signcert = NULL; EVP_PKEY *pkey = NULL; STACK_OF(X509) *intercerts = NULL; CMS_ContentInfo *cms = NULL; GBytes *res = NULL; int flags = CMS_DETACHED | CMS_BINARY; g_return_val_if_fail(content != NULL, NULL); g_return_val_if_fail(certfile != NULL, NULL); g_return_val_if_fail(keyfile != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); signcert = load_cert(certfile, &ierror); if (signcert == NULL) { g_propagate_error(error, ierror); goto out; } pkey = load_key(keyfile, &ierror); if (pkey == NULL) { g_propagate_error(error, ierror); goto out; } intercerts = sk_X509_new_null(); for (gchar **intercertpath = interfiles; intercertpath && *intercertpath != NULL; intercertpath++) { X509 *intercert = load_cert(*intercertpath, &ierror); if (intercert == NULL) { g_propagate_error(error, ierror); goto out; } sk_X509_push(intercerts, intercert); } cms = CMS_sign(signcert, pkey, intercerts, incontent, flags); if (cms == NULL) { unsigned long err; const gchar *data; int errflags; err = ERR_get_error_line_data(NULL, NULL, &data, &errflags); g_set_error( error, R_SIGNATURE_ERROR, R_SIGNATURE_ERROR_INVALID, "failed to create signature: %s", (errflags & ERR_TXT_STRING) ? data : ERR_error_string(err, NULL)); goto out; } if (!i2d_CMS_bio(outsig, cms)) { g_set_error_literal( error, R_SIGNATURE_ERROR, R_SIGNATURE_ERROR_SERIALIZE_SIG, "failed to serialize signature"); goto out; } res = bytes_from_bio(outsig); if (!res) { g_set_error_literal( error, R_SIGNATURE_ERROR, R_SIGNATURE_ERROR_UNKNOWN, "Read zero bytes"); goto out; } /* keyring was given, perform verification to obtain trust chain */ if (r_context()->config->keyring_path) { g_autoptr(CMS_ContentInfo) vcms = NULL; g_autoptr(X509_STORE) store = NULL; STACK_OF(X509) *verified_chain = NULL; g_message("Keyring given, doing signature verification"); if (!cms_verify(content, res, &vcms, &store, &ierror)) { g_propagate_error(error, ierror); res = NULL; goto out; } if (!cms_get_cert_chain(vcms, store, &verified_chain, &ierror)) { g_propagate_error(error, ierror); res = NULL; goto out; } for (int i = 0; i < sk_X509_num(verified_chain); i++) { const ASN1_TIME *expiry_time; struct tm *next_month; time_t now; time_t comp; time(&now); next_month = gmtime(&now); next_month->tm_mon += 1; if (next_month->tm_mon == 12) next_month->tm_mon = 0; comp = timegm(next_month); expiry_time = X509_get0_notAfter(sk_X509_value(verified_chain, i)); /* Check if expiry time is within last month */ if (X509_cmp_current_time(expiry_time) == 1 && X509_cmp_time(expiry_time, &comp) == -1) { char buf[BUFSIZ]; X509_NAME_oneline(X509_get_subject_name(sk_X509_value(verified_chain, i)), buf, sizeof buf); g_warning("Certificate %d (%s) will exipre in less than a month!", i + 1, buf); } } sk_X509_pop_free(verified_chain, X509_free); } else { g_message("No keyring given, skipping signature verification"); } out: ERR_print_errors_fp(stdout); BIO_free_all(incontent); BIO_free_all(outsig); return res; }