blob: 9426de32f04d95b344c78a655b7edf3633cee252 [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Check the validity of the policy extension manifest.
*
* This function is invoked by entd before the policy is loaded in order to
* check the validity of the extension manifest. If this function returns
* false, entd exits and does not restart until the next user logs in.
*
* @param manifest {Object} The deserialized extension manifest for the
* enterprise policy.
*/
const Slot = entd.crypto.Pkcs11.Slot;
const Token = entd.crypto.Pkcs11.Token;
const Session = entd.crypto.Pkcs11.Session;
const PkObject = entd.crypto.Pkcs11.Object;
const CSR = entd.crypto.OpenSSL.CSR;
const X509 = entd.crypto.OpenSSL.X509;
// PKCS#11 to use for token management.
Policy.OPENSSL_ENGINE = 'pkcs11';
// PKCS#11 slot index to use.
Policy.PKCS11_SLOT = 0;
entd.verifyManifest =
function verifyManifest(manifest) {
function fail(msg) {
entd.syslog.error('verifyManifest: ' + msg);
return false;
};
if (!('name' in manifest) || typeof manifest.name != 'string')
return fail('Invalid or missing "name"');
if (!manifest.name.match(/chrom(e|ium)\s?os enterprise policy/i))
return fail('Invalid manifest name: ' + manifest.name);
if (!('description' in manifest) || typeof manifest.description != 'string')
return fail('Invalid or missing "description"');
var ary = manifest.description.match(
/chrom(?:e|ium)\s?os enterprise policy for (\S+)(\s+.*)?/i);
if (!ary)
return fail('Invalid manifest description: ' + manifest.description);
try {
entd.hostname = ary[1];
} catch (ex) {
return fail('Error setting hostname to "' + ary[1] + '": ' + ex);
}
if (manifest.permissions) {
perms = manifest.permissions;
if (typeof perms == "string")
perms = [ perms ];
if (!(perms instanceof Array))
return fail('Invalid type for permissions: ' + perms);
if (perms.length != 1 || perms[0].indexOf("http://127.0.0.1/") != 0)
return fail('Invalid permissions: ' + perms);
}
return true;
}
entd.http.RESPONSE_OK = 200;
/**
* Policy constructor.
*
* Constructs a new policy object. The Policy class provides a framework
* for entd security policies. It allows a policy implementor to declare
* most of their policy, without having to write too much JavaScript.
*
* A policy implementor may choose to ignore this class and create their
* own policy infrastructure if necessary. The Enterprise Daemon does not
* require this specific class, or any class like it.
*
* @param {Object} manifest The extension manifest, as reported by entd.
*/
function Policy(manifest) {
this.logMessages = [];
this.info('Initializing: ' + manifest.description);
this.info('Current user: ' + entd.username);
// Certificate definitions to be populated later by this.addCertificate().
this.certificates = {};
// Save off the entire manifest in case we need it later.
this.manifest = manifest;
// Set the required magic header value for the callback server.
if ('requestHeaderValue' in manifest)
entd.callbackServer.requestHeaderValue = manifest.requestHeaderValue;
this.variables_ = {};
if (manifest.variables) {
// The manifest may have set some parameters for this policy. If
// so, we want to copy them into the params object after making sure
// the variable names are safe to use as keys.
for (name in manifest.variables)
this.setVariable(name, params[name]);
}
// The CSR definition can reference properties of the params object. We
// copy the user info there so that it can be substituted into the CSR fields.
if (!entd.username)
throw new Error('Unable to determine current username');
var ary = entd.username.match(/([^@]+)@(.*)/);
this.setVariable('userEmail', entd.username);
this.setVariable('userName', ary[1]);
this.setVariable('userDomain', ary[2]);
this.callbacks = new Policy.Callbacks(this);
// Load the browser policy settings from the manifest, if they're set. Keep
// track of whether or not the policy has changed, so that we can warn the
// user that they need to restart.
this.browserPolicyChanged = false;
if (manifest.browser) {
if (manifest.browser.managedPolicy) {
if (this.setBrowserPolicy("managed", manifest.browser.managedPolicy))
this.browserPolicyChanged = true;
}
if (manifest.browser.recommendedPolicy) {
if (this.setBrowserPolicy("recommended",
manifest.browser.recommendedPolicy)) {
this.browserPolicyChanged = true;
}
}
}
// Kick off PKCS11 initialization.
this.pkcs11 = {
api: null,
error: null,
initCount: 0
};
entd.setTimeout(util.bindp(this, 'initPkcs11'), 1);
}
// Security Officer PIN for the PKCS11 token.
Policy.PKCS11_SO_PIN = '000000';
// User PIN for the PKCS11 token.
Policy.PKCS11_USER_PIN = '111111';
/**
* Parse an lsb-release file's contents.
* @param {string} contents File contents.
* @return {Object} dictionary of key/value pairs in that file.
*/
Policy.parseLsbRelease =
function parseLsbRelease(contents) {
var lines = contents.split('\n');
var result = {};
for (var i = 0; i < lines.length; ++i) {
// Ignore comments.
if (lines[i].search(/[\s]*#/) >= 0)
continue;
var pieces = lines[i].split('=');
// Ignore lines not in X=Y format.
if (pieces.length != 2)
continue;
result[pieces[0]] = pieces[1];
}
return result;
};
/**
* Parse the ChromeOS version out of the parsed lsb-release file contents.
* @param {Object} parsedContents Dictionary of key/value pairs.
* @returns {Array.<Object>} Array of version numbers.
*/
Policy.parseChromeOSVersion =
function parseChromeOSVersion(parsedContents) {
const versionKey = 'CHROMEOS_RELEASE_VERSION';
if (!(versionKey in parsedContents))
return [];
var versionString = parsedContents[versionKey];
var numbers = versionString.split('.');
return numbers.map(function(str) { return parseInt(str) });
}
/**
* Compare the given Chrome OS version to current version.
* @param {Array.<Number>} minVersion minimum version (inclusive) that will
* return true
* @param {Boolean} Whether minVersion is less or equal to given version.
*/
Policy.isAtLeastVersion =
function isAtLeastVersion(minVersion) {
/* Search to find the first different version number.
* Consider that first different version element:
* If current version is greater than min version, true.
* If current version is less than min version, false.
* If there are no difference before one or other version ends:
* If min version has more elements, false.
* Otherwise the two are equal or current is slightly newer, true.
*/
var i = 0;
while (i < entd.parsedChromeOSVersion.length && i < minVersion.length) {
if (entd.parsedChromeOSVersion[i] != minVersion[i]) {
return entd.parsedChromeOSVersion[i] > minVersion[i];
}
++i;
}
return i == minVersion.length;
}
/**
* Log an information status message.
*
* @param {string} str The informational message to log.
* @return {string} The string logged.
*/
Policy.prototype.info =
function info(str) { return this.log_('info', str) };
/**
* Log a warning status message.
*
* @param {string} str The warning message to log.
* @return {string} The string logged.
*/
Policy.prototype.warn =
function warn(str) { return this.log_('warn', str) };
/**
* Log an error status message.
*
* @param {string} str The error message to log.
* @return {string} The string logged.
*/
Policy.prototype.error =
function error(str) { return this.log_('error', str) };
Policy.prototype.log_ =
function log_(type, str) {
entd.syslog[type](str);
this.logMessages.push(str);
};
/**
* Return a log marker.
*
* The return value of this function can be passed back in to
* Policy.prototype.getLog() to fetch the log messages that have occurred since
* the mark.
*
* @return {int} A log marker.
*/
Policy.prototype.getLogMark =
function getLogMark() {
return this.logMessages.length - 1;
}
/**
* Get all of the log messages that have occurred since a given mark.
*
* Returns all log messages that have occurred since a given mark as a single
* string. If no mark is provided, all log messages are returned.
*
* @param {int} mark Optional. The mark representing the starting point in the
* log that you are interested in.
* @return {string} Newline delimited log messages.
*/
Policy.prototype.getLog =
function getLog(mark) {
if (!mark)
return this.logMessages.join('\n');
return this.logMessages.slice(mark + 1).join('\n');
}
/**
* Record the starting point of an asynchronous operation on a given object.
*
* Use this in conjunction with Policy.prototype.stop() to record status and
* log messages about an asynchronous operation pertaining to the given
* object.
*
* The object in question should be considered unstable (because some
* ongoing operation on the object has not completed) until a corresponding
* stop() call is made.
*
* @param {Object} obj The object that is the subject of the operation.
* @param {string} state A short identifier for the operation being started.
* @param {string} msg Optional. An informational message to log.
*/
Policy.prototype.start =
function start(obj, state, msg) {
obj.state = 'start:' + state;
obj.log = "";
obj.mark_ = this.getLogMark();
if (msg)
this.info(msg);
}
/**
* Record the completion of an asynchrnous operation on a given object.
*
* Call this after an operation started with Policy.prototype.start() has
* completed. This indicates that the current state of the object is stable,
* although the operation itself may not have been successful, or another
* operation may be required before the object is ready for use.
*
* The stop state may be any short idenfitier, though by convention you should
* use 'error' to mean that the operation completed with an error condition,
* and 'ready' when the object is has been fully initialized and is ready for
* general use.
*
* @param {Object} obj The object that is the subject of the operation.
* @param {string} state A short identifier for the operation being started.
* @param {string} msg Optional. A message to log. If the state parameter is
* 'error', then the message is logged with the error severity, otherwise it
* will be logged as an informational message.
*/
Policy.prototype.stop =
function stop(obj, state, msg) {
if (msg) {
if (state == 'error')
this.error(msg);
else
this.info(msg);
}
obj.state = 'stop:' + state;
obj.log = this.getLog(obj.mark);
obj.mark_ = null;
}
/**
* Set a variable for this policy.
*
* Variables can be referenced in various parts of the policy and
* certificate configuration. This function is used to assign a value to
* a variable.
*
* @param {string} name The name of the variable.
* @param {string} value The value of the variable.
*/
Policy.prototype.setVariable =
function setVariable(name, value) {
this.variables_[util.toKey(name)] = value;
};
/**
* Get a variable for this policy.
*
* Variables can be referenced in various parts of the policy and
* certificate configuration. This function is used to retrieve the value of
* a variable.
*
* @param {string} name The name of the variable.
* @param {string} opt_default Optional default value.
*/
Policy.prototype.getVariable =
function getVariable(name, opt_default) {
var key = util.toKey(name);
if (key in this.variables_)
return this.variables_[key];
return defval;
};
/**
* Add a certificate definition to this policy.
*
* @param {string} type An identifier for the certificate definition.
* @param {Object} params The parameters for this certificate. See the
* documentation for the Policy.Certificate constructor for details.
*/
Policy.prototype.addCertificate =
function addCertificate(type, params) {
var cert = new Policy.Certificate(this, type, params);
this.certificates[util.toKey(type)] = cert;
return cert;
};
/**
* Initialize the PKCS#11 API.
*
* If the API fails to initialize, then this will retry every second until
* initialization succeeds.
*/
Policy.prototype.initPkcs11 =
function initPkcs11() {
if (this.pkcs11.api) {
// Already initialized.
return true;
}
this.start(this.pkcs11, 'init');
++this.pkcs11.initCount;
this.info('Initializing PKCS#11 library, attempt: ' +
this.pkcs11.initCount);
// Compute retry interval, starting at 1 second, doubling every failure,
// until reaching an hour between checks.
var doubling = Math.min(this.pkcs11.initCount - 1, 12);
var next_retry = 1000 * (1 << doubling);
if ('isTokenReady' in entd.tpm) {
if (!entd.tpm.isTokenReady) {
// The TPM token is not yet initialized by cryptohomed and we
// must not load PKCS11 until it has finished. Re-check again
// in a second.
this.info('Cryptohome has not yet initialized TPM token, delaying.');
entd.setTimeout(util.bindp(this, "initPkcs11"), next_retry);
return false;
}
}
try {
this.pkcs11.api = new entd.crypto.Pkcs11();
this.info('entd.cryto.Pkcs11 initialized.');
} catch (ex) {
this.error('PKCS#11 library failed to initialize: ' + ex);
this.pkcs11.error = ex;
}
if (!this.pkcs11.api) {
// Initialization failed, try again.
entd.setTimeout(util.bindp(this, "initPkcs11"), next_retry);
return false;
}
for (var key in this.certificates) {
this.info('checking certificate: ' + key);
var cert = this.certificates[key];
if (cert.isInstalled()) {
cert.onInstall_(/* firstInstall: */ false);
}
}
var pkcs11 = this.pkcs11.api;
for (var i = 0; i < pkcs11.slots.length; ++i) {
var token = pkcs11.slots[i].token;
if (!token)
continue;
if (this.checkToken(token)) {
this.stop(token, 'ready');
} else {
this.stop(token, 'unknown');
}
}
var engine = new entd.crypto.OpenSSL.Engine(Policy.OPENSSL_ENGINE);
this.engine = engine;
this.stop(this.pkcs11, 'ready');
this.info('pkcs#11 ready');
return true;
}
/**
* Check if a PKCS11 token appears to be ready for use.
*
* This method returns true if the given token has been initialized, both
* PINs have been set, and neither PIN is locked.
*/
Policy.prototype.checkToken =
function checkToken(token) {
token.refresh();
return ((token.flags & Token.CKF_TOKEN_INITIALIZED) &&
(token.flags & Token.CKF_USER_PIN_INITIALIZED) &&
!(token.flags & (Token.CKF_SO_PIN_TO_BE_CHANGED ||
Token.CKF_USER_PIN_TO_BE_CHANGED ||
Token.CKF_SO_PIN_LOCKED ||
Token.CKF_USER_PIN_LOCKED)));
}
/**
* Initialize a PKCS11 token.
*
* This performs *only* the token initialization. Callers must also reset
* the SO and User PINs before the token is usable.
*
* @param {entd.crypto.Pkcs11.Token} token The token to initialize.
* @param {string} tokenLabel Optional. The label to assign to the new token.
*/
Policy.prototype.initToken =
function initToken(token, tokenLabel) {
if (!tokenLabel)
tokenLabel = "Initialized by CrOS";
this.start(token, 'init',
'Initializing token: ' + tokenLabel);
var sopin;
token.refresh();
if (token.flags & Token.CKF_SO_PIN_TO_BE_CHANGED) {
// If the SO pin hasn't been initialized yet, then it's the one
// assigned by opencryptoki.
sopin = Token.DEFAULT_SO_PIN;
} else {
// Otherwise, it *should be* the one we assigned when we initialized it.
sopin = Policy.PKCS11_SO_PIN;
}
try {
token.initToken(sopin, tokenLabel);
} catch (ex) {
this.stop(token, 'error', 'Initialization failed: ' + ex);
return false;
}
token.refresh();
if (!(token.flags & Token.CKF_TOKEN_INITIALIZED)) {
this.stop(token, 'error', 'Token did not initialize.');
return;
}
// Also, all of the certificate objects need to have their state reset.
for (var key in this.certificates)
this.certificates[key].state = 'stop:unknown';
this.stop(token, 'init', 'Token initialized.');
this.info('token state: ' + token.state);
return true;
}
/**
* Set a PIN on a PKCS11 token.
*
* Logs in to the given token using oldPin, then resets the pin to newPin and
* logs out.
*
* @param {entd.crypto.Pkcs11.Token} token The target token.
* @param {int} userType The type of user to reset. Either Session.CKU_SO or
* Session.CKU_USER.
* @param {string} oldPin The current PIN for this user.
* @param {string} newPin The new PIN for this user.
*
* @return {boolean} True if the operation succeeded, false otherwise.
*/
Policy.prototype.setTokenPin =
function setTokenPins(token, userType, oldPin, newPin) {
var pinType = (userType == Session.CKU_SO ? 'so' : 'user') + '-pin';
this.start(token, pinType, 'Resetting PIN: ' + pinType);
if (oldPin == newPin) {
if (this.checkToken(token)) {
this.stop(token, 'ready');
} else {
this.stop(token, pinType);
}
return true;
}
try {
session = this.loginToken(token, userType, oldPin);
if (!session)
this.stop(token, 'error', 'Failed to login to token');
} catch (ex) {
this.stop(token, 'error', 'Exception initializing PIN: ' + ex);
session.logoutAndClose();
return false;
}
this.info('PIN Reset for: ' + pinType);
try {
session.setPin(oldPin, newPin);
this.info('PIN Reset complete.');
} catch (ex) {
this.stop(token, 'error', 'Exception changing PIN: ' + ex);
session.logoutAndClose();
return false;
}
if (this.checkToken(token)) {
this.stop(token, 'ready');
} else {
this.stop(token, pinType);
}
session.logoutAndClose();
return true;
}
/**
* Generate a RSA key pair on the PKCS#11 token.
*
* 'label' and 'id' parameters are user-friendly values that will be applied
* to the generated keys. They can be used later for documentation and/or
* key search/match.
*
* @param {Session} session An open read/write session to the device.
* @param {Integer} id Object ID to apply to public and private keys.
* @param {String} label Label to apply to public and private keys.
*
* @return {void} No return value. On failure function throws an exception.
*/
Policy.prototype.generateKeyPair =
function generateKeyPair(session, id, label) {
this.info('Generating key pair id: ' + id + ' label: ' + label +
' bits:2048.');
session.generateKeyPair(
Session.CKM_RSA_PKCS_KEY_PAIR_GEN,
[
// Public key properties.
[PkObject.CKA_ENCRYPT, true],
[PkObject.CKA_VERIFY, true],
[PkObject.CKA_WRAP, true],
[PkObject.CKA_MODULUS_BITS, 2048],
],
[
// Private key properties.
[PkObject.CKA_PRIVATE, true],
[PkObject.CKA_SENSITIVE, true],
[PkObject.CKA_SIGN, true],
[PkObject.CKA_DECRYPT, true],
[PkObject.CKA_UNWRAP, true],
],
[
// Common properties of public & private.
[PkObject.CKA_TOKEN, true],
[PkObject.CKA_LABEL, label],
[PkObject.CKA_ID, id]
]
);
}
/**
* Certificate Definition constructor
*
* A Policy.Certificate defines the parameters required to generate a
* CSR and store the resulting certificate. It does *not* represent the actual
* certificate itself.
*
* The params object should be a plain JavaScript object with the following
* properties:
*
* - 'label': A string defining a human readable label that uniquely identifies
* this certificate.
*
* - 'onInstall': (optional) A function to invoke when the certificate is
* successfully installed, or when entd starts up after having already
* installed the certificate. The function will receive a single boolean
* parameter which indicates whether or not this is the first install
* of the certificate (parameter is true), or a restart of entd.
*
* - 'csr': An object defining the parameters of the Certificate Signing
* Request. The object may have the following properties:
* - 'subject': A string continaing the subject for the CSR.
* - 'host': The hostname where the CSR should be sent. This must be
* the same as, or a subdomain of, entd.hostname.
* - 'port': (optional) The port on the target host where the CSR should
* be sent. Defaults to 443.
* - 'auth': (optional) HTTP Basic authorization for the request. Any
* variable references will be resolved before sending the request.
* - 'path': The path to where the CSR should be sent. CSRs are
* sent using the HTTP POST method. Any variable references in the
* path are resolved before sending the request.
* - 'post_params': An optional JavaScript object containing parameters
* that should be sent with the POST. Variable references in the
* values will be resolved before sending the request.
*
* - 'issue': An object defining the parameters of the Certificate issue
* request. The object may have the following properties:
* - 'host': The hostname where the Certificate issue request should
* be sent. This must be the same as, or a subdomain of, entd.hostname.
* - 'port': (optional) The port on the target host where the Certificate
* issue request should be sent. Defaults to 443.
* - 'auth': (optional) HTTP Basic authorization for the request. Any
* variable references will be resolved before sending the request.
* - 'path': The path to where the Certificate issue request should be
* sent. Certificate issue requests are sent using the HTTP GET
* method. Any variable references in the path are resolved before
* sending the request.
*
* See entd.crypto.Certificate() for the conrete certificate class.
*
* @param {Policy} policy The parent policy of this certificate definition.
* @param {string} type An identifier for the certificate definition.
* @param {Object} params The parameters for this certificate definition.
*/
Policy.Certificate =
function PolicyCertificate(policy, type, params) {
this.policy = policy;
this.type = type;
// Backwards compatibility.
if (!('id' in params)) {
params.id = params.key_identifier || 3;
}
util.ensureProperties(params, ['label', 'id', 'csr', 'issuer'], 'params');
this.label = params.label;
this.id = parseInt(params.id);
this.onInstall = params.onInstall;
util.ensureProperties(params.csr, ['subject', 'host', 'path'], 'params.csr');
this.csr = params.csr;
util.ensureProperties(params.issuer, ['host', 'path'], 'params.issuer');
this.issuer = params.issuer;
this.variables_ = util.clone(this.policy.variables_);
if ('variables' in params) {
for (var key in params.variables)
this.setVariable(key, params.variables[key]);
}
this.userVariables = params.userVariables || null;
this.state = 'stop:unknown';
};
/**
* Get a variable for this certificate definition.
*
* Variables can be referenced in various parts of the policy and
* certificate configuration. This function is used to retrieve the value of
* a variable.
*
* @param {string} name The name of the variable.
* @param {string} opt_default Optional default value.
*/
Policy.Certificate.prototype.setVariable = Policy.prototype.setVariable;
/**
* Set a variable for this certificate definition.
*
* Variables can be referenced in various parts of the policy and
* certificate configuration. This function is used to assign a value to
* a variable.
*
* @param {string} name The name of the variable.
* @param {string} value The value of the variable.
*/
Policy.Certificate.prototype.getVariable = Policy.prototype.getVariable;
/**
* Replace variable references with their values.
*
* This function will replace zero or more variable references in the given
* string with the variable values associated with this certificate
* definition.
*
* See the documentation for util.replaceVars() for details on the variable
* syntax.
*
* @param {string} str The string containing variable references.
* @return {string} The source string with all variable references resolved.
*/
Policy.Certificate.prototype.replaceVars =
function cert_replaceVars(str) {
return util.replaceVars(str, util.bindp(this, 'getVariable'));
};
/**
* Log an information status message about this certificate definition.
*
* @param {string} str The informational message to log.
* @return {string} The string logged.
*/
Policy.Certificate.prototype.info =
function info(str) { return this.log_('info', str) };
/**
* Log a warning status message about this certificate definition.
*
* @param {string} str The warning message to log.
* @return {string} The string logged.
*/
Policy.Certificate.prototype.warn =
function warn(str) { return this.log_('warn', str) };
/**
* Log an error status message about this certificate definition.
*
* @param {string} str The error message to log.
* @return {string} The string logged.
*/
Policy.Certificate.prototype.error =
function error(str) {
return this.log_('error', str);
};
/**
* Log the fact that an asynchronous operation has started for this certificate.
*
* See Policy.prototype.start.
*/
Policy.Certificate.prototype.start =
function start(state, msg) {
if (msg)
this.info(msg);
this.policy.start(this, state);
}
/**
* Log the fact that an asynchronous operation has started for this certificate.
*
* See Policy.prototype.start.
*/
Policy.Certificate.prototype.stop =
function stop(state, msg) {
if (msg) {
if (state == 'error')
this.error(msg);
else
this.info(msg);
}
return this.policy.stop(this, state);
}
Policy.Certificate.prototype.log_ =
function log_(type, str) {
return this.policy[type]('Certificate ' + this.type + ': ' + str);
};
/**
* Finalize certificate installation and invoke any user specific onInstall
* function.
*
* @param {boolean} firstInstall True if this is the first time the certificate
* has been installed. False if the certificate was already there when we
* started.
*/
Policy.Certificate.prototype.onInstall_ =
function onInstall(firstInstall) {
this.start('install');
this.path = 'SETTINGS:key_id=' + util.intToHex(this.id) +
',cert_id=' + util.intToHex(this.id) +
',pin=' + Policy.PKCS11_USER_PIN;
this.info('Certificate installed to: ' + this.path);
if (typeof this.onInstall == 'function') {
try {
this.onInstall(firstInstall);
} catch (ex) {
this.stop('error', 'Exception running post-install callback: ' + ex);
return;
}
}
this.stop('ready');
};
/*
* Opens slot with a private session, using user's PIN.
*
* @param {Session.CKU_*} userType Type of session to open.
* @param {String} pin PIN to open the device.
* @returns {Session} Opened session or null if operation failed.
*/
Policy.prototype.loginToken =
function loginToken(token, sessionType, pin) {
var pkcs11 = this.pkcs11.api;
if (!pin) {
switch (sessionType) {
case Session.CKU_USER:
pin = Policy.PKCS11_USER_PIN;
break;
case Session.CKU_SO:
pin = Policy.PKCS11_SO_PIN;
break;
default:
this.error('Unknown user type');
return null;
}
}
var session = null;
this.info('Opening session and logging into token.');
try {
token.closeAllSessions();
session = token.openSession(Token.CKF_RW_SESSION);
} catch (ex) {
this.error('Unable to open session: ' + ex);
return null;
}
try {
if (!session.login(sessionType, pin)) {
this.error('Unable to log in user into token.');
session.logoutAndClose();
return null;
}
} catch (ex) {
this.error('Failed to login user into token: ' + ex);
session.logoutAndClose();
return null;
}
return session;
}
/**
* Determine if this certificate definition has been successfully installed.
*
* @return {boolean} A boolean indicating whether or not this certificate
* definition has been successfully installed in the PKCS#11 device.
*/
Policy.prototype.findObjectById =
function findObjectById(session, type, id, typeString) {
var objList = session.findObjects([[PkObject.CKA_CLASS, type],
[PkObject.CKA_ID, id]]);
if (objList) {
switch (objList.length) {
case 1:
// Found matching object.
return objList[0];
case 0:
// Object not found.
return null;
default:
// Too many objects.
this.error('Too many matching objects: ' + typeString);
return null;
}
} else {
this.error('error', 'Problem with PKCS#11 token.');
return null;
}
};
/**
* Fetch a certificate by ID
*/
Policy.prototype.findCertificateById =
function findCertificateById(session, id) {
return this.findObjectById(session, PkObject.CKO_CERTIFICATE, id,
'certificates');
};
/**
* Fetch a private key by ID
*/
Policy.prototype.findPrivateKeyById =
function findPrivateKeyById(session, id) {
return this.findObjectById(session, PkObject.CKO_PRIVATE_KEY, id,
'private keys');
};
/**
* Remove certificate(s) and key(s) associated with the certificate type
*/
Policy.prototype.removeObjectsById =
function removeObjectsById(session, id) {
var types = [PkObject.CKO_CERTIFICATE,
PkObject.CKO_PUBLIC_KEY,
PkObject.CKO_PRIVATE_KEY];
this.info('Removing all objects for slot: ' + Policy.PKCS11_SLOT + ' id: ' +
id);
for (type in types) {
var objList = session.findObjects([[PkObject.CKA_CLASS, types[type]],
[PkObject.CKA_ID, id]]);
if (!objList) {
this.error('findObjects returned null.');
return;
}
for (obj in objList)
objList[obj].destroy();
}
}
/**
* Store a X.509 PEM encoded certificate in the PKCS#11 device.
*/
Policy.Certificate.prototype.storeCertificate =
function storeCertificate(session, id, label, subject, certificate) {
session.createObject(
[
[PkObject.CKA_LABEL, label],
[PkObject.CKA_ID, id],
[PkObject.CKA_TOKEN, true],
[PkObject.CKA_CLASS, PkObject.CKO_CERTIFICATE],
[PkObject.CKA_SUBJECT, subject],
[PkObject.CKA_CERTIFICATE_TYPE, PkObject.CKC_X_509],
[PkObject.CKA_VALUE, certificate]
]
);
}
/**
* Determine if this certificate definition and matching private key has
* been successfully installed.
*
* @return {boolean} A boolean indicating whether or not this certificate
* definition has been successfully installed in the PKCS#11 device.
*/
Policy.Certificate.prototype.isInstalled =
function isInstalled() {
// Check if the corresponding token is accepting commands (this is
// important to prevent race conditions when initializing the token,
// such as interleaved calls to 'listCertificate' while performing
// initialization asynchronously.)
var pkcs11 = this.policy.pkcs11.api;
var token = pkcs11.slots[Policy.PKCS11_SLOT].token;
if (!this.policy.checkToken(token))
return false;
var session = null;
try {
session = this.policy.loginToken(token, Session.CKU_USER);
if (!session)
return this.error('Cannot login to token.');
var cert = policy.findCertificateById(session, this.id);
var key = policy.findPrivateKeyById(session, this.id);
session.logoutAndClose();
return (cert != null && key != null);
} catch(ex) {
session.logoutAndClose();
this.error('Error checking certificate is installed: ', ex);
}
return false;
};
/**
* Initiate the CSR process for this certificate definition.
*/
Policy.Certificate.prototype.initiateCSR =
function initiateCSR() {
// Set up a request object.
var r = new entd.http.Request(this.csr.host, this.replaceVars(this.csr.path));
if ('port' in this.csr)
r.port = this.csr.port;
if ('auth' in this.csr)
r.auth = this.replaceVars(this.csr.auth);
// We use this property in the callbacks to make log messages more useful.
r.description = 'Certificate Signing Request';
// Stash this so that we can refer to it later in on onCSRComplete.
r.certificate = this;
this.start('key', 'Generating key pair');
// Get the subject from the definition and replace any variable references.
var subject = this.replaceVars(this.csr.subject);
this.info('CSR Subject: ' + subject);
var pkcs11 = this.policy.pkcs11.api;
var token = pkcs11.slots[Policy.PKCS11_SLOT].token;
var session = policy.loginToken(token, Session.CKU_USER);
if (!session)
return this.stop('error', 'Cannot login to token.');
// Remove any existing entries matching this.id.
this.policy.removeObjectsById(session, this.id);
try {
// Generate a key pair.
this.policy.generateKeyPair(session, this.id, this.label);
} catch(e) {
session.logoutAndClose();
return this.stop('error', 'Failed to create key on PKCS#11 device: ' + e);
}
this.stop('key', 'Key generation complete');
session.logoutAndClose();
this.start('csr', 'Initiating Certificate Signing Request');
// create a CSR using the generated key pair.
var engine = this.policy.engine;
this.info('Generating CSR for id: ' + this.id + ' subject: ' + subject);
var csr = engine.createCSR(this.id, subject);
// Copy the CSR as a string to the environment.
this.setVariable('csr', csr.toFormat(CSR.CSR_FORMAT_PEM_TEXT));
// Copy all of the POST parameters after replacing the variable references.
if ('post_params' in this.csr) {
r.params = [];
for (var key in this.csr.post_params) {
var value = this.csr.post_params[key];
var isRaw = util.isRawString(value);
value = this.replaceVars(value);
if (isRaw) {
r.params.push(encodeURI(key) + '=' + value);
} else {
r.params.push(encodeURI(key) + '=' + encodeURI(value));
}
}
}
// Hook up our callbacks.
r.onComplete = util.fwdp(this, 'onCSRComplete_');
r.onTimeout = util.fwdp(this, 'onHTTPTimeout_');
r.onError = util.fwdp(this, 'onHTTPError_');
// Fire off the POST request.
entd.http.POST(r);
this.info('POSTing to ' + r.url);
};
/**
* Called when a http request times out.
*/
Policy.Certificate.prototype.onHTTPTimeout_ =
function onHTTPTimeout(request) {
this.stop('error', 'HTTP Timeout during ' + request.description);
};
/**
* Called when a http request errors out.
*/
Policy.Certificate.prototype.onHTTPError_ =
function onHTTPError_(request, reason) {
this.stop('error', 'Error sending request: ' + reason + ', during ' +
request.description);
};
/**
* Called when the CSR request completes with anything other than a timeout.
*/
Policy.Certificate.prototype.onCSRComplete_ =
function onCSRComplete_(request, response) {
this.info('CSR completed with http status: ' + response.code);
if (response.code != entd.http.RESPONSE_OK) {
var reason;
switch (response.code) {
case 401:
reason = 'Invalid password';
break;
case 500:
reason = 'Internal server error';
break;
default:
reason = 'HTTP Error ' + response.code;
}
this.stop('error', reason + ' during ' + request.description);
return;
}
try {
this.parseCSR(response);
} catch (ex) {
this.stop('error', 'Error parsing request id: ' + ex);
return;
}
this.stop('csr');
};
/**
* Fetch a cert that was just created for us as the result of a CSR.
*/
Policy.Certificate.prototype.getCert =
function getCert() {
this.start('cert', 'Getting Certificate');
var r = new entd.http.Request(this.issuer.host,
this.replaceVars(this.issuer.path));
if ('port' in this.issuer)
r.port = this.issuer.port;
if ('auth' in this.issuer)
r.auth = this.replaceVars(this.issuer.auth);
// Stash this so that we can refer to it later in on onIssuanceComplete.
r.certificate = this;
// We use this property in the callbacks to make log messages more useful.
r.description = 'Certificate Issue Request';
r.onComplete = util.fwdp(this, 'onIssuanceComplete_');
r.onTimeout = util.fwdp(this, 'onHTTPTimeout_');
r.onError = util.fwdp(this, 'onHTTPError_');
entd.http.GET(r);
this.info('GETing from ' + r.url);
};
// Called when the certificate-issue request completes with anything
// other than a timeout.
Policy.Certificate.prototype.onIssuanceComplete_ =
function onIssuanceComplete(request, response) {
if (response.code != entd.http.RESPONSE_OK) {
this.stop('error', 'HTTP error ' + response.code + ' during ' +
request.description);
return;
}
// X.509 PEM encoded certificate response.
var cert = response.content;
// Open a session with the device.
var pkcs11 = this.policy.pkcs11.api;
var token = pkcs11.slots[Policy.PKCS11_SLOT].token;
var session = this.policy.loginToken(token, Session.CKU_USER);
if (!session)
return this.stop('error', "Can't add certificate: missing session object");
// Convert response to DER.
var x509 = new entd.crypto.OpenSSL.X509(cert, X509.X509_FORMAT_PEM_TEXT);
var x509_der = x509.toFormat(X509.X509_FORMAT_DER);
// Store certificate in the PKCS#11 token.
this.storeCertificate(session, this.id, this.label, this.subject, x509_der);
this.stop('cert');
session.logoutAndClose();
};
Policy.prototype.setBrowserPolicy =
function setBrowserPolicy(type, sourcePolicy) {
var targetPolicy;
if (type == "managed") {
targetPolicy = entd.browser.managedPolicy;
} else if (type == "recommended") {
targetPolicy = entd.browser.recommendedPolicy;
} else {
throw "Invalid browser policy type: " + type;
}
this.info("Synchronizing browser policy: " + type);
var changed = false;
for (var p in targetPolicy) {
if (!(p in sourcePolicy)) {
delete targetPolicy[p];
changed = true;
}
}
for (var p in sourcePolicy) {
if (targetPolicy[p] != sourcePolicy[p]) {
targetPolicy[p] = sourcePolicy[p];
changed = true;
}
}
if (changed) {
this.info("Browser restart is suggested.");
} else {
this.info("Browser policy has not changed.");
}
return changed;
}
/**
* Policy.Callbacks constructor.
*
* Policy callbacks contain the functions that can be invoked through the
* callback server. Each function can take a single parameter which can
* be any primitive JavaScript value (Object, Array, number, or string),
* or any combination of primitive JavaScript values.
*
* Policy implementors may add their own callbacks by extending this object,
* as in...
*
* Policy.Callbacks.prototype['cb:my_awesome_callback'] = function (arg) {
* return 'I am awesome.';
* }
*
* Callbacks are not installed in the callback server by default.
* Policy implementors that want to make use of these callbacks should
* start the callback server in their policy, as in...
*
* entd.onLoad = function (manifest) {
* var policy = new Policy(manifest);
* entd.callbackServer.start(policy.callbacks);
* }
*
* @param {Policy} policy The parent policy for these callbacks.
*/
Policy.Callbacks =
function PolicyCallbacks(policy) {
this.policy = policy;
};
/**
* Return information about the current policy.
*
* This policy callback returns the policy description, according to the
* extension manifest,and the current username.
*
* @return {Object} An object with 'description' and 'username' properties.
*/
Policy.Callbacks.prototype['cb:info'] =
function cb_info() {
var callback_data = {
description: this.policy.manifest.description,
version: this.policy.manifest.version,
systemVersion: entd.parsedChromeOSVersion,
lsbRelease: entd.parsedLsbRelease,
username: entd.username,
browserPolicyChanged: this.policy.browserPolicyChanged,
isLibcrosLoaded: entd.isLibcrosLoaded,
tpm: {
isReady: (entd.isLibcrosLoaded ? entd.tpm.isReady : true),
isEnabled: (entd.isLibcrosLoaded ? entd.tpm.isEnabled : true),
isOwned: (entd.isLibcrosLoaded ? entd.tpm.isOwned : true),
isBeingOwned: (entd.isLibcrosLoaded ? entd.tpm.isBeingOwned : false),
statusString: (entd.isLibcrosLoaded ? entd.tpm.statusString :
"libcros not loaded")
},
pkcs11: {
state: this.policy.pkcs11.state,
log: this.policy.getLog(this.policy.pkcs11)
}
}
if ('isTokenReady' in entd.tpm) {
callback_data.pkcs11.isTokenReady =
(entd.isLibcrosLoaded ? entd.tpm.isTokenReady : false);
}
return Policy.CallbackSuccess(callback_data);
};
/**
* Set the user PIN on a given token.
*
* This is a no-op if the oldPin and newPin are the same value.
*
* @param {Object} arg An object with the following properties:
* - 'slotId' An integer representing the slot that contains the target token.
* - 'oldPin' An optional string representing the current PIN. Defaults to
* Token.DEFAULT_USER_PIN or Policy.PKCS11_USER_PIN, depending on the
* state of the token.
* - 'oldPin' An optional string representing the new PIN. Defaults to
* Policy.PKCS11_USER_PIN.
*
* Setting the user pin is an asynchronous operation. While the set is in
* progress the token state will be 'start:user-pin'. If the operation
* completes successfully, the token state should become 'stop:ready', although
* if the operation completes successfully but the token is not ready due to
* some unexpected condition, it will become 'stop:user-pin'. On error it will
* become 'stop:error'.
* TODO(crosbug.com/14277): Remove SetPIN functions.
*/
Policy.Callbacks.prototype['cb:setUserPin'] =
function cb_setUserPin(arg) {
var pkcs11 = this.policy.pkcs11.api;
if (!pkcs11)
return Policy.CallbackError('Pkcs11 not initialized');
if (!('slotId' in arg))
return Policy.CallbackError('Missing required parameter: slotId');
var token = pkcs11.slots[arg.slotId].token;
if (!token)
return Policy.CallbackError('Invalid slotId: ' + arg.slotId);
if (!arg.oldPin) {
token.refresh();
if (token.flags & Token.CKF_USER_PIN_TO_BE_CHANGED) {
arg.oldPin = Token.DEFAULT_USER_PIN;
} else {
arg.oldPin = Policy.PKCS11_USER_PIN;
}
}
if (!arg.newPin)
arg.newPin = Policy.PKCS11_USER_PIN;
arg.userType = 'user';
return this.setPin_(arg);
}
/**
* Set the Security Officer PIN on a given token.
*
* This is a no-op if the oldPin and newPin are the same value.
*
* @param {Object} arg An object with the following properties:
* - 'slotId' An integer representing the slot that contains the target token.
* - 'oldPin' An optional string representing the current PIN. Defaults to
* Token.DEFAULT_SO_PIN or Policy.PKCS11_SO_PIN, depending on the
* state of the token.
* - 'oldPin' An optional string representing the new PIN. Defaults to
* Policy.PKCS11_SO_PIN.
*
* Setting the user pin is an asynchronous operation. While the set is in
* progress the token state will be 'start:so-pin'. If the operation
* completes successfully the token state will become 'stop:so-pin'. On
* error it will become 'stop:error'.
*/
Policy.Callbacks.prototype['cb:setSoPin'] =
function cb_setSoPin(arg) {
var pkcs11 = this.policy.pkcs11.api;
if (!pkcs11)
return Policy.CallbackError('Pkcs11 not initialized');
if (!('slotId' in arg))
return Policy.CallbackError('Missing required parameter: slotId');
var token = pkcs11.slots[arg.slotId].token;
if (!token)
return Policy.CallbackError('Invalid slotId: ' + arg.slotId);
if (!arg.oldPin) {
token.refresh();
if (token.flags & Token.CKF_SO_PIN_TO_BE_CHANGED) {
arg.oldPin = Token.DEFAULT_SO_PIN;
} else {
arg.oldPin = Policy.PKCS11_SO_PIN;
}
}
if (!arg.newPin)
arg.newPin = Policy.PKCS11_SO_PIN;
arg.userType = 'so';
return this.setPin_(arg);
}
/**
* Common code for cb:setUserPin and cb:setSoPin.
*/
Policy.Callbacks.prototype.setPin_ =
function cb_setPin_(arg) {
var pkcs11 = this.policy.pkcs11.api;
if (!pkcs11)
return Policy.CallbackError('Pkcs11 not initialized');
if (!('slotId' in arg))
return Policy.CallbackError('Missing required parameter: slotId');
var token = pkcs11.slots[arg.slotId].token;
if (!token)
return Policy.CallbackError('Invalid slotId: ' + arg.slotId);
if (!arg.oldPin)
return Policy.CallbackError('Missing required parameter: oldPin');
if (!arg.newPin)
return Policy.CallbackError('Missing required parameter: newPin');
if (!arg.userType)
return Policy.CallbackError('Missing required parameter: userType');
var userType;
if (arg.userType == "user") {
userType = Session.CKU_USER;
} else if (arg.userType == "so") {
userType = Session.CKU_SO;
} else {
return Policy.CallbackError('Invalid userType: ' + arg.userType);
}
var policy = this.policy;
entd.setTimeout(function () {
policy.setTokenPin(token, userType, arg.oldPin, arg.newPin);
}, 1);
return Policy.CallbackSuccess('Resetting so pin');
}
/**
* Initialize a PKCS11 token.
*
* @param {Object} arg An object with the following properties:
* - 'slotId' An integer representing the slot that contains the target token.
*
* Initializing a PKCS11 token is an asynchronous operation. While the
* initialization is in progress the token state will be 'start:init'. If the
* operation completes successfully the token state will become 'stop:init'.
* On error it will become 'stop:error'.
* TODO(crosbug.com/14277): Remove initToken function.
*/
Policy.Callbacks.prototype['cb:initToken'] =
function cb_initToken(arg) {
var pkcs11 = this.policy.pkcs11.api;
if (!pkcs11)
return Policy.CallbackError('Pkcs11 not initialized');
if (!('slotId' in arg))
return Policy.CallbackError('Mising parameter: slotId');
if (!(arg.slotId in pkcs11.slots))
return Policy.CallbackError('Invalid slotId: ' + arg.slotId);
var token = pkcs11.slots[arg.slotId].token;
if (!token)
return Policy.CallbackError('Slot has no token: ' + arg.slotId);
var policy = this.policy;
entd.setTimeout(function () { policy.initToken(token, arg.tokenLabel) }, 1);
return Policy.CallbackSuccess('Initializing token');
}
/**
* Return a list known PKCS11 tokens.
*/
Policy.Callbacks.prototype['cb:listTokens'] =
function cb_listTokens() {
var pkcs11 = this.policy.pkcs11.api;
if (!pkcs11)
return Policy.CallbackError('Pkcs11 not initialized');
var tokenList = new Array();
for (var i in pkcs11.slots) {
var slot = pkcs11.slots[i];
if (!slot.token)
continue;
var token = slot.token;
token.refresh();
var tokenData = { slotId: i, log: '' };
for (var key in slot.token)
tokenData[key] = slot.token[key];
tokenData.uintFlags = tokenData.flags;
tokenData.flags = new Object();
for (var key in Token) {
// The client doesn't have access to the constants in
// entd.pkcs11.crypto.Token.CKF_*, so we copy the applicable flags
// onto the tokenData, minus the "CKF_" prefix.
if (key.match(/^CKF_/))
tokenData.flags[key.substr(4)] = !!(token.flags & Token[key]);
}
tokenList.push(tokenData);
}
return Policy.CallbackSuccess({ tokenList: tokenList });
}
/**
* Return the list of certificate definitions.
*
* This policy callback returns a list of certificate definitions, and their
* status.
*
* Each returned certificate definition will be an object with the
* following properties:
*
* - 'id' String identifier for the certificate definition.
* - 'label' String label for the certificate definition.
* - 'userVariables' Object continaing the variables that must be
* provided by the user.
* - 'installed' Boolean indicating whether or not this certificate
* has been successfully installed.
* - 'status' String containing the last status message for this
* certificate definition.
*
* @return {Object} An object containing one property for each certificate
* definition. Callers should assume the property names are opaque
* tokens.
*/
Policy.Callbacks.prototype['cb:listCertificates'] =
function cb_listCerts(arg) {
var pkcs11 = this.policy.pkcs11.api;
if (!pkcs11)
return Policy.CallbackError('Pkcs11 not initialized');
var rv = {};
for (key in this.policy.certificates) {
var cert = this.policy.certificates[key];
var token = pkcs11.slots[Policy.PKCS11_SLOT].token;
rv[key] = {
id: cert.type,
key: key,
label: cert.label,
userVariables: cert.userVariables,
installed: cert.isInstalled() ? 1 : 0,
log: cert.log,
state: cert.state,
};
}
return Policy.CallbackSuccess(rv);
};
/**
* Initiate a Certificate Signing Request.
*
* This policy callback is used to initiate a Certificate Signing Request
* for a given certificate definition. If the certificate is already
* installed, this will attempt to replace it.
*
* @param {Object} arg An object with the following properties:
* - 'certificateId' A string identifying the certificate.
* - 'variables' An object containing the user variables required for this
* certificate.
*
* The CSR is an asynchronous operation. While the in progress the certificate
* state will be 'start:csr'. If the operation completes successfully the
* certificate state will become 'stop:csr'. On error it will become
* 'stop:error'.
*/
Policy.Callbacks.prototype['cb:initiateCSR'] =
function cb_initiateCSR(arg) {
// If they forgot to reference a certificate, or we can't locate the
// one they referenced, we return a hard CallbackError. If the UI notices
// a CallbackError, its only recourse is to show a generic alert message.
if (!arg)
return Policy.CallbackError('Missing arguments');
var cert = this.policy.certificates[util.toKey(arg.certificateId)];
if (!cert)
return Policy.CallbackError('Unknown certificate: ' + arg.certificateId);
// If we can find the cert, then we return a CallbackSuccess and set the
// status of the cert, even on failure. If the UI sees a CallbackSuccess,
// it can associate the attached message with the certificate UI.
// It will ultimately know the correct success or failure state of the CSR
// from the "isInstalled" property returned by the listCertificates callback.
if (cert.userVariables) {
if (!arg.variables)
return Policy.CallbackSuccess(cert.error('Missing parameter: variables'));
for (name in cert.userVariables) {
if (!(name in arg.variables)) {
return Policy.CallbackSuccess(
cert.error('Missing certificate variable: ' + name));
}
cert.setVariable(name, String(arg.variables[name]));
}
}
// Create CSR asynchronously.
entd.setTimeout(function() { cert.initiateCSR() }, 1);
return Policy.CallbackSuccess(cert.info('Initiating CSR for: ' + cert.label));
};
/**
* Retrieve a certificate.
*
* This policy callback is used to request a certificate after the successful
* completion of a CSR.
*
* @param {Object} arg An object with the following properties:
* - 'certificateId' A string identifying the certificate.
*
* The certificate request is an asynchronous operation. While the in progress
* the certificate state will be 'start:cert'. If the operation completes
* successfully the certificate state will become 'stop:cert'. On error it
* will become 'stop:error'.
*/
Policy.Callbacks.prototype['cb:getCert'] =
function cb_getCert(arg) {
// If they forgot to reference a certificate, or we can't locate the
// one they referenced, we return a hard CallbackError. If the UI notices
// a CallbackError, its only recourse is to show a generic alert message.
if (!arg)
return Policy.CallbackError('Missing arguments');
var cert = this.policy.certificates[util.toKey(arg.certificateId)];
if (!cert)
return Policy.CallbackError('Unknown certificate: ' + arg.certificateId);
cert.getCert();
return Policy.CallbackSuccess(cert.info('Fetching certificate for: ' +
cert.label));
}
/**
* Complete the installation of a certificate.
*
* This policy callback is used to complete the installation of a certificate
* that has been successfully requested with cb:getCert.
*
* @param {Object} arg An object with the following properties:
* - 'certificateId' A string identifying the certificate.
*
* The certificate install is an asynchronous operation. While the in progress
* the certificate state will be 'start:init'. If the operation completes
* successfully the certificate state will become 'stop:ready'. On error it
* will become 'stop:error'.
*/
Policy.Callbacks.prototype['cb:installCert'] =
function cb_installCert(arg) {
// If they forgot to reference a certificate, or we can't locate the
// one they referenced, we return a hard CallbackError. If the UI notices
// a CallbackError, its only recourse is to show a generic alert message.
if (!arg)
return Policy.CallbackError('Missing arguments');
var cert = this.policy.certificates[util.toKey(arg.certificateId)];
if (!cert)
return Policy.CallbackError('Unknown certificate: ' + arg.certificateId);
cert.onInstall_(/* firstInstall: */ true);
return Policy.CallbackSuccess(cert.info('Installing certificate: ' +
cert.label));
}
Policy.Callbacks.prototype['cb:restart'] =
function cb_restart(arg) {
entd.syslog.info("Restarting by client request.");
// Exit code two means we haven't errored, but would like to be restarted.
entd.scheduleShutdown(2);
return Policy.CallbackSuccess('Restarting');
}
/**
* Return an object indicating that a callback succeeded.
*/
Policy.CallbackSuccess =
function CallbackSuccess(data) {
return { status: 'success', data: data };
};
/**
* Return an object indicating that a callback encountered an error.
*/
Policy.CallbackError =
function CallbackError(data) {
// Log the error to syslogs for further diagnosis.
entd.syslog.error(data);
return { status: 'error', data: data };
};
/**
* Namespace for utility functions...
*/
var util = new Object();
/**
* Returns a version of str with variable references replaced.
*
* Variables should be of the form %(name) or %FLAG(name).
*
* At the moment the only supported FLAG is 'uri', which uri encodes the value
* before placing it in the string. For example, if the value of
* vars['percent'] is '%', then %uri(percent) would become '%25'.
*
* This function throws an error if an unknown variable is referenced or
* an unknown FLAG is used.
*
* @param {string} str A string containing zero or more variable references.
* @param {Object|function(string):string} vars An object containing the
* variables, or a function that returns a variable value given a name.
*/
util.replaceVars =
function replaceVars(str, vars) {
function uriall_replace (ch) {
// Replace EVERY character with a percent sign, followed by the character's
// value in hex. It's like an extreme uri encoding, and some CSR servers
// seem to require it.
rv = ch.charCodeAt(0).toString(16);
return '%' + (rv.length > 1 ? rv : ('0' + rv));
}
function cb(m, flag, name) {
if (typeof vars == 'function')
value = vars(name);
else
value = vars[name];
if (typeof value == 'undefined')
throw new Error('replaceVars: Unknown variable name: ' + name);
if (!flag)
return value;
switch (flag) {
case 'uri': return encodeURI(value);
case 'uriall':
return value.replace(/.|\n/g, uriall_replace);
default:
throw new Error('replaceVars: Unknown flag: ' + flag +
', while replacing variable: ' + name);
}
}
return str.replace(/%([a-z]*)\(([^\)]+)\)/g, cb);
};
/**
* Bind a function to an object.
*
* The function returned by util.bind will invoke obj.func(...), passing along
* any parameters. For example...
*
* var f = util.bind(window, window.alert);
* f('hello world');
*
* This will create a new function f() which is an alias for window.alert().
* This is especially useful for defining callbacks the should be invoked on
* a particular object.
*
* @param {Object} obj The object to bind to.
* @param {function} func The function to bind.
* @return {function} A function that invokes obj.func().
*/
util.bind =
function bind(obj, func) {
return function(var_args) { return func.apply(obj, arguments) }
};
/**
* Bind a function to an object, where the function is already a property
* of the target object.
*
* This is performs the same service as util.bind(), except the second
* parameter is a string that refers to a function property on the object.
* For example:
*
* var f = util.bindp(window, 'alert');
* f('hello world');
*
* Compare this to the example in util.bind(), where 'window' was mentioned
* twice.
*
* @param {Object} obj The object to bind to.
* @param {string} prop The property containing the function to bind.
* @return {function} A function that invokes obj.func().
*/
util.bindp =
function bindp(obj, prop) {
if (typeof obj[prop] != 'function')
throw new Error('Property is missing or not a function: ' + prop);
return util.bind(obj, obj[prop]);
};
/**
* Forward a method from one object to another object.
*
* The function returned by util.fwd() will invoke obj.func(this, ...), where
* 'this' is the object that the function was originally applied to.
* For example:
*
* request.onComplete = util.fwd(obj, obj.onComplete);
* obj.onComplete = function(request, data) { ... }
*
* In this scenario, assume that the request is used as part of an asynchronous
* API, and that the onComplete() method of the request is invoked when
* the request completes.
*
* If we had used util.bind(), as in:
*
* request.onComplete = util.bind(obj, obj.onComplete);
* obj.onComplete = function(data) { ... }
*
* Then the onComplete() method would have been applied to 'obj', but the
* request object would have been lost.
*
* @param {Object} obj The object to bind to.
* @param {function} func The function to bind.
* @return {function} A function that invokes obj.func().
*/
util.fwd =
function fwd(obj, func) {
return function(var_args) {
var args = Array.apply(null, arguments);
args.unshift(this);
func.apply(obj, args);
}
};
/**
* Forward a method from one object to another object.
*
* This is performs the same service as util.fwd(), except the second
* parameter is a string that refers to a function property on the object.
* For example:
*
* request.onComplete = util.fwdp(obj, 'onComplete');
* obj.onComplete = function(request, data) { ... }
*
* Compare this to the example in util.bind(), where 'obj' was mentioned
* twice.
*
* @param {Object} obj The object to bind to.
* @param {string} prop The property containing the function to bind.
* @return {function} A function that invokes obj.func().
*/
util.fwdp =
function fwdp(obj, prop) {
if (typeof obj[prop] != 'function')
throw new Error('Property is missing or not a function: ' + prop);
return util.fwd(obj, obj[prop]);
};
/**
* Make a string safe to use as a key.
*
* When reading from a JavaScript object that is being used as a hash, it
* it possible to clash with default properties (for example, 'toString')
* in a way that may introduce bugs or security issues.
*
* Using util.toKey() and util.fromKey() can address this problem by ensuring
* that every non-default property starts with a known prefix.
*
* This is not needed for basic object-as-hash usage, but should be used in
* situations where a hash lookup could come from an untrusted source.
*/
util.toKey =
function toKey(str) {
return 'key:' + str;
};
/**
* Recovers the underlying string from a key.
*
* The is the conjugate of util.toKey().
*/
util.fromKey =
function fromKey(str) {
if (str.substr(0, 4) != 'key:')
throw new Error('Not a key: ' + str);
return str.substr(4);
};
/**
* Perform a shallow copy of an object.
*
* If multiple objects are provided then properties from all objects are
* copied onto the new clone.
*
* @param {...Object} var_args One or more objects to clone.
* @return {Object} A new object with all of the properties of the source
* objects.
*/
util.clone =
function clone(var_args) {
if (!arguments.length)
throw new Error('util.clone: Missing argument');
var rv = {};
for (var i = 0; i < arguments.length; ++i) {
for (var key in arguments[i])
rv[key] = arguments[i][key];
}
return rv;
};
/**
* Left pad a string to a given length, using a given character.
*
* @param {string} str The string to pad.
* @param {int} len The desired string length.
* @param {string} ch A single character to use as the padding.
*/
util.lpad =
function pad(str, len, ch) {
if (typeof str != "string")
str = str.toString();
while (str.length < len);
str = ch + str;
return str;
}
/**
* Ensures that an object has a set of required properties.
*
* This function throws an exception if the given object is missing any of the
* specified properties. It does not test the value of the property, only
* that it exists.
*
* @param {Object} obj The object to check.
* @param {Array} names The property names to check.
* @param {string} opt_objname An optional name for the object being checked.
* Used as part of the exception message of the check fails.
*/
util.ensureProperties =
function ensureProperties(obj, names, opt_objname) {
msg = 'Missing required property: ';
if (opt_objname)
msg += opt_objname + '.'
for (var i = 0; i < names.length; ++i) {
if (!(names[i] in obj))
throw new Error(msg + names[i]);
}
};
/**
* Return a string which indicates that it should not be escaped.
*/
util.RawString =
function RawString(str) {
if (!(str instanceof String))
str = new String(str);
str.isRaw_ = true;
return str;
}
/**
* Detect a raw string.
*/
util.isRawString =
function isRawString(str) {
return str instanceof String && str.isRaw_ == true;
}
/**
* Convert a number to hex format.
*/
util.intToHex =
function intToHex(val) {
var hex = Number(val).toString(16);
return hex;
}
/**
* Convert a string into its binary representation.
*/
util.stringToBinaryString =
function stringToBinaryString(str) {
var result = [];
for (i = 0; i < str.length; i++) {
var d = str.charCodeAt(i);
var h = d.toString(16);
if (h.length == 1)
result.push('0');
result.push(h);
}
return result.join('');
}
/**
* Initialization of entd version information. Must be done early and
* regardless of Policy being created since it may be used in
* policy.js's entd.Unload.
*/
function __init__() {
entd.parsedLsbRelease = Policy.parseLsbRelease(entd.lsbRelease);
entd.parsedChromeOSVersion =
Policy.parseChromeOSVersion(entd.parsedLsbRelease);
entd.syslog.info('Parsed ChromeOS version: ' + entd.parsedChromeOSVersion);
}
__init__();