| // Copyright 2021 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package devicepolicy |
| |
| // Implementation of the Manager interface for managing Chrome OS device policy. |
| // For more detail on Chrome OS device policy, see |
| // http://www.chromium.org/developers/how-tos/enterprise/protobuf-encoded-policy-blobs |
| |
| import ( |
| "crypto/rand" |
| "crypto/rsa" |
| "crypto/x509" |
| "errors" |
| "os" |
| "path/filepath" |
| "reflect" |
| |
| pmpb "policy_manager/policymanagerproto" |
| "policy_manager/sysapi" |
| |
| "github.com/golang/glog" |
| "github.com/golang/protobuf/proto" |
| ) |
| |
| const ( |
| // publicKeyFile is the file containing the public key that will be used |
| // to verify the signature on the device policy file. |
| // The key will be encoded using DER-encoded PKIX format. |
| // Note that the file name is decided by Chrome OS. |
| publicKeyFile = "/var/lib/whitelist/owner.key" |
| |
| // privateKeyFile is the file containing the private key that will be |
| // used to sign the policy file. |
| // The key will be encoded using ASN.1 DER encoded form. |
| // Note that we are signing the policy file locally since we do not want |
| // to do any key management on the server side. |
| privateKeyFile = "/var/lib/whitelist/private.key" |
| |
| // keyFilePerm is the permission for public/private key files. |
| keyFilePerm = os.FileMode(0600) |
| |
| // rsaKeyBits is the number of bits to use to generate the private key. |
| rsaKeyBits = 1024 |
| ) |
| |
| // managerImpl implements the Manager interface. |
| type managerImpl struct { |
| // api is the interface used to call system API. |
| api sysapi.APIHandler |
| } |
| |
| // createKeyFiles creates a new public and private key pair and writes them to |
| // disk. |
| // Note that when the key files are being created, any existing policy cannot be |
| // read and verified by Chrome OS components such as update_engine. |
| func createKeyFiles(api sysapi.APIHandler) (*rsa.PrivateKey, error) { |
| // Generate RSA key pair. |
| key, err := rsa.GenerateKey(rand.Reader, rsaKeyBits) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Serialize the public key to DER-encoded PKIX format and output to |
| // file. |
| publicKeyBytes, err := x509.MarshalPKIXPublicKey(key.Public()) |
| if err != nil { |
| return nil, err |
| } |
| err = api.WriteFile(publicKeyFile, publicKeyBytes, keyFilePerm) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Serialize the private key to ASN.1 DER encoded form and output to |
| // file. |
| privateKeyBytes := x509.MarshalPKCS1PrivateKey(key) |
| if err != nil { |
| return nil, err |
| } |
| err = api.WriteFile(privateKeyFile, privateKeyBytes, keyFilePerm) |
| if err != nil { |
| return nil, err |
| } |
| |
| glog.V(1).Info("new keys created") |
| return key, nil |
| } |
| |
| // checkKeyFiles checks the private/public key files are present on disk and |
| // form a valid key pair. |
| // An error is returned if the private or public key file is not present or if |
| // the keys don't match. |
| // If no error, then the private key is returned. |
| func checkKeyFiles(api sysapi.APIHandler) (*rsa.PrivateKey, error) { |
| // Get private key. |
| privateKeyBytes, err := api.ReadFile(privateKeyFile) |
| if err != nil { |
| return nil, err |
| } |
| privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBytes) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Get public key. |
| publicKeyBytes, err := api.ReadFile(publicKeyFile) |
| if err != nil { |
| return nil, err |
| } |
| publicKey, err := x509.ParsePKIXPublicKey(publicKeyBytes) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Verify that the public and private keys match. |
| if !reflect.DeepEqual(publicKey, privateKey.Public()) { |
| return nil, errors.New("public and private keys don't match") |
| } |
| |
| return privateKey, nil |
| } |
| |
| // getKeys returns the private key of the public/private key pair present on |
| // disk. If no valid key pair exsits, an error will be returned unless the |
| // caller has set generateIfMissing to true. |
| func getKeys(api sysapi.APIHandler, generateIfMissing bool) (*rsa.PrivateKey, error) { |
| // generateKeysIfNeeded is called when there is no valid key pair on |
| // disk and should consult the generateIfMissing flag to decide wheher |
| // new keys should be generated. |
| generateKeysIfNeeded := func() (*rsa.PrivateKey, error) { |
| if generateIfMissing { |
| return createKeyFiles(api) |
| } |
| return nil, errors.New("missing key files") |
| } |
| |
| // Check if valid public/private key files are present. |
| if !api.FileExists(privateKeyFile) || !api.FileExists(publicKeyFile) { |
| glog.V(1).Info("keys missing") |
| return generateKeysIfNeeded() |
| } else if privateKey, err := checkKeyFiles(api); err != nil { |
| // The keys on disk do not form a valid private/public key pair. |
| glog.V(1).Info("invalid key pair") |
| return generateKeysIfNeeded() |
| } else { |
| return privateKey, nil |
| } |
| } |
| |
| // getDefaultInstanceConfigFromBase returns the instance config derived from given |
| // baseConfig. All missing fields in baseConfig are populated with default values. |
| func getDefaultInstanceConfigFromBase(baseConfig *pmpb.InstanceConfig) *pmpb.InstanceConfig { |
| // default policy allows all updates, disables cos monitoring logging and monitoring. |
| result := &pmpb.InstanceConfig{ |
| MetricsEnabled: proto.Bool(false), |
| TargetVersionPrefix: proto.String(""), |
| RebootAfterUpdate: proto.Bool(false), |
| UpdateScatterSeconds: proto.Int64(33), |
| HealthMonitorConfig: &pmpb.HealthMonitorConfig{ |
| Enforced: proto.Bool(false), |
| LoggingEnabled: proto.Bool(false), |
| MonitoringEnabled: proto.Bool(false), |
| }, |
| } |
| |
| // Override fields from baseConfig. |
| proto.Merge(result, baseConfig) |
| return result |
| } |
| |
| // NewManager returns a new Manager. |
| func NewManager(api sysapi.APIHandler) Manager { |
| return &managerImpl{api} |
| } |
| |
| // InitDevicePolicy initializes instance's environment to support device policy. |
| // The initialization process includes (in order): |
| // 1. Create the directory to store policy file if it doesn't exist. |
| // 2. Detect if a valid public/private key pair is present. If not, create a new |
| // key pair and store them on disk. In addition, the private key is stored |
| // in memory with the manager. |
| // 3. New devicepolicy will be created and written to disk (overwriting any previous |
| // settings), All missing fields will have default values. If the given InstanceConfig |
| // is valid (not nil), new devicepolicy will be generated from it; if the given |
| // InstanceConfig is nil, which indicates metadata server fetching error, new devicepolicy |
| // will be generated from current devicepolicy, which is stored on disk. |
| func (manager *managerImpl) InitDevicePolicy(baseConfig *pmpb.InstanceConfig) error { |
| // Make sure the policy directory exists. |
| dir := filepath.Dir(policyFile) |
| if err := manager.api.MkdirAll(dir, policyFilePerm); err != nil { |
| return err |
| } |
| |
| // Create new keys if no valid keys are present. |
| _, err := getKeys(manager.api, true) |
| if err != nil { |
| return err |
| } |
| |
| onDiskConfig, err := manager.GetInstanceConfig() |
| // If failed to fetch config from metadata server, use the copy on disk. |
| if baseConfig == nil && err == nil { |
| glog.V(1).Infof("Reusing existing device policy for initialization: %v", onDiskConfig) |
| return manager.SetInstanceConfig(onDiskConfig) |
| } |
| |
| // Overwrite the copy of device policy on disk using baseConfig. |
| return manager.SetInstanceConfig(baseConfig) |
| } |
| |
| // SetInstanceConfig sets a new device policy from an InstanceConfig. The |
| // new policy will atomically replace the existing policy. |
| // This function requires that a valid public key is present before proceeding |
| // with generating the policy file. This is to prevent the situation where the |
| // policy file is written but not the public key file, causing Chrome OS |
| // components to ignore the policy because the signature cannot be verified. |
| func (manager *managerImpl) SetInstanceConfig(baseConfig *pmpb.InstanceConfig) error { |
| // We need to populate unspecified fields with default values. |
| config := getDefaultInstanceConfigFromBase(baseConfig) |
| |
| // Get the private key from disk. Don't generate new keys if valid |
| // public/private key pair is missing. |
| privateKey, err := getKeys(manager.api, false) |
| if err != nil { |
| return err |
| } |
| publicKey := privateKey.Public().(*rsa.PublicKey) |
| |
| // Read the current device policy. Ignore error if policy file is |
| // missing. |
| policy := new(DevicePolicy) |
| err = policy.ReadPolicyFromDisk(publicKey, manager.api, true) |
| if err != nil { |
| return err |
| } |
| |
| currentConfig := new(pmpb.InstanceConfig) |
| policy.ToInstanceConfig(currentConfig) |
| |
| // Set the device policy based on new config. |
| policyChanged := policy.SetFromInstanceConfig(config) |
| if !policyChanged { |
| glog.Infof("Skipping device policy update. Current: {%+v}", currentConfig) |
| return nil |
| } else { |
| newConfig := new(pmpb.InstanceConfig) |
| policy.ToInstanceConfig(newConfig) |
| glog.Infof("Updating device policy. Instance config changed from {%+v} to {%+v}", |
| currentConfig, newConfig) |
| } |
| |
| err = policy.SignAndWrite(privateKey, manager.api) |
| |
| if err == nil { |
| dbus_error := signalPropertyChange() |
| if dbus_error != nil { |
| glog.Warningf("Can't send PropertyChange signal on D-bus. "+ |
| "Some consumers may not get device policy update: %s", dbus_error) |
| } |
| } |
| return err |
| } |
| |
| func (manager *managerImpl) GetInstanceConfig() (config *pmpb.InstanceConfig, err error) { |
| // Get the private key from disk. Error out if they are missing. |
| privateKey, err := getKeys(manager.api, false) |
| if err != nil { |
| return nil, err |
| } |
| publicKey := privateKey.Public().(*rsa.PublicKey) |
| |
| // Read the current device policy. |
| policy := new(DevicePolicy) |
| err = policy.ReadPolicyFromDisk(publicKey, manager.api, false) |
| if err != nil { |
| return nil, err |
| } |
| |
| config = new(pmpb.InstanceConfig) |
| policy.ToInstanceConfig(config) |
| |
| return config, nil |
| } |