1#!/usr/bin/python2.7 2# Copyright 2019 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""This implements a simple tool to create policy blobs signed with a given 7key. It can create both device and user policies. The output will consist of 8two files a policy file and the owner.key file which contains the policy 9signature. 10 11The input file is JSON. The root dictionary contains a list under the 12key "managed_users". Keys in the root dictionary identify request scopes. 13The user-request scope is described by a dictionary that holds two 14sub-dictionaries: "mandatory" and "recommended". Both these hold the policy 15definitions as key/value stores, their format is identical to what the Linux 16implementation reads from /etc. 17The device-scope holds the policy-definition directly as key/value stores 18in the protobuf-format. 19 20Example: 21 22{ 23 "google/chromeos/device" : { 24 "guest_mode_enabled" : false 25 }, 26 "google/chromeos/user" : { 27 "mandatory" : { 28 "HomepageLocation" : "http://www.chromium.org", 29 "IncognitoEnabled" : false 30 }, 31 "recommended" : { 32 "JavascriptEnabled": false 33 } 34 } 35} 36 37""" 38 39import optparse 40import os 41import re 42import sys 43import time 44import tlslite 45import tlslite.api 46import tlslite.utils 47 48# The name and availability of the json module varies in python versions. 49try: 50 import simplejson as json 51except ImportError: 52 try: 53 import json 54 except ImportError: 55 json = None 56 57import asn1der 58import device_management_backend_pb2 as dm 59import cloud_policy_pb2 as cp 60import chrome_device_policy_pb2 as dp 61 62# ASN.1 object identifier for PKCS#1/RSA. 63PKCS1_RSA_OID = '\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01' 64 65def SetProtobufMessageField(group_message, field, field_value): 66 '''Sets a field in a protobuf message. 67 68 Args: 69 group_message: The protobuf message. 70 field: The field of the message to set, it shuold be a member of 71 group_message.DESCRIPTOR.fields. 72 field_value: The value to set. 73 ''' 74 if field.label == field.LABEL_REPEATED: 75 assert type(field_value) == list 76 entries = group_message.__getattribute__(field.name) 77 for list_item in field_value: 78 entries.append(list_item) 79 return 80 elif field.type == field.TYPE_BOOL: 81 assert type(field_value) == bool 82 elif field.type == field.TYPE_STRING: 83 assert type(field_value) == str or type(field_value) == unicode 84 elif field.type == field.TYPE_INT64: 85 assert type(field_value) == int 86 elif (field.type == field.TYPE_MESSAGE and 87 field.message_type.name == 'StringList'): 88 assert type(field_value) == list 89 entries = group_message.__getattribute__(field.name).entries 90 for list_item in field_value: 91 entries.append(list_item) 92 return 93 else: 94 raise Exception('Unknown field type %s' % field.type) 95 group_message.__setattr__(field.name, field_value) 96 97def GatherDevicePolicySettings(settings, policies): 98 '''Copies all the policies from a dictionary into a protobuf of type 99 CloudDeviceSettingsProto. 100 101 Args: 102 settings: The destination ChromeDeviceSettingsProto protobuf. 103 policies: The source dictionary containing policies in JSON format. 104 ''' 105 for group in settings.DESCRIPTOR.fields: 106 # Create protobuf message for group. 107 group_message = eval('dp.' + group.message_type.name + '()') 108 # Indicates if at least one field was set in |group_message|. 109 got_fields = False 110 # Iterate over fields of the message and feed them from the 111 # policy config file. 112 for field in group_message.DESCRIPTOR.fields: 113 field_value = None 114 if field.name in policies: 115 got_fields = True 116 field_value = policies[field.name] 117 SetProtobufMessageField(group_message, field, field_value) 118 if got_fields: 119 settings.__getattribute__(group.name).CopyFrom(group_message) 120 121def GatherUserPolicySettings(settings, policies): 122 '''Copies all the policies from a dictionary into a protobuf of type 123 CloudPolicySettings. 124 125 Args: 126 settings: The destination: a CloudPolicySettings protobuf. 127 policies: The source: a dictionary containing policies under keys 128 'recommended' and 'mandatory'. 129 ''' 130 for group in settings.DESCRIPTOR.fields: 131 # Create protobuf message for group. 132 group_message = eval('cp.' + group.message_type.name + '()') 133 # We assume that this policy group will be recommended, and only switch 134 # it to mandatory if at least one of its members is mandatory. 135 group_message.policy_options.mode = cp.PolicyOptions.RECOMMENDED 136 # Indicates if at least one field was set in |group_message|. 137 got_fields = False 138 # Iterate over fields of the message and feed them from the 139 # policy config file. 140 for field in group_message.DESCRIPTOR.fields: 141 field_value = None 142 if field.name in policies['mandatory']: 143 group_message.policy_options.mode = cp.PolicyOptions.MANDATORY 144 field_value = policies['mandatory'][field.name] 145 elif field.name in policies['recommended']: 146 field_value = policies['recommended'][field.name] 147 if field_value != None: 148 got_fields = True 149 SetProtobufMessageField(group_message, field, field_value) 150 if got_fields: 151 settings.__getattribute__(group.name).CopyFrom(group_message) 152 153def ProcessCloudPolicy(policy_type, 154 policy_def, policy_key, 155 username, 156 output_path): 157 """Creates a policy blob. 158 159 Encodes the policy into protobuf representation, signs it and saves it. 160 161 Args: 162 policy_type: can be 'google/chromeos/user' or 'google/chromeos/device'. 163 policy_def: The JSON file containing the policy definition. 164 policy_key: A private key to be used to sign the blob. 165 username: Username to be integrated in the policy blob. 166 output_path: A directory where to put the output files. 167 """ 168 policy = json.loads(open(policy_def).read()) 169 policy_value = '' 170 if (policy_type in policy): 171 if policy_type == 'google/chromeos/user': 172 settings = cp.CloudPolicySettings() 173 GatherUserPolicySettings(settings, policy[policy_type]) 174 policy_value = settings.SerializeToString() 175 elif policy_type == 'google/chromeos/device': 176 settings = dp.ChromeDeviceSettingsProto() 177 GatherDevicePolicySettings(settings, policy[policy_type]) 178 policy_value = settings.SerializeToString() 179 180 key = tlslite.api.parsePEMKey(open(policy_key).read(), private=True) 181 182 algorithm = asn1der.Sequence( 183 [ asn1der.Data(asn1der.OBJECT_IDENTIFIER, PKCS1_RSA_OID), 184 asn1der.Data(asn1der.NULL, '') ]) 185 rsa_pubkey = asn1der.Sequence([ asn1der.Integer(key.n), 186 asn1der.Integer(key.e) ]) 187 pubkey = asn1der.Sequence([ algorithm, asn1der.Bitstring(rsa_pubkey) ]) 188 key_version = 1 189 190 # Fill the policy data protobuf. 191 policy_data = dm.PolicyData() 192 policy_data.policy_type = policy_type 193 policy_data.timestamp = int(time.time() * 1000) 194 policy_data.request_token = "DEV_TOKEN" 195 policy_data.policy_value = policy_value 196 policy_data.machine_name = "MEAN_MACHINE" 197 policy_data.public_key_version = 1 198 policy_data.username = username 199 policy_data.device_id = "1337_1D" 200 signed_data = policy_data.SerializeToString() 201 202 response = dm.DeviceManagementResponse() 203 fetch_response = response.policy_response.responses.add() 204 fetch_response.policy_data = signed_data 205 fetch_response.policy_data_signature = bytes( 206 key.hashAndSign(signed_data)) 207 fetch_response.new_public_key = pubkey 208 209 open("%s/policy" % output_path,"wb"). 210 write(fetch_response.SerializeToString()); 211 open("%s/owner.key" % output_path,"wb").write(pubkey); 212 213def main(options): 214 ProcessCloudPolicy(options.policy_type, 215 options.policy_def, options.policy_key, 216 options.policy_user, 217 options.output_path); 218 219if __name__ == '__main__': 220 option_parser = optparse.OptionParser() 221 option_parser.add_option('-k', '--policy-key', default="mykey", 222 dest='policy_key', 223 help='Specify a path to a PEM-encoded private key ' 224 'to use for policy signing.') 225 option_parser.add_option('-p', '--policy-def', default="device_management", 226 dest='policy_def', 227 help='Specify a path to a PEM-encoded private key ' 228 'to use for policy signing.') 229 option_parser.add_option('-u', '--policy-user', default='user@example.com', 230 dest='policy_user', 231 help='Specify the user name the server should ' 232 'report back to the client as the user owning the ' 233 'token used for making the policy request.') 234 option_parser.add_option('-o', '--output-path', default='.', 235 dest='output_path', 236 help='Specifies the directory to output policy ' 237 'files to.') 238 option_parser.add_option('-t', '--type', default='google/chromeos/device', 239 dest='policy_type', 240 help='Specifies the type of policy to create.') 241 options, args = option_parser.parse_args() 242 243 sys.exit(main(options)) 244