Source: security/pib/pib-indexeddb.js

/**
 * Copyright (C) 2018 Regents of the University of California.
 * @author: Jeff Thompson <[email protected]>
 *
 * 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.
 */

/**
 * PibIndexedDb extends PibImpl and is used by the Pib class to store the
 * contents of the PIB using the browser's IndexedDB service.
 * @constructor
 */
var PibIndexedDb = function PibIndexedDb()
{
  // Call the base constructor.
  PibImpl.call(this);

  this.database = new Dexie("pib");
  this.database.version(1).stores({
    // A table for global values. It currently only has tpmLocator and
    // defaultIdentityUri.
    // "key" is the key like "tpmLocator" // string
    // "value" is the value. For "defaultIdentityUri" the value is the
    //         default identity name URI, or absent if not defined. // string
    globals: "key",

    // "identityNameUri" is the identity name URI          // string
    // "defaultKeyUri" is the default key name URI or null // string
    identities: "identityNameUri",

    // "keyNameUri" is the key name URI                             // string
    // "keyDer" is the public key DER                               // Uint8Array
    // "defaultCertificateUri" is the default cert name URI or null // string
    keys: "keyNameUri",

    // "certificateNameUri" is the certificate name URI // string
    // "encoding" is the certificate wire encoding      // Uint8Array
    certificates: "certificateNameUri"
  });
  this.database.open();
};

PibIndexedDb.prototype = new PibImpl();
PibIndexedDb.prototype.name = "PibIndexedDb";

exports.PibIndexedDb = PibIndexedDb;

PibIndexedDb.getScheme = function() { return "pib-indexeddb"; }

// TpmLocator management.

/**
 * Set the corresponding TPM information to tpmLocator. This method does not
 * reset the contents of the PIB.
 * @param {string} tpmLocator The TPM locator string.
 * @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 TPM locator is set.
 */
PibIndexedDb.prototype.setTpmLocatorPromise = function(tpmLocator, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.setTpmLocatorPromise is only supported for async")));

  return this.database.globals.put({ key: "tpmLocator", value: tpmLocator });
};

/**
 * Get the TPM Locator.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which returns the TPM locator string.
 */
PibIndexedDb.prototype.getTpmLocatorPromise = function(useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.getTpmLocatorPromise is only supported for async")));

  return this.database.globals.get("tpmLocator")
  .then(function(entry) {
    if (entry)
      return Promise.resolve(entry.value);
    else
      return Promise.resolve("");
  });
};

// Identity management.

/**
 * Check for the existence of an identity.
 * @param {Name} identityName The name of the identity.
 * @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 identity exists,
 * otherwise false.
 */
PibIndexedDb.prototype.hasIdentityPromise = function(identityName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.hasIdentityPromise is only supported for async")));

  return this.database.identities.where("identityNameUri").equals
    (identityName.toUri())
  .count()
  .then(function(count) {
    return Promise.resolve(count > 0);
  });
};

/**
 * Add the identity. If the identity already exists, do nothing. If no default
 * identity has been set, set the added identity as the default.
 * @param {Name} identityName The name of the identity to add. This copies the
 * 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 is added.
 */
PibIndexedDb.prototype.addIdentityPromise = function(identityName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.addIdentityPromise is only supported for async")));

  var thisPib = this;
  return this.hasIdentityPromise(identityName)
  .then(function(hasIdentity) {
    if (!hasIdentity)
      return thisPib.database.identities.put
        ({ identityNameUri: identityName.toUri(), defaultKeyUri: null });
    else
      return Promise.resolve();
  })
  .then(function() {
    return thisPib.database.globals.get("defaultIdentityUri");
  })
  .then(function(entry) {
    if (!entry)
      // No default identity, so make this the default.
      return thisPib.setDefaultIdentityPromise(identityName);
    else
      return Promise.resolve();
  });
};

/**
 * Remove the identity and its related keys and certificates. If the default
 * identity is being removed, no default identity will be selected. If the
 * identity does not exist, do nothing.
 * @param {Name} identityName The name of the identity to remove.
 * @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 removed.
 */
