isherman | 40e54e0 | 2014-10-15 02:20:07 | [diff] [blame] | 1 | // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "components/proximity_auth/client.h" |
| 6 | |
| 7 | #include "base/json/json_reader.h" |
| 8 | #include "base/json/json_writer.h" |
| 9 | #include "base/values.h" |
isherman | 40e54e0 | 2014-10-15 02:20:07 | [diff] [blame] | 10 | #include "components/proximity_auth/client_observer.h" |
| 11 | #include "components/proximity_auth/connection.h" |
tengs | f32e94c | 2015-04-09 07:49:10 | [diff] [blame] | 12 | #include "components/proximity_auth/cryptauth/base64url.h" |
isherman | 40e54e0 | 2014-10-15 02:20:07 | [diff] [blame] | 13 | #include "components/proximity_auth/remote_status_update.h" |
| 14 | #include "components/proximity_auth/secure_context.h" |
| 15 | #include "components/proximity_auth/wire_message.h" |
| 16 | |
| 17 | namespace proximity_auth { |
| 18 | namespace { |
| 19 | |
| 20 | // The key names of JSON fields for messages sent between the devices. |
| 21 | const char kTypeKey[] = "type"; |
| 22 | const char kNameKey[] = "name"; |
| 23 | const char kDataKey[] = "data"; |
| 24 | const char kEncryptedDataKey[] = "encrypted_data"; |
| 25 | |
| 26 | // The types of messages that can be sent and received. |
| 27 | const char kMessageTypeLocalEvent[] = "event"; |
| 28 | const char kMessageTypeRemoteStatusUpdate[] = "status_update"; |
| 29 | const char kMessageTypeDecryptRequest[] = "decrypt_request"; |
| 30 | const char kMessageTypeDecryptResponse[] = "decrypt_response"; |
| 31 | const char kMessageTypeUnlockRequest[] = "unlock_request"; |
| 32 | const char kMessageTypeUnlockResponse[] = "unlock_response"; |
| 33 | |
| 34 | // The name for an unlock event originating from the local device. |
| 35 | const char kUnlockEventName[] = "easy_unlock"; |
| 36 | |
| 37 | // Serializes the |value| to a JSON string and returns the result. |
| 38 | std::string SerializeValueToJson(const base::Value& value) { |
| 39 | std::string json; |
| 40 | base::JSONWriter::Write(&value, &json); |
| 41 | return json; |
| 42 | } |
| 43 | |
| 44 | // Returns the message type represented by the |message|. This is a convenience |
| 45 | // wrapper that should only be called when the |message| is known to specify its |
| 46 | // message type, i.e. this should not be called for untrusted input. |
| 47 | std::string GetMessageType(const base::DictionaryValue& message) { |
| 48 | std::string type; |
| 49 | message.GetString(kTypeKey, &type); |
| 50 | return type; |
| 51 | } |
| 52 | |
| 53 | } // namespace |
| 54 | |
| 55 | Client::Client(scoped_ptr<Connection> connection, |
| 56 | scoped_ptr<SecureContext> secure_context) |
| 57 | : connection_(connection.Pass()), secure_context_(secure_context.Pass()) { |
| 58 | DCHECK(connection_->IsConnected()); |
| 59 | connection_->AddObserver(this); |
| 60 | } |
| 61 | |
| 62 | Client::~Client() { |
| 63 | if (connection_) |
| 64 | connection_->RemoveObserver(this); |
| 65 | } |
| 66 | |
| 67 | void Client::AddObserver(ClientObserver* observer) { |
| 68 | observers_.AddObserver(observer); |
| 69 | } |
| 70 | |
| 71 | void Client::RemoveObserver(ClientObserver* observer) { |
| 72 | observers_.RemoveObserver(observer); |
| 73 | } |
| 74 | |
| 75 | bool Client::SupportsSignIn() const { |
| 76 | return (secure_context_->GetProtocolVersion() == |
| 77 | SecureContext::PROTOCOL_VERSION_THREE_ONE); |
| 78 | } |
| 79 | |
| 80 | void Client::DispatchUnlockEvent() { |
| 81 | base::DictionaryValue message; |
| 82 | message.SetString(kTypeKey, kMessageTypeLocalEvent); |
| 83 | message.SetString(kNameKey, kUnlockEventName); |
| 84 | queued_messages_.push_back(PendingMessage(message)); |
| 85 | ProcessMessageQueue(); |
| 86 | } |
| 87 | |
| 88 | void Client::RequestDecryption(const std::string& challenge) { |
| 89 | if (!SupportsSignIn()) { |
| 90 | VLOG(1) << "[Client] Dropping decryption request, as remote device " |
| 91 | << "does not support protocol v3.1."; |
| 92 | FOR_EACH_OBSERVER(ClientObserver, |
| 93 | observers_, |
| 94 | OnDecryptResponse(scoped_ptr<std::string>())); |
| 95 | return; |
| 96 | } |
| 97 | |
| 98 | // TODO(isherman): Compute the encrypted message data for realz. |
| 99 | const std::string encrypted_message_data = challenge; |
| 100 | std::string encrypted_message_data_base64; |
| 101 | Base64UrlEncode(encrypted_message_data, &encrypted_message_data_base64); |
| 102 | |
| 103 | base::DictionaryValue message; |
| 104 | message.SetString(kTypeKey, kMessageTypeDecryptRequest); |
| 105 | message.SetString(kEncryptedDataKey, encrypted_message_data_base64); |
| 106 | queued_messages_.push_back(PendingMessage(message)); |
| 107 | ProcessMessageQueue(); |
| 108 | } |
| 109 | |
| 110 | void Client::RequestUnlock() { |
| 111 | if (!SupportsSignIn()) { |
| 112 | VLOG(1) << "[Client] Dropping unlock request, as remote device does not " |
| 113 | << "support protocol v3.1."; |
| 114 | FOR_EACH_OBSERVER(ClientObserver, observers_, OnUnlockResponse(false)); |
| 115 | return; |
| 116 | } |
| 117 | |
| 118 | base::DictionaryValue message; |
| 119 | message.SetString(kTypeKey, kMessageTypeUnlockRequest); |
| 120 | queued_messages_.push_back(PendingMessage(message)); |
| 121 | ProcessMessageQueue(); |
| 122 | } |
| 123 | |
| 124 | Client::PendingMessage::PendingMessage() { |
| 125 | } |
| 126 | |
| 127 | Client::PendingMessage::PendingMessage(const base::DictionaryValue& message) |
| 128 | : json_message(SerializeValueToJson(message)), |
| 129 | type(GetMessageType(message)) { |
| 130 | } |
| 131 | |
| 132 | Client::PendingMessage::~PendingMessage() { |
| 133 | } |
| 134 | |
| 135 | void Client::ProcessMessageQueue() { |
| 136 | if (pending_message_ || queued_messages_.empty() || |
| 137 | connection_->is_sending_message()) |
| 138 | return; |
| 139 | |
| 140 | pending_message_.reset(new PendingMessage(queued_messages_.front())); |
| 141 | queued_messages_.pop_front(); |
| 142 | |
| 143 | connection_->SendMessage(make_scoped_ptr(new WireMessage( |
| 144 | std::string(), secure_context_->Encode(pending_message_->json_message)))); |
| 145 | } |
| 146 | |
| 147 | void Client::HandleRemoteStatusUpdateMessage( |
| 148 | const base::DictionaryValue& message) { |
| 149 | scoped_ptr<RemoteStatusUpdate> status_update = |
| 150 | RemoteStatusUpdate::Deserialize(message); |
| 151 | if (!status_update) { |
| 152 | VLOG(1) << "[Client] Unexpected remote status update: " << message; |
| 153 | return; |
| 154 | } |
| 155 | |
| 156 | FOR_EACH_OBSERVER( |
| 157 | ClientObserver, observers_, OnRemoteStatusUpdate(*status_update)); |
| 158 | } |
| 159 | |
| 160 | void Client::HandleDecryptResponseMessage( |
| 161 | const base::DictionaryValue& message) { |
| 162 | std::string base64_data; |
| 163 | std::string decrypted_data; |
| 164 | scoped_ptr<std::string> response; |
| 165 | if (!message.GetString(kDataKey, &base64_data) || base64_data.empty()) { |
| 166 | VLOG(1) << "[Client] Decrypt response missing '" << kDataKey << "' value."; |
| 167 | } else if (!Base64UrlDecode(base64_data, &decrypted_data)) { |
| 168 | VLOG(1) << "[Client] Unable to base64-decode decrypt response."; |
| 169 | } else { |
| 170 | response.reset(new std::string(decrypted_data)); |
| 171 | } |
| 172 | FOR_EACH_OBSERVER( |
| 173 | ClientObserver, observers_, OnDecryptResponse(response.Pass())); |
| 174 | } |
| 175 | |
| 176 | void Client::HandleUnlockResponseMessage(const base::DictionaryValue& message) { |
| 177 | FOR_EACH_OBSERVER(ClientObserver, observers_, OnUnlockResponse(true)); |
| 178 | } |
| 179 | |
isherman | fd7c534 | 2015-04-24 00:43:53 | [diff] [blame] | 180 | void Client::OnConnectionStatusChanged(Connection* connection, |
isherman | 40e54e0 | 2014-10-15 02:20:07 | [diff] [blame] | 181 | Connection::Status old_status, |
| 182 | Connection::Status new_status) { |
isherman | fd7c534 | 2015-04-24 00:43:53 | [diff] [blame] | 183 | DCHECK_EQ(connection, connection_.get()); |
isherman | 40e54e0 | 2014-10-15 02:20:07 | [diff] [blame] | 184 | if (new_status != Connection::CONNECTED) { |
| 185 | VLOG(1) << "[Client] Secure channel disconnected..."; |
| 186 | connection_->RemoveObserver(this); |
| 187 | connection_.reset(); |
| 188 | FOR_EACH_OBSERVER(ClientObserver, observers_, OnDisconnected()); |
| 189 | // TODO(isherman): Determine whether it's also necessary/appropriate to fire |
| 190 | // this notification from the destructor. |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | void Client::OnMessageReceived(const Connection& connection, |
| 195 | const WireMessage& wire_message) { |
| 196 | std::string json_message = secure_context_->Decode(wire_message.payload()); |
| 197 | scoped_ptr<base::Value> message_value(base::JSONReader::Read(json_message)); |
| 198 | if (!message_value || !message_value->IsType(base::Value::TYPE_DICTIONARY)) { |
| 199 | VLOG(1) << "[Client] Unable to parse message as JSON: " << json_message |
| 200 | << "."; |
| 201 | return; |
| 202 | } |
| 203 | |
| 204 | base::DictionaryValue* message; |
| 205 | bool success = message_value->GetAsDictionary(&message); |
| 206 | DCHECK(success); |
| 207 | |
| 208 | std::string type; |
| 209 | if (!message->GetString(kTypeKey, &type)) { |
| 210 | VLOG(1) << "[Client] Missing '" << kTypeKey |
| 211 | << "' key in message: " << json_message << "."; |
| 212 | return; |
| 213 | } |
| 214 | |
| 215 | // Remote status updates can be received out of the blue. |
| 216 | if (type == kMessageTypeRemoteStatusUpdate) { |
| 217 | HandleRemoteStatusUpdateMessage(*message); |
| 218 | return; |
| 219 | } |
| 220 | |
| 221 | // All other messages should only be received in response to a message that |
| 222 | // the client sent. |
| 223 | if (!pending_message_) { |
| 224 | VLOG(1) << "[Client] Unexpected message received: " << json_message; |
| 225 | return; |
| 226 | } |
| 227 | |
| 228 | std::string expected_type; |
| 229 | if (pending_message_->type == kMessageTypeDecryptRequest) |
| 230 | expected_type = kMessageTypeDecryptResponse; |
| 231 | else if (pending_message_->type == kMessageTypeUnlockRequest) |
| 232 | expected_type = kMessageTypeUnlockResponse; |
| 233 | else |
| 234 | NOTREACHED(); // There are no other message types that expect a response. |
| 235 | |
| 236 | if (type != expected_type) { |
| 237 | VLOG(1) << "[Client] Unexpected '" << kTypeKey << "' value in message. " |
| 238 | << "Expected '" << expected_type << "' but received '" << type |
| 239 | << "'."; |
| 240 | return; |
| 241 | } |
| 242 | |
| 243 | if (type == kMessageTypeDecryptResponse) |
| 244 | HandleDecryptResponseMessage(*message); |
| 245 | else if (type == kMessageTypeUnlockResponse) |
| 246 | HandleUnlockResponseMessage(*message); |
| 247 | else |
| 248 | NOTREACHED(); // There are no other message types that expect a response. |
| 249 | |
| 250 | pending_message_.reset(); |
| 251 | ProcessMessageQueue(); |
| 252 | } |
| 253 | |
| 254 | void Client::OnSendCompleted(const Connection& connection, |
| 255 | const WireMessage& wire_message, |
| 256 | bool success) { |
| 257 | if (!pending_message_) { |
| 258 | VLOG(1) << "[Client] Unexpected message sent."; |
| 259 | return; |
| 260 | } |
| 261 | |
| 262 | // In the common case, wait for a response from the remote device. |
| 263 | // Don't wait if the message could not be sent, as there won't ever be a |
| 264 | // response in that case. Likewise, don't wait for a response to local |
| 265 | // event messages, as there is no response for such messages. |
| 266 | if (success && pending_message_->type != kMessageTypeLocalEvent) |
| 267 | return; |
| 268 | |
| 269 | // Notify observer of failure if sending the message fails. |
| 270 | // For local events, we don't expect a response, so on success, we |
| 271 | // notify observers right away. |
| 272 | if (pending_message_->type == kMessageTypeDecryptRequest) { |
| 273 | FOR_EACH_OBSERVER(ClientObserver, |
| 274 | observers_, |
| 275 | OnDecryptResponse(scoped_ptr<std::string>())); |
| 276 | } else if (pending_message_->type == kMessageTypeUnlockRequest) { |
| 277 | FOR_EACH_OBSERVER(ClientObserver, observers_, OnUnlockResponse(false)); |
| 278 | } else if (pending_message_->type == kMessageTypeLocalEvent) { |
| 279 | FOR_EACH_OBSERVER(ClientObserver, observers_, OnUnlockEventSent(success)); |
| 280 | } else { |
| 281 | VLOG(1) << "[Client] Message of unknown type '" << pending_message_->type |
| 282 | << "sent."; |
| 283 | } |
| 284 | |
| 285 | pending_message_.reset(); |
| 286 | ProcessMessageQueue(); |
| 287 | } |
| 288 | |
| 289 | } // namespace proximity_auth |