1 /*
2 * Copyright 2017, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #ifndef TEEUI_GENERICOPERATION_H_
18 #define TEEUI_GENERICOPERATION_H_
19
20 #include <teeui/cbor.h>
21 #include <teeui/generic_messages.h>
22 #include <teeui/msg_formatting.h>
23 #include <teeui/utils.h>
24
25 namespace teeui {
26
hasOption(UIOption option,const MsgVector<UIOption> & uiOptions)27 inline bool hasOption(UIOption option, const MsgVector<UIOption>& uiOptions) {
28 for (auto& o : uiOptions) {
29 if (o == option) return true;
30 }
31 return false;
32 }
33
34 /**
35 * The generic Confirmation Operation:
36 *
37 * Derived needs to implement:
38 * Derived::TimeStamp needs to be a timestamp type
39 * Derived::now() needs to return a TimeStamp value denoting the current point in time.
40 * Derived::hmac256 with the following prototype which computes the 32-byte HMAC-SHA256 over the
41 * concatenation of all provided "buffers" keyed with "key".
42 *
43 * optional<Hmac> HMacImplementation::hmac256(const AuthTokenKey& key,
44 * std::initializer_list<ByteBufferProxy> buffers);
45 *
46 * ResponseCode initHook();
47 * Gets called on PromptUserConfirmation. If initHook returns anything but ResponseCode::OK,
48 * The operation is not started and the result is returned to the HAL service.
49 * void abortHook();
50 * Gets called on Abort. Allows the implementation to perform cleanup.
51 * void finalizeHook();
52 * Gets called on FetchConfirmationResult.
53 * ResponseCode testCommandHook(TestModeCommands testCmd);
54 * Gets called on DeliverTestCommand and allows the implementation to react to test commands.
55 *
56 * And optionally:
57 * WriteStream vendorCommandHook(ReadStream in, WriteStream out);
58 *
59 * The latter allows Implementations to implement custom protocol extensions.
60 *
61 */
62 template <typename Derived, typename TimeStamp> class Operation {
63 using HMacer = HMac<Derived>;
64
65 public:
Operation()66 Operation()
67 : error_(ResponseCode::Ignored), formattedMessageLength_(0),
68 maginifiedViewRequested_(false), invertedColorModeRequested_(false),
69 languageIdLength_(0) {}
70
init(const MsgString & promptText,const MsgVector<uint8_t> & extraData,const MsgString & locale,const MsgVector<UIOption> & options)71 ResponseCode init(const MsgString& promptText, const MsgVector<uint8_t>& extraData,
72 const MsgString& locale, const MsgVector<UIOption>& options) {
73 // An hmacKey needs to be installed before we can commence operation.
74 if (!hmacKey_) return ResponseCode::Unexpected;
75 if (error_ != ResponseCode::Ignored) return ResponseCode::OperationPending;
76 confirmationTokenScratchpad_ = {};
77
78 // We need to access the prompt text multiple times. Once for formatting the CBOR message
79 // and again for rendering the dialog. It is vital that the prompt does not change
80 // in the meantime. As of this point the prompt text is in a shared buffer and therefore
81 // susceptible to TOCTOU attacks. Note that promptText.size() resides on the stack and
82 // is safe to access multiple times. So now we copy the prompt string into the
83 // scratchpad promptStringBuffer_ from where we can format the CBOR message and then
84 // pass it to the renderer.
85 if (promptText.size() >= uint32_t(MessageSize::MAX))
86 return ResponseCode::UIErrorMessageTooLong;
87 auto pos = std::copy(promptText.begin(), promptText.end(), promptStringBuffer_);
88 *pos = 0; // null-terminate the prompt for the renderer.
89
90 using namespace ::teeui::cbor;
91 using CborError = ::teeui::cbor::Error;
92 // Note the extra data is accessed only once for formatting the CBOR message. So it is safe
93 // to read it from the shared buffer directly. Anyway we don't trust or interpret the
94 // extra data in any way so all we do is take a snapshot and we don't care if it is
95 // modified concurrently.
96 auto state = write(WriteState(formattedMessageBuffer_),
97 map(pair(text("prompt"), text(promptStringBuffer_, promptText.size())),
98 pair(text("extra"), bytes(extraData))));
99 switch (state.error_) {
100 case CborError::OK:
101 break;
102 case CborError::OUT_OF_DATA:
103 return ResponseCode::UIErrorMessageTooLong;
104 case CborError::MALFORMED_UTF8:
105 return ResponseCode::UIErrorMalformedUTF8Encoding;
106 case CborError::MALFORMED:
107 default:
108 return ResponseCode::Unexpected;
109 }
110 formattedMessageLength_ = state.data_ - formattedMessageBuffer_;
111
112 if (locale.size() >= kMaxLocaleSize) return ResponseCode::UIErrorMessageTooLong;
113 pos = std::copy(locale.begin(), locale.end(), languageIdBuffer_);
114 *pos = 0;
115 languageIdLength_ = locale.size();
116
117 invertedColorModeRequested_ = hasOption(UIOption::AccessibilityInverted, options);
118 maginifiedViewRequested_ = hasOption(UIOption::AccessibilityMagnified, options);
119
120 // on success record the start time
121 startTime_ = Derived::now();
122 if (!startTime_.isOk()) {
123 return ResponseCode::SystemError;
124 }
125
126 auto rc = static_cast<Derived*>(this)->initHook();
127
128 if (rc == ResponseCode::OK) {
129 error_ = ResponseCode::OK;
130 }
131 return rc;
132 }
133
setHmacKey(const AuthTokenKey & key)134 void setHmacKey(const AuthTokenKey& key) { hmacKey_ = key; }
hmacKey()135 optional<AuthTokenKey> hmacKey() const { return hmacKey_; }
136
abort()137 void abort() {
138 if (isPending()) {
139 error_ = ResponseCode::Aborted;
140 static_cast<Derived*>(this)->abortHook();
141 }
142 }
143
userCancel()144 void userCancel() {
145 if (isPending()) {
146 error_ = ResponseCode::Canceled;
147 }
148 }
149
fetchConfirmationResult()150 std::tuple<ResponseCode, MsgVector<uint8_t>, MsgVector<uint8_t>> fetchConfirmationResult() {
151 std::tuple<ResponseCode, MsgVector<uint8_t>, MsgVector<uint8_t>> result;
152 auto& [rc, message, token] = result;
153 rc = error_;
154 if (error_ == ResponseCode::OK) {
155 message = {&formattedMessageBuffer_[0],
156 &formattedMessageBuffer_[formattedMessageLength_]};
157 if (confirmationTokenScratchpad_) {
158 token = {confirmationTokenScratchpad_->begin(),
159 confirmationTokenScratchpad_->end()};
160 }
161 }
162 error_ = ResponseCode::Ignored;
163 static_cast<Derived*>(this)->finalizeHook();
164 return result;
165 }
166
isPending()167 bool isPending() const { return error_ != ResponseCode::Ignored; }
168
getPrompt()169 const MsgString getPrompt() const {
170 return {&promptStringBuffer_[0], &promptStringBuffer_[strlen(promptStringBuffer_)]};
171 }
172
deliverTestCommand(TestModeCommands testCommand)173 ResponseCode deliverTestCommand(TestModeCommands testCommand) {
174 constexpr const auto testKey = AuthTokenKey::fill(static_cast<uint8_t>(TestKeyBits::BYTE));
175 auto rc = static_cast<Derived*>(this)->testCommandHook(testCommand);
176 if (rc != ResponseCode::OK) return rc;
177 switch (testCommand) {
178 case TestModeCommands::OK_EVENT: {
179 if (isPending()) {
180 signConfirmation(testKey);
181 return ResponseCode::OK;
182 } else {
183 return ResponseCode::Ignored;
184 }
185 }
186 case TestModeCommands::CANCEL_EVENT: {
187 bool ignored = !isPending();
188 userCancel();
189 return ignored ? ResponseCode::Ignored : ResponseCode::OK;
190 }
191 default:
192 return ResponseCode::Ignored;
193 }
194 }
195
dispatchCommandMessage(ReadStream in_,WriteStream out)196 WriteStream dispatchCommandMessage(ReadStream in_, WriteStream out) {
197 auto [in, proto] = readProtocol(in_);
198 if (proto == kProtoGeneric) {
199 auto [in_cmd, cmd] = readCommand(in);
200 switch (cmd) {
201 case Command::PromptUserConfirmation:
202 return command(CmdPromptUserConfirmation(), in_cmd, out);
203 case Command::FetchConfirmationResult:
204 return command(CmdFetchConfirmationResult(), in_cmd, out);
205 case Command::DeliverTestCommand:
206 return command(CmdDeliverTestCommand(), in_cmd, out);
207 case Command::Abort:
208 return command(CmdAbort(), in_cmd, out);
209 default:
210 return write(Message<ResponseCode>(), out, ResponseCode::Unimplemented);
211 }
212 }
213 return static_cast<Derived*>(this)->extendedProtocolHook(proto, in, out);
214 }
215
216 protected:
217 /*
218 * The extendedProtocolHoock allows implementations to implement custom protocols on top of
219 * the default commands.
220 * This function is only called if "Derived" does not implement the extendedProtocolHoock
221 * and writes ResponseCode::Unimplemented to the response buffer.
222 */
extendedProtocolHook(Protocol proto,ReadStream,WriteStream out)223 inline WriteStream extendedProtocolHook(Protocol proto, ReadStream, WriteStream out) {
224 return write(Message<ResponseCode>(), out, ResponseCode::Unimplemented);
225 }
226
227 private:
command(CmdPromptUserConfirmation,ReadStream in,WriteStream out)228 WriteStream command(CmdPromptUserConfirmation, ReadStream in, WriteStream out) {
229 auto [in_, promt, extra, locale, options] = read(PromptUserConfirmationMsg(), in);
230 if (!in_) return write(PromptUserConfirmationResponse(), out, ResponseCode::SystemError);
231 auto rc = init(promt, extra, locale, options);
232 return write(PromptUserConfirmationResponse(), out, rc);
233 }
command(CmdFetchConfirmationResult,ReadStream in,WriteStream out)234 WriteStream command(CmdFetchConfirmationResult, ReadStream in, WriteStream out) {
235 auto [rc, message, token] = fetchConfirmationResult();
236 return write(ResultMsg(), out, rc, message, token);
237 }
command(CmdDeliverTestCommand,ReadStream in,WriteStream out)238 WriteStream command(CmdDeliverTestCommand, ReadStream in, WriteStream out) {
239 auto [in_, token] = read(DeliverTestCommandMessage(), in);
240 if (!in_) return write(DeliverTestCommandResponse(), out, ResponseCode::SystemError);
241 auto rc = deliverTestCommand(token);
242 return write(DeliverTestCommandResponse(), out, rc);
243 }
command(CmdAbort,ReadStream in,WriteStream out)244 WriteStream command(CmdAbort, ReadStream in, WriteStream out) {
245 abort();
246 return out;
247 }
248
getMessage()249 MsgVector<uint8_t> getMessage() {
250 MsgVector<uint8_t> result;
251 if (error_ != ResponseCode::OK) return {};
252 return {&formattedMessageBuffer_[0], &formattedMessageBuffer_[formattedMessageLength_]};
253 }
254
255 protected:
signConfirmation(const AuthTokenKey & key)256 void signConfirmation(const AuthTokenKey& key) {
257 if (error_ != ResponseCode::OK) return;
258 confirmationTokenScratchpad_ = HMacer::hmac256(key, "confirmation token", getMessage());
259 if (!confirmationTokenScratchpad_) {
260 error_ = ResponseCode::Unexpected;
261 }
262 }
263
264 protected:
265 ResponseCode error_ = ResponseCode::Ignored;
266 uint8_t formattedMessageBuffer_[uint32_t(MessageSize::MAX)];
267 size_t formattedMessageLength_ = 0;
268 char promptStringBuffer_[uint32_t(MessageSize::MAX)];
269 optional<Hmac> confirmationTokenScratchpad_;
270 TimeStamp startTime_;
271 optional<AuthTokenKey> hmacKey_;
272 bool maginifiedViewRequested_;
273 bool invertedColorModeRequested_;
274 constexpr static size_t kMaxLocaleSize = 64;
275 char languageIdBuffer_[kMaxLocaleSize];
276 size_t languageIdLength_;
277 };
278
279 } // namespace teeui
280
281 #endif // CONFIRMATIONUI_1_0_DEFAULT_GENERICOPERATION_H_
282