Source: security/identity/basic-identity-storage.js

/**
 * Copyright (C) 2015-2016 Regents of the University of California.
 * @author: Jeff Thompson <jefft0@remap.ucla.edu>
 * From ndn-cxx security by Yingdi Yu <yingdi@cs.ucla.edu>.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

/** @ignore */
var Name = require('../../name.js').Name; /** @ignore */
var Blob = require('../../util/blob.js').Blob; /** @ignore */
var Sqlite3Promise = require('../../util/sqlite3-promise.js').Sqlite3Promise; /** @ignore */
var KeyLocator = require('../../key-locator.js').KeyLocator; /** @ignore */
var SecurityException = require('../security-exception.js').SecurityException; /** @ignore */
var IdentityCertificate = require('../certificate/identity-certificate.js').IdentityCertificate; /** @ignore */
var IdentityStorage = require('./identity-storage.js').IdentityStorage; /** @ignore */
var path = require('path');

/**
 * BasicIdentityStorage extends IdentityStorage to implement basic storage of
 * identity, public keys and certificates using the Node.js sqlite3 module.
 * Create a new BasicIdentityStorage to use the SQLite3 file in the default
 * location, or the optional given file.
 * @param {string} databaseFilePath (optional) The path of the SQLite3 file. If
 * omitted, use the default file (~/.ndn/ndnsec-public-info.db).
 * @constructor
 */
var BasicIdentityStorage = function BasicIdentityStorage(databaseFilePath)
{
  // Call the base constructor.
  IdentityStorage.call(this);

  databaseFilePath = databaseFilePath || path.join
    (BasicIdentityStorage.getUserHomePath(), ".ndn", "ndnsec-public-info.db");
  this.database_ = new Sqlite3Promise
    (databaseFilePath, BasicIdentityStorage.initializeDatabasePromise_);
};

BasicIdentityStorage.prototype = new IdentityStorage();
BasicIdentityStorage.prototype.name = "BasicIdentityStorage";

exports.BasicIdentityStorage = BasicIdentityStorage;

/**
 * Check if the specified identity already exists.
 * @param {Name} identityName The identity name.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @returns {Promise} A promise which returns true if the identity exists.
 */
BasicIdentityStorage.prototype.doesIdentityExistPromise = function
  (identityName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.doesIdentityExistPromise is only supported for async")));

  return this.getPromise_
    ("SELECT count(*) FROM Identity WHERE identity_name=?", identityName.toUri())
  .then(function(row) {
    if (row["count(*)"] > 0)
      return Promise.resolve(true);
    else
      return Promise.resolve(false);
  });
};

/**
 * Add a new identity. Do nothing if the identity already exists.
 * @param {Name} identityName The identity name to be added.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which fulfills when the identity is added.
 */
BasicIdentityStorage.prototype.addIdentityPromise = function(identityName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.addIdentityPromise is only supported for async")));

  var thisStorage = this;
  var identityUri = identityName.toUri();
  return this.doesIdentityExistPromise(identityName)
  .then(function(exists) {
    if (exists)
      return Promise.resolve();

    return thisStorage.runPromise_
      ("INSERT INTO Identity (identity_name) VALUES(?)", identityUri);
  });
};

/**
 * Check if the specified key already exists.
 * @param {Name} keyName The name of the key.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which returns true if the key exists.
 */
BasicIdentityStorage.prototype.doesKeyExistPromise = function(keyName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.doesKeyExistPromise is only supported for async")));

  var keyId = keyName.get(-1).toEscapedString();
  var identityName = keyName.getPrefix(-1);

  return this.getPromise_
    ("SELECT count(*) FROM Key WHERE identity_name=? AND key_identifier=?",
     [identityName.toUri(), keyId])
  .then(function(row) {
    if (row["count(*)"] > 0)
      return Promise.resolve(true);
    else
      return Promise.resolve(false);
  });
};

/**
 * Add a public key to the identity storage. Also call addIdentity to ensure
 * that the identityName for the key exists. However, if the key already
   * exists, do nothing.
 * @param {Name} keyName The name of the public key to be added.
 * @param {number} keyType Type of the public key to be added from KeyType, such
 * as KeyType.RSA..
 * @param {Blob} publicKeyDer A blob of the public key DER to be added.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which fulfills when complete.
 */
