rpm 签名 生成 rpm 包签名的代码流程如下(以 tag rpm-4.14.2.1-release 为例):
1 2 3 4 5 6 7 8 9 10 11 12 13 rpmPkgSign rpmPushMacro ("_gpg_digest_algo" ) rpmPushMacro ("_gpg_name" ) rpmSign replaceSignature makeGPGSignature runGPG rpmPushMacro ("__plaintext_filename" ) ; rpmPushMacro ("__signature_filename" ); rpmExpand ("%{?__gpg_sign_cmd}" ) execve makeSigTag
展开 __gpg_sign_cmd
宏如下,显然 rpm 签名是通过调用 gpg 命令生成的:
1 2 3 4 5 6 7 $ rpm --eval %{?__gpg_sign_cmd} /usr/bin/gpg gpg --no-verbose --no-armor --no-secmem-warning -u "%{_gpg_name}" -sbo %{__signature_filename} %{__plaintext_filename}
gpg 签名时间戳 gpg 生成签名的流程如下(以 gnupg 2.2.8 为例):
1 2 3 4 5 6 7 sign_file write_signature_packets if (timestamp) sig->timestamp = timestamp; else sig->timestamp = make_timestamp ();
注意其中对 timestamp
的处理,默认使用当前时间,如果需要修改这个时间戳,可以通过参数传递进来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 diff -Nur gnupg-2.2.4.orig/g10/gpg.c gnupg-2.2.4/g10/gpg.c @@ -345,6 +345,7 @@ oFastListMode, oListOnly, oIgnoreTimeConflict, + oTimestamp, oIgnoreValidFrom, oIgnoreCrcError, oIgnoreMDCError, @@ -803,6 +804,7 @@ ARGPARSE_s_n (oPrintPKARecords, "print-pka-records", "@"), ARGPARSE_s_n (oPrintDANERecords, "print-dane-records", "@"), ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"), + ARGPARSE_s_i (oTimestamp, "timestamp", "@"), ARGPARSE_s_n (oIgnoreValidFrom, "ignore-valid-from", "@"), ARGPARSE_s_n (oIgnoreCrcError, "ignore-crc-error", "@"), ARGPARSE_s_n (oIgnoreMDCError, "ignore-mdc-error", "@"), @@ -3332,6 +3334,9 @@ case oPrintDANERecords: print_dane_records = 1; break; case oListOnly: opt.list_only=1; break; case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; + case oTimestamp: + opt.timestamp = pargs.r.ret_int; + break; case oIgnoreValidFrom: opt.ignore_valid_from = 1; break; case oIgnoreCrcError: opt.ignore_crc_error = 1; break; case oIgnoreMDCError: opt.ignore_mdc_error = 1; break; diff -Nur gnupg-2.2.4.orig/g10/options.h gnupg-2.2.4/g10/options.h @@ -199,6 +199,7 @@ int fast_list_mode; int legacy_list_mode; int ignore_time_conflict; + u32 timestamp; int ignore_valid_from; int ignore_crc_error; int ignore_mdc_error; diff -Nur gnupg-2.2.4.orig/g10/sign.c gnupg-2.2.4/g10/sign.c @@ -1103,7 +1103,7 @@ /* write the signatures */ rc = write_signature_packets (ctrl, sk_list, out, mfx.md, opt.textmode && !outfile? 0x01 : 0x00, - 0, duration, detached ? 'D':'S', NULL); + opt.timestamp, duration, detached ? 'D':'S', NULL); if( rc ) goto leave;
不过需要注意的是,如果指定的时间戳是一个过去的时间,需要指定 --ignore-time-conflict
选项:
1 2 3 4 5 $ ./gpg -sbo x --timestamp 1546275661 misc.c gpg: key DA3BFDCF8D77F675 was created 8864814 seconds in the future (time warp or clock problem) gpg: signing failed: Time conflict $ ./gpg -sbo x --ignore-time-conflict --timestamp 1546275661 misc.c gpg: key DA3BFDCF8D77F675 was created 8864814 seconds in the future (time warp or clock problem)
改写 rpm rpm-autosign.exp 1 2 3 4 5 6 7 #!/usr/bin/expect -f set timeout -1spawn env LANG=C LC_ALL=C rpm --addsign {*}$argv expect -exact "Enter pass phrase: " send -- "\r" expect eof
rewrite-rpm.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 import ioimport structclass RPMError (Exception ): pass RPM_LEAD_MAGIC = '\xed\xab\xee\xdb' RPM_HEADER_MAGIC = '\x8e\xad\xe8' RPMTAG_BUILDTIME = 1006 RPMTAG_SIZE = 1009 def search_magic (rpm, magic ): pos = rpm.tell() while True : chunk = rpm.read(len (magic)) if not chunk or len (chunk) != len (magic): return None if chunk == magic: return pos pos += 1 rpm.seek(pos) def _read_signature (rpm ): pos = search_magic(rpm, RPM_HEADER_MAGIC) if pos is None : raise RPMError('invalid RPM file, signature header not found' ) _ = ord (rpm.read(1 )) _ = rpm.read(4 ) num_entries, = struct.unpack(b'!i' , rpm.read(4 )) store_size, = struct.unpack(b'!i' , rpm.read(4 )) entry_fmt = struct.Struct(b'!iiii' ) for _ in range (num_entries): _ = rpm.read(entry_fmt.size) _ = rpm.read(store_size) def _update_rpm (rpm, tags ): pos = search_magic(rpm, RPM_HEADER_MAGIC) if pos is None : raise RPMError('invalid RPM file, rpm header not found' ) _ = ord (rpm.read(1 )) _ = rpm.read(4 ) num_entries, = struct.unpack('!i' , rpm.read(4 )) store_size, = struct.unpack('!i' , rpm.read(4 )) entry_fmt = struct.Struct('!iiii' ) entries = [] for _ in range (num_entries): entry = entry_fmt.unpack(rpm.read(entry_fmt.size)) entries.append(entry) pos = rpm.tell() store = io.BytesIO(rpm.read(store_size)) for tag, typ, offset, count in entries: d = tags.get(tag) if d is None : continue hd = struct.pack('!i' , d) store.seek(offset) store.write(hd) store.seek(0 ) rpm.seek(pos) rpm.write(store.read()) def update_rpm (rpm, tags ): lead_fmt = struct.Struct(b'!4sBBhh66shh16s' ) data = rpm.read(lead_fmt.size) lead = lead_fmt.unpack(data) magic = lead[0 ] if magic != RPM_LEAD_MAGIC: raise RPMError('invalid RPM file' ) _read_signature(rpm) _update_rpm(rpm, tags) def parse_args (): import argparse parser = argparse.ArgumentParser() parser.add_argument( '--build-time' , type =int , metavar='BUILDTIME' , help ='rpm package build time' ) parser.add_argument( '--size' , type =int , metavar='SIZE' , help ='rpm package size' ) parser.add_argument( 'rpm' , type =str , help ='rpm package to rewrite' ) return parser.parse_args() def main (): args = parse_args() tags = { RPMTAG_BUILDTIME: args.build_time, RPMTAG_SIZE: args.size } with open (args.rpm, 'r+b' ) as rpm: update_rpm(rpm, tags) if __name__ == '__main__' : main()
rewrite-rpm.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #!/bin/bash SCRIPT_DIR=$(cd -P $(dirname $0 ) && pwd -P) KEYID=C7E8A950 setup_gpg () { export GNUPGHOME=${SCRIPT_DIR} /gpg if ! gpg --list-keys 2>&1 | grep ${KEYID} > /dev/null; then echo "Can not find signing key" 1>&2 return 1 fi } sign_rpm () { rpm="$1 " timestamp=$2 keyid=$3 gpg_cmd='%{__gpg} \ gpg --timestamp %{_gpg_timestamp} --ignore-time-conflict --no-verbose --no-armor \ %{?_gpg_digest_algo:--digest-algo %{_gpg_digest_algo}} \ --no-secmem-warning \ %{?_gpg_sign_cmd_extra_args:%{_gpg_sign_cmd_extra_args}} \ -u "%{_gpg_name}" -sbo %{__signature_filename} %{__plaintext_filename}' rpm --delsign ${rpm} ${SCRIPT_DIR} /rpm-autosign.exp \ --define "_gpg_timestamp ${timestamp} " \ --define "_gpg_name ${keyid} " \ --define "__gpg_sign_cmd ${gpg_cmd} " \ ${rpm} } update_rpm () { ref_rpm="$1 " rpm="$2 " keyid=$3 build_time=$(rpm -qp --queryformat '%{BUILDTIME}' --nodigest --nosignature ${ref_rpm} ) size=$(rpm -qp --queryformat '%{SIZE}' --nodigest --nosignature ${ref_rpm} ) signature_time=$(rpm -qpi --nodigest --nosignature ${ref_rpm} | awk 'BEGIN {FS = ","}; /Signature\s*:/ {print $2}' | xargs -I{} date -d {} +%s) ${SCRIPT_DIR} /rewrite-rpm.py --build-time ${build_time} --size ${size} ${rpm} sign_rpm ${rpm} ${signature_time} ${keyid} } setup_gpg if [ $? -ne 0 ]; then exit 1 fi update_rpm "$1 " "$2 " ${KEYID}
时间与 epoch 之间的转换 1 2 3 4 5 6 7 8 $ date -d '2019/01/02 11:11:11' +%s 1546398671 $ date -d @1546398671 Wed Jan 2 11:11:11 CST 2019 $ date -d @1546398671 +%s 1546398671 $ date -d 'Mon 23 Sep 2019 03:49:44 PM CST' +%s 1569224984
参考资料 RPM File Format
http://ftp.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html
Description of RPM file format
https://rpm.org/devel_doc/file_format.html
RPM Package File Structure
https://docs.fedoraproject.org/en-US/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ch-package-structure.html
A pure python rpm reader
https://github.com/mjvm/pyrpm
Read rpm archive files
https://github.com/srossross/rpmfile
Golang implementation of parsing RPM packages
https://github.com/sassoftware/go-rpmutils