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