BasicIdentityStorage.prototype.addKeyPromise = function
  (keyName, keyType, publicKeyDer, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.addKeyPromise is only supported for async")));

  if (keyName.size() === 0)
    return Promise.resolve();

  var thisStorage = this;
  return this.doesKeyExistPromise(keyName)
  .then(function(exists) {
    if (exists)
      return Promise.resolve();

    var identityName = keyName.getPrefix(-1);
    var identityUri = identityName.toUri();

    return thisStorage.addIdentityPromise(identityName)
    .then(function() {
      var keyId = keyName.get(-1).toEscapedString();
      var keyBuffer = publicKeyDer.buf();

      return thisStorage.runPromise_
        ("INSERT INTO Key (identity_name, key_identifier, key_type, public_key) VALUES(?,?,?,?)",
         [identityUri, keyId, keyType, keyBuffer]);
    });
  });
};

/**
 * Get the public key DER blob from the identity storage.
 * @param {Name} keyName The name of the requested public key.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which returns the DER Blob, or a promise rejected
 * with SecurityException if the key doesn't exist.
 */
BasicIdentityStorage.prototype.getKeyPromise = function(keyName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.getKeyPromise is only supported for async")));

  if (keyName.size() === 0)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage::getKeyPromise: Empty keyName")));

  var identityUri = keyName.getPrefix(-1).toUri();
  var keyId = keyName.get(-1).toEscapedString();

  return this.getPromise_
    ("SELECT public_key FROM Key WHERE identity_name=? AND key_identifier=?",
     [identityUri, keyId])
  .then(function(row) {
    if (row)
      return Promise.resolve(new Blob(row.public_key, false));
    else
      return Promise.reject(new SecurityException(new Error
        ("BasicIdentityStorage::getKeyPromise: The key does not exist")));
  });
};

/**
 * Check if the specified certificate already exists.
 * @param {Name} certificateName The name of the certificate.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which returns true if the certificate exists.
 */
BasicIdentityStorage.prototype.doesCertificateExistPromise = function
  (certificateName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.doesCertificateExistPromise is only supported for async")));

  return this.getPromise_
    ("SELECT count(*) FROM Certificate WHERE cert_name=?", certificateName.toUri())
  .then(function(row) {
    if (row["count(*)"] > 0)
      return Promise.resolve(true);
    else
      return Promise.resolve(false);
  });
};

/**
 * Add a certificate to the identity storage. Also call addKey to ensure that
 * the certificate key exists. If the certificate is already installed, don't
 * replace it.
 * @param {IdentityCertificate} certificate The certificate to be added.  This
 * makes a copy of the certificate.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which fulfills when finished.
 */
BasicIdentityStorage.prototype.addCertificatePromise = function
  (certificate, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.addCertificatePromise is only supported for async")));

  var certificateName = certificate.getName();
  var keyName = certificate.getPublicKeyName();

  var thisStorage = this;
  return this.addKeyPromise
    (keyName, certificate.getPublicKeyInfo().getKeyType(),
     certificate.getPublicKeyInfo().getKeyDer(), useSync)
  .then(function() {
    return thisStorage.doesCertificateExistPromise(certificateName);
  })
  .then(function(exists) {
    if (exists)
      return Promise.resolve();

    var keyId = keyName.get(-1).toEscapedString();
    var identity = keyName.getPrefix(-1);

    // Insert the certificate.

    var signature = certificate.getSignature();
    var signerName = KeyLocator.getFromSignature(signature).getKeyName();
    // Convert from milliseconds to seconds since 1/1/1970.
    var notBefore = Math.floor(certificate.getNotBefore() / 1000.0);
    var notAfter = Math.floor(certificate.getNotAfter() / 1000.0);
    var encodedCert = certificate.wireEncode().buf();

    return thisStorage.runPromise_
      ("INSERT INTO Certificate (cert_name, cert_issuer, identity_name, key_identifier, not_before, not_after, certificate_data) " +
       "VALUES (?,?,?,?,?,?,?)",
       [certificateName.toUri(), signerName.toUri(), identity.toUri(), keyId,
        notBefore, notAfter, encodedCert]);
  });
};

/**
 * Get a certificate from the identity storage.
 * @param {Name} certificateName The name of the requested certificate.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which returns the requested
 * IdentityCertificate, or a promise rejected with SecurityException if the
 * certificate doesn't exist.
 */
