예제 #1
0
def git_extract_command(args):
    import git
    from otsclient.git import deserialize_ascii_armored_timestamp, extract_sig_from_git_commit
    from opentimestamps.core.git import GitTreeTimestamper

    repo = git.Repo(search_parent_directories=True)
    repo_base_path = repo.working_tree_dir

    commit = repo.commit(args.commit)
    serialized_signed_commit = commit.data_stream[3].read()

    git_commit, gpg_sig = extract_sig_from_git_commit(serialized_signed_commit)

    if not gpg_sig:
        logging.error("%s is not signed" % args.commit)
        sys.exit(1)

    (major_version, minor_version,
     commit_stamp) = deserialize_ascii_armored_timestamp(git_commit, gpg_sig)

    if commit_stamp is None:
        logging.error("%s is signed, but not timestamped" % args.commit)
        sys.exit(1)

    elif minor_version != 1:
        logging.error(
            "Commit was timestamped, but --rehash-trees was not used; can't extract per-file timestamp."
        )
        sys.exit(1)

    stamper = GitTreeTimestamper(commit.tree)

    # args.path is relative to the CWD, but for git we need a path relative to
    # the repo base.
    #
    # FIXME: Does this work with bare repos?
    # FIXME: Does this always work when the user has specified a different
    # commit than HEAD?
    git_tree_path = os.path.relpath(args.path, start=repo_base_path)

    if git_tree_path.startswith('..'):
        logging.error("%r is outside repository" % args.path)
        sys.exit(1)

    try:
        file_stamp = stamper[git_tree_path]

    # FIXME: better if these were ots-git-specific exceptions
    except (FileNotFoundError, ValueError) as exp:
        logging.error("%s", exp)
        sys.exit(1)

    blob = commit.tree[git_tree_path]
    if args.annex and blob.mode == 0o120000:
        fd = io.BytesIO()
        blob.stream_data(fd)
        link_contents = fd.getvalue()

        if b'SHA256' in link_contents:
            hex_digest_start = link_contents.find(b'--')
            if hex_digest_start < 0:
                logging.error("%r not a git-annex symlink" % args.path)
                sys.exit(1)
            hex_digest_start += 2

            hex_digest = link_contents[hex_digest_start:hex_digest_start +
                                       32 * 2]

            new_file_stamp = DetachedTimestampFile(
                OpSHA256(), Timestamp(binascii.unhexlify(hex_digest)))

            new_file_stamp.timestamp.ops.add(OpHexlify()) \
                                    .ops.add(OpPrepend(link_contents[0:hex_digest_start])) \
                                    .ops.add(OpAppend(link_contents[hex_digest_start+32*2:])) \
                                    .ops[OpSHA256()] = file_stamp.timestamp

            file_stamp = new_file_stamp

        else:
            logging.error("%r not a SHA256 git-annex symlink" % args.path)
            sys.exit(1)

    elif blob.mode == 0o120000:
        logging.error("%r is a symlink; see --annex" % args.path)
        sys.exit(1)

    # Merge the two timestamps

    # First, we need to find the tip of the file timestamp
    tip = file_stamp.timestamp
    while tip.ops:
        assert len(tip.ops) == 1  # FIXME: should handle divergence
        tip = tuple(tip.ops.values())[0]

    # Second, splice it to the commit timestamp.
    #
    # Remember that the commit timestamp was on SHA256(SHA256(git_commit) +
    # SHA256(gpg_sig)), and the commitment to the tree is in the first op - an
    # OpAppend - so we have to create an OpPrepend:
    append_commit_stamp = tip.ops.add(OpPrepend(commit_stamp.msg))
    append_commit_stamp.merge(tuple(commit_stamp.ops.values())[0])

    timestamp_file_path = None
    try:
        if args.timestamp_file is None:
            timestamp_file_path = args.path + '.ots'
            args.timestamp_file = open(timestamp_file_path, 'xb')

        else:
            timestamp_file_path = args.timestamp_file.name

        with args.timestamp_file as fd:
            ctx = StreamSerializationContext(fd)
            file_stamp.serialize(ctx)

    except IOError as exp:
        logging.error("Failed to create timestamp %r: %s" %
                      (timestamp_file_path, exp))
        sys.exit(1)
