summaryrefslogtreecommitdiffstats
path: root/src/pcrlock/pcrlock-firmware.c
blob: 73c68c2237949bbd69c9685c36ce19f063745132 (plain)
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include <openssl/evp.h>

#include "pcrlock-firmware.h"
#include "unaligned.h"

static int tcg_pcr_event2_digests_size(
                const TCG_EfiSpecIdEventAlgorithmSize *algorithms,
                size_t n_algorithms,
                size_t *ret) {

        size_t m = 0;

        assert(algorithms || n_algorithms == 0);
        assert(ret);

        FOREACH_ARRAY(a, algorithms, n_algorithms) {

                if (a->digestSize > UINT32_MAX - offsetof(TPMT_HA, digest) - m)
                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Accumulated hash size too large");

                m += offsetof(TPMT_HA, digest) + a->digestSize;
        }

        *ret = m;
        return 0;
}

int validate_firmware_event(
                const TCG_PCR_EVENT2 *event,
                size_t left,
                const TCG_EfiSpecIdEventAlgorithmSize *algorithms,
                size_t n_algorithms,
                const TCG_PCR_EVENT2 **ret_next_event,
                size_t *ret_left,
                const void **ret_payload,
                size_t *ret_payload_size) {

        size_t digests_size;
        int r;

        assert(event);
        assert(algorithms || n_algorithms == 0);
        assert(ret_next_event);
        assert(ret_left);

        if (left == 0) {
                *ret_next_event = NULL;
                *ret_left = 0;
                return 0;
        }

        r = tcg_pcr_event2_digests_size(algorithms, n_algorithms, &digests_size);
        if (r < 0)
                return r;

        if (left < (uint64_t) offsetof(TCG_PCR_EVENT2, digests.digests) + (uint64_t) digests_size + sizeof(uint32_t))
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event header too short.");

        if (event->digests.count != n_algorithms)
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Number of digests in event doesn't match log.");

        uint32_t eventSize = unaligned_read_ne32((const uint8_t*) &event->digests.digests + digests_size);
        uint64_t size = (uint64_t) offsetof(TCG_PCR_EVENT2, digests.digests) + (uint64_t) digests_size + sizeof(uint32_t) + eventSize;

        if (size > left)
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event header too short.");

        *ret_next_event = (const TCG_PCR_EVENT2*) ((const uint8_t*) event + size);
        *ret_left = left - size;

        if (ret_payload)
                *ret_payload = (const uint8_t*) &event->digests.digests + digests_size + sizeof(uint32_t);
        if (ret_payload_size)
                *ret_payload_size = eventSize;

        return 1;
}

int validate_firmware_header(
                const void *start,
                size_t size,
                const TCG_EfiSpecIdEventAlgorithmSize **ret_algorithms,
                size_t *ret_n_algorithms,
                const TCG_PCR_EVENT2 **ret_first,
                size_t *ret_left) {

        assert(start || size == 0);
        assert(ret_algorithms);
        assert(ret_n_algorithms);
        assert(ret_first);
        assert(ret_left);

        if (size < offsetof(TCG_PCClientPCREvent, event))
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log too short for TCG_PCClientPCREvent.");

        const TCG_PCClientPCREvent *h = start;

        if (size < (uint64_t) offsetof(TCG_PCClientPCREvent, event) + (uint64_t) h->eventDataSize)
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log too short for TCG_PCClientPCREvent events data.");

        if (h->pcrIndex != 0)
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected PCR index %" PRIu32, h->pcrIndex);
        if (h->eventType != EV_NO_ACTION)
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected event type 0x%" PRIx32, h->eventType);
        if (!memeqzero(h->digest, sizeof(h->digest)))
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected non-zero digest.");

        if (h->eventDataSize < offsetof(TCG_EfiSpecIDEvent, digestSizes))
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header too short for TCG_EfiSpecIdEvent.");

        const TCG_EfiSpecIDEvent *id = (const TCG_EfiSpecIDEvent*) h->event;

        /* Signature as per "TCG PC Client Specific Platform Firmware Profile Specification"
         * (https://trustedcomputinggroup.org/resource/pc-client-specific-platform-firmware-profile-specification/),
         * section 10.4.5.1 "Specification ID Version Event" (at least in version 1.05 Revision 23 of the
         * spec) */
        if (memcmp(id->signature,
                   (const uint8_t[]) { 0x53, 0x70, 0x65, 0x63, 0x20, 0x49, 0x44, 0x20, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x33, 0x00 },
                   sizeof(id->signature)) != 0)
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing TPM2 event log signature.");

        if (id->numberOfAlgorithms <= 0)
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Number of advertised hash algorithms is zero.");
        if (id->numberOfAlgorithms > UINT32_MAX / sizeof(TCG_EfiSpecIdEventAlgorithmSize))
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Number of advertised hash algorithms too large.");

        log_debug("TPM PC Client Platform Firmware Profile: family %u.%u, revision %u.%u",
                  id->specVersionMajor, id->specVersionMinor,
                  id->specErrata / 100, id->specErrata % 100);

        if (h->eventDataSize < (uint64_t) offsetof(TCG_EfiSpecIDEvent, digestSizes) + (uint64_t) (id->numberOfAlgorithms * sizeof(TCG_EfiSpecIdEventAlgorithmSize)) + 1U)
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header doesn't fit all algorithms.");

        uint8_t vendorInfoSize = *((const uint8_t*) id + offsetof(TCG_EfiSpecIDEvent, digestSizes) + (id->numberOfAlgorithms * sizeof(TCG_EfiSpecIdEventAlgorithmSize)));
        if (h->eventDataSize != offsetof(TCG_EfiSpecIDEvent, digestSizes) + (id->numberOfAlgorithms * sizeof(TCG_EfiSpecIdEventAlgorithmSize)) + 1U + vendorInfoSize)
                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header doesn't fit vendor info.");

        for (size_t i = 0; i < id->numberOfAlgorithms; i++) {
                const EVP_MD *implementation;
                const char *a;

                a = tpm2_hash_alg_to_string(id->digestSizes[i].algorithmId);
                if (!a) {
                        log_notice("Event log advertises unknown hash algorithm 0x%4x, can't validate.", id->digestSizes[i].algorithmId);
                        continue;
                }

                implementation = EVP_get_digestbyname(a);
                if (!implementation) {
                        log_notice("Event log advertises hash algorithm '%s' we don't implement, can't validate.", a);
                        continue;
                }

                if (EVP_MD_size(implementation) !=  id->digestSizes[i].digestSize)
                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Advertised digest size for '%s' is wrong, refusing.", a);
        }

        *ret_algorithms = id->digestSizes;
        *ret_n_algorithms = id->numberOfAlgorithms;

        size_t offset = offsetof(TCG_PCClientPCREvent, event) + h->eventDataSize;
        *ret_first = (TCG_PCR_EVENT2*) ((const uint8_t*) h + offset);
        *ret_left = size - offset;

        return 0;
}