BasicIdentityStorage.prototype.getCertificatePromise = function
  (certificateName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.getCertificatePromise is only supported for async")));

  return this.getPromise_
    ("SELECT certificate_data FROM Certificate WHERE cert_name=?",
     certificateName.toUri())
  .then(function(row) {
    if (row) {
      var certificate = new IdentityCertificate()
      try {
        certificate.wireDecode(new Blob(row.certificate_data, false))
      } catch (ex) {
        return Promise.reject(new SecurityException(new Error
          ("BasicIdentityStorage.getCertificatePromise: The certificate cannot be decoded")));
      }
      return Promise.resolve(certificate);
    }
    else
      return Promise.reject(new SecurityException(new Error
        ("BasicIdentityStorage.getCertificatePromise: The certificate does not exist")));
  });
};

/*****************************************
 *           Get/Set Default             *
 *****************************************/

/**
 * Get the default identity.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which returns the Name of default identity, or a
 * promise rejected with SecurityException if the default identity is not set.
 */
BasicIdentityStorage.prototype.getDefaultIdentityPromise = function(useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.getDefaultIdentityPromise is only supported for async")));

  return this.getPromise_
    ("SELECT identity_name FROM Identity WHERE default_identity=1")
  .then(function(row) {
    if (row)
      return Promise.resolve(new Name(row.identity_name));
    else
      return Promise.reject(new SecurityException(new Error
        ("BasicIdentityStorage::getDefaultIdentityPromise: The default identity is not defined")));
  });
};

/**
 * Get the default key name for the specified identity.
 * @param {Name} identityName The identity name.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which returns the default key Name, or a promise
 * rejected with SecurityException if the default key name for the identity is
 * not set.
 */
BasicIdentityStorage.prototype.getDefaultKeyNameForIdentityPromise = function
  (identityName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.getDefaultKeyNameForIdentityPromise is only supported for async")));

  return this.getPromise_
    ("SELECT key_identifier FROM Key WHERE identity_name=? AND default_key=1",
     identityName.toUri())
  .then(function(row) {
    if (row)
      return Promise.resolve(new Name(identityName).append(row.key_identifier));
    else
      return Promise.reject(new SecurityException(new Error
        ("BasicIdentityStorage::getDefaultKeyNameForIdentityPromise: The default key for the identity is not defined")));
  });
};

/**
 * Get the default certificate name for the specified key.
 * @param {Name} keyName The key name.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which returns the default certificate Name, or a
 * promise rejected with SecurityException if the default certificate name for
 * the key name is not set.
 */
BasicIdentityStorage.prototype.getDefaultCertificateNameForKeyPromise = function
  (keyName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.getDefaultCertificateNameForKeyPromise is only supported for async")));

  var keyId = keyName.get(-1).toEscapedString();
  var identityName = keyName.getPrefix(-1);

  return this.getPromise_
    ("SELECT cert_name FROM Certificate WHERE identity_name=? AND key_identifier=? AND default_cert=1",
     [identityName.toUri(), keyId])
  .then(function(row) {
    if (row)
      return Promise.resolve(new Name(row.cert_name));
    else
      return Promise.reject(new SecurityException(new Error
        ("BasicIdentityStorage::getDefaultCertificateNameForKeyPromise: The default certificate for the key name is not defined")));
  });
};

/**
 * Append all the identity names to the nameList.
 * @param {Array<Name>} nameList Append result names to nameList.
 * @param {boolean} isDefault If true, add only the default identity name. If
 * false, add only the non-default identity names.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which fulfills when the names are added to
 * nameList.
 */
BasicIdentityStorage.prototype.getAllIdentitiesPromise = function
  (nameList, isDefault, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.getAllIdentitiesPromise is only supported for async")));

  var query;
  if (isDefault)
    query = "SELECT identity_name FROM Identity WHERE default_identity=1";
  else
    query = "SELECT identity_name FROM Identity WHERE default_identity=0";

  return this.eachPromise_(query, [], function(err, row) {
    nameList.push(new Name(row.identity_name));
  });
};

