aboutsummaryrefslogtreecommitdiff
path: root/pw_software_update/public/pw_software_update/update_bundle_accessor.h
blob: 83b014e1211403815c66e8ae65dfd68c1c829b35 (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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// Copyright 2021 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#pragma once

#include <cstddef>

#include "pw_blob_store/blob_store.h"
#include "pw_protobuf/map_utils.h"
#include "pw_protobuf/message.h"
#include "pw_software_update/bundled_update_backend.h"
#include "pw_software_update/manifest_accessor.h"
#include "pw_stream/memory_stream.h"

namespace pw::software_update {
class BundledUpdateBackend;

constexpr std::string_view kUserManifestTargetFileName = "user_manifest";

// UpdateBundleAccessor is responsible for parsing, verifying and providing
// target payload access of a software update bundle. It takes the following as
// inputs:
//
// 1. A software update bundle via `BlobStore`.
// 2. A `BundledUpdateBackend`, which implements project-specific update
//    operations such as enforcing project update policies and
//    verifying/applying target files on device.
//
// The verification is done according to TUF process. Payload can only be
// accessed after successful verification.
//
// Exmple of use:
//
// UpdateBundleAccessor bundle(blob,helper);
// auto status = bundle.OpenAndVerify();
// if (!status.ok()) {
//   // handle error
//   ...
// }
//
// // Examine and use payload.
// auto exist = bundle.IsTargetPayloadIncluded("audio");
// if (!exist.ok() || !exist.value()) {
//   // handle error
//   ...
// }
//
// auto payload_reader = bundle.GetTargetPayload("audio");
// // Process payload
// ...
//
// // Get bundle's manifest and write it to the given writer.
// status = bundle.PersistManifest(staged_manifest_writer);
// if (!status.ok()) {
//   // handle error
//   ...
// }
//
// status = bundle.Close();
// if (!status.ok()) {
//   // handle error
//   ...
// }
class UpdateBundleAccessor {
 public:
  // UpdateBundleAccessor
  // bundle - The software update bundle data on storage.
  // backend - Project-specific BundledUpdateBackend.
  // disable_verification - Disable verification.
  constexpr UpdateBundleAccessor(blob_store::BlobStore& bundle,
                                 BundledUpdateBackend& backend,
                                 bool disable_verification = false)
      : bundle_(bundle),
        backend_(backend),
        bundle_reader_(bundle_),
        disable_verification_(disable_verification) {}

  // Opens and verifies the software update bundle.
  //
  // Specifically, the opening process opens a blob reader to the given bundle
  // and initializes the bundle proto parser. No write will be allowed to the
  // bundle until Close() is called.
  //
  // If bundle verification is enabled (see the `option` argument in
  // the constructor), the verification process does the following:
  //
  // 1. Check whether the bundle contains an incoming new root metadata. If it
  // does, it verifies the root against the current on-device root. If
  // successful, the on-device root will be updated to the new root.
  //
  // 2. Verify the targets metadata against the current trusted root.
  //
  // 3. Either verify all target payloads (size and hash) or defer that
  // verification till when a target is accessed.
  //
  // 4. Invoke the backend to do downstream verification of the bundle.
  //
  // Returns:
  // OK - Bundle was successfully opened and verified.
  Status OpenAndVerify();

  // Closes the bundle by invalidating the verification and closing
  // the reader to release the read-only lock
  //
  // Returns:
  // OK - success.
  // DATA_LOSS - Error writing data or fail to verify written data.
  Status Close();

  // Writes the manifest of the staged bundle to the given writer.
  //
  // Returns:
  // FAILED_PRECONDITION - Bundle is not open and verified.
  // TODO(pwbug/456): Add other error codes if necessary.
  Status PersistManifest(stream::Writer& staged_manifest_writer);

  // Is the target payload present in the bundle (not personalized out).
  //
  // Returns:
  // OK - Whether or not the target_file was included in the UpdateBundle or
  //      whether it was personalized out.
  // FAILED_PRECONDITION - Bundle is not open and verified.
  // TODO(pwbug/456): Add other error codes if necessary.
  Result<bool> IsTargetPayloadIncluded(std::string_view target_file);

  // Returns a reader for the target file by `target_file` in the update
  // bundle.
  //
  // Returns:
  // A reader instance for the target file.
  // TODO(pwbug/456): Figure out a way to propagate error.
  stream::IntervalReader GetTargetPayload(std::string_view target_file);

  // Returns a protobuf::Message representation of the update bundle.
  //
  // Returns:
  // An instance of protobuf::Message of the udpate bundle.
  // FAILED_PRECONDITION - Bundle is not open and verified.
  protobuf::Message GetDecoder();

  ManifestAccessor GetManifestAccessor() { return ManifestAccessor(this); };

 private:
  blob_store::BlobStore& bundle_;
  BundledUpdateBackend& backend_;
  blob_store::BlobStore::BlobReader bundle_reader_;
  protobuf::Message decoder_;
  bool disable_verification_;
  bool bundle_verified_ = false;

  // Opens the bundle for read-only access and readies the parser.
  Status DoOpen();

  // Performs TUF and downstream custom verification.
  Status DoVerify();

  // The method checks whether the update bundle contains a root metadata
  // different from the on-device one. If it does, it performs the following
  // verification and upgrade flow:
  //
  // 1. Verify the signatures according to the on-device trusted
  // disable_verificationroot metadata
  //    obtained from the backend.
  // 2. Verify content of the new root metadata, including:
  //    1) Check role magic field.
  //    2) Check signature requirement. Specifically, check that no key is
  //       reused across different roles and keys are unique in the same
  //       requirement.
  //    3) Check key mapping. Specifically, check that all keys are unique,
  //       ECDSA keys, and the key ids are exactly the SHA256 of `key type +
  //       key scheme + key value`.
  // 3. Verify the signatures against the new root metadata.
  // 4. Check rollback.
  // 5. Update on-device root metadata.
  Status UpgradeRoot();

  // The method verifies the top-level targets metadata against the trusted
  // root. The verification includes the following:
  //
  // 1. Verify the signatures of the targets metadata.
  // 2. Check the content of the targets metadata.
  // 3. Check rollback against the version from on-device manifest, if one
  //    exists (the manifest may be reset in the case of key rotation).
  //
  // TODO(pwbug/456): Should manifest persisting be handled here? The current
  // API design of this class exposes a PersistManifest() method, which implies
  // that manifest persisting is handled by some higher level logic.
  Status VerifyTargetsMetadata();

  // A helper to get the on-device trusted root metadata. It returns an
  // instance of SignedRootMetadata proto message.
  protobuf::Message GetOnDeviceTrustedRoot();

  // The method performs verification of the target payloads. Specifically, it
  // 1. For target payloads found in the bundle, verify its size and hash.
  // 2. For target payloads not found in the bundle, call downstream to verify
  // it and report back.
  Status VerifyTargetsPayloads();
};

}  // namespace pw::software_update