| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2012 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 """A bare-bones test server for testing cloud policy support. | |
| 6 | |
| 7 This implements a simple cloud policy test server that can be used to test | |
| 8 chrome's device management service client. The policy information is read from | |
| 9 the file named device_management in the server's data directory. It contains | |
| 10 enforced and recommended policies for the device and user scope, and a list | |
| 11 of managed users. | |
| 12 | |
| 13 The format of the file is JSON. The root dictionary contains a list under the | |
| 14 key "managed_users". It contains auth tokens for which the server will claim | |
| 15 that the user is managed. The token string "*" indicates that all users are | |
| 16 claimed to be managed. Other keys in the root dictionary identify request | |
| 17 scopes. The user-request scope is described by a dictionary that holds two | |
| 18 sub-dictionaries: "mandatory" and "recommended". Both these hold the policy | |
| 19 definitions as key/value stores, their format is identical to what the Linux | |
| 20 implementation reads from /etc. | |
| 21 The device-scope holds the policy-definition directly as key/value stores in the | |
| 22 protobuf-format. | |
| 23 | |
| 24 Example: | |
| 25 | |
| 26 { | |
| 27 "google/chromeos/device" : { | |
| 28 "guest_mode_enabled" : false | |
| 29 }, | |
| 30 "google/chromeos/user" : { | |
| 31 "mandatory" : { | |
| 32 "HomepageLocation" : "http://www.chromium.org", | |
| 33 "IncognitoEnabled" : false | |
| 34 }, | |
| 35 "recommended" : { | |
| 36 "JavascriptEnabled": false | |
| 37 } | |
| 38 }, | |
| 39 "google/chromeos/publicaccount/user@example.com" : { | |
| 40 "mandatory" : { | |
| 41 "HomepageLocation" : "http://www.chromium.org" | |
| 42 }, | |
| 43 "recommended" : { | |
| 44 } | |
| 45 }, | |
| 46 "managed_users" : [ | |
| 47 "secret123456" | |
| 48 ], | |
| 49 "current_key_index": 0 | |
| 50 } | |
| 51 | |
| 52 """ | |
| 53 | |
| 54 import cgi | |
| 55 import hashlib | |
| 56 import logging | |
| 57 import os | |
| 58 import random | |
| 59 import re | |
| 60 import sys | |
| 61 import time | |
| 62 import tlslite | |
| 63 import tlslite.api | |
| 64 import tlslite.utils | |
| 65 | |
| 66 # The name and availability of the json module varies in python versions. | |
| 67 try: | |
| 68 import simplejson as json | |
| 69 except ImportError: | |
| 70 try: | |
| 71 import json | |
| 72 except ImportError: | |
| 73 json = None | |
| 74 | |
| 75 import asn1der | |
| 76 import device_management_backend_pb2 as dm | |
| 77 import cloud_policy_pb2 as cp | |
| 78 import chrome_device_policy_pb2 as dp | |
| 79 | |
| 80 # ASN.1 object identifier for PKCS#1/RSA. | |
| 81 PKCS1_RSA_OID = '\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01' | |
| 82 | |
| 83 # SHA256 sum of "0". | |
| 84 SHA256_0 = hashlib.sha256('0').digest() | |
| 85 | |
| 86 # List of bad machine identifiers that trigger the |valid_serial_number_missing| | |
| 87 # flag to be set set in the policy fetch response. | |
| 88 BAD_MACHINE_IDS = [ '123490EN400015' ]; | |
| 89 | |
| 90 # List of machines that trigger the server to send kiosk enrollment response | |
| 91 # for the register request. | |
| 92 KIOSK_MACHINE_IDS = [ 'KIOSK' ]; | |
| 93 | |
| 94 class RequestHandler(object): | |
| 95 """Decodes and handles device management requests from clients. | |
| 96 | |
| 97 The handler implements all the request parsing and protobuf message decoding | |
| 98 and encoding. It calls back into the server to lookup, register, and | |
| 99 unregister clients. | |
| 100 """ | |
| 101 | |
| 102 def __init__(self, server, path, headers, request): | |
| 103 """Initialize the handler. | |
| 104 | |
| 105 Args: | |
| 106 server: The TestServer object to use for (un)registering clients. | |
| 107 path: A string containing the request path and query parameters. | |
| 108 headers: A rfc822.Message-like object containing HTTP headers. | |
| 109 request: The request data received from the client as a string. | |
| 110 """ | |
| 111 self._server = server | |
| 112 self._path = path | |
| 113 self._headers = headers | |
| 114 self._request = request | |
| 115 self._params = None | |
| 116 | |
| 117 def GetUniqueParam(self, name): | |
| 118 """Extracts a unique query parameter from the request. | |
| 119 | |
| 120 Args: | |
| 121 name: Names the parameter to fetch. | |
| 122 Returns: | |
| 123 The parameter value or None if the parameter doesn't exist or is not | |
| 124 unique. | |
| 125 """ | |
| 126 if not self._params: | |
| 127 self._params = cgi.parse_qs(self._path[self._path.find('?') + 1:]) | |
| 128 | |
| 129 param_list = self._params.get(name, []) | |
| 130 if len(param_list) == 1: | |
| 131 return param_list[0] | |
| 132 return None; | |
| 133 | |
| 134 def HandleRequest(self): | |
| 135 """Handles a request. | |
| 136 | |
| 137 Parses the data supplied at construction time and returns a pair indicating | |
| 138 http status code and response data to be sent back to the client. | |
| 139 | |
| 140 Returns: | |
| 141 A tuple of HTTP status code and response data to send to the client. | |
| 142 """ | |
| 143 rmsg = dm.DeviceManagementRequest() | |
| 144 rmsg.ParseFromString(self._request) | |
| 145 | |
| 146 logging.debug('gaia auth token -> ' + | |
| 147 self._headers.getheader('Authorization', '')) | |
| 148 logging.debug('oauth token -> ' + str(self.GetUniqueParam('oauth_token'))) | |
| 149 logging.debug('deviceid -> ' + str(self.GetUniqueParam('deviceid'))) | |
| 150 self.DumpMessage('Request', rmsg) | |
| 151 | |
| 152 request_type = self.GetUniqueParam('request') | |
| 153 # Check server side requirements, as defined in | |
| 154 # device_management_backend.proto. | |
| 155 if (self.GetUniqueParam('devicetype') != '2' or | |
| 156 self.GetUniqueParam('apptype') != 'Chrome' or | |
| 157 (request_type != 'ping' and | |
| 158 len(self.GetUniqueParam('deviceid')) >= 64) or | |
| 159 len(self.GetUniqueParam('agent')) >= 64): | |
| 160 return (400, 'Invalid request parameter') | |
| 161 if request_type == 'register': | |
| 162 return self.ProcessRegister(rmsg.register_request) | |
| 163 elif request_type == 'unregister': | |
| 164 return self.ProcessUnregister(rmsg.unregister_request) | |
| 165 elif request_type == 'policy' or request_type == 'ping': | |
| 166 return self.ProcessPolicy(rmsg.policy_request, request_type) | |
| 167 elif request_type == 'enterprise_check': | |
| 168 return self.ProcessAutoEnrollment(rmsg.auto_enrollment_request) | |
| 169 else: | |
| 170 return (400, 'Invalid request parameter') | |
| 171 | |
| 172 def CheckGoogleLogin(self): | |
| 173 """Extracts the auth token from the request and returns it. The token may | |
| 174 either be a GoogleLogin token from an Authorization header, or an OAuth V2 | |
| 175 token from the oauth_token query parameter. Returns None if no token is | |
| 176 present. | |
| 177 """ | |
| 178 oauth_token = self.GetUniqueParam('oauth_token') | |
| 179 if oauth_token: | |
| 180 return oauth_token | |
| 181 | |
| 182 match = re.match('GoogleLogin auth=(\\w+)', | |
| 183 self._headers.getheader('Authorization', '')) | |
| 184 if match: | |
| 185 return match.group(1) | |
| 186 | |
| 187 return None | |
| 188 | |
| 189 def ProcessRegister(self, msg): | |
| 190 """Handles a register request. | |
| 191 | |
| 192 Checks the query for authorization and device identifier, registers the | |
| 193 device with the server and constructs a response. | |
| 194 | |
| 195 Args: | |
| 196 msg: The DeviceRegisterRequest message received from the client. | |
| 197 | |
| 198 Returns: | |
| 199 A tuple of HTTP status code and response data to send to the client. | |
| 200 """ | |
| 201 # Check the auth token and device ID. | |
| 202 auth = self.CheckGoogleLogin() | |
| 203 if not auth: | |
| 204 return (403, 'No authorization') | |
| 205 | |
| 206 policy = self._server.GetPolicies() | |
| 207 if ('*' not in policy['managed_users'] and | |
| 208 auth not in policy['managed_users']): | |
| 209 return (403, 'Unmanaged') | |
| 210 | |
| 211 device_id = self.GetUniqueParam('deviceid') | |
| 212 if not device_id: | |
| 213 return (400, 'Missing device identifier') | |
| 214 | |
| 215 token_info = self._server.RegisterDevice(device_id, | |
| 216 msg.machine_id, | |
| 217 msg.type) | |
| 218 | |
| 219 # Send back the reply. | |
| 220 response = dm.DeviceManagementResponse() | |
| 221 response.register_response.device_management_token = ( | |
| 222 token_info['device_token']) | |
| 223 response.register_response.machine_name = token_info['machine_name'] | |
| 224 response.register_response.enrollment_type = token_info['enrollment_mode'] | |
| 225 | |
| 226 self.DumpMessage('Response', response) | |
| 227 | |
| 228 return (200, response.SerializeToString()) | |
| 229 | |
| 230 def ProcessUnregister(self, msg): | |
| 231 """Handles a register request. | |
| 232 | |
| 233 Checks for authorization, unregisters the device and constructs the | |
| 234 response. | |
| 235 | |
| 236 Args: | |
| 237 msg: The DeviceUnregisterRequest message received from the client. | |
| 238 | |
| 239 Returns: | |
| 240 A tuple of HTTP status code and response data to send to the client. | |
| 241 """ | |
| 242 # Check the management token. | |
| 243 token, response = self.CheckToken(); | |
| 244 if not token: | |
| 245 return response | |
| 246 | |
| 247 # Unregister the device. | |
| 248 self._server.UnregisterDevice(token['device_token']); | |
| 249 | |
| 250 # Prepare and send the response. | |
| 251 response = dm.DeviceManagementResponse() | |
| 252 response.unregister_response.CopyFrom(dm.DeviceUnregisterResponse()) | |
| 253 | |
| 254 self.DumpMessage('Response', response) | |
| 255 | |
| 256 return (200, response.SerializeToString()) | |
| 257 | |
| 258 def ProcessPolicy(self, msg, request_type): | |
| 259 """Handles a policy request. | |
| 260 | |
| 261 Checks for authorization, encodes the policy into protobuf representation | |
| 262 and constructs the response. | |
| 263 | |
| 264 Args: | |
| 265 msg: The DevicePolicyRequest message received from the client. | |
| 266 | |
| 267 Returns: | |
| 268 A tuple of HTTP status code and response data to send to the client. | |
| 269 """ | |
| 270 for request in msg.request: | |
| 271 if (request.policy_type in | |
| 272 ('google/chrome/user', | |
| 273 'google/chromeos/user', | |
| 274 'google/chromeos/device', | |
| 275 'google/chromeos/publicaccount')): | |
| 276 if request_type != 'policy': | |
| 277 return (400, 'Invalid request type') | |
| 278 else: | |
| 279 return self.ProcessCloudPolicy(request) | |
| 280 else: | |
| 281 return (400, 'Invalid policy_type') | |
| 282 | |
| 283 def ProcessAutoEnrollment(self, msg): | |
| 284 """Handles an auto-enrollment check request. | |
| 285 | |
| 286 The reply depends on the value of the modulus: | |
| 287 1: replies with no new modulus and the sha256 hash of "0" | |
| 288 2: replies with a new modulus, 4. | |
| 289 4: replies with a new modulus, 2. | |
| 290 8: fails with error 400. | |
| 291 16: replies with a new modulus, 16. | |
| 292 32: replies with a new modulus, 1. | |
| 293 anything else: replies with no new modulus and an empty list of hashes | |
| 294 | |
| 295 These allow the client to pick the testing scenario its wants to simulate. | |
| 296 | |
| 297 Args: | |
| 298 msg: The DeviceAutoEnrollmentRequest message received from the client. | |
| 299 | |
| 300 Returns: | |
| 301 A tuple of HTTP status code and response data to send to the client. | |
| 302 """ | |
| 303 auto_enrollment_response = dm.DeviceAutoEnrollmentResponse() | |
| 304 | |
| 305 if msg.modulus == 1: | |
| 306 auto_enrollment_response.hash.append(SHA256_0) | |
| 307 elif msg.modulus == 2: | |
| 308 auto_enrollment_response.expected_modulus = 4 | |
| 309 elif msg.modulus == 4: | |
| 310 auto_enrollment_response.expected_modulus = 2 | |
| 311 elif msg.modulus == 8: | |
| 312 return (400, 'Server error') | |
| 313 elif msg.modulus == 16: | |
| 314 auto_enrollment_response.expected_modulus = 16 | |
| 315 elif msg.modulus == 32: | |
| 316 auto_enrollment_response.expected_modulus = 1 | |
| 317 | |
| 318 response = dm.DeviceManagementResponse() | |
| 319 response.auto_enrollment_response.CopyFrom(auto_enrollment_response) | |
| 320 return (200, response.SerializeToString()) | |
| 321 | |
| 322 def SetProtobufMessageField(self, group_message, field, field_value): | |
| 323 '''Sets a field in a protobuf message. | |
| 324 | |
| 325 Args: | |
| 326 group_message: The protobuf message. | |
| 327 field: The field of the message to set, it should be a member of | |
| 328 group_message.DESCRIPTOR.fields. | |
| 329 field_value: The value to set. | |
| 330 ''' | |
| 331 if field.label == field.LABEL_REPEATED: | |
| 332 assert type(field_value) == list | |
| 333 entries = group_message.__getattribute__(field.name) | |
| 334 if field.message_type is None: | |
| 335 for list_item in field_value: | |
| 336 entries.append(list_item) | |
| 337 else: | |
| 338 # This field is itself a protobuf. | |
| 339 sub_type = field.message_type | |
| 340 for sub_value in field_value: | |
| 341 assert type(sub_value) == dict | |
| 342 # Add a new sub-protobuf per list entry. | |
| 343 sub_message = entries.add() | |
| 344 # Now iterate over its fields and recursively add them. | |
| 345 for sub_field in sub_message.DESCRIPTOR.fields: | |
| 346 if sub_field.name in sub_value: | |
| 347 value = sub_value[sub_field.name] | |
| 348 self.SetProtobufMessageField(sub_message, sub_field, value) | |
| 349 return | |
| 350 elif field.type == field.TYPE_BOOL: | |
| 351 assert type(field_value) == bool | |
| 352 elif field.type == field.TYPE_STRING: | |
| 353 assert type(field_value) == str or type(field_value) == unicode | |
| 354 elif field.type == field.TYPE_INT64: | |
| 355 assert type(field_value) == int | |
| 356 elif (field.type == field.TYPE_MESSAGE and | |
| 357 field.message_type.name == 'StringList'): | |
| 358 assert type(field_value) == list | |
| 359 entries = group_message.__getattribute__(field.name).entries | |
| 360 for list_item in field_value: | |
| 361 entries.append(list_item) | |
| 362 return | |
| 363 else: | |
| 364 raise Exception('Unknown field type %s' % field.type) | |
| 365 group_message.__setattr__(field.name, field_value) | |
| 366 | |
| 367 def GatherDevicePolicySettings(self, settings, policies): | |
| 368 '''Copies all the policies from a dictionary into a protobuf of type | |
| 369 CloudDeviceSettingsProto. | |
| 370 | |
| 371 Args: | |
| 372 settings: The destination ChromeDeviceSettingsProto protobuf. | |
| 373 policies: The source dictionary containing policies in JSON format. | |
| 374 ''' | |
| 375 for group in settings.DESCRIPTOR.fields: | |
| 376 # Create protobuf message for group. | |
| 377 group_message = eval('dp.' + group.message_type.name + '()') | |
| 378 # Indicates if at least one field was set in |group_message|. | |
| 379 got_fields = False | |
| 380 # Iterate over fields of the message and feed them from the | |
| 381 # policy config file. | |
| 382 for field in group_message.DESCRIPTOR.fields: | |
| 383 field_value = None | |
| 384 if field.name in policies: | |
| 385 got_fields = True | |
| 386 field_value = policies[field.name] | |
| 387 self.SetProtobufMessageField(group_message, field, field_value) | |
| 388 if got_fields: | |
| 389 settings.__getattribute__(group.name).CopyFrom(group_message) | |
| 390 | |
| 391 def GatherUserPolicySettings(self, settings, policies): | |
| 392 '''Copies all the policies from a dictionary into a protobuf of type | |
| 393 CloudPolicySettings. | |
| 394 | |
| 395 Args: | |
| 396 settings: The destination: a CloudPolicySettings protobuf. | |
| 397 policies: The source: a dictionary containing policies under keys | |
| 398 'recommended' and 'mandatory'. | |
| 399 ''' | |
| 400 for field in settings.DESCRIPTOR.fields: | |
| 401 # |field| is the entry for a specific policy in the top-level | |
| 402 # CloudPolicySettings proto. | |
| 403 | |
| 404 # Look for this policy's value in the mandatory or recommended dicts. | |
| 405 if field.name in policies.get('mandatory', {}): | |
| 406 mode = cp.PolicyOptions.MANDATORY | |
| 407 value = policies['mandatory'][field.name] | |
| 408 elif field.name in policies.get('recommended', {}): | |
| 409 mode = cp.PolicyOptions.RECOMMENDED | |
| 410 value = policies['recommended'][field.name] | |
| 411 else: | |
| 412 continue | |
| 413 | |
| 414 # Create protobuf message for this policy. | |
| 415 policy_message = eval('cp.' + field.message_type.name + '()') | |
| 416 policy_message.policy_options.mode = mode | |
| 417 field_descriptor = policy_message.DESCRIPTOR.fields_by_name['value'] | |
| 418 self.SetProtobufMessageField(policy_message, field_descriptor, value) | |
| 419 settings.__getattribute__(field.name).CopyFrom(policy_message) | |
| 420 | |
| 421 def ProcessCloudPolicy(self, msg): | |
| 422 """Handles a cloud policy request. (New protocol for policy requests.) | |
| 423 | |
| 424 Checks for authorization, encodes the policy into protobuf representation, | |
| 425 signs it and constructs the repsonse. | |
| 426 | |
| 427 Args: | |
| 428 msg: The CloudPolicyRequest message received from the client. | |
| 429 | |
| 430 Returns: | |
| 431 A tuple of HTTP status code and response data to send to the client. | |
| 432 """ | |
| 433 | |
| 434 token_info, error = self.CheckToken() | |
| 435 if not token_info: | |
| 436 return error | |
| 437 | |
| 438 if msg.machine_id: | |
| 439 self._server.UpdateMachineId(token_info['device_token'], msg.machine_id) | |
| 440 | |
| 441 # Response is only given if the scope is specified in the config file. | |
| 442 # Normally 'google/chromeos/device', 'google/chromeos/user' and | |
| 443 # 'google/chromeos/publicaccount' should be accepted. | |
| 444 policy = self._server.GetPolicies() | |
| 445 policy_value = '' | |
| 446 policy_key = msg.policy_type | |
| 447 if msg.settings_entity_id: | |
| 448 policy_key += '/' + msg.settings_entity_id | |
| 449 if msg.policy_type in token_info['allowed_policy_types']: | |
| 450 if (msg.policy_type == 'google/chromeos/user' or | |
| 451 msg.policy_type == 'google/chrome/user' or | |
| 452 msg.policy_type == 'google/chromeos/publicaccount'): | |
| 453 settings = cp.CloudPolicySettings() | |
| 454 self.GatherUserPolicySettings(settings, policy.get(policy_key, {})) | |
| 455 elif msg.policy_type == 'google/chromeos/device': | |
| 456 settings = dp.ChromeDeviceSettingsProto() | |
| 457 self.GatherDevicePolicySettings(settings, policy.get(policy_key, {})) | |
| 458 | |
| 459 # Sign with 'current_key_index', defaulting to key 0. | |
| 460 signing_key = None | |
| 461 req_key = None | |
| 462 current_key_index = policy.get('current_key_index', 0) | |
| 463 nkeys = len(self._server.keys) | |
| 464 if (msg.signature_type == dm.PolicyFetchRequest.SHA1_RSA and | |
| 465 current_key_index in range(nkeys)): | |
| 466 signing_key = self._server.keys[current_key_index] | |
| 467 if msg.public_key_version in range(1, nkeys + 1): | |
| 468 # requested key exists, use for signing and rotate. | |
| 469 req_key = self._server.keys[msg.public_key_version - 1]['private_key'] | |
| 470 | |
| 471 # Fill the policy data protobuf. | |
| 472 policy_data = dm.PolicyData() | |
| 473 policy_data.policy_type = msg.policy_type | |
| 474 policy_data.timestamp = int(time.time() * 1000) | |
| 475 policy_data.request_token = token_info['device_token'] | |
| 476 policy_data.policy_value = settings.SerializeToString() | |
| 477 policy_data.machine_name = token_info['machine_name'] | |
| 478 policy_data.valid_serial_number_missing = ( | |
| 479 token_info['machine_id'] in BAD_MACHINE_IDS) | |
| 480 policy_data.settings_entity_id = msg.settings_entity_id | |
| 481 | |
| 482 if signing_key: | |
| 483 policy_data.public_key_version = current_key_index + 1 | |
| 484 if msg.policy_type == 'google/chromeos/publicaccount': | |
| 485 policy_data.username = msg.settings_entity_id | |
| 486 else: | |
| 487 # For regular user/device policy, there is no way for the testserver to | |
| 488 # know the user name belonging to the GAIA auth token we received (short | |
| 489 # of actually talking to GAIA). To address this, we read the username from | |
| 490 # the policy configuration dictionary, or use a default. | |
| 491 policy_data.username = policy.get('policy_user', 'user@example.com') | |
| 492 policy_data.device_id = token_info['device_id'] | |
| 493 signed_data = policy_data.SerializeToString() | |
| 494 | |
| 495 response = dm.DeviceManagementResponse() | |
| 496 fetch_response = response.policy_response.response.add() | |
| 497 fetch_response.policy_data = signed_data | |
| 498 if signing_key: | |
| 499 fetch_response.policy_data_signature = ( | |
| 500 signing_key['private_key'].hashAndSign(signed_data).tostring()) | |
| 501 if msg.public_key_version != current_key_index + 1: | |
| 502 fetch_response.new_public_key = signing_key['public_key'] | |
| 503 if req_key: | |
| 504 fetch_response.new_public_key_signature = ( | |
| 505 req_key.hashAndSign(fetch_response.new_public_key).tostring()) | |
| 506 | |
| 507 self.DumpMessage('Response', response) | |
| 508 | |
| 509 return (200, response.SerializeToString()) | |
| 510 | |
| 511 def CheckToken(self): | |
| 512 """Helper for checking whether the client supplied a valid DM token. | |
| 513 | |
| 514 Extracts the token from the request and passed to the server in order to | |
| 515 look up the client. | |
| 516 | |
| 517 Returns: | |
| 518 A pair of token information record and error response. If the first | |
| 519 element is None, then the second contains an error code to send back to | |
| 520 the client. Otherwise the first element is the same structure that is | |
| 521 returned by LookupToken(). | |
| 522 """ | |
| 523 error = 500 | |
| 524 dmtoken = None | |
| 525 request_device_id = self.GetUniqueParam('deviceid') | |
| 526 match = re.match('GoogleDMToken token=(\\w+)', | |
| 527 self._headers.getheader('Authorization', '')) | |
| 528 if match: | |
| 529 dmtoken = match.group(1) | |
| 530 if not dmtoken: | |
| 531 error = 401 | |
| 532 else: | |
| 533 token_info = self._server.LookupToken(dmtoken) | |
| 534 if (not token_info or | |
| 535 not request_device_id or | |
| 536 token_info['device_id'] != request_device_id): | |
| 537 error = 410 | |
| 538 else: | |
| 539 return (token_info, None) | |
| 540 | |
| 541 logging.debug('Token check failed with error %d' % error) | |
| 542 | |
| 543 return (None, (error, 'Server error %d' % error)) | |
| 544 | |
| 545 def DumpMessage(self, label, msg): | |
| 546 """Helper for logging an ASCII dump of a protobuf message.""" | |
| 547 logging.debug('%s\n%s' % (label, str(msg))) | |
| 548 | |
| 549 class TestServer(object): | |
| 550 """Handles requests and keeps global service state.""" | |
| 551 | |
| 552 def __init__(self, policy_path, private_key_paths): | |
| 553 """Initializes the server. | |
| 554 | |
| 555 Args: | |
| 556 policy_path: Names the file to read JSON-formatted policy from. | |
| 557 private_key_paths: List of paths to read private keys from. | |
| 558 """ | |
| 559 self._registered_tokens = {} | |
| 560 self.policy_path = policy_path | |
| 561 | |
| 562 self.keys = [] | |
| 563 if private_key_paths: | |
| 564 # Load specified keys from the filesystem. | |
| 565 for key_path in private_key_paths: | |
| 566 try: | |
| 567 key = tlslite.api.parsePEMKey(open(key_path).read(), private=True) | |
| 568 except IOError: | |
| 569 print 'Failed to load private key from %s' % key_path | |
| 570 continue | |
| 571 | |
| 572 assert key is not None | |
| 573 self.keys.append({ 'private_key' : key }) | |
| 574 else: | |
| 575 # Generate 2 private keys if none were passed from the command line. | |
| 576 for i in range(2): | |
| 577 key = tlslite.api.generateRSAKey(512) | |
| 578 assert key is not None | |
| 579 self.keys.append({ 'private_key' : key }) | |
| 580 | |
| 581 # Derive the public keys from the private keys. | |
| 582 for entry in self.keys: | |
| 583 key = entry['private_key'] | |
| 584 | |
| 585 algorithm = asn1der.Sequence( | |
| 586 [ asn1der.Data(asn1der.OBJECT_IDENTIFIER, PKCS1_RSA_OID), | |
| 587 asn1der.Data(asn1der.NULL, '') ]) | |
| 588 rsa_pubkey = asn1der.Sequence([ asn1der.Integer(key.n), | |
| 589 asn1der.Integer(key.e) ]) | |
| 590 pubkey = asn1der.Sequence([ algorithm, asn1der.Bitstring(rsa_pubkey) ]) | |
| 591 entry['public_key'] = pubkey; | |
| 592 | |
| 593 def GetPolicies(self): | |
| 594 """Returns the policies to be used, reloaded form the backend file every | |
| 595 time this is called. | |
| 596 """ | |
| 597 policy = {} | |
| 598 if json is None: | |
| 599 print 'No JSON module, cannot parse policy information' | |
| 600 else : | |
| 601 try: | |
| 602 policy = json.loads(open(self.policy_path).read()) | |
| 603 except IOError: | |
| 604 print 'Failed to load policy from %s' % self.policy_path | |
| 605 return policy | |
| 606 | |
| 607 def HandleRequest(self, path, headers, request): | |
| 608 """Handles a request. | |
| 609 | |
| 610 Args: | |
| 611 path: The request path and query parameters received from the client. | |
| 612 headers: A rfc822.Message-like object containing HTTP headers. | |
| 613 request: The request data received from the client as a string. | |
| 614 Returns: | |
| 615 A pair of HTTP status code and response data to send to the client. | |
| 616 """ | |
| 617 handler = RequestHandler(self, path, headers, request) | |
| 618 return handler.HandleRequest() | |
| 619 | |
| 620 def RegisterDevice(self, device_id, machine_id, type): | |
| 621 """Registers a device or user and generates a DM token for it. | |
| 622 | |
| 623 Args: | |
| 624 device_id: The device identifier provided by the client. | |
| 625 | |
| 626 Returns: | |
| 627 The newly generated device token for the device. | |
| 628 """ | |
| 629 dmtoken_chars = [] | |
| 630 while len(dmtoken_chars) < 32: | |
| 631 dmtoken_chars.append(random.choice('0123456789abcdef')) | |
| 632 dmtoken = ''.join(dmtoken_chars) | |
| 633 allowed_policy_types = { | |
| 634 dm.DeviceRegisterRequest.BROWSER: ['google/chrome/user'], | |
| 635 dm.DeviceRegisterRequest.USER: ['google/chromeos/user'], | |
| 636 dm.DeviceRegisterRequest.DEVICE: [ | |
| 637 'google/chromeos/device', | |
| 638 'google/chromeos/publicaccount' | |
| 639 ], | |
| 640 dm.DeviceRegisterRequest.TT: ['google/chromeos/user', | |
| 641 'google/chrome/user'], | |
| 642 } | |
| 643 if machine_id in KIOSK_MACHINE_IDS: | |
| 644 enrollment_mode = dm.DeviceRegisterResponse.RETAIL | |
| 645 else: | |
| 646 enrollment_mode = dm.DeviceRegisterResponse.ENTERPRISE | |
| 647 self._registered_tokens[dmtoken] = { | |
| 648 'device_id': device_id, | |
| 649 'device_token': dmtoken, | |
| 650 'allowed_policy_types': allowed_policy_types[type], | |
| 651 'machine_name': 'chromeos-' + machine_id, | |
| 652 'machine_id': machine_id, | |
| 653 'enrollment_mode': enrollment_mode, | |
| 654 } | |
| 655 return self._registered_tokens[dmtoken] | |
| 656 | |
| 657 def UpdateMachineId(self, dmtoken, machine_id): | |
| 658 """Updates the machine identifier for a registered device. | |
| 659 | |
| 660 Args: | |
| 661 dmtoken: The device management token provided by the client. | |
| 662 machine_id: Updated hardware identifier value. | |
| 663 """ | |
| 664 if dmtoken in self._registered_tokens: | |
| 665 self._registered_tokens[dmtoken]['machine_id'] = machine_id | |
| 666 | |
| 667 def LookupToken(self, dmtoken): | |
| 668 """Looks up a device or a user by DM token. | |
| 669 | |
| 670 Args: | |
| 671 dmtoken: The device management token provided by the client. | |
| 672 | |
| 673 Returns: | |
| 674 A dictionary with information about a device or user that is registered by | |
| 675 dmtoken, or None if the token is not found. | |
| 676 """ | |
| 677 return self._registered_tokens.get(dmtoken, None) | |
| 678 | |
| 679 def UnregisterDevice(self, dmtoken): | |
| 680 """Unregisters a device identified by the given DM token. | |
| 681 | |
| 682 Args: | |
| 683 dmtoken: The device management token provided by the client. | |
| 684 """ | |
| 685 if dmtoken in self._registered_tokens.keys(): | |
| 686 del self._registered_tokens[dmtoken] | |
| OLD | NEW |