/**
 * Append all the key names of a particular identity to the nameList.
 * @param {Name} identityName The identity name to search for.
 * @param {Array<Name>} nameList Append result names to nameList.
 * @param {boolean} isDefault If true, add only the default key name. If false,
 * add only the non-default key names.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which fulfills when the names are added to
 * nameList.
 */
BasicIdentityStorage.prototype.getAllKeyNamesOfIdentityPromise = function
  (identityName, nameList, isDefault, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.getAllKeyNamesOfIdentityPromise is only supported for async")));

  var query;
  if (isDefault)
    query = "SELECT key_identifier FROM Key WHERE default_key=1 and identity_name=?";
  else
    query = "SELECT key_identifier FROM Key WHERE default_key=0 and identity_name=?";

  return this.eachPromise_(query, identityName.toUri(), function(err, row) {
    nameList.push(new Name(identityName).append(row.key_identifier));
  });
};

/**
 * Append all the certificate names of a particular key name to the nameList.
 * @param {Name} keyName The key name to search for.
 * @param {Array<Name>} nameList Append result names to nameList.
 * @param {boolean} isDefault If true, add only the default certificate name.
 * If false, add only the non-default certificate names.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which fulfills when the names are added to
 * nameList.
 */
BasicIdentityStorage.prototype.getAllCertificateNamesOfKeyPromise = function
  (keyName, nameList, isDefault, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.getAllCertificateNamesOfKeyPromise is only supported for async")));

  var query;
  if (isDefault)
    query = "SELECT cert_name FROM Certificate" +
            "  WHERE default_cert=1 and identity_name=? and key_identifier=?";
  else
    query = "SELECT cert_name FROM Certificate" +
            "  WHERE default_cert=0 and identity_name=? and key_identifier=?";

  return this.eachPromise_
    (query, [keyName.getPrefix(-1).toUri(), keyName.get(-1).toEscapedString()],
     function(err, row) {
    nameList.push(new Name(row.cert_name));
  });
};

/**
 * Set the default identity.  If the identityName does not exist, then clear the
 * default identity so that getDefaultIdentity() throws an exception.
 * @param {Name} identityName The default identity name.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which fulfills when the default identity is set.
 */
BasicIdentityStorage.prototype.setDefaultIdentityPromise = function
  (identityName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.setDefaultIdentityPromise is only supported for async")));

  var thisStorage = this;

  // Reset the previous default identity.
  return this.runPromise_
    ("UPDATE Identity SET default_identity=0 WHERE default_identity=1")
  .then(function() {
    // Set the current default identity.
    return thisStorage.runPromise_
      ("UPDATE Identity SET default_identity=1 WHERE identity_name=?",
       identityName.toUri());
  });
};

/**
 * Set a key as the default key of an identity. The identity name is inferred
 * from keyName.
 * @param {Name} keyName The name of the key.
 * @param {Name} identityNameCheck (optional) The identity name to check that the
 * keyName contains the same identity name. If an empty name, it is ignored.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which fulfills when the default key name is set.
 */
BasicIdentityStorage.prototype.setDefaultKeyNameForIdentityPromise = function
  (keyName, identityNameCheck, useSync)
{
  useSync = (typeof identityNameCheck === "boolean") ? identityNameCheck : useSync;
  identityNameCheck = (identityNameCheck instanceof Name) ? identityNameCheck : null;

  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.setDefaultKeyNameForIdentityPromise is only supported for async")));

  var keyId = keyName.get(-1).toEscapedString();
  var identityName = keyName.getPrefix(-1);

  if (identityNameCheck != null && identityNameCheck.size() != 0 &&
      !identityNameCheck.equals(identityName))
    return Promise.reject(new SecurityException(new Error
      ("Specified identity name does not match the key name")));

  var thisStorage = this;

  // Reset the previous default key.
  var identityUri = identityName.toUri();
  return this.runPromise_
    ("UPDATE Key SET default_key=0 WHERE default_key=1 and identity_name=?",
     identityUri)
  .then(function() {
    // Set the current default key.
    return thisStorage.runPromise_
      ("UPDATE Key SET default_key=1 WHERE identity_name=? AND key_identifier=?",
       [identityUri, keyId]);
  });
};

/**
 * Set the default key name for the specified identity.
 * @param {Name} keyName The key name.
 * @param {Name} certificateName The certificate name.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which fulfills when the default certificate name
 * is set.
 */