예제 #2
0
def main():
    parser = otsclient.args.make_common_options_arg_parser()

    parser.add_argument("-g",
                        "--gpg-program",
                        action="store",
                        default="/usr/bin/gpg",
                        help="Path to the GnuPG binary (default %(default)s)")

    parser.add_argument(
        '-c',
        '--calendar',
        metavar='URL',
        dest='calendar_urls',
        action='append',
        type=str,
        default=[
            "https://calendar.bitmark.one",
            "https://a.pool.opentimestamps.org",
            "https://b.pool.opentimestamps.org",
            "https://a.pool.eternitywall.com", "https://ots.btc.catallaxy.com"
        ],
        help=
        'Create timestamp with the aid of a remote calendar. May be specified multiple times. Default: %(default)r'
    )
    parser.add_argument(
        '-b',
        '--btc-wallet',
        dest='use_btc_wallet',
        action='store_true',
        help='Create timestamp locally with the local Bitcoin wallet.')
    parser.add_argument("gpgargs",
                        nargs=argparse.REMAINDER,
                        help='Arguments passed to GnuPG binary')

    parser.add_argument("--timeout",
                        type=int,
                        default=5,
                        help="Timeout before giving up on a calendar. "
                        "Default: %(default)d")

    parser.add_argument("-m",
                        type=int,
                        default="2",
                        help="Commitments are sent to remote calendars,"
                        "in the event of timeout the timestamp is considered "
                        "done if at least M calendars replied. "
                        "Default: %(default)s")

    parser.add_argument('--rehash-trees',
                        action='store_true',
                        help=argparse.SUPPRESS)

    args = otsclient.args.handle_common_options(parser.parse_args(), parser)

    logging.basicConfig(format='ots: %(message)s')

    args.verbosity = args.verbose - args.quiet
    if args.verbosity == 0:
        logging.root.setLevel(logging.INFO)
    elif args.verbosity > 0:
        logging.root.setLevel(logging.DEBUG)
    elif args.verbosity == -1:
        logging.root.setLevel(logging.WARNING)
    elif args.verbosity < -1:
        logging.root.setLevel(logging.ERROR)

    if len(args.gpgargs) == 0 or args.gpgargs[0] != '--':
        parser.error("You need to have '--' as the last argument; see docs")

    args.gpgargs = args.gpgargs[1:]

    parser = argparse.ArgumentParser()
    parser.add_argument("-bsau", action="store")
    parser.add_argument("--verify", action="store")
    gpgargs = parser.parse_known_args(args.gpgargs)[0]

    if gpgargs.bsau:
        with subprocess.Popen([args.gpg_program] + args.gpgargs,
                              stdin=subprocess.PIPE,
                              stdout=subprocess.PIPE) as gpg_proc:
            logging.debug("Reading Git commit")
            git_commit = sys.stdin.buffer.read()

            logging.debug("Git commit: %r" % git_commit)

            # FIXME: can this fail to write all bytes?
            n = gpg_proc.stdin.write(git_commit)
            logging.debug("Wrote %d bytes to GnuPG out of %d" %
                          (n, len(git_commit)))
            gpg_proc.stdin.close()

            gpg_sig = gpg_proc.stdout.read()

            # GnuPG produces no output on failure
            if not gpg_sig:
                sys.exit(1)

            logging.debug("PGP sig: %r" % gpg_sig)

            # Timestamp the commit and tag together
            signed_commit_timestamp = Timestamp(
                hash_signed_commit(git_commit, gpg_sig))
            final_timestamp = signed_commit_timestamp

            # with git tree rehashing
            minor_version = 1

            # CWD will be the git repo, so this should get us the right one
            repo = git.Repo()

            hextree_start = None
            if git_commit.startswith(b'tree '):
                hextree_start = 5
            elif git_commit.startswith(b'object '):
                # I believe this is always a git tag
                hextree_start = 7
            else:
                raise AssertionError("Don't know what to do with %r" %
                                     git_commit)

            hextree = git_commit[hextree_start:hextree_start + 20 * 2].decode()
            tree = repo.tree(hextree)
            tree.path = ''

            tree_stamper = GitTreeTimestamper(tree)

            final_timestamp = signed_commit_timestamp.ops.add(
                OpAppend(tree_stamper.timestamp.msg)).ops.add(OpSHA256())

            otsclient.cmds.create_timestamp(final_timestamp,
                                            args.calendar_urls, args)

            if args.wait:
                # Interpreted as override by the upgrade command
                # FIXME: need to clean this bad abstraction up!
                args.calendar_urls = []
                otsclient.cmds.upgrade_timestamp(signed_commit_timestamp, args)

            sys.stdout.buffer.write(gpg_sig)
            write_ascii_armored(signed_commit_timestamp, sys.stdout.buffer,
                                minor_version)

    elif gpgargs.verify:
        # Verify
        with open(gpgargs.verify, 'rb') as gpg_sig_fd:
            gpg_sig = gpg_sig_fd.read()
            git_commit = sys.stdin.buffer.read()

            (major_version, minor_version,
             timestamp) = deserialize_ascii_armored_timestamp(
                 git_commit, gpg_sig)
            if timestamp is None:
                print("OpenTimestamps: No timestamp found", file=sys.stderr)
            else:
                good = otsclient.cmds.verify_timestamp(timestamp, args)

                if good:
                    logging.info("Good timestamp")
                else:
                    logging.warning("Could not verify timestamp!")
            sys.stderr.flush()

            logging.debug("Running GnuPG binary: %r" %
                          ([args.gpg_program] + args.gpgargs))
            with subprocess.Popen([args.gpg_program] + args.gpgargs,
                                  stdin=subprocess.PIPE) as gpg_proc:
                gpg_proc.stdin.write(git_commit)
                gpg_proc.stdin.close()
