• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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