BasicIdentityStorage.prototype.setDefaultCertificateNameForKeyPromise = function
  (keyName, certificateName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.setDefaultCertificateNameForKeyPromise is only supported for async")));

  var keyId = keyName.get(-1).toEscapedString();
  var identityName = keyName.getPrefix(-1);
  var thisStorage = this;

  // Reset the previous default certificate.
  var identityUri = identityName.toUri();
  return this.runPromise_
    ("UPDATE Certificate SET default_cert=0 WHERE default_cert=1 AND identity_name=? AND key_identifier=?",
     [identityUri, keyId])
  .then(function() {
    // Set the current default certificate.
    return thisStorage.runPromise_
      ("UPDATE Certificate SET default_cert=1 WHERE identity_name=? AND key_identifier=? AND cert_name=?",
       [identityUri, keyId, certificateName.toUri()]);
  });
};

/*****************************************
 *            Delete Methods             *
 *****************************************/

/**
 * Delete a certificate.
 * @param {Name} certificateName The certificate name.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which fulfills when the certificate info is
 * deleted.
 */
BasicIdentityStorage.prototype.deleteCertificateInfoPromise = function
  (certificateName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.deleteCertificateInfoPromise is only supported for async")));

  if (certificateName.size() === 0)
    return

  return this.runPromise_
    ("DELETE FROM Certificate WHERE cert_name=?", certificateName.toUri());
};

/**
 * Delete a public key and related certificates.
 * @param {Name} keyName The key name.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which fulfills when the public key info is
 * deleted.
 */
BasicIdentityStorage.prototype.deletePublicKeyInfoPromise = function(keyName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.deletePublicKeyInfoPromise is only supported for async")));

  if (keyName.size() === 0)
    return Promise.resolve();

  var thisStorage = this;
  var keyId = keyName.get(-1).toEscapedString();
  var identityName = keyName.getPrefix(-1);

  return this.runPromise_
    ("DELETE FROM Certificate WHERE identity_name=? AND key_identifier=?",
     [identityName.toUri(), keyId])
  .then(function() {
    return thisStorage.runPromise_
      ("DELETE FROM Key WHERE identity_name=? and key_identifier=?",
       [identityName.toUri(), keyId]);
  });
};

/**
 * Delete an identity and related public keys and certificates.
 * @param {Name} identityName The identity name.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which fulfills when the identity info is deleted.
 */
BasicIdentityStorage.prototype.deleteIdentityInfoPromise = function
  (identityName, useSync)
{
  if (useSync)
    return Promise.reject(new SecurityException(new Error
      ("BasicIdentityStorage.deleteIdentityInfoPromise is only supported for async")));

  var thisStorage = this;
  var identity = identityName.toUri();

  return this.runPromise_
    ("DELETE FROM Certificate WHERE identity_name=?", identity)
  .then(function() {
    return thisStorage.runPromise_("DELETE FROM Key WHERE identity_name=?", identity);
  })
  .then(function() {
    return thisStorage.runPromise_("DELETE FROM Identity WHERE identity_name=?", identity);
  });
};

/**
 * Retrieve the user's current home directory
 * @returns {string} path to the user's home directory
 */
BasicIdentityStorage.getUserHomePath = function() {
  return process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
};

/**
 * Call Sqlite3Promise.runPromise, wrapping an Error in SecurityException.
 */
BasicIdentityStorage.prototype.runPromise_ = function(sql, params)
{
  return this.database_.runPromise(sql, params)
  .catch(function(error) {
    return Promise.reject(new SecurityException(error));
  });
};

/**
 * Call Sqlite3Promise.getPromise, wrapping an Error in SecurityException.
 */
BasicIdentityStorage.prototype.getPromise_ = function(sql, params)
{
  return this.database_.getPromise(sql, params)
  .catch(function(error) {
    return Promise.reject(new SecurityException(error));
  });
};

/**
 * Call Sqlite3Promise.eachPromise, wrapping an Error in SecurityException.
 */
BasicIdentityStorage.prototype.eachPromise_ = function(sql, params, onRow)
{
  return this.database_.eachPromise(sql, params, onRow)
  .catch(function(error) {
    return Promise.reject(new SecurityException(error));
  });
};

/**
 * Retrieve the user's current home directory
 * @returns {string} path to the user's home directory
 */
