/* pfs-signatures.c: * **************************************************************** * Copyright (C) 2003 Tom Lord * * See the file "COPYING" for further information about * the copyright and warranty status of this work. */ #include "config-include/config-options.h" #include "po/gettext.h" #include "hackerlab/bugs/panic.h" #include "hackerlab/os/sys/types.h" #include "hackerlab/os/sys/wait.h" #include "hackerlab/os/signal.h" #include "hackerlab/char/str.h" #include "hackerlab/hash/md5-utils.h" #include "hackerlab/hash/sha1-utils.h" #include "hackerlab/rx-posix/regex.h" #include "hackerlab/vu/safe.h" #include "hackerlab/fs/file-names.h" #include "libfsutils/file-contents.h" #include "libfsutils/tmp-files.h" #include "libawk/associative.h" #include "libawk/trim.h" #include "libarch/debug.h" #include "libarch/my.h" #include "libarch/namespace.h" #include "libarch/archives.h" #include "libarch/cached-archive.h" #include #include "libarch/pfs-signatures.h" /* __STDC__ prototypes for static functions */ static void exec_shell_command (t_uchar * command); static t_uchar * archive_signing_rule_file (t_uchar * archive, int no_default); static t_uchar * archive_signature_checking_rule_file (t_uchar * archive, int no_default, int signed_archive); static int arch_pfs_memoize_checksum_file (struct arch_archive *archive, t_uchar const * revision, t_uchar * file_contents); static int old_pfs_sign_for_archive (struct arch_archive * archive, t_uchar * revision, t_uchar * sigfile, int in_fd, int out_fd); static int gpgme_check_sig(struct arch_archive * archive, t_uchar ** signed_message); static t_uchar * arch_pfs_revision_key (struct arch_pfs_archive * arch, t_uchar const * revision); static int arch_pfs_check_checksum_data (struct arch_pfs_archive * arch, t_uchar const * revision); static int arch_pfs_check_one_checksum_file (struct arch_pfs_archive *arch, t_uchar const * revision, t_uchar const * revision_dir, t_uchar const *check_file_name, int checksum_expected); static int arch_pfs_old_check_signature_for_archive (struct arch_archive * archive, t_uchar * signed_message); static int run_shell (t_uchar const *shell, int in_fd, int out_fd) { /* Generate the signature anew */ int pid = fork (); if (pid == -1) { safe_printfmt (2, "unable to fork to run %s\n", shell); return -1; } if (pid) { int status; int wait_pid; wait_pid = waitpid (pid, &status, 0); if (wait_pid < 0) { panic_msg ("error waiting for signature generation subprocess"); kill (0, SIGKILL); panic ("error waiting for subprocess"); } if (WIFSIGNALED (status)) { safe_printfmt (2, "\n"); safe_printfmt (2, "signature subprocess killed by signal %d\n", WTERMSIG (status)); safe_printfmt (2, "\n"); return -1; } else if (!WIFEXITED (status)) { panic_msg ("waitpid returned for a non-exited process"); kill (0, SIGKILL); panic ("waitpid returned for a non-exited process"); } else { int exit_status; exit_status = WEXITSTATUS (status); if (exit_status) { safe_printfmt (2, ("signature command exited with non-0 status (%d)\n" "\n" "command: %s\n" "\n"), exit_status, shell); return -1; } else return 0; } } else { safe_move_fd (in_fd, 0); safe_move_fd (out_fd, 1); exec_shell_command ((t_uchar *)shell); panic ("not reached"); } panic ("not reached"); kill (0, SIGKILL); return -1; } static int copy_signature(struct arch_archive *from, struct arch_archive *to, t_uchar const * sigfile, t_uchar const * revision, int out_fd) { /* Copy signature from another archive. */ struct arch_pfs_archive * pfs_sig_from; t_uchar * revdir_from = 0; t_uchar * sigpath = 0; t_uchar * signed_checksum = 0; revdir_from = arch_fs_archive_revision_path (from, 0, revision); sigpath = file_name_in_vicinity (0, revdir_from, sigfile); /* EVIL. GARH * RBC 20050309 this evil occurs because copying the signatures * is an abstraction violation: signing code is not (instrinsically) * pfs specific, making it archive level will fix this bogon. */ if (!str_cmp (from->vtable->type, "pfs")) pfs_sig_from = (struct arch_pfs_archive *)from; else if (arch_archive_is_cached_connection (from)) pfs_sig_from = (struct arch_pfs_archive *)(arch_cached_archive_as_pfs(from)); else { safe_printfmt (2, _("\n" "\n" "********************************\n" "\n" "ARCH DOES NOT KNOW HOW TO COPY\n" "CHECKSUMS FROM\n" "%s(%s)\n" "TO\n" "%s(%s)\n" "THIS IS A BUG, PLEASE REPORT IT\n" "HOW TO COPY FROM THAT ARCHIVE.\n" "\n" "********************************\n" "\n" "\n"), from->location,from->vtable->type, to->location, to->vtable->type); exit (2); } signed_checksum = arch_pfs_file_contents (pfs_sig_from->pfs, sigpath, 1); if (!signed_checksum || arch_pfs_memoize_checksum_file (to, revision, signed_checksum)) { safe_printfmt (2, ("\n" "\n" "********************************\n" "\n" "ERROR READING CHECKSUM FILE FROM \n" "%s(%s)\n" "\n" "********************************\n" "\n" "\n"), from->location,from->vtable->type); exit (2); } safe_write_retry (out_fd, signed_checksum, str_length (signed_checksum)); lim_free (0, signed_checksum); lim_free (0, revdir_from); lim_free (0, sigpath); return 0; } int arch_pfs_has_signing_rule (t_uchar * archive) { t_uchar * rule_file = 0; int answer = 0; rule_file = archive_signing_rule_file (archive, 1); answer = !!rule_file; lim_free (0, rule_file); return answer; } /* TODO tidy this up into appropriate source files */ #include "libinifile/inifile.h" static t_uchar * get_gpg_command (struct arch_archive * archive) { inifile_t inifile; t_uchar *result; t_uchar *options; arch_archives_get_archive_ini (archive->official_name, &inifile); result = inifile_get_single_string (&inifile, "", "gpg_command", "gpg"); if (!str_length(result)) { lim_free (0, result); result = str_save (0, "gpg"); } options = inifile_get_single_string (&inifile, "", "gpg_options", NULL); if (options) { result = str_replace (result, str_alloc_cat_many (0, result, " ", options, str_end)); lim_free (0, options); } inifile_finalise (&inifile); return result; } /** * \brief generate a signed checksum file for an archive * * \param archive the archive the checksum will be going in * \param revision the revision the checksum is for * \param sigfile the name of the old sigfile if we are copying from another archive * \param in_fd stdin for the signing process * \param out_fd where to write the signature file to (stdout for external processes) * \param from_archive the archive handle we can copy the old signature from * \return 0 on success */ int arch_pfs_sign_for_archive (struct arch_archive * archive, t_uchar * revision, t_uchar * sigfile, int in_fd, int out_fd, struct arch_archive *from_archive) { int answer = 0; if (!archive->in_registry) return old_pfs_sign_for_archive (archive, revision, sigfile, in_fd, out_fd); /* determine need for mirror vs signing here */ if (from_archive) answer = copy_signature(from_archive, archive, sigfile, revision, out_fd); else { t_uchar * gpg_command = get_gpg_command (archive); t_uchar * rule = str_alloc_cat (0, gpg_command, " --clearsign"); answer = run_shell (rule, in_fd, out_fd); lim_free (0, rule); lim_free (0, gpg_command); } if (!answer) return 0; safe_printfmt (2, _("\n" "(You may also have to use baz lock-revision -b before\n" " retrying this transaction. See baz lock-revision -H)\n" "\n")); return -1; } int old_pfs_sign_for_archive (struct arch_archive * archive, t_uchar * revision, t_uchar * sigfile, int in_fd, int out_fd) { t_uchar * rule_file = 0; t_uchar * rule = 0; int answer = 0; /* This can go completely when we stop supporting old style signing rules * i.e. baz 1.4 * RBC 20050309 */ rule_file = archive_signing_rule_file (archive->registered_name, 0); if (!rule_file) { safe_printfmt (2, ("\n" "\n" "********************************\n" "SIGNATURE DEMANDED FOR ARCHIVE\n" " %s\n" "BUT NO RULE PROVIDED\n" "\n" "Consider creating ~/.arch-params/signing/%s\n" " or ~/.arch-params/signing/=default\n" "\n" "********************************\n" "\n" "\n"), archive->registered_name, archive->registered_name); generic_error_exit: safe_printfmt (2, ("\n" "(You may also have to use baz lock-revision -b before\n" " retrying this transaction. See baz lock-revision -H)\n" "\n")); answer = -1; generic_return: lim_free (0, rule_file); lim_free (0, rule); return answer; } rule = file_contents (rule_file); rule = trim_surrounding_ws (rule); if (arch_valid_archive_name (rule)) { /* Copy signature from another archive. */ /* FIXME-REMOVENAME nuke this legacy gpg rules * DO NOT turn this into _connect_branch */ struct arch_archive * sig_from = arch_archive_connect_ext (rule, NULL, 0); answer = copy_signature(sig_from, archive, sigfile, revision, out_fd); arch_archive_close (sig_from); return 0; } else if (run_shell (rule, in_fd, out_fd)) { goto generic_error_exit; } else { goto generic_return; } } int arch_pfs_check_signature_for_archive (struct arch_archive * archive, t_uchar ** signed_message) { if (!archive->in_registry) return arch_pfs_old_check_signature_for_archive (archive, *signed_message); if (archive->signed_archive) return gpgme_check_sig(archive, signed_message); return 0; } int arch_pfs_old_check_signature_for_archive (struct arch_archive * archive, t_uchar * signed_message) { t_uchar * rule_file = 0; t_uchar * rule = 0; t_uchar * tmp_path = 0; int tmp_fd = -1; int pid; int answer = 0; rule_file = archive_signature_checking_rule_file (archive->official_name, 0, archive->signed_archive); if (!rule_file) { generic_return: lim_free (0, rule_file); lim_free (0, rule); lim_free (0, tmp_path); if (tmp_fd > 0) safe_close (tmp_fd); return answer; } tmp_path = tmp_file_name_in_tmp ("checksum-contents"); tmp_fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0); safe_unlink (tmp_path); if (signed_message) safe_write_retry (tmp_fd, signed_message, str_length (signed_message)); safe_lseek (tmp_fd, (off_t)0, SEEK_SET); rule = file_contents (rule_file); pid = fork (); if (pid == -1) { safe_printfmt (2, "unable to fork to run %s\n", rule); answer = -1; goto generic_return; } if (pid) { int status; int wait_pid; wait_pid = waitpid (pid, &status, 0); if (wait_pid < 0) { panic_msg ("error waiting for signature checking subprocess"); kill (0, SIGKILL); panic ("error waiting for subprocess"); } if (WIFSIGNALED (status)) { safe_printfmt (2, "\n"); safe_printfmt (2, "signature checking subprocess killed by signal %d\n", WTERMSIG (status)); safe_printfmt (2, "\n"); answer = -1; goto generic_return; } else if (!WIFEXITED (status)) { panic_msg ("waitpid returned for a non-exited process"); kill (0, SIGKILL); panic ("waitpid returned for a non-exited process"); } else { int exit_status; exit_status = WEXITSTATUS (status); if (exit_status) answer = -1; goto generic_return; } } else { safe_move_fd (tmp_fd, 0); exec_shell_command (rule); panic ("not reached"); } panic ("not reached"); kill (0, SIGKILL); return -1; } static void exec_shell_command (t_uchar * command) { char * argv[5]; argv[0] = "sh"; argv[1] = "-e"; argv[2] = "-c"; argv[3] = (char *)command; argv[4] = 0; execv (cfg__posix_shell, argv); panic ("not reached"); } static t_uchar * archive_signing_rule_file (t_uchar * archive, int no_default) { t_uchar * home; t_uchar * rules_dir = 0; t_uchar * answer = 0; home = getenv ("HOME"); invariant (!!home); rules_dir = file_name_in_vicinity (0, arch_my_arch_params(), "signing"); answer = arch_archives_signature_signing_rule_file (archive); if (safe_access (answer, F_OK)) { lim_free (0, answer); answer = 0; if (!no_default) { answer = file_name_in_vicinity (0, rules_dir, "=default"); if (safe_access (answer, F_OK)) { lim_free (0, answer); answer = 0; } } } lim_free (0, rules_dir); return answer; } static t_uchar * archive_signature_checking_rule_file (t_uchar * archive, int no_default, int signed_archive) { static assoc_table already_checked = 0; static assoc_table already_know = 0; static t_uchar exact[] = "exact"; static t_uchar yes[] = "yes"; t_uchar * check_rule_name = 0; t_uchar * home; t_uchar * rules_dir = 0; t_uchar * answer = 0; t_uchar * checked = 0; int exact_find = 0; checked = assoc_ref (already_checked, archive); answer = assoc_ref (already_know, archive); if (answer) { if (no_default && (checked != exact)) { answer = 0; } else { answer = str_save (0, answer); } } else if (!checked) { if (signed_archive) check_rule_name = str_alloc_cat (0, archive, ".check"); else check_rule_name = str_alloc_cat (0, archive, ".unsigned-check"); home = getenv ("HOME"); invariant (!!home); rules_dir = file_name_in_vicinity (0, arch_my_arch_params(), "signing"); answer = file_name_in_vicinity (0, rules_dir, check_rule_name); if (!safe_access (answer, F_OK)) { exact_find = 1; } else { lim_free (0, answer); if (no_default) answer = 0; else { if (signed_archive) answer = file_name_in_vicinity (0, rules_dir, "=default.check"); else answer = file_name_in_vicinity (0, rules_dir, "=default.unsigned-check"); if (safe_access (answer, F_OK)) { lim_free (0, answer); answer = 0; } } } if (answer || !no_default) { assoc_set (&already_checked, archive, exact_find ? exact : yes); if (answer) { assoc_set (&already_know, archive, answer); } else if (signed_archive) { safe_printfmt (2, ("\n" "WARNING: no rule found for checking signatures from %s\n" "\n" " Consider creating ~/.arch-params/signing/%s.check\n" " or ~/.arch-params/signing/=default.check\n" "\n"), archive, archive); } } } lim_free (0, check_rule_name); lim_free (0, rules_dir); return answer; } static assoc_table remembered_md5s = 0; /* * keys are * $url $revision $file * * values are (ascii, hex) md5 checksums. */ static assoc_table remembered_sha1s = 0; /* * keys are * $url $revision $file * * values are (ascii, hex) SHA1 checksums. */ static assoc_table checked_revisions = 0; /* * keys are * $url $revision * * values are "yes" meaning that the checksum * file for that revision has been read. */ static assoc_table checksummed_revisions = 0; /* * keys are * $url $revision * * values are "yes" meaning that a checksum * file for that revision was found. */ void arch_pfs_invalidate_checksum_data (struct arch_pfs_archive * arch, t_uchar * revision) { t_uchar *revision_key = arch_pfs_revision_key (arch, revision); if (assoc_ref (checked_revisions, revision_key)) assoc_del (checked_revisions, revision_key); lim_free (0, revision_key); } t_uchar * arch_pfs_revision_key (struct arch_pfs_archive * arch, t_uchar const * revision) { /* FIXME-REMOVENAME registered_name -> location here */ return str_alloc_cat_many (0, arch->arch.registered_name, " ", revision, str_end); } int arch_pfs_ensure_checksum_data (struct arch_pfs_archive * arch, t_uchar const * revision) { int answer = 0; t_uchar * revision_key = arch_pfs_revision_key (arch, revision); if (!assoc_ref (checked_revisions, revision_key)) answer = arch_pfs_check_checksum_data (arch, revision); lim_free (0, revision_key); return answer; } int arch_pfs_check_checksum_data (struct arch_pfs_archive * arch, t_uchar const * revision) { t_uchar * revision_key = arch_pfs_revision_key (arch, revision); t_uchar * revision_dir = 0; # define N_CHECKSUM_FILES 3 static char * checksum_file_name[N_CHECKSUM_FILES] = { "checksum", "checksum.cacherev", "ancestry.gz.checksum" }; int x; int answer = 0; if (!arch->arch.in_registry) { /* If the user thinks he wants to signature-check this archive, * but the archive doesn't think it's signed, then freak out. * FIXME: 20030512 RBC - remove this whole block * when we remove support for registered named in baz 1.4 */ t_uchar * check_rule = archive_signature_checking_rule_file (arch->arch.registered_name, 1, 1); if (check_rule && !arch->arch.signed_archive) { safe_printfmt (2, _("\n" "\n" "********************************\n" "\n" "ARCHIVE NOT SIGNED BUT YOU HAVE A RULE\n" "FOR SIGNATURE CHECKING THIS ARCHIVE.\n" "\n" " archive: %s\n" " revision %s\n" "\n" " your rule file: %s\n" "\n" "********************************\n" "\n" "\n"), arch->arch.registered_name, revision, check_rule); answer = -1; } lim_free (0, check_rule); } else { if (arch_archive_check_signed_status (&arch->arch, -1)) answer = -1; } revision_dir = arch_fs_archive_revision_path (&arch->arch, 0, revision); for (x = 0; x < N_CHECKSUM_FILES; ++x) answer = answer | arch_pfs_check_one_checksum_file (arch, revision, revision_dir, checksum_file_name[x], !x); assoc_set (&checked_revisions, revision_key, "yes"); lim_free (0, revision_dir); lim_free (0, revision_key); return answer; } int arch_pfs_check_one_checksum_file (struct arch_pfs_archive *arch, t_uchar const * revision, t_uchar const * revision_dir, t_uchar const *checksum_file_name, int checksum_expected) { int answer = 0; t_uchar * file_path = 0; t_uchar * file_contents = 0; t_uchar * revision_key = arch_pfs_revision_key (arch, revision); file_path = file_name_in_vicinity (0, revision_dir, checksum_file_name); file_contents = arch_pfs_file_contents (arch->pfs, file_path, 1); if (file_contents) { assoc_set (&checksummed_revisions, revision_key, "yes"); } else if (checksum_expected) { if (arch->arch.signed_archive) { safe_printfmt (2, ("\n" "\n" "********************************\n" "\n" "NO CHECKSUM %s FOUND FOR REVISION!\n" " IN ARCHIVE THAT'S SUPPOSED TO\n" " BE SIGNED!\n" "\n" " archive: %s\n" " revision %s\n" "\n" "********************************\n" "\n" "\n"), checksum_file_name, arch->arch.registered_name, revision); answer = -1; } else if (arch->arch.type != arch_archive_tla) { /* vanilla tla is the last format that did not have mandatory checksums */ safe_printfmt (2, ("\n" "\n" "********************************\n" "\n" "NO CHECKSUM %s FOUND FOR REVISION!\n" "\n" " revision: %s/%s\n" " URL: %s\n" "\n" "********************************\n" "\n" "\n"), checksum_file_name, arch->arch.official_name, revision, arch->arch.location); answer = -1; } else { safe_printfmt (2, ("\n" "********************************\n" "NO CHECKSUM %s FOUND FOR REVISION\n" " (unsigned archive, continuing anyway)\n" "\n" " archive: %s\n" " revision %s\n" "\n" "********************************\n" "\n"), checksum_file_name, arch->arch.registered_name, revision); } } /* if we have file data, or its an unsigned archive ??? * and the check fails.? */ debug (dbg_archive_pfs, 3, "checking signature for %s on revision %s/%s\n", checksum_file_name, arch->arch.official_name, revision); if ((file_contents || !arch->arch.signed_archive) && arch_pfs_check_signature_for_archive (&arch->arch, &file_contents)) { safe_printfmt (2, ("\n" "\n" "********************************\n" "\n" "INVALID SIGNATURE ON REVISION!\n" " archive: %s\n" " revision %s\n" " checksum file: %s\n" "\n" "********************************\n" "\n" "\n"), arch->arch.registered_name, revision, checksum_file_name); answer = -1; } else if (file_contents && arch_pfs_memoize_checksum_file (&arch->arch, revision, file_contents)) { safe_printfmt (2, ("\n" "\n" "********************************\n" "\n" "INVALID CHECKSUM FILE SYNTAX FOR REVISION!\n" " archive: %s\n" " revision %s\n" " checksum file: %s\n" "\n" "********************************\n" "\n" "\n"), arch->arch.registered_name, revision, checksum_file_name); answer = -1; } lim_free (0, revision_key); lim_free (0, file_path); lim_free (0, file_contents); return answer; } static void arch_pfs_insert_key (t_uchar *line, t_uchar *key_prefix, regmatch_t *file, regmatch_t *hash, assoc_table *table) { t_uchar * file_name = 0; t_uchar * assoc_key = 0; t_uchar * val = 0; file_name = str_save_n (0, line + file->rm_so, file->rm_eo - file->rm_so); assoc_key = str_alloc_cat (0, key_prefix, file_name); val = str_save_n (0, line + hash->rm_so, hash->rm_eo - hash->rm_so); assoc_set (table, assoc_key, val); lim_free (0, file_name); lim_free (0, assoc_key); lim_free (0, val); } static int arch_pfs_memoize_checksum_file (struct arch_archive *archive, t_uchar const * revision, t_uchar * file_contents) { static char file_head_pattern[] = "^Signature-for: ([^/]+)/(.+)$"; static char md5_pattern[] = "^md5 ([^\n ]+) ([a-f0-9]+)"; static char sha1_pattern[] = "^sha1 ([^\n ]+) ([a-f0-9]+)"; static int is_compiled = 0; static regex_t file_head_preg; static regex_t md5_preg; static regex_t sha1_preg; regmatch_t pmatch[3]; if (!is_compiled) { if (regcomp (&file_head_preg, file_head_pattern, REG_EXTENDED | REG_NEWLINE)) panic ("unable to compile checksum-file head regexp"); if (regcomp (&md5_preg, md5_pattern, REG_EXTENDED)) panic ("unable to compile checksum-file MD5 regexp"); if (regcomp (&sha1_preg, sha1_pattern, REG_EXTENDED)) panic ("unable to compile checksum-file SHA1 regexp"); is_compiled = 1; } if (regexec (&file_head_preg, file_contents, 3, pmatch, 0)) return -1; if (str_cmp_n (archive->official_name, str_length (archive->official_name), file_contents + pmatch[1].rm_so, (pmatch[1].rm_eo - pmatch[1].rm_so))) return -1; if (str_cmp_n (revision, str_length (revision), file_contents + pmatch[2].rm_so, (pmatch[2].rm_eo - pmatch[2].rm_so))) return -1; { t_uchar * key_prefix = 0; /* so this is the URL to the revision, for all intents and purposes * so it should be location not archive-name */ invariant (archive->location != NULL); key_prefix = str_alloc_cat_many (0, archive->location, " ", revision, " ", str_end); file_contents = str_chr_index (file_contents, '\n'); if (file_contents) file_contents++; while (file_contents && *file_contents) { if (!regexec (&md5_preg, file_contents, 3, pmatch, 0)) arch_pfs_insert_key (file_contents, key_prefix, &pmatch[1], &pmatch[2], &remembered_md5s); else if (!regexec (&sha1_preg, file_contents, 3, pmatch, 0)) arch_pfs_insert_key (file_contents, key_prefix, &pmatch[1], &pmatch[2], &remembered_sha1s); else ; /* * Ignore this line for now; it may be another hash or other metadata * that was added by a future tla version. */ file_contents = str_chr_index (file_contents, '\n'); if (file_contents) file_contents++; } lim_free (0, key_prefix); } return 0; } int arch_pfs_checksum_anticipates_file (struct arch_archive * archive, t_uchar const * revision, t_uchar * file) { t_uchar * key = 0; int answer; key = str_alloc_cat_many (0, archive->location, " ", revision, " ", file, str_end); /* * Only MD5 is required for now. Perhaps at a later point we will * also require SHA1. */ answer = !!assoc_ref (remembered_md5s, key); lim_free (0, key); return answer; } int arch_pfs_checksum_governs (struct arch_pfs_archive * archive, t_uchar const * revision) { t_uchar * key = 0; int answer; #undef FIXME /* Under what circumstance should it be an error that a revision * lacks a checksum file? (*_ensure_* above should handle this) */ key = arch_pfs_revision_key (archive, revision); answer = !!assoc_ref (checksummed_revisions, key); lim_free (0, key); return answer; } int arch_pfs_checksum_governs_strictly (struct arch_pfs_archive * arch) { return arch->arch.signed_archive; } static void arch_pfs_validate_checksums (struct arch_archive *arch, t_uchar *revision, t_uchar *tail, t_uchar *key, t_uchar *contents_md5, t_uchar *contents_sha1) { t_uchar * remembered_md5 = 0; t_uchar * remembered_sha1 = 0; remembered_md5 = assoc_ref (remembered_md5s, key); remembered_sha1 = assoc_ref (remembered_sha1s, key); if ((remembered_md5 && str_cmp (remembered_md5, contents_md5)) || (remembered_sha1 && str_cmp (remembered_sha1, contents_sha1))) { safe_printfmt (2, ("\n" "********************************\n" " MISMATCHED ARCHIVE CHECKSUM\n" "\n" " archive: %s\n" " revision: %s\n" " url: %s\n" " file: %s\n" "\n" " expected:\n"), arch->official_name, revision, arch->location, tail); if (remembered_md5) safe_printfmt (2, (" MD5: %s\n"), remembered_md5); if (remembered_sha1) safe_printfmt (2, (" SHA1: %s\n"), remembered_sha1); safe_printfmt (2, (" got:\n")); if (remembered_md5) safe_printfmt (2, (" MD5: %s\n"), contents_md5); if (remembered_sha1) safe_printfmt (2, (" SHA1: %s\n"), contents_sha1); safe_printfmt (2, ("\n" "********************************\n" "\n")); exit (2); } } t_uchar * arch_pfs_checked_file_contents (struct arch_pfs_archive * arch, t_uchar * revision, t_uchar * path) { if (!arch_pfs_checksum_governs (arch, revision)) return arch_pfs_file_contents (arch->pfs, path, 0); else { t_uchar * tail = 0; t_uchar * key = 0; t_uchar * contents = 0; t_uchar * contents_md5 = 0; t_uchar * contents_sha1 = 0; tail = file_name_tail (0, path); key = arch_pfs_revision_key (arch, revision); contents = arch_pfs_file_contents (arch->pfs, path, 0); contents_md5 = md5_ascii_for_str (0, contents); contents_sha1 = sha1_ascii_for_str (0, contents); arch_pfs_validate_checksums (&arch->arch, revision, tail, key, contents_md5, contents_sha1); lim_free (0, tail); lim_free (0, key); lim_free (0, contents_md5); lim_free (0, contents_sha1); return contents; } } void arch_pfs_checked_get_file (struct arch_pfs_archive * arch, t_uchar * revision, int out_fd, t_uchar * path) { if (!arch_pfs_checksum_governs (arch, revision)) arch_pfs_get_file (arch->pfs, out_fd, path, 0); else { t_uchar * tail = 0; t_uchar * key = 0; t_uchar * tmp_path = 0; int tmp_fd = -1; t_uchar buf[4096]; md5_context_t md5c = 0; sha1_context_t sha1c = 0; t_uchar computed_md5_raw[16]; t_uchar computed_sha1_raw[20]; t_uchar * computed_md5 = 0; t_uchar * computed_sha1 = 0; tail = file_name_tail (0, path); key = arch_pfs_revision_key (arch, revision); tmp_path = tmp_file_name_in_tmp ("arch-file"); tmp_fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0); safe_unlink (tmp_path); arch_pfs_get_file (arch->pfs, tmp_fd, path, 0); safe_lseek (tmp_fd, (off_t)0, SEEK_SET); md5c = make_md5_context (0); sha1c = make_sha1_context (0); while (1) { ssize_t amt; amt = safe_read_retry (tmp_fd, buf, sizeof (buf)); if (!amt) break; else { md5_scan (md5c, buf, (size_t)amt); sha1_scan (sha1c, buf, (size_t)amt); safe_write_retry (out_fd, buf, (size_t)amt); } } md5_final (computed_md5_raw, md5c); sha1_final (computed_sha1_raw, sha1c); computed_md5 = md5_alloc_ascii (0, computed_md5_raw); computed_sha1 = sha1_alloc_ascii (0, computed_sha1_raw); arch_pfs_validate_checksums (&arch->arch, revision, tail, key, computed_md5, computed_sha1); lim_free (0, tail); lim_free (0, key); lim_free (0, tmp_path); safe_close (tmp_fd); free_md5_context (0, md5c); lim_free (0, computed_md5); } } static rel_table inifile_get_ws_list (inifile_t *inifile, t_uchar const * section, t_uchar const *key) { int index; rel_table answer = NULL; rel_table temp = inifile_get_key_values (inifile, section, key); rel_for_each (temp, index) { if (!str_length (temp[index][0])) { rel_free_table (answer); answer = NULL; } else { rel_table next_add = rel_ws_split (temp[index][0]); rel_append_x (&answer, next_add); rel_free_table (next_add); } } rel_free_table (temp); return answer; } /** * \brief check a signed message with the policy for archive * \param archive the archive the message came from * \param signed_message the clearsigned message, is replaced with the signed content. * \return 0 on success; */ int gpgme_check_sig(struct arch_archive * archive, t_uchar ** signed_message) { int result; static assoc_table missing_keys = 0; gpgme_error_t error; gpgme_ctx_t context; gpgme_data_t signature_data; gpgme_data_t signed_content; invariant (GPG_ERR_NO_ERROR == gpgme_new (&context)); invariant (GPG_ERR_NO_ERROR == gpgme_data_new_from_mem (&signature_data, *signed_message, str_length (*signed_message), 0)); invariant (GPG_ERR_NO_ERROR == gpgme_data_new (&signed_content)); error = gpgme_op_verify (context, signature_data, NULL, signed_content); if (error == GPG_ERR_NO_DATA) { /* not signed at all */ safe_printfmt (2, "Not a signed checksum.\n"); goto error_exit; } else if (error == GPG_ERR_INV_VALUE) { /* didn't get good data to pass in */ safe_printfmt (2, "No valid data to check.\n"); goto error_exit; } else if (error == GPG_ERR_NO_ERROR) { size_t len; gpgme_verify_result_t verify; gpgme_signature_t sig; inifile_t inifile; rel_table fingerprints = NULL; rel_table ids = NULL; result = 0; arch_archives_get_archive_ini (archive->official_name, &inifile); fingerprints = inifile_get_ws_list (&inifile, "", "allowed_fingerprints"); ids = inifile_get_ws_list (&inifile, "", "allowed_ids"); inifile_finalise (&inifile); /* ok verified the signature */ verify = gpgme_op_verify_result (context); sig = verify->signatures; while (sig) { gpgme_key_t key; int found = 0; gpgme_get_key (context, sig->fpr, &key, 0); if (!key && assoc_ref(missing_keys, sig->fpr) == NULL) { assoc_set (&missing_keys, sig->fpr, sig->fpr); safe_printfmt (2, _("Key for signature missing - cannot verify signature (%s)\n"), sig->fpr); } if (!key || key->revoked || key->expired || key->disabled || key->invalid) { sig = sig->next; continue; } //safe_printfmt (2, "%p %d %s %s %d %d\n", sig, gpg_err_code (sig->status), gpg_strerror (gpg_err_code (sig->status)), sig->fpr, sig->wrong_key_usage, sig->summary); // validity ? /* policy on ids, and fingerprints */ /* is the fingerprint in the allowed list */ if (rel_n_records (fingerprints)) { int index; rel_for_each (fingerprints, index) if (!str_casecmp (sig->fpr, fingerprints[index][0])) found = -1; } /* is the id or uids requested available ? */ #if 0 safe_printfmt (2, "key: rev %d exp: %d dis %d inv %d pro %d\n", key->revoked, key->expired, key->disabled, key->invalid, key->protocol); #endif if (rel_n_records (ids)) { int index; gpgme_user_id_t uid = key->uids; gpgme_subkey_t subkey; while (uid) { if (uid->validity == GPGME_VALIDITY_MARGINAL || uid->validity == GPGME_VALIDITY_FULL || uid->validity == GPGME_VALIDITY_ULTIMATE) { //safe_printfmt (2, "valid: %s\n", uid->email); rel_for_each (ids, index) if (!str_casecmp (uid->email, ids[index][0])) found = -1; } #if 0 else safe_printfmt (2, "not valid: %d:%s\n", uid->validity, uid->email); #endif uid = uid->next; } subkey = key->subkeys; while (subkey) { if (subkey->revoked || subkey->expired || subkey->disabled || subkey->invalid) { subkey = subkey->next; continue; } rel_for_each (ids, index) if (!str_casecmp (subkey->keyid, ids[index][0])) found = -1; subkey = subkey->next; } } if (!found && (rel_n_records(ids) || rel_n_records (fingerprints))) { safe_printfmt (2, _("No ID or fingerprint found in the allowed list for this checksum.\n")); result = -1; } sig = sig->next; } rel_free_table (fingerprints); rel_free_table (ids); if (result) goto error_exit; /* policy on archive name can be checked now */ /* now we need to get a list of the trustable uids that the creator and archive * can match against */ *signed_message = str_replace (*signed_message, gpgme_data_release_and_get_mem (signed_content, &len)); *signed_message = str_replace (*signed_message, str_save_n (0, *signed_message, len)); goto cleanup_exit; } else { /* crypto error result */ safe_printfmt (2, "Failed to verify signature data: error: %d (%s)\n", error, gpgme_strerror (error)); safe_printfmt (2, "checksum we attempted to verify:\n=====\n%s\n=====\n", *signed_message); goto error_exit; } error_exit: result = -1; gpgme_data_release (signed_content); goto cleanup_exit; cleanup_exit: gpgme_release (context); gpgme_data_release (signature_data); return result; } /* tag: Tom Lord Wed Dec 24 19:41:23 2003 (pfs-signatures.c) */