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)
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()
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)
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)