BasicIdentityStorage.getUserHomePath = function() {
  return process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
};

BasicIdentityStorage.initializeDatabasePromise_ = function(database)
{
  // Check if the ID table exists.
  return database.getPromise
    ("SELECT name FROM sqlite_master WHERE type='table' And name='Identity'")
  .then(function(row) {
    if (row)
      return Promise.resolve();
    else {
      return database.runPromise(BasicIdentityStorage.INIT_ID_TABLE1)
      .then(function() {
        return database.runPromise(BasicIdentityStorage.INIT_ID_TABLE2);
      });
    }
  })
  .then(function() {
    // Check if the Key table exists.
    return database.getPromise
      ("SELECT name FROM sqlite_master WHERE type='table' And name='Key'");
  })
  .then(function(row) {
    if (row)
      return Promise.resolve();
    else {
      return database.runPromise(BasicIdentityStorage.INIT_KEY_TABLE1)
      .then(function() {
        return database.runPromise(BasicIdentityStorage.INIT_KEY_TABLE2);
      });
    }
  })
  .then(function() {
    // Check if the Certificate table exists.
    return database.getPromise
      ("SELECT name FROM sqlite_master WHERE type='table' And name='Certificate'");
  })
  .then(function(row) {
    if (row)
      return Promise.resolve();
    else {
      return database.runPromise(BasicIdentityStorage.INIT_CERT_TABLE1)
      .then(function() {
        return database.runPromise(BasicIdentityStorage.INIT_CERT_TABLE2);
      })
      .then(function() {
        return database.runPromise(BasicIdentityStorage.INIT_CERT_TABLE3);
      });
    }
  });
};

BasicIdentityStorage.INIT_ID_TABLE1 =
"CREATE TABLE IF NOT EXISTS                                           \n" +
"  Identity(                                                          \n" +
"      identity_name     BLOB NOT NULL,                               \n" +
"      default_identity  INTEGER DEFAULT 0,                           \n" +
"                                                                     \n" +
"      PRIMARY KEY (identity_name)                                    \n" +
"  );                                                                 \n" +
"                                                                     \n";
BasicIdentityStorage.INIT_ID_TABLE2 =
"CREATE INDEX identity_index ON Identity(identity_name);              \n";

BasicIdentityStorage.INIT_KEY_TABLE1 =
"CREATE TABLE IF NOT EXISTS                                           \n" +
"  Key(                                                               \n" +
"      identity_name     BLOB NOT NULL,                               \n" +
"      key_identifier    BLOB NOT NULL,                               \n" +
"      key_type          INTEGER,                                     \n" +
"      public_key        BLOB,                                        \n" +
"      default_key       INTEGER DEFAULT 0,                           \n" +
"      active            INTEGER DEFAULT 0,                           \n" +
"                                                                     \n" +
"      PRIMARY KEY (identity_name, key_identifier)                    \n" +
"  );                                                                 \n" +
"                                                                     \n";
BasicIdentityStorage.INIT_KEY_TABLE2 =
"CREATE INDEX key_index ON Key(identity_name);                        \n";

BasicIdentityStorage.INIT_CERT_TABLE1 =
"CREATE TABLE IF NOT EXISTS                                           \n" +
"  Certificate(                                                       \n" +
"      cert_name         BLOB NOT NULL,                               \n" +
"      cert_issuer       BLOB NOT NULL,                               \n" +
"      identity_name     BLOB NOT NULL,                               \n" +
"      key_identifier    BLOB NOT NULL,                               \n" +
"      not_before        TIMESTAMP,                                   \n" +
"      not_after         TIMESTAMP,                                   \n" +
"      certificate_data  BLOB NOT NULL,                               \n" +
"      valid_flag        INTEGER DEFAULT 1,                           \n" +
"      default_cert      INTEGER DEFAULT 0,                           \n" +
"                                                                     \n" +
"      PRIMARY KEY (cert_name)                                        \n" +
"  );                                                                 \n" +
"                                                                     \n";
BasicIdentityStorage.INIT_CERT_TABLE2 =
"CREATE INDEX cert_index ON Certificate(cert_name);           \n";
BasicIdentityStorage.INIT_CERT_TABLE3 =
"CREATE INDEX subject ON Certificate(identity_name);          \n";