예제 #3
0
def git_extract_command(args):
    import git
    from otsclient.git import deserialize_ascii_armored_timestamp, extract_sig_from_git_commit
    from opentimestamps.core.git import GitTreeTimestamper

    repo = git.Repo(search_parent_directories=True)
    repo_base_path = repo.working_tree_dir

    commit = repo.commit(args.commit)
    serialized_signed_commit = commit.data_stream[3].read()

    git_commit, gpg_sig = extract_sig_from_git_commit(serialized_signed_commit)

    if not gpg_sig:
        logging.error("%s is not signed" % args.commit)
        sys.exit(1)

    (major_version, minor_version, commit_stamp) = deserialize_ascii_armored_timestamp(git_commit, gpg_sig)

    if commit_stamp is None:
        logging.error("%s is signed, but not timestamped" % args.commit)
        sys.exit(1)

    elif minor_version != 1:
        logging.error("Commit was timestamped, but --rehash-trees was not used; can't extract per-file timestamp.")
        sys.exit(1)


    stamper = GitTreeTimestamper(commit.tree)

    # args.path is relative to the CWD, but for git we need a path relative to
    # the repo base.
    #
    # FIXME: Does this work with bare repos?
    # FIXME: Does this always work when the user has specified a different
    # commit than HEAD?
    git_tree_path = os.path.relpath(args.path, start=repo_base_path)

    if git_tree_path.startswith('..'):
        logging.error("%r is outside repository" % args.path)
        sys.exit(1)

    try:
        file_stamp = stamper[git_tree_path]

    # FIXME: better if these were ots-git-specific exceptions
    except (FileNotFoundError, ValueError) as exp:
        logging.error("%s", exp)
        sys.exit(1)

    blob = commit.tree[git_tree_path]
    if args.annex and blob.mode == 0o120000:
        fd = io.BytesIO()
        blob.stream_data(fd)
        link_contents = fd.getvalue()

        if b'SHA256' in link_contents:
            hex_digest_start = link_contents.find(b'--')
            if hex_digest_start < 0:
                logging.error("%r not a git-annex symlink" % args.path)
                sys.exit(1)
            hex_digest_start += 2

            hex_digest = link_contents[hex_digest_start:hex_digest_start+32*2]

            new_file_stamp = DetachedTimestampFile(OpSHA256(), Timestamp(binascii.unhexlify(hex_digest)))

            new_file_stamp.timestamp.ops.add(OpHexlify()) \
                                    .ops.add(OpPrepend(link_contents[0:hex_digest_start])) \
                                    .ops.add(OpAppend(link_contents[hex_digest_start+32*2:])) \
                                    .ops[OpSHA256()] = file_stamp.timestamp

            file_stamp = new_file_stamp

        else:
            logging.error("%r not a SHA256 git-annex symlink" % args.path)
            sys.exit(1)


    elif blob.mode == 0o120000:
        logging.error("%r is a symlink; see --annex" % args.path)
        sys.exit(1)

    # Merge the two timestamps

    # First, we need to find the tip of the file timestamp
    tip = file_stamp.timestamp
    while tip.ops:
        assert len(tip.ops) == 1 # FIXME: should handle divergence
        tip = tuple(tip.ops.values())[0]

    # Second, splice it to the commit timestamp.
    #
    # Remember that the commit timestamp was on SHA256(SHA256(git_commit) +
    # SHA256(gpg_sig)), and the commitment to the tree is in the first op - an
    # OpAppend - so we have to create an OpPrepend:
    append_commit_stamp = tip.ops.add(OpPrepend(commit_stamp.msg))
    append_commit_stamp.merge(tuple(commit_stamp.ops.values())[0])

    timestamp_file_path = None
    try:
        if args.timestamp_file is None:
            timestamp_file_path = args.path + '.ots'
            args.timestamp_file = open(timestamp_file_path, 'xb')

        else:
            timestamp_file_path = args.timestamp_file.name

        with args.timestamp_file as fd:
            ctx = StreamSerializationContext(fd)
            file_stamp.serialize(ctx)

    except IOError as exp:
        logging.error("Failed to create timestamp %r: %s" % (timestamp_file_path, exp))
        sys.exit(1)
예제 #4
0
def git_extract_command(args):
    import git
    from otsclient.git import deserialize_ascii_armored_timestamp, extract_sig_from_git_commit
    from opentimestamps.core.git import GitTreeTimestamper

    repo = git.Repo()

    commit = repo.commit(args.commit)
    serialized_signed_commit = commit.data_stream[3].read()

    git_commit, gpg_sig = extract_sig_from_git_commit(serialized_signed_commit)

    if not gpg_sig:
        logging.error("%s is not signed" % args.commit)
        sys.exit(1)

    commit_stamp = deserialize_ascii_armored_timestamp(git_commit, gpg_sig)

    if commit_stamp is None:
        logging.error("%s is signed, but not timestamped" % args.commit)
        sys.exit(1)

    stamper = GitTreeTimestamper(commit.tree)

    try:
        file_stamp = stamper[args.path]
    except Exception as exp:
        # FIXME
        logging.error("%r", exp)
        sys.exit(1)

    blob = commit.tree[args.path]
    if args.annex and blob.mode == 0o120000:
        fd = io.BytesIO()
        blob.stream_data(fd)
        link_contents = fd.getvalue()

        if b'SHA256' in link_contents:
            hex_digest_start = link_contents.find(b'--')
            if hex_digest_start < 0:
                logging.error("%r not a git-annex symlink" % args.path)
                sys.exit(1)
            hex_digest_start += 2

            hex_digest = link_contents[hex_digest_start:hex_digest_start+32*2]

            new_file_stamp = DetachedTimestampFile(OpSHA256(), Timestamp(binascii.unhexlify(hex_digest)))

            new_file_stamp.timestamp.ops.add(OpHexlify()) \
                                    .ops.add(OpPrepend(link_contents[0:hex_digest_start])) \
                                    .ops.add(OpAppend(link_contents[hex_digest_start+32*2:])) \
                                    .ops[OpSHA256()] = file_stamp.timestamp

            file_stamp = new_file_stamp

        else:
            logging.error("%r not a SHA256 git-annex symlink" % args.path)
            sys.exit(1)


    elif blob.mode == 0o120000:
        logging.error("%r is a symlink; see --annex" % args.path)
        sys.exit(1)

    # Merge the two timestamps

    # First, we need to find the tip of the file timestamp
    tip = file_stamp.timestamp
    while tip.ops:
        assert len(tip.ops) == 1 # FIXME: should handle divergence
        tip = tuple(tip.ops.values())[0]

    # Second, splice it to the commit timestamp.
    #
    # Remember that the commit timestamp was on SHA256(SHA256(git_commit) +
    # SHA256(gpg_sig)), and the commitment to the tree is in the first op - an
    # OpAppend - so we have to create an OpPrepend:
    append_commit_stamp = tip.ops.add(OpPrepend(commit_stamp.msg))
    append_commit_stamp.merge(tuple(commit_stamp.ops.values())[0])

    if args.timestamp_file is None:
        args.timestamp_file = open(args.path + '.ots', 'xb')

    with args.timestamp_file as fd:
        ctx = StreamSerializationContext(fd)
        file_stamp.serialize(ctx)