PibIndexedDb.prototype.removeIdentityPromise = function(identityName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.removeIdentityPromise is only supported for async")));

  var identityNameUri = identityName.toUri();
  var thisPib = this;

  // We don't use triggers, so manually delete from keys and certificates.
  // Iterate through each key and certificate to find ones that match
  // identityName. This is a little inefficient, but we don't expect the
  // in-browswer database to be very big, we don't expect to delete often, and
  // this is simpler than complicating the database schema to store the
  // identityName with each key and certificate.
  return this.database.certificates.each(function(entry) {
    if (CertificateV2.extractIdentityFromCertName
        (new Name(entry.certificateNameUri)).equals(identityName))
      thisPib.database.certificates.delete(entry.certificateNameUri);
  })
  .then(function() {
    return thisPib.database.keys.each(function(entry) {
      if (PibKey.extractIdentityFromKeyName
          (new Name(entry.keyNameUri)).equals(identityName))
        thisPib.database.keys.delete(entry.keyNameUri);
    });
  })
  .then(function() {
    return thisPib.database.identities.delete(identityNameUri);
  })
  .then(function() {
    // Clear the default identity, if it is this identity.
    return thisPib.database.globals.get("defaultIdentityUri")
  })
  .then(function(entry) {
    if (entry && entry.value == identityNameUri)
      return thisPib.database.globals.delete("defaultIdentityUri");
    else
      return Promise.resolve();
  });
};

/**
 * Erase all certificates, keys, and identities.
 * @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 identities are cleared.
 */
PibIndexedDb.prototype.clearIdentitiesPromise = function(useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.clearIdentitiesPromise is only supported for async")));

  var thisPib = this;
  return this.database.globals.delete("defaultIdentityUri")
  // We don't use triggers, so manually delete from keys and certificates.
  .then(function() {
    return thisPib.database.certificates.clear();
  })
  .then(function() {
    return thisPib.database.keys.clear();
  })
  .then(function() {
    return thisPib.database.identities.clear();
  });
};

/**
 * Get the names of all the identities.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return {Promise} A promise which returns a fresh set of identity names
 * as an array of Name. The Name objects are fresh copies.
 */
PibIndexedDb.prototype.getIdentitiesPromise = function(useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.getIdentitiesPromise is only supported for async")));

  var identities = [];

  return this.database.identities.each(function(entry) {
    identities.push(new Name(entry.identityNameUri));
  })
  .then(function() {
    return Promise.resolve(identities);
  });
};

/**
 * Set the identity with the identityName as the default identity. If the
 * identity with identityName does not exist, then it will be created.
 * @param {Name} identityName The name for the default identity. This copies the
 * 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.
 */
PibIndexedDb.prototype.setDefaultIdentityPromise = function(identityName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.setDefaultIdentityPromise is only supported for async")));

  var thisPib = this;
  return this.hasIdentityPromise(identityName)
  .then(function(hasIdentity) {
    if (!hasIdentity)
      // Use the same command from addIdentityPromise, but don't call it because
      // it again calls this function.
      return thisPib.database.identities.put
        ({ identityNameUri: identityName.toUri(), defaultKeyUri: null });
    else
      return Promise.resolve();
  })
  .then(function() {
    return thisPib.database.globals.put
      ({ key: "defaultIdentityUri", value: identityName.toUri() });
  });
};

/**
 * 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 the default
 * identity as a fresh copy, or a promise rejected with Pib.Error for no default
 * identity.
 */
PibIndexedDb.prototype.getDefaultIdentityPromise = function(useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.getDefaultIdentityPromise is only supported for async")));

  return this.database.globals.get("defaultIdentityUri")
  .then(function(entry) {
    if (entry)
      return Promise.resolve(new Name(entry.value));
    else
      return Promise.reject(new Pib.Error(new Error("No default identity")));
  });
};

// Key management.

/**
 * Check for the existence of a key with keyName.
 * @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,
 * otherwise false. Return false if the identity does not exist.
 */
PibIndexedDb.prototype.hasKeyPromise = function(keyName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.hasKeyPromise is only supported for async")));

  return this.database.keys.where("keyNameUri").equals(keyName.toUri())
  .count()
  .then(function(count) {
    return Promise.resolve(count > 0);
  });
};

/**
 * Add the key. If a key with the same name already exists, overwrite the key.
 * If the identity does not exist, it will be created. If no default key for the
 * identity has been set, then set the added key as the default for the
 * identity. If no default identity has been set, identity becomes the default.
 * @param {Name} identityName The name of the identity that the key belongs to.
 * This copies the name.
 * @param {Name} keyName The name of the key. This copies the name.
 * @param {Buffer} key The public key bits. This copies the array.
 * @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 key is added.
 */
PibIndexedDb.prototype.addKeyPromise = function
  (identityName, keyName, key, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.addKeyPromise is only supported for async")));

  var thisPib = this;

  // Ensure the identity exists.
  return this.addIdentityPromise(identityName)
  .then(function() {
    return thisPib.hasKeyPromise(keyName);
  })
  .then(function(hasKey) {
    if (!hasKey)
      return thisPib.database.keys.put
        ({ keyNameUri: keyName.toUri(), keyDer: key,
           defaultCertificateUri: null });
    else
      // Update the keyDer and keep the defaultCertificateUri.
      return thisPib.database.keys.update(keyName.toUri(), { keyDer: key });
  })
  .then(function() {
    // Check for the default key.
    return thisPib.database.identities.get(identityName.toUri());
  })
  .then(function(entry) {
    if (entry && entry.defaultKeyUri != null)
      // Make sure the default key still exists, since removeKey doesn't clear it.
      return thisPib.hasKeyPromise(new Name(entry.defaultKeyUri));
    else
      return Promise.resolve(false);
  })
  .then(function(hasDefaultKey) {
    if (hasDefaultKey)
      // We already have a default key, so do nothing.
      return Promise.resolve();
    else
      return thisPib.setDefaultKeyOfIdentityPromise(identityName, keyName);
  });
};

/**
 * Remove the key with keyName and its related certificates. If the key does not
 * exist, do nothing.
 * @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 fulfills when the key is removed.
 */
PibIndexedDb.prototype.removeKeyPromise = function(keyName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.removeKeyPromise is only supported for async")));

  var thisPib = this;

  // We don't use triggers, so manually delete from certificates.
  // Iterate through each certificate to find ones that match keyName. This is
  // a little inefficient, but we don't expect the in-browswer database to be
  // very big, we don't expect to delete often, and this is simpler than
  // complicating the database schema to store the keyName with each certificate.
  return this.database.certificates.each(function(entry) {
    if (CertificateV2.extractKeyNameFromCertName
        (new Name(entry.certificateNameUri)).equals(keyName))
      thisPib.database.certificates.delete(entry.certificateNameUri);
  })
  .then(function() {
    return thisPib.database.keys.delete(keyName.toUri());
  });
};

/**
 * Get the key bits of a key with name keyName.
 * @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 the key bits as a Blob, or a
 * promise rejected with Pib.Error if the key does not exist.
 */
PibIndexedDb.prototype.getKeyBitsPromise = function(keyName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.getKeyBitsPromise is only supported for async")));

  return this.database.keys.get(keyName.toUri())
  .then(function(entry) {
    if (entry)
      return Promise.resolve(new Blob(entry.keyDer));
    else
      return Promise.reject(new Pib.Error(new Error
        ("Key `" + keyName.toUri() + "` does not exist")));
  });
};

/**
 * Get all the key names of the identity with the name identityName. The
 * returned key names can be used to create a KeyContainer. With a key name and
 * a backend implementation, one can create a Key front end instance.
 * @param {Name} identityName The name of the identity.
 * @param {boolean} useSync (optional) If true then return a rejected promise
 * since this only supports async code.
 * @return SyncPromise} A promise which returns the set of key names as an array
 * of Name. The Name objects are fresh copies. If the identity does not exist,
 * return an empty array.
 */
PibIndexedDb.prototype.getKeysOfIdentityPromise = function(identityName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.getKeysOfIdentityPromise is only supported for async")));

  var keyNames = [];

  // Iterate through each key to find ones that match identityName.
  // This is a little inefficient, but we don't expect the in-browser
  // database to be very big, and this is simpler than complicating the database
  // schema to store the identityName with each key.
  return this.database.keys.each(function(entry) {
    var keyName = new Name(entry.keyNameUri);

    if (PibKey.extractIdentityFromKeyName(keyName).equals(identityName))
      keyNames.push(keyName);
  })
  .then(function() {
    return Promise.resolve(keyNames);
  });
};

/**
 * Set the key with keyName as the default key for the identity with name
 * identityName.
 * @param {Name} identityName The name of the identity. This copies the name.
 * @param {Name} keyName The name of the key. This copies the 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 key is set,
 * or a promise rejected with Pib.Error if the key does not exist.
 */
PibIndexedDb.prototype.setDefaultKeyOfIdentityPromise = function
  (identityName, keyName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.setDefaultKeyOfIdentityPromise is only supported for async")));

  var thisPib = this;
  return this.hasKeyPromise(keyName)
  .then(function(hasKey) {
    if (!hasKey)
      return Promise.reject(new Pib.Error(new Error
        ("Key `" + keyName.toUri() + "` does not exist")));
    else
      return Promise.resolve();
  })
  .then(function() {
    // update does nothing if the identityName doesn't exist.
    return thisPib.database.identities.update
      (identityName.toUri(), { defaultKeyUri: keyName.toUri() });
  });
};

/**
 * Get the name of the default key for the identity with name identityName.
 * @param {Name} identityName The name of the 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 the default key as
 * a fresh copy, or a promise rejected with Pib.Error if the identity does not
 * exist.
 */
PibIndexedDb.prototype.getDefaultKeyOfIdentityPromise = function
  (identityName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.getDefaultKeyOfIdentityPromise is only supported for async")));

  var thisPib = this;

  return this.database.identities.get(identityName.toUri())
  .then(function(entry) {
    if (entry) {
      if (entry.defaultKeyUri != null) {
        // Make sure the key still exists.
        var keyName = new Name(entry.defaultKeyUri);

        return thisPib.hasKeyPromise(keyName)
        .then(function(hasKey) {
          if (hasKey)
            return Promise.resolve(keyName);
          else
            return Promise.reject(new Pib.Error(new Error
              ("No default key for identity `" + identityName.toUri() + "`")));
        });
      }
      else
        return Promise.reject(new Pib.Error(new Error
          ("No default key for identity `" + identityName.toUri() + "`")));
    }
    else
      return Promise.reject(new Pib.Error(new Error
        ("Identity `" + identityName.toUri() + "` does not exist")));
  });
};

// Certificate management.

/**
 * Check for the existence of a certificate with name certificateName.
 * @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,
 * otherwise false.
 */
PibIndexedDb.prototype.hasCertificatePromise = function(certificateName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.hasCertificatePromise is only supported for async")));

  return this.database.certificates.where("certificateNameUri").equals
    (certificateName.toUri())
  .count()
  .then(function(count) {
    return Promise.resolve(count > 0);
  });
};

/**
 * Add the certificate. If a certificate with the same name (without implicit
 * digest) already exists, then overwrite the certificate. If the key or
 * identity does not exist, they will be created. If no default certificate for
 * the key has been set, then set the added certificate as the default for the
 * key. If no default key was set for the identity, it will be set as the
 * default key for the identity. If no default identity was selected, the
 * certificate's identity becomes the default.
 * @param {CertificateV2} certificate The certificate to add. This copies the
 * object.
 * @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 is added.
 */
PibIndexedDb.prototype.addCertificatePromise = function(certificate, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.addCertificatePromise is only supported for async")));

  var keyName = certificate.getKeyName();
  var keyNameUri = keyName.toUri();
  var thisPib = this;

  // Ensure the key exists.
  var content = certificate.getContent();
  return this.addKeyPromise(certificate.getIdentity(), keyName, content.buf())
  .then(function() {
    // Insert the certificate.
    // wireEncode returns the cached encoding if available.
    return thisPib.database.certificates.put
      ({ certificateNameUri: certificate.getName().toUri(),
         encoding: certificate.wireEncode().buf() });
  })
  .then(function() {
    // Check for the default certificate.
    return thisPib.database.keys.get(keyNameUri);
  })
  .then(function(entry) {
    if (entry && entry.defaultCertificateUri != null)
      // Make sure the default certificate still exists, since removeCertiticate
      // doesn't clear it..
      return thisPib.hasCertificatePromise(new Name(entry.defaultCertificateUri));
    else
      return Promise.resolve(false);
  })
  .then(function(hasDefaultCertificate) {
    if (hasDefaultCertificate)
      // We already have a default certificate, so do nothing.
      return Promise.resolve();
    else
      return thisPib.setDefaultCertificateOfKeyPromise
        (keyName, certificate.getName());
  });
};

/**
 * Remove the certificate with name certificateName. If the certificate does not
 * exist, do nothing.
 * @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 fulfills when the certificate is
 * removed.
 */
PibIndexedDb.prototype.removeCertificatePromise = function
  (certificateName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.removeCertificatePromise is only supported for async")));

  return this.database.certificates.delete(certificateName.toUri());
};

/**
 * Get the certificate with name certificateName.
 * @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 the CertificateV2, or a promise
 * rejected with Pib.Error if the certificate does not exist.
 */
PibIndexedDb.prototype.getCertificatePromise = function(certificateName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.getCertificatePromise is only supported for async")));

  return this.database.certificates.get(certificateName.toUri())
  .then(function(entry) {
    if (entry) {
      var certificate = new CertificateV2();
      certificate.wireDecode(entry.encoding);
      return Promise.resolve(certificate);
    }
    else
      return Promise.reject(new Pib.Error(new Error
        ("Certificate `" + certificateName.toUri() + "` does not exit")));
  });
};

/**
 * Get a list of certificate names of the key with id keyName. The returned
 * certificate names can be used to create a PibCertificateContainer. With a
 * certificate name and a backend implementation, one can obtain the certificate.
 * @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 the set of certificate names as
 * an array of Name. The Name objects are fresh copies. If the key does not
 * exist, return an empty array.
 */
PibIndexedDb.prototype.getCertificatesOfKeyPromise = function(keyName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.getCertificatesOfKeyPromise is only supported for async")));

  var certificateNames = [];

  // Iterate through each certificate to find ones that match keyName.
  // This is a little inefficient, but we don't expect the in-browser
  // database to be very big, and this is simpler than complicating the database
  // schema to store the keyName with each certificate.
  return this.database.certificates.each(function(entry) {
    var certificateName = new Name(entry.certificateNameUri);

    if (CertificateV2.extractKeyNameFromCertName(certificateName).equals(keyName))
      certificateNames.push(certificateName);
  })
  .then(function() {
    return Promise.resolve(certificateNames);
  });
};

/**
 * Set the cert with name certificateName as the default for the key with
 * keyName.
 * @param {Name} keyName The name of the key.
 * @param {Name} certificateName The name of the certificate. This copies the
 * 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
 * is set, or a promise rejected with Pib.Error if the certificate with name
 * certificateName does not exist.
 */
PibIndexedDb.prototype.setDefaultCertificateOfKeyPromise = function
  (keyName, certificateName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.setDefaultCertificateOfKeyPromise is only supported for async")));

  var thisPib = this;
  return this.hasCertificatePromise(certificateName)
  .then(function(hasCertificate) {
    if (!hasCertificate)
      return Promise.reject(new Pib.Error(new Error
        ("Certificate `" + certificateName.toUri() + "` does not exist")));
    else
      return Promise.resolve();
  })
  .then(function() {
     // update does nothing if the keyName doesn't exist.
    return thisPib.database.keys.update
      (keyName.toUri(), { defaultCertificateUri: certificateName.toUri() });
  });
};

/**
 * Get the default certificate for the key with eyName.
 * @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 a copy of the default
 * CertificateV2, or a promise rejected with Pib.Error if the default
 * certificate does not exist.
 */
PibIndexedDb.prototype.getDefaultCertificateOfKeyPromise = function
  (keyName, useSync)
{
  if (useSync)
    return Promise.reject(new PibImpl.Error(new Error
      ("PibIndexedDb.getDefaultCertificateOfKeyPromise is only supported for async")));

  var thisPib = this;

  return this.database.keys.get(keyName.toUri())
  .then(function(entry) {
    if (entry && entry.defaultCertificateUri != null)
      return thisPib.getCertificatePromise(new Name(entry.defaultCertificateUri));
    else
      return Promise.reject(new Pib.Error(new Error
        ("No default certificate for key `" + keyName.toUri() + "`")));
  });
};