@@ -86,45 +86,146 @@ tpm1_pcrread() {
8686 DO_WITH_DEBUG tpm pcrread -ix " $index " | hex2bin >> " $file "
8787}
8888
89- # usage: tpmr calcfuturepcr [-a] <input_file> <output_file>
90- # Uses the scratch PCR to calculate a future PCR value (TPM2 23, TPM1 16). The
91- # data in input file are hashed into a PCR, and the PCR value is placed in
92- # output_file.
93- # -a: Append to output_file. Default is to overwrite
94- tpm2_calcfuturepcr () {
95- TRACE " Under /bin/tpmr:tpm2_calcfuturepcr"
96- if [ " $1 " = " -a" ]; then
97- APPEND=y
98- shift
99- fi
100-
101- input_file=" $1 "
102- output_file=" $2 "
89+ # is_hash - Check if a value is a valid hash of a given type
90+ # usage: is_hash <alg> <value>
91+ is_hash () {
92+ # Must only contain 0-9a-fA-F
93+ if [ " $( echo -n " $2 " | tr -d ' 0-9a-fA-F' | wc -c) " -ne 0 ]; then return 1; fi
94+ # SHA-1 hashes are 40 chars
95+ if [ " $1 " = " sha1" ] && [ " ${# 2} " -eq 40 ]; then return 0; fi
96+ # SHA-256 hashes are 64 chars
97+ if [ " $1 " = " sha256" ] && [ " ${# 2} " -eq 64 ]; then return 0; fi
98+ return 1
99+ }
103100
104- if [ -z " $APPEND " ]; then
105- true > " $output_file "
106- fi
101+ # extend_pcr_state - extend a PCR state value with more hashes or raw data (which is hashed)
102+ # usage:
103+ # extend_pcr_state <alg> <initial_state> <files/hashes...>
104+ # alg - either 'sha1' or 'sha256' to specify algorithm
105+ # initial_state - a hash value setting the initial state
106+ # files/hashes... - any number of files or hashes, state is extended once for each item
107+ extend_pcr_state () {
108+ local alg=" $1 "
109+ local state=" $2 "
110+ local next extend
111+ shift 2
112+
113+ while [ " $# " -gt 0 ]; do
114+ next=" $1 "
115+ shift
116+ if is_hash " $alg " " $next " ; then
117+ extend=" $next "
118+ else
119+ extend=" $( " ${alg} sum" < " $next " | cut -d' ' -f1) "
120+ fi
121+ state=" $( echo " $state$extend " | hex2bin | " ${alg} sum" | cut -d' ' -f1) "
122+ done
123+ echo " $state "
124+ }
107125
108- tpm2 pcrreset -Q 23
109- DO_WITH_DEBUG tpmr extend -ix 23 -if " $input_file "
110- DO_WITH_DEBUG tpm2 pcrread -Q -o >( cat >> " $output_file " ) sha256:23
111- tpm2 pcrreset -Q 23
126+ # There are 3 (and a half) possible formats of event log, each of them requires
127+ # different arguments for grep. Those formats are shown below as heredocs to
128+ # keep all the data, including whitespaces:
129+ # 1) TPM2 log, which can hold multiple hash algorithms at once:
130+ : << 'EOF '
131+ TPM2 log:
132+ Specification: 2.00
133+ Platform class: PC Client
134+ TPM2 log entry 1:
135+ PCR: 2
136+ Event type: Action
137+ Digests:
138+ SHA256: de73053377e1ae5ba5d2b637a4f5bfaeb410137722f11ef135e7a1be524e3092
139+ SHA1: 27c4f1fa214480c8626397a15981ef3a9323717f
140+ Event data: FMAP: FMAP
141+ EOF
142+ # 2) TPM1.2 log (aka TCPA), digest is always SHA1:
143+ : << 'EOF '
144+ TCPA log:
145+ Specification: 1.21
146+ Platform class: PC Client
147+ TCPA log entry 1:
148+ PCR: 2
149+ Event type: Action
150+ Digest: 27c4f1fa214480c8626397a15981ef3a9323717f
151+ Event data: FMAP: FMAP
152+ EOF
153+ # 3) coreboot-specific format:
154+ # 3.5) older versions printed 'coreboot TCPA log', even though it isn't TCPA
155+ : << 'EOF '
156+ coreboot TPM log:
157+
158+ PCR-2 27c4f1fa214480c8626397a15981ef3a9323717f SHA1 [FMAP: FMAP]
159+ EOF
160+
161+ # awk script to handle all of the above. Note this gets squashed to one line so
162+ # semicolons are required.
163+ AWK_PROG='
164+ BEGIN {
165+ getline;
166+ hash_regex="([a-fA-F0-9]{40,})";
167+ if ($0 == "TPM2 log:") {
168+ RS="\n[^[:space:]]";
169+ pcr="PCR: " pcr;
170+ alg=toupper(alg) ": " hash_regex;
171+ } else if ($0 == "TCPA log:") {
172+ RS="\n[^[:space:]]";
173+ pcr="PCR: " pcr;
174+ alg="Digest: " hash_regex;
175+ } else if ($0 ~ /^coreboot (TCPA|TPM) log:$/) {
176+ pcr="PCR-" pcr;
177+ alg=hash_regex " " toupper(alg) " ";
178+ } else {
179+ print "Unknown TPM event log format:", $0 > "/dev/stderr";
180+ exit -1;
181+ }
112182}
113- tpm1_calcfuturepcr () {
114- TRACE " Under /bin/tpmr:tpm1_calcfuturepcr"
115- if [ " $1 " = " -a" ]; then
116- APPEND=y
117- shift
183+ $0 ~ pcr {
184+ match($0, alg);
185+ print gensub(alg, "\\1", "g", substr($0, RSTART, RLENGTH));
186+ }
187+ '
188+
189+ # usage: replay_pcr <alg> <pcr_num> [ <input_file>|<input_hash> ... ]
190+ # Replays PCR value from CBMEM event log. Note that this contains only the
191+ # measurements performed by firmware, without those performed by Heads (USB
192+ # modules, LUKS header etc). First argument is PCR number, followed by optional
193+ # hashes and/or files extended to given PCR after firmware. Resulting PCR value
194+ # is returned in binary form.
195+ replay_pcr () {
196+ TRACE " Under /bin/tpmr:replay_pcr"
197+ if [ -z " $2 " ] ; then
198+ >&2 echo " No PCR number passed"
199+ return
118200 fi
119-
120- input_file=" $1 "
121- output_file=" $2 "
122-
123- if [ -z " $APPEND " ]; then
124- true > " $output_file "
201+ if [ " $2 " -ge 8 ] ; then
202+ >&2 echo " Illegal PCR number ($2 )"
203+ return
125204 fi
126-
127- DO_WITH_DEBUG tpm calcfuturepcr -ix 16 -if " $input_file " | hex2bin >> " $output_file "
205+ local log=` cbmem -L`
206+ local alg=" $1 "
207+ local pcr=" $2 "
208+ local alg_digits=0
209+ # SHA-1 hashes are 40 chars
210+ if [ " $alg " = " sha1" ] ; then alg_digits=40; fi
211+ # SHA-256 hashes are 64 chars
212+ if [ " $alg " = " sha256" ] ; then alg_digits=64; fi
213+ shift 2
214+ replayed_pcr=$( extend_pcr_state $alg $( printf " %.${alg_digits} d" 0) \
215+ $( echo " $log " | awk -v alg=$alg -v pcr=$pcr -f <( echo $AWK_PROG ) ) $@ )
216+ echo $replayed_pcr | hex2bin
217+ DEBUG " Replayed cbmem -L clean boot state of PCR=$pcr ALG=$alg : $replayed_pcr "
218+ # To manually introspect current PCR values:
219+ # PCR-2:
220+ # tpmr calcfuturepcr 2 | xxd -p
221+ # PCR-4, in case of recovery shell (bash used for process substitution):
222+ # bash -c "tpmr calcfuturepcr 4 <(echo -n recovery)" | xxd -p
223+ # PCR-4, in case of normal boot passing through kexec-select-boot:
224+ # bash -c "tpmr calcfuturepcr 4 <(echo -n generic)" | xxd -p
225+ # PCR-5, depending on which modules are loaded for given board:
226+ # tpmr calcfuturepcr 5 module0.ko module1.ko module2.ko | xxd -p
227+ # PCR-6 and PCR-7: similar to 5, but with different files passed
228+ # (6: luks header, 7: user related cbfs files loaded from cbfs-init)
128229}
129230
130231tpm2_extend () {
@@ -601,7 +702,7 @@ if [ "$CONFIG_TPM2_TOOLS" != "y" ]; then
601702 pcrsize)
602703 echo " $PCR_SIZE " ;;
603704 calcfuturepcr)
604- shift ; tpm1_calcfuturepcr " $@ " ;;
705+ shift ; replay_pcr " sha1 " " $@ " ;;
605706 destroy)
606707 shift ; tpm1_destroy " $@ " ;;
607708 seal)
@@ -634,7 +735,7 @@ case "$subcmd" in
634735 pcrsize)
635736 echo " $PCR_SIZE " ;;
636737 calcfuturepcr)
637- tpm2_calcfuturepcr " $@ " ;;
738+ replay_pcr " sha256 " " $@ " ;;
638739 extend)
639740 tpm2_extend " $@ " ;;
640741 counter_read)
0 commit comments