diff --git a/README.markdown b/README.markdown
index bd64da6..d939270 100644
--- a/README.markdown
+++ b/README.markdown
@@ -6,4 +6,11 @@ Manages OpenSSH public keys for Drupal accounts.
 * Allow associating more than one key to a user.
 * Disallow adding the same key twice by calculating and using the key fingerprint.
 * Validates the key format.
-* Checks keys against the openssh-blacklist using ssh-vulnkey.
+* Complete access control API.
+
+
+Todos
+====================
+
+* Add API documentation
+* Re-add validation (validate on cron?)
diff --git a/css/sshkey.css b/css/sshkey.css
deleted file mode 100644
index a403035..0000000
--- a/css/sshkey.css
+++ /dev/null
@@ -1,7 +0,0 @@
-#sshkey-public-key-wrapper .add-public-key-form {
-  padding-top: 1em;
-}
-
-#sshkey-public-key-wrapper .sshkey-no-public-keys {
-  text-align: center;
-}
\ No newline at end of file
diff --git a/sshkey.admin.inc b/sshkey.admin.inc
index 0a2e2b7..ac96c2b 100644
--- a/sshkey.admin.inc
+++ b/sshkey.admin.inc
@@ -1,67 +1,11 @@
 <?php
 
 function sshkey_settings_form() {
-  $form = array();
-
-  $form['sshkey_key_directory'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Key directory'),
-    '#description' => t('The directory in the filesystem that is used to store public keys.'),
-    '#size' => 40,
-    '#maxlength' => 255,
-    '#required' => TRUE,
-    '#default_value' => sshkey_get_key_directory(),
-  );
-
-  $form['sshkey_ssh_vulnkey'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Vulnerable key check'),
-    '#description' => t('The path to ssh-vulnkey that can check a key against a blacklist of compromised keys, make sure that you\'ve installed openssh-blacklist.'),
-    '#size' => 40,
-    '#maxlength' => 255,
-    '#default_value' => variable_get('sshkey_ssh_vulnkey', ''),
+  $form['sshkey_help'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Help text for public key listing pages and the add public key forms'),
+    '#default_value' => variable_get('sshkey_help', t('Need help with public keys? View the excellent GitHub.com SSH public key help at <a href="http://github.com/guides/providing-your-ssh-key" target="_blank">http://github.com/guides/providing-your-ssh-key</a>.')),
   );
 
-  $form['save'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save'),
-  );
-
-  return $form;
-}
-
-function sshkey_settings_form_validate(&$form, $form_state) {
-  $values = $form_state['values'];
-
-  if (file_exists($values['sshkey_key_directory'])) {
-    if (!is_writable($values['sshkey_key_directory'])) {
-      form_set_error('sshkey_key_directory', t('The key directory is not writeable'));
-    }
-  }
-  else {
-    if (!mkdir($values['sshkey_key_directory'])) {
-      form_set_error('sshkey_key_directory', t('The key directory doesn\'t exist and cannot be created automatically.'));
-    }
-    else {
-      drupal_set_message(t('The directory @dir has been created', array(
-        '@dir' => $values['sshkey_key_directory'],
-      )));
-    }
-  }
-
-  if (!empty($values['sshkey_ssh_vulnkey'])) {
-    if (!file_exists($values['sshkey_ssh_vulnkey'])) {
-      form_set_error('sshkey_ssh_vulnkey', t('The ssh-vulnkey executable doesn\'t exist'));
-    }
-    else if (!is_executable($values['sshkey_ssh_vulnkey'])) {
-      form_set_error('sshkey_ssh_vulnkey', t('The ssh-vulnkey executable isn\'t executable'));
-    }
-  }
-}
-
-function sshkey_settings_form_submit($form, $form_state) {
-  $values = $form_state['values'];
-
-  variable_set('sshkey_ssh_vulnkey', $values['sshkey_ssh_vulnkey']);
-  variable_set('sshkey_key_directory', $values['sshkey_key_directory']);
+  return system_settings_form($form);
 }
diff --git a/sshkey.api.php b/sshkey.api.php
new file mode 100644
index 0000000..4cbc91b
--- /dev/null
+++ b/sshkey.api.php
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the SSH key module.
+ */
+
+/**
+ * Control access to an SSH key.
+ *
+ * Modules may implement this hook if they want to have a say in whether or not
+ * a given user has access to perform a given operation on an SSH key.
+ *
+ * The administrative account (user ID #1) always passes any access check, so
+ * this hook is not called in that case. Users with the "administer SSH public
+ * keys" permission may always view and edit SSH keys through the
+ * administrative interface.
+ *
+ * Note that not all modules will want to influence access on all SSH keys. If
+ * your module does not want to actively grant or block access, return
+ * SSHKEY_ACCESS_IGNORE or simply return nothing. Blindly returning FALSE will
+ * break other node access modules.
+ *
+ * @param $op
+ *   The operation to be performed. Possible values:
+ *   - 'view'
+ *   - 'create'
+ *   - 'edit'
+ *   - 'delete'
+ * @param $entity_type
+ *   The entity type that the key operation is associated with.
+ * @param $entity_id
+ *   The entity ID that the key operation is associated with.
+ * @param $key
+ *   The SSH key object.
+ * @param $account
+ *   A user object representing the user for whom the operation is to be
+ *   performed.
+ *
+ * @return
+ *   SSHKEY_ACCESS_ALLOW if the operation is to be allowed; SSHKEY_ACCESS_DENY
+ *   if the operation is to be denied; SSHKEY_ACCESSS_IGNORE to not affect this
+ *   operation at all.
+ */
+function hook_sshkey_access($op, $entity_type, $entity_id, $key, $account) {
+
+}
+
+/**
+ * Act on SSH keys being loaded from the database.
+ *
+ * @param $keys
+ *   An array of SSH keys, keyed by key ID.
+ */
+function hook_sshkey_load(array &$keys) {
+
+}
+
+/**
+ * Act on an SSH key being inserted or updated.
+ *
+ * This hook is invoked from sshkey_save() before the SSH key is saved to the
+ * database.
+ *
+ * @param $key
+ *   The SSH key object being inserted or updated.
+ */
+function hook_sshkey_alter($key) {
+
+}
+
+/**
+ * Respond to creation of a new SSH key.
+ *
+ * This hook is invoked from sshkey_save() after the SSH key is inserted into
+ * the sshkey table in the database.
+ *
+ * @param $key
+ *   The SSH key object that was created.
+ */
+function hook_sshkey_insert($key) {
+
+}
+
+/**
+ * Respond to updates to an SSH key.
+ *
+ * This hook is invoked from sshkey_save() after the SSH key is updated in the
+ * sshkey table in the database.
+ *
+ * @param $key
+ *   The SSH key object that was updated.
+ */
+function hook_sshkey_update($key) {
+
+}
+
+/**
+ * Respond to an SSH key deletion.
+ *
+ * This hook is invoked from sshkey_delete_multiple() after the SSH key has
+ * been removed from the sshkey table in the database.
+ *
+ * @param $key
+ *   The SSH key object that was deleted.
+ */
+function hook_sshkey_delete($key) {
+
+}
+
+/**
+ * Perform SSH key validation before a SSH key is created or updated.
+ *
+ * This hook is invoked from sshkey_validate(), after a user has has finished
+ * editing an SSH key and is submitting it. It is invoked at the end of all the
+ * standard validation steps.
+ *
+ * To indicate a validation error, use form_set_error().
+ *
+ * Note: Changes made to the $key object within your hook implementation will
+ * have no effect. The preferred method to change a key's content is to use
+ * hook_sshkey_alter() instead. If it is really necessary to change the SSH key
+ * at the validate stage, you can use form_set_value().
+ *
+ * @param $key
+ *   The SSH key being validated.
+ * @param $form
+ *   The form being used to edit the SSH key.
+ * @param $form_state
+ *   The form state array.
+ */
+function hook_sshkey_validate($key, $form, $form_state) {
+
+}
diff --git a/sshkey.inc b/sshkey.inc
deleted file mode 100644
index 0f459f4..0000000
--- a/sshkey.inc
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-
-/**
- * Parses a public key.
- *
- * @param string $pubkey
- * @return array
- *  An array containing the algo, key, comment, fingerprint, decoded key data as data and the sanitized
- *  public key as pubkey.
- * @throws Exception
- *  A exception is thrown if the public key can't be parsed (error code
- *  SSHKEY_PARSE_ERROR).
- */
-function sshkey_public_key_parse($pubkey) {
-  // Be tolerant of linebreaks and extra spaces.
-  $pubkey = preg_replace('/\n|\r/', '', $pubkey);
-  $pubkey = preg_replace('/\s{2,}/', ' ', $pubkey);
-  $pubkey = trim($pubkey);
-
-  // Explode the key into it's components.
-  $parts = explode(' ', $pubkey, 3);
-  if (count($parts) < 2) {
-    throw new Exception(t('Could not parse the public key'), SSHKEY_PARSE_ERROR);
-  }
-
-  // Shift out the parts of the public key.
-  $key = array(
-    'algo' => array_shift($parts),
-    'key' => array_shift($parts),
-    'comment' => '',
-    'pubkey' => $pubkey,
-  );
-  if (!empty($parts)) {
-    $key['comment'] = array_shift($parts);
-  }
-
-  // Decode the key data so that we can create a fingerprint.
-  $key['data'] = base64_decode($key['key']);
-  if ($key['data'] === FALSE) {
-    throw new Exception(t('Could not decode the key data'), SSHKEY_PARSE_ERROR);
-  }
-  $key['fingerprint'] = hash('md5', $key['data']);
-
-  return $key;
-}
-
-/**
- * Checks a key against the ubuntu/debian blacklist using ssh-vulnkey.
- *
- * @param string $pubkey
- * @return bool|object
- *  Returns TRUE if the key is blacklisted. FALSE if the key has been verified
- *  as non-blacklisted. Or NULL if we couldn't verify the key.
- */
-function sshkey_public_key_blacklisted($pubkey) {
-  $vulnkey = variable_get('sshkey_ssh_vulnkey', '');
-  $blacklisted = NULL;
-
-  if (!empty($vulnkey) && is_executable($vulnkey)) {
-    $descriptorspec = array(
-       0 => array("pipe", "r"), // stdin
-       1 => array("pipe", "w"), // stdout
-    );
-    // Open ssh-vulnkey in verbose mode so that we get output
-    // even when the key isn't blacklisted.
-    $process = proc_open(sprintf('%s -v -', $vulnkey), $descriptorspec, $pipes);
-
-    // Write the public key to stdin
-    fwrite($pipes[0], $pubkey);
-    fclose($pipes[0]);
-
-    // Get result from stdout
-    $result = stream_get_contents($pipes[1]);
-    fclose($pipes[1]);
-
-    // Close process handle and get the return code
-    $retcode = proc_close($process);
-    // Retcode 0 means that the code is blacklisted
-    if ($retcode == 0) {
-      $blacklisted = TRUE;
-    }
-    else {
-      // Check response so that we know if the key really has been checked
-      // we're looking for the response message 'Not blacklisted' as it could
-      // also be 'Unknown (blacklist file not installed)' and in that case
-      // we'll just let the response ($blacklisted) remain as NULL.
-      list($line) = explode("\n", $result);
-      list($file, $line_num, $message, $key) = explode(':', $line, 4);
-      if (trim($message) == 'Not blacklisted') {
-        $blacklisted = FALSE;
-      }
-    }
-  }
-  return $blacklisted;
-}
-
-/**
- * Migrates key files to a new directory.
- *
- * @param string $directory
- * @return void
- * @throws Exception
- *  Throws a exception if: the current directory isn't readable (error code
- *  SSHKEY_ACCESS_DENIED); all keys couldn't be copied to the new
- *  directory (error code SSHKEY_IO_FAILURE).
- */
-function sshkey_migrate_key_files($directory) {
-  $current_directory = sshkey_get_key_directory();
-  // Check if keys should be moved. If the current directory doesn't exist
-  // we can safely ignore the move parameter.
-  if (file_exists($current_directory)) {
-    if (!is_readable($current_directory)) {
-      throw new Exception(t('Move keys was specified but the current directory cannot be read from'), SSHKEY_ACCESS_DENIED);
-    }
-    $remove = array();
-
-    // Copy all public key files to the new directory.
-    $dh = opendir($current_directory);
-    while ($file = readdir($dh)) {
-      if (substr($file, -3) === '.pub') {
-        if (!copy($current_directory . '/' . $file, $directory . '/' . $file)) {
-          throw new Exception(t('Could not copy all keys from the current directory'), SSHKEY_IO_FAILURE);
-        }
-        $remove[] = $current_directory . '/' . $file;
-      }
-    }
-    // Remove all the public keys that we've moved.
-    // Failure to remove a public key is a strictly non-fatal error.
-    foreach ($remove as $file) {
-      if (is_writable($file)) {
-        unlink($file);
-      }
-    }
-  }
-}
diff --git a/sshkey.info b/sshkey.info
index 18ca648..b1be8c3 100644
--- a/sshkey.info
+++ b/sshkey.info
@@ -1,5 +1,4 @@
-name = "SSH Key"
-package = "SSH Keys"
-description = "Manages public keys for user accounts"
-core = "6.x"
-php = "5.x"
\ No newline at end of file
+name = "SSH public keys"
+description = "Allows users to associated public keys with their user accounts."
+core = 6.x
+php = 5.x
\ No newline at end of file
diff --git a/sshkey.install b/sshkey.install
index d27fb39..81d0d13 100644
--- a/sshkey.install
+++ b/sshkey.install
@@ -1,50 +1,84 @@
 <?php
 
-function sshkey_install() {
-  drupal_install_schema('sshkey');
-}
-
-function sshkey_uninstall() {
-  drupal_uninstall_schema('sshkey');
-}
-
+/**
+ * Implements hook_schema().
+ */
 function sshkey_schema() {
-  $schema = array();
-
   $schema['sshkey'] = array(
-    'description' => 'Stores ssh keys for users',
+    'description' => 'Stores SSH public keys associated with entities.',
     'fields' => array(
-      'fingerprint' => array(
-        'description' => 'The md5 hash for the public key',
-        'type' => 'char',
-        'length' => 32,
+      'key_id' => array(
+        'description' => 'The account associated with the key.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
         'not null' => TRUE,
       ),
-      'name' => array(
-        'description' => 'The name of the key file.',
+      'entity_type' => array(
+        'description' => 'Primary key with entity_id; the type of entity (e.g. node, user, etc.).',
         'type' => 'varchar',
-        'length' => 255,
+        'length' => 32,
         'not null' => TRUE,
-        'default' => ''
+        'default' => '',
       ),
-      'created' => array(
+      'entity_id' => array(
+        'description' => 'Primary key with entity_type; the ID of the entity.',
         'type' => 'int',
+        'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
-        'description' => 'The time that the key was created, as a Unix timestamp.',
       ),
-      'uid' => array(
-        'description' => 'The account associated with the public key.',
+      'fingerprint' => array(
+        'description' => 'The unique fingerprint (MD5 hash) of the key.',
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+      ),
+      'title' => array(
+        'description' => 'The nickname of the key file.',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => ''
+      ),
+      'value' => array(
+        'description' => 'The raw key value.',
+        'type' => 'text',
+        'size' => 'big',
+        'not null' => TRUE,
+      ),
+      'changed' => array(
+        'description' => 'The time that the key was created or updated, as a UNIX timestamp.',
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
+        'default' => 0,
       ),
     ),
-    'primary key' => array('fingerprint'),
+    'primary key' => array('key_id'),
+    'unique keys' => array(
+      'fingerprint' => array('fingerprint'),
+    ),
     'indexes' => array(
-      'uid' => array('uid'),
+      'entity_type_id' => array('entity_type', 'entity_id'),
     ),
   );
 
   return $schema;
-}
\ No newline at end of file
+}
+
+/**
+ * Implements hook_install().
+ */
+function sshkey_install() {
+  drupal_install_schema('sshkey');
+}
+
+/**
+ * Impelements hook_uninstall().
+ */
+function sshkey_uninstall() {
+  drupal_uninstall_schema('sshkey');
+
+  // Remove variables.
+  variable_del('sshkey_help');
+}
diff --git a/sshkey.module b/sshkey.module
index d0978f5..f1e64a2 100644
--- a/sshkey.module
+++ b/sshkey.module
@@ -1,354 +1,467 @@
 <?php
 
-define('SSHKEY_DUPLICATE_KEY_ERROR', 1);
-define('SSHKEY_PARSE_ERROR', 2);
-define('SSHKEY_IO_FAILURE', 4);
-define('SSHKEY_TAMPERING', 8);
-define('SSHKEY_NOT_FOUND', 404);
-define('SSHKEY_ACCESS_DENIED', 401);
+/**
+ * Allows SSH keys to be associated with entities.
+ */
+
+/**
+ * Modules should return this value from hook_sshkey_access() to allow access
+ * to an SSH key.
+ */
+define('SSHKEY_ACCESS_ALLOW', 'allow');
+
+/**
+ * Modules should return this value from hook_sshkey_access() to deny access
+ * to a SSH key.
+ */
+define('SSHKEY_ACCESS_DENY', 'deny');
+
+/**
+ * Modules should return this value from hook_sshkey_access() to not affect
+ * SSH key access.
+ */
+define('SSHKEY_ACCESS_IGNORE', NULL);
+
+/**
+ * Exception thrown if a public key does not parse correctly.
+ */
+class SSHKeyParseException extends Exception { }
+
+/**
+ * Implements hook_help().
+ */
+function sshkey_help($path, $arg) {
+  switch ($path) {
+    case 'user/%/ssh-keys':
+    case 'ssh-keys/%/%':
+    case 'ssh-keys/%/%/add':
+      if ($help_text = variable_get('sshkey_help', t('Need help with public keys? View the excellent GitHub.com SSH public key help at <a href="http://github.com/guides/providing-your-ssh-key" target="_blank">http://github.com/guides/providing-your-ssh-key</a>.'))) {
+        return '<p>' . filter_xss_admin($help_text) . '</p>';
+      }
+  }
+}
 
 /**
- * Implementation of hook_perm().
+ * Implements hook_perm().
  */
 function sshkey_perm() {
   $perm = array(
-    'administer sshkey',
-    'register sshkeys',
+    'view any SSH public keys',
+    'view own SSH public keys',
+    'manage any SSH public keys',
+    'manage own SSH public keys',
+    'administer SSH public keys',
   );
   return $perm;
 }
 
 /**
- * Implementation of hook_menu().
+ * Implements hook_menu().
  */
 function sshkey_menu() {
-  $menu = array();
-
-  $menu['user/%user/sshkeys'] = array(
+  $items['user/%/ssh-keys'] = array(
     'title' => 'SSH Keys',
-    'page callback' => 'sshkey_page_list_public_keys',
-    'page arguments' => array(1),
-    'access callback' => 'sshkey_edit_keys_access',
-    'access arguments' => array(1),
+    'page callback' => 'sshkey_list_page',
+    'page arguments' => array('user', 1),
+    'access callback' => 'sshkey_access',
+    'access arguments' => array('view', 'user', 1),
     'file' => 'sshkey.pages.inc',
     'type' => MENU_LOCAL_TASK,
+    'weight' => 10,
   );
 
-  $menu['user/%user/sshkeys/list'] = array(
-    'title' => 'SSH Keys',
-    'page callback' => 'sshkey_page_list_public_keys',
-    'page arguments' => array(1),
-    'access callback' => 'sshkey_edit_keys_access',
-    'access arguments' => array(1),
+  // The first two arguments for the following CRUD pages are entity type,
+  // followed by entity ID.
+  $items['ssh-keys/%/%/add'] = array(
+    'title' => 'Add a SSH key',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('sshkey_edit_form', 1, 2),
+    'access callback' => 'sshkey_access',
+    'access arguments' => array('create', 1, 2),
     'file' => 'sshkey.pages.inc',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'modal' => TRUE,
   );
-
-  $menu['user/%user/sshkeys/%sshkey_public_key/edit'] = array(
-    'title' => 'Edit',
-    'page callback' => 'sshkey_page_edit_public_key',
-    'page arguments' => array(3),
-    'access callback' => 'sshkey_edit_keys_access',
-    'access arguments' => array(1, 3),
+  $items['ssh-keys/%/%/edit/%sshkey'] = array(
+    'title' => 'Edit SSH key',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('sshkey_edit_form', 1, 2, 4),
+    'access callback' => 'sshkey_access',
+    'access arguments' => array('edit', 1, 2, 4),
     'file' => 'sshkey.pages.inc',
-    'type' => MENU_LOCAL_TASK,
-    'weight' => 9,
+    'modal' => TRUE,
   );
-
-  $menu['user/%user/sshkeys/%sshkey_public_key/delete'] = array(
-    'title' => 'Delete',
-    'page callback' => 'sshkey_page_delete_public_key',
-    'page arguments' => array(3),
-    'access callback' => 'sshkey_edit_keys_access',
-    'access arguments' => array(1, 3),
+  $items['ssh-keys/%/%/delete/%sshkey'] = array(
+    'title' => 'Delete SSH key',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('sshkey_delete_form', 4),
+    'access callback' => 'sshkey_access',
+    'access arguments' => array('delete', 1, 2, 4),
     'file' => 'sshkey.pages.inc',
-    'type' => MENU_LOCAL_TASK,
-    'weight' => 10,
+    'modal' => TRUE,
   );
 
-  $menu['admin/user/sshkeys'] = array(
-    'title' => 'SSH Key settings',
-    'description' => 'Configure the SSH key settings for user accounts',
+  $items['admin/user/ssh-keys'] = array(
+    'title' => 'SSH public key settings',
+    'description' => 'Configure the SSH public key settings for user accounts.',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('sshkey_settings_form'),
-    'access arguments' => array('administer sshkeys'),
+    'access arguments' => array('administer SSH public keys'),
     'file' => 'sshkey.admin.inc',
-    'type' => MENU_NORMAL_ITEM,
   );
 
-  return $menu;
+  return $items;
 }
 
 /**
- * Implementation of hook_theme().
+ * Implements hook_user().
  */
-function sshkey_theme() {
-  $theme = array();
-
-  // Providing a wrapper theme for the table to allow individual styling.
-  $theme['sshkey_table'] = array(
-    'arguments' => array(
-      'header' => array(),
-      'rows' => array(),
-      'attributes' => array(),
-      'caption' => NULL,
-    ),
-  );
-
-  $theme['sshkey_public_key_list_page'] = array(
-    'template' => 'sshkey_public_key_list_page',
-    'arguments' => array(
-      'table' => '',
-      'form' => '',
-    ),
-  );
-
-  return $theme;
+function sshkey_user($op, $edit, $account, $category = NULL) {
+  if ($op == 'delete') {
+    sshkey_delete_all_by_entity('user', $account->uid);
+  }
 }
 
 /**
- * Theme function for the ssh key table. @see theme_table().
+ * Access callback for SSH public key operations.
  */
-function theme_sshkey_table($header, $rows, $attributes = array(), $caption = NULL) {
-  // Check if we should display the "on empty"-message.
-  if (empty($rows)) {
-    $rows[] = array(
-      'empty' => array(
-        'data'  => t('No keys have been added yet.'),
-        'class' => 'sshkey-no-public-keys',
-        'colspan' => count($header),
-      ),
-    );
+function sshkey_access($op, $entity_type = NULL, $entity_id = NULL, $key = NULL, $account = NULL) {
+  static $rights = array();
+
+  if (!in_array($op, array('view', 'create', 'edit', 'delete'), TRUE)) {
+    // If $op was not one of the supported ones, we return access denied.
+    return FALSE;
   }
 
-  return theme('table', $header, $rows, $attributes, $caption);
-}
+  // Default user_access() checks to use the current user.
+  if (!isset($account)) {
+    $account = $GLOBALS['user'];
+  }
 
-/**
- * Returns the directory used for storing public keys.
- *
- * @return string
- */
-function sshkey_get_key_directory() {
-  $default = file_directory_path() . '/sshkeys';
-  return variable_get('sshkey_key_directory', $default);
+  $cid = is_object($key) ? $key->key_id : 0;
+
+  // Make sure the entity type and IDs match the existing key's entity data.
+  if (is_object($key) && ($key->entity_type != $entity_type || $key->entity_id != $entity_id)) {
+    return FALSE;
+  }
+
+  // Make sure that an actual entity object exists to attach to.
+  if (!sshkey_entity_object_load($entity_type, array($entity_id))) {
+    return FALSE;
+  }
+
+  // If we've already checked access for this key, user and op, return from
+  // cache.
+  if (isset($rights[$account->uid][$cid][$op])) {
+    return $rights[$account->uid][$cid][$op];
+  }
+
+  // Admins can do everything.
+  if (user_access('administer SSH public keys', $account)) {
+    $rights[$account->uid][$cid][$op] = TRUE;
+    return TRUE;
+  }
+
+  // We grant access to the key if both of the following conditions are met:
+  // - No modules say to deny access.
+  // - At least one module says to grant access.
+  $access = module_invoke_all('sshkey_access', $op, $entity_type, $entity_id, $key, $account);
+  if (in_array(SSHKEY_ACCESS_DENY, $access, TRUE)) {
+    $rights[$account->uid][$cid][$op] = FALSE;
+    return FALSE;
+  }
+  elseif (in_array(SSHKEY_ACCESS_ALLOW, $access, TRUE)) {
+    $rights[$account->uid][$cid][$op] = TRUE;
+    return TRUE;
+  }
+
+  return FALSE;
 }
 
 /**
- * Changes the directory that is used for storing public keys.
- *
- * @param string $directory
- *  The new directory that public keys should be stored in.
- * @param bool $move_keys
- *
- * @return void
- * @throws Exception
- *  Throws a exception if: the new directory doesn't exist and cannot be
- *  created (error code SSHKEY_NOT_FOUND); the new directory isn't
- *  writeable (error code SSHKEY_ACCESS_DENIED); $move_keys is TRUE and
- *  the current directory isn't readable (error code SSHKEY_ACCESS_DENIED);
- *  all keys couldn't be copied to the new directory (error code
- *  SSHKEY_IO_FAILURE).
+ * Implements hook_sshkey_access().
  */
-function sshkey_set_key_directory($directory, $move_keys=TRUE) {
-  if (!file_exists($directory)) {
-    if (!mkdir($directory, 0777, TRUE)) {
-      throw new Exception('The directory does not exist and could not be created', SSHKEY_NOT_FOUND);
+function sshkey_sshkey_access($op, $entity_type, $entity_id, $key, $account) {
+  if ($op == 'view') {
+    if (user_access('view any SSH public keys')) {
+      return SSHKEY_ACCESS_ALLOW;
+    }
+    else {
+      $entity = sshkey_entity_object_load($entity_type, $entity_id);
+      if (isset($entity->uid) && $entity->uid == $account->uid && user_access('view own SSH public keys')) {
+        return SSHKEY_ACCESS_ALLOW;
+      }
     }
   }
-  if (!is_writable($directory)) {
-    throw new Exception('The directory is not writeable', SSHKEY_ACCESS_DENIED);
-  }
-
-  if ($move_keys) {
-    module_load_include('inc', 'sshkey');
-    sshkey_migrate_key_files($directory);
+  else {
+    if (user_access('manage any SSH public keys')) {
+      return SSHKEY_ACCESS_ALLOW;
+    }
+    else {
+      $entity = sshkey_entity_object_load($entity_type, $entity_id);
+      if (isset($entity->uid) && $entity->uid == $account->uid && user_access('manage own SSH public keys')) {
+        return SSHKEY_ACCESS_ALLOW;
+      }
+    }
   }
 
-  variable_set('sshkey_key_directory', $directory);
+  return SSHKEY_ACCESS_IGNORE;
 }
 
 /**
- * Access callback used for checking if the currently logged in user may edit
- * the ssh-keys for the given account. Access is granted if the user has the
- * 'register sshkeys' permission AND (may edit the user account OR has the
- * permission 'administer sshkey').
- *
- * @param object $account
- * @param array $key
- * @return bool
- *  TRUE if access is granted, otherwise FALSE is returned.
+ * Load an SSH public key and optionally by entity type and ID.
  */
-function sshkey_edit_keys_access($account, $key=NULL) {
-  $access = FALSE;
-  if (!$key || $account->uid == $key['uid']) {
-    $access = user_edit_access($account);
-    $access = $access || user_access('administer sshkey');
-    $access = $access && user_access('register sshkeys');
-  }
-  return $access;
+function sshkey_load($key_id, $reset = FALSE) {
+  $keys = sshkey_load_multiple(array($key_id), $reset);
+  return !empty($keys) ? reset($keys) : FALSE;
 }
 
 /**
- * Lists public keys for a user.
- *
- * @param int $uid
- * @return array
+ * Load an SSH public key by fingerprint.
  */
-function sshkey_user_public_keys_list($uid) {
-  $keys = array();
-
-  $res = db_query("SELECT * FROM {sshkey} WHERE uid=%d", array(
-    ':uid' => $uid,
-  ));
-  while ($key = db_fetch_array($res)) {
-    $keys[] = $key;
-  }
-  return $keys;
+function sshkey_load_by_fingerprint($fingerprint) {
+  $key_id = db_result(db_query("SELECT key_id FROM {sshkey} WHERE fingerprint = '%s'", $fingerprint));
+  $keys = sshkey_load_multiple(array($key_id));
+  return !empty($keys) ? reset($keys) : FALSE;
 }
 
 /**
- * Loads a public key from the database.
- *
- * @param string $fingerprint
- * @return array
+ * Load all SSH public keys associated with an entity.
  */
-function sshkey_public_key_load($fingerprint) {
-  $res = db_query("SELECT * FROM {sshkey} WHERE fingerprint='%s'", array(
-    ':fingerprint' => $fingerprint,
-  ));
-  $record = db_fetch_array($res);
-  if (!$record) {
-    $record = FALSE;
-  }
-  return $record;
+function sshkey_load_all_by_entity($entity_type, $entity_id) {
+  $key_ids = sshkey_db_fetch_col(db_query("SELECT key_id FROM {sshkey} WHERE entity_type = '%s' AND entity_id = %d", $entity_type, $entity_id));
+  return  sshkey_load_multiple($key_ids);
 }
 
 /**
- * Saves a public key.
- *
- * @param int $uid
- *  The user id to associate the public key with.
- * @param string $pubkey
- *  The public key.
- * @param string $name
- *  Optional. The name of the public key. The comment from the public key will
- *  be used if no name is supplied.
- * @return array
- *  An array containing the fingerprint, name and uid for the key.
- * @throws Exception
- *  A exception is thrown if the public key already is registered (error code
- *  SSHKEY_DUPLICATE_KEY_ERROR) or if the public key can't be parsed (error
- *  code SSHKEY_PARSE_ERROR).
+ * Load multiple SSH public keys.
  */
-function sshkey_public_key_save($uid, $pubkey, $name=NULL) {
-  module_load_include('inc', 'sshkey');
+function sshkey_load_multiple($key_ids = array(), $reset = FALSE) {
+  static $keys = array();
 
-  if (is_string($pubkey)) {
-    $pubkey = sshkey_public_key_parse($pubkey);
+  if ($reset) {
+    $keys = array();
   }
 
-  if (empty($name)) {
-    $name = $pubkey['comment'];
-  }
-  if (drupal_strlen($name) > 255) {
-    $name = drupal_substr($name, 0, 255);
+  if (empty($key_ids)) {
+    return array();
   }
 
-  $exists = sshkey_public_key_load($pubkey['fingerprint']);
-  if ($exists) {
-    throw new Exception('The public key is already registered', SSHKEY_DUPLICATE_KEY_ERROR);
+  if ($new_key_ids = array_diff($key_ids, array_keys($keys))) {
+    $query = db_query("SELECT * FROM {sshkey} WHERE key_id IN (" . db_placeholders($new_key_ids, 'int') . ")", $new_key_ids);
+    $new_keys = array();
+    while ($key = db_fetch_object($query)) {
+      $new_keys[$key->key_id] = $key;
+    }
+
+    sshkey_invoke_hook($new_keys, 'load');
+    $keys += $new_keys;
   }
 
-  $dir = sshkey_get_key_directory();
-  if (!file_exists($dir)) {
-    if(!mkdir($dir)) {
-      throw new Exception('Could not create the sshkey directory', SSHKEY_ACCESS_DENIED);
+  return array_intersect_key($keys, array_combine($key_ids, $key_ids));
+}
+
+/**
+ * Save a SSH public key.
+ */
+function sshkey_save($key) {
+  try {
+    if (!empty($key->key_id) && !isset($key->original)) {
+      $key->original = db_fetch_object(db_query("SELECT * FROM {sshkey} WHERE key_id = %d", $key->key_id));
+    }
+
+    // Determine if we will be inserting a new node.
+    if (!isset($key->is_new)) {
+      $key->is_new = empty($key->key_id);
+    }
+
+    // The changed timestamp is always updated for bookkeeping purposes.
+    $key->changed = time();
+
+    if (!isset($key->entity_type) && !isset($key->entity_id)) {
+      $key->entity_type = 'user';
+      $key->entity_id = $GLOBALS['user']->uid;
+    }
+
+    // Re-fingerprint the key.
+    $parsed = sshkey_parse($key->value, $key);
+    $key->fingerprint = $parsed['fingerprint'];
+
+    // Add a default name based on public key comment if available.
+    if (isset($parsed['comment'])) {
+      $key->value = trim(substr($key->value, 0, -strlen($parsed['comment'])));
+      if (empty($key->title)) {
+        $key->title = truncate_utf8($parsed['comment'], 128, TRUE);
+      }
+    }
+
+    // Allow other modules to alter the public key before saving.
+    drupal_alter('sshkey', $key);
+
+    // Save the key to the database.
+    drupal_write_record('sshkey', $key, $key->is_new ? array() : array('key_id'));
+
+    // Invoke post-save hooks.
+    if ($key->is_new) {
+      module_invoke_all('sshkey_insert', $key);
     }
+    else {
+      module_invoke_all('sshkey_update', $key);
+    }
+
+    // Clear internal properties.
+    unset($key->is_new);
+    unset($key->original);
+
+    // Clear the static loading cache.
+    sshkey_load_multiple(FALSE, TRUE);
+  }
+  catch (Exception $e) {
+    watchdog_exception('sshkey', $e);
+    throw $e;
   }
 
-  $record = array(
-    'fingerprint' => $pubkey['fingerprint'],
-    'name' => $name,
-    'uid' => $uid,
-    'created' => time(),
-  );
-  drupal_write_record('sshkey', $record);
-  file_put_contents(sshkey_path_to_public_key($pubkey['fingerprint']), $pubkey['pubkey']);
+  return $key;
+}
 
-  module_invoke_all('sshkey_public_key_created', $record);
+/**
+ * Delete an SSH public key.
+ */
+function sshkey_delete($key_ids) {
+  return sshkey_delete_multiple(array($key_ids));
+}
 
-  return $record;
+/**
+ * Delete multiple SSH public keys.
+ */
+function sshkey_delete_multiple($key_ids) {
+  if (empty($key_ids)) {
+    return;
+  }
+
+  try {
+    $keys = sshkey_load_multiple($key_ids);
+    foreach ($keys as $key_id => $key) {
+      module_invoke_all('sshkey_delete', $key);
+    }
+
+    db_query("DELETE FROM {sshkey} WHERE key_id IN (" . db_placeholders($key_ids, 'int') . ")", $key_ids);
+  }
+  catch (Exception $e) {
+    watchdog_exception('sshkey', $e);
+    throw $e;
+  }
+
+  // Clear the static loading cache..
+  sshkey_load_multiple(FALSE, TRUE);
 }
 
 /**
- * Updates the name of a public key.
- *
- * @param string $fingerprint 
- * @param string $name 
- * @return void
+ * Delete all SSH public keys associated with an entity.
  */
-function sshkey_public_key_update($fingerprint, $name) {
-  $record = array(
-    'fingerprint' => $fingerprint,
-    'name' => $name,
-  );
-  drupal_write_record('sshkey', $record, array('fingerprint'));
+function sshkey_delete_all_by_entity($entity_type, $entity_id) {
+  $key_ids = sshkey_db_fetch_col(db_query("SELECT key_id FROM {sshkey} WHERE entity_type = '%s' AND entity_id = %d", $entity_type, $entity_id));
+  return !empty($key_ids) ? sshkey_delete_multiple($key_ids) : FALSE;
 }
 
 /**
- * Gets the path to a public key file.
- *
- * @param string $fingerprint
- * @return string
+ * Validate an SSH public key.
  */
-function sshkey_path_to_public_key($fingerprint) {
-  return sshkey_get_key_directory() . '/' . $fingerprint . '.pub';
+function sshkey_validate($key, $form, &$form_state) {
+  $key = (object) $key;
+
+  try {
+    $parsed = sshkey_parse($key->value);
+    $existing_key = sshkey_load_by_fingerprint($parsed['fingerprint']);
+    if (!empty($existing_key->key_id) && $existing_key->key_id != $key->key_id) {
+      form_set_error('value', t('The public key with fingerprint %fingerprint is already in use.', array('%fingerprint' => $parsed['fingerprint'])));
+    }
+  }
+  catch (SSHKeyParseException $e) {
+    form_set_error('value', $e->getMessage());
+  }
+
+  // Allow other modules to validate the SSH public key.
+  foreach (module_implements('sshkey_validate') as $module) {
+    $function = $module . '_sshkey_validate';
+    $function($key, $form, $form_state);
+  }
 }
 
 /**
- * Loads the key data from the public key file.
+ * Parses a SSH public key.
  *
- * @param string $fingerprint 
- * @return array
- *  An array containing the algo, key, comment, fingerprint, decoded key data as data and the sanitized
- *  public key as pubkey.
- * @throws Exception
- *  A exception is thrown if the public key file can't be parsed (error code
- *  SSHKEY_PARSE_ERROR). Or if the key file has been manipulated (error code
- *  SSHKEY_TAMPERING).
+ * @param string $key_raw
+ *   The string with the raw SSH public key.
  */
-function sshkey_get_public_key_data($fingerprint) {
-  module_load_include('inc', 'sshkey');
-  $path = sshkey_path_to_public_key($fingerprint);
-  $pubkey = FALSE;
+function sshkey_parse($key_raw) {
+  $parsed['raw'] = trim(preg_replace('/\s+/', ' ', $key_raw));
 
-  if (file_exists($path)) {
-    $raw_pubkey = file_get_contents($path);
-    $pubkey = sshkey_public_key_parse($raw_pubkey);
+  // The SSH key should be in the form
+  $key_parts = explode(' ', $parsed['raw'], 3);
+  if (count($key_parts) < 2) {
+    throw new SSHKeyParseException(t('The key is invalid.'));
+  }
+
+  $parsed['algorithm'] = $key_parts[0];
+  if (!in_array($parsed['algorithm'], array('ssh-rsa', 'ssh-dss'))) {
+    throw new SSHKeyParseException(t("The key is invalid. It must begin with <em>ssh-rsa</em> or <em>ssh-dss</em>."));
+  }
+
+  $parsed['key'] = $key_parts[1];
+  $key_base64_decoded = base64_decode($parsed['key']);
+  if ($key_base64_decoded === FALSE) {
+    throw new SSHKeyParseException(t('The key could not be decoded.'));
+  }
+  $parsed['fingerprint'] = md5($key_base64_decoded);
+
+  if (isset($key_parts[2])) {
+    $parsed['comment'] = $key_parts[2];
+  }
 
-    if ($pubkey['fingerprint'] !== $fingerprint) {
-      throw new Exception(t('The public key file has been tampered with'), SSHKEY_TAMPERING);
+  return $parsed;
+}
+
+function sshkey_extract_entity_id($entity_type, $entity) {
+  $ids = array(
+    'node' => 'nid',
+    'taxonomy_term', 'tid',
+    'user' => 'uid',
+  );
+
+  if (isset($ids[$entity_type])) {
+    return $entity->{$ids[$entity_type]};
+  }
+}
+
+function sshkey_entity_object_load($entity_type, $entity_id) {
+  static $entities = array();
+
+  if (!isset($entities[$entity_type][$entity_id])) {
+    $entity = FALSE;
+    switch ($entity_type) {
+      case 'node':
+        $entity = node_load($entity_id);
+        break;
+      case 'taxonomy_term':
+        $entity = taxonomy_get_term($entity_id);
+        break;
+      case 'user':
+        $entity = user_load(array('uid' => $entity_id));
+        break;
     }
+    $entities[$entity_type][$entity_id] = $entity;
   }
 
-  return $pubkey;
+  return $entities[$entity_type][$entity_id];
 }
 
 /**
- * Deletes a public key from the system.
- *
- * @param string $fingerprint
- * @return void
+ * Backport of the DBTNG fetchCol() from Drupal 7.
  */
-function sshkey_public_key_delete($fingerprint) {
-  $key = sshkey_public_key_load($fingerprint);
-  if ($key) {
-    db_query("DELETE FROM {sshkey} WHERE fingerprint='%s'", array(
-      ':fingerprint' => $fingerprint,
-    ));
-    unlink(sshkey_path_to_public_key($fingerprint));
-
-    module_invoke_all('sshkey_public_key_deleted', $key);
+function sshkey_db_fetch_col($query) {
+  $row = array();
+  while ($result = db_result($query)) {
+    $row[] = $result;
   }
+  return $row;
 }
diff --git a/sshkey.pages.inc b/sshkey.pages.inc
index bc416c8..ca3d617 100644
--- a/sshkey.pages.inc
+++ b/sshkey.pages.inc
@@ -1,253 +1,188 @@
 <?php
 
-function sshkey_page_list_public_keys($user=NULL) {
-  drupal_add_css(drupal_get_path('module', 'sshkey') . '/css/sshkey.css');
-
-  $keys = sshkey_user_public_keys_list($user->uid);
-
-  $header = array(
-    array('data' => t('Fingerprint'), 'class' => 'sshkey-public-key-fingerprint'),
-    array('data' => t('Name'),        'class' => 'sshkey-public-key-name'),
-    array('data' => t('Created'),     'class' => 'sshkey-public-key-created'),
-    array('data' => t('Operations'),  'class' => 'sshkey-public-key-operations'),
-  );
-  $rows = array();
-
-  foreach ($keys as $key) {
-    $segmented = '';
-    for ($i=0; $i<8; $i+=2) {
-      $segmented .= substr($key['fingerprint'], $i, 2) . ':';
-    }
-
-    $data = array(
-      'fingerprint' => array(
-        'data'  => $segmented . '...',
-        'class' => 'sshkey-public-key-key',
-      ),
-      'name' => array(
-        'data'  => $key['name'],
-        'class' => 'sshkey-public-key-name',
-      ),
-      'created' => array(
-        'data'  => format_date($key['created']),
-        'class' => 'sshkey-public-key-created',
-      ),
-    );
-
-    $operations = array();
-    $operations[] = array(
-      'title' => t('Edit'),
-      'href'  => sprintf('user/%d/sshkeys/%s/edit', $key['uid'], $key['fingerprint']),
-      'query' => drupal_get_destination(),
-    );
-    $operations[] = array(
-      'title' => t('Delete'),
-      'href'  => sprintf('user/%d/sshkeys/%s/delete', $key['uid'], $key['fingerprint']),
-      'query' => drupal_get_destination(),
-    );
-
-    $rows[] = array(
-      'data' => $data + array(
-        'operations' => array(
-          'data'  => theme('links', $operations),
-          'class' => 'sshkey-public-key-operations',
-        ),
-      ),
-      'class' => 'sshkey-public-key-operations',
+function sshkey_list_page($entity_type, $entity_id) {
+  $destination = drupal_get_destination();
+
+  // Add the local actions.
+  if (sshkey_access('create', $entity_type, $entity_id)) {
+    $local_actions['add'] = array(
+      'title' => t('Add a public key'),
+      'href' => "ssh-keys/{$entity_type}/{$entity_id}/add",
+      'query' => $destination,
+      'modal' => TRUE,
     );
+    $build['actions'] = theme('links', $local_actions, array('class' => 'item-list action-links'));
   }
 
-  $table = theme('sshkey_table', $header, $rows, array('id' => 'sshkey-public-list-key'));
-  $form = drupal_get_form('sshkey_add_public_key_form', $user);
-  return theme('sshkey_public_key_list_page', $table, $form);
-}
-
-function sshkey_add_public_key_form($form_state, $account) {
-  $form = array();
-
-  $form['user_object'] = array(
-    '#type' => 'value',
-    '#value' => $account,
-  );
-
-  $form['name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Name'),
-    '#description' => t('The name of the public key. Will be filled in from the key comment if omitted.'),
-    '#size' => 40,
-    '#maxlength' => 255,
-  );
-  
-  $form['public_key'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Public key'),
-    '#description' => t('The contents of your public key file.'),
-    '#cols' => 40,
-    '#rows' => 5,
-    '#required' => TRUE,
+  $header = array(
+    'name' => array('data' => t('Title'), 'field' => 'title', 'sort' => 'asc'),
+    'fingerprint' => array('data' => t('Fingerpint'), 'field' => 'fingerprint'),
+    'operations' => array('data' => t('Operations')),
   );
 
-  $form['save'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save'),
-  );
+  $limit = 25;
+  $query = pager_query("SELECT key_id FROM {sshkey} WHERE entity_type = '%s' AND entity_id = %d" . tablesort_sql($header), $limit, 0, NULL, $entity_type, $entity_id);
+  $key_ids = sshkey_db_fetch_col($query);
+  $keys = sshkey_load_multiple($key_ids);
 
-  return $form;
-}
+  $rows = array();
+  foreach ($key_ids as $key_id) {
+    $key = $keys[$key_id];
 
-function sshkey_add_public_key_form_validate($form, $form_state) {
-  module_load_include('inc', 'sshkey');
-  $values = $form_state['values'];
-  $user = $values['user_object'];
+    $rows[$key_id]['title'] = check_plain($key->title);
 
-  try {
-    // Parse the key
-    $pubkey = sshkey_public_key_parse($values['public_key']);
+    $rows[$key_id]['fingerprint'] = $key->fingerprint;
 
-    // Check if the key is blacklisted.
-    if (sshkey_public_key_blacklisted($pubkey['pubkey'])) {
-      form_set_error('public_key', t('The key you have used is blacklisted'));
+    $operations = array();
+    if (sshkey_access('edit', $entity_type, $entity_id, $key)) {
+      $operations['edit'] = array(
+        'title' => t('Edit'),
+        'href' => "ssh-keys/{$entity_type}/{$entity_id}/edit/{$key_id}",
+        'query' => $destination,
+        'modal' => TRUE,
+      );
     }
-
-    // Check if the key exists
-    $exists = sshkey_public_key_load($pubkey['fingerprint']);
-    if ($exists) {
-      // Be a bit helpful and tell the user if the key has been added by herself,
-      // or if it's been added by someone else. Not sure how to treat the double-
-      // add scenario.
-      if ($exists['uid'] == $user->uid) {
-        form_set_error('public_key', t('You have already added this public key.'));
-      }
-      else {
-        form_set_error('public_key', t('This public key has already been added by someone else, contact the webmaster at @email if you have any questions or suspect malicious intent.', array(
-          '@email' => variable_get('site_mail', ''),
-        )));
-      }
+    if (sshkey_access('delete', $entity_type, $entity_id, $key)) {
+      $operations['delete'] = array(
+        'title' => t('Delete'),
+        'href' => "ssh-keys/{$entity_type}/{$entity_id}/delete/{$key_id}",
+        'query' => $destination,
+        'modal' => TRUE,
+      );
     }
+    $rows[$key_id]['operations'] = array(
+      'data' => theme('links', $operations, array('class' => 'links inline')),
+    );
   }
-  catch (Exception $e) {
-    form_set_error('public_key', t('There was a problem with the key: !message', array(
-      '!message' => $e->getMessage(),
-    )));
+
+  if (empty($rows)) {
+    $empty_text[] = t('No SSH public keys available.');
+    if (sshkey_access('create', $entity_type, $entity_id)) {
+      $empty_text[] = l(t('Add a public key'), "ssh-keys/{$entity_type}/{$entity_id}/add", array('query' => $destination, 'modal' => TRUE));
+    }
+    $rows['add'] = array(array(
+      'data' => implode(' ', $empty_text),
+      'colspan' => count($header),
+      'class' => 'empty add',
+    ));
   }
-}
 
-function sshkey_add_public_key_form_submit($form, $form_state) {
-  $values = $form_state['values'];
-  $user = $values['user_object'];
+  $build['keys_table'] = theme('table', $header, $rows);
+  $build['keys_table_pager'] = theme('pager', NULL, $limit, 0);
 
-  try {
-    sshkey_public_key_save($user->uid, $values['public_key'], $values['name']);
-  }
-  catch (Exception $e) {
-    drupal_set_message(t('There was a problem saving your key: !message', array(
-      '!message' => $e->getMessage(),
-    )), 'error');
-  }
-}
+  //$build['pre_form_title'] = '<div><hr /><h3>Add a public key</h3>';
+  //$build['add_form'] = drupal_get_form('sshkey_edit_form', $entity_type, $entity_id);
+  //$build['post_add_form'] = '</div>';
 
-function sshkey_page_edit_public_key($key) {
-  return drupal_get_form('sshkey_edit_public_key_form', $key);
+  return implode("\n", $build);
 }
 
-function sshkey_edit_public_key_form($form_state, $key) {
-  $form = array();
-  $data = sshkey_get_public_key_data($key['fingerprint']);
-
-  $segmented = array();
-  for ($i=0; $i<strlen($key['fingerprint']); $i+=2) {
-    $segmented[] = substr($key['fingerprint'], $i, 2);
+function sshkey_edit_form($form_state, $entity_type, $entity_id, $key = NULL) {
+  $entity = sshkey_entity_object_load($entity_type, $entity_id);
+
+  if (!isset($key)) {
+    $key = new stdClass();
+    $key->is_new = TRUE;
+    $key->key_id = NULL;
+    $key->fingerprint = NULL;
+    $key->title = '';
+    $key->value = '';
+    $key->entity_type = $entity_type;
+    $key->entity_id = $entity_id;
   }
-  $readable_fingerprint = join($segmented, ':');
 
-  $form['key_object'] = array(
+  $form['#sshkey'] = $key;
+  $form['#entity'] = $entity;
+
+  $form['key_id'] = array(
     '#type' => 'value',
-    '#value' => $data,
+    '#value' => isset($key->key_id) ? $key->key_id : NULL,
   );
-
-  $form['name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Name'),
-    '#description' => t('The name that should be used for this key.'),
-    '#size' => 40,
-    '#maxlength' => 255,
-    '#default_value' => $key['name'],
+  $form['entity_type'] = array(
+    '#type' => 'value',
+    '#value' => $entity_type,
   );
-
-  $form['created'] = array(
-    '#type' => 'item',
-    '#title' => t('Created'),
-    '#value' => format_date($key['created']),
+  $form['entity_id'] = array(
+    '#type' => 'value',
+    '#value' => $entity_id,
   );
-
-  $form['fingerprint'] = array(
-    '#type' => 'item',
-    '#title' => t('Fingerprint'),
-    '#value' => $readable_fingerprint,
+  $form['title'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Title'),
+    '#default_value' => $key->title,
+    '#maxlength' => 128,
   );
 
-  $form['public_key'] = array(
+  //$form['key_upload'] = array(
+  //  '#type' => 'file',
+  //  '#title' => t('Upload your private key file'),
+  //);
+
+  $form['value'] = array(
     '#type' => 'textarea',
-    '#title' => t('Public key'),
-    '#disabled' => TRUE,
-    '#value' => $data['pubkey'],
-    '#rows' => 8,
+    '#title' => t('Key'),
+    '#default_value' => $key->value,
+    '#rows' => 10,
+    '#wysiwyg' => FALSE,
+    '#required' => TRUE,
   );
 
-  $form['save'] = array(
+  $form['actions'] = array(
+    '#weight' => 100,
+  );
+  $form['actions']['save'] = array(
     '#type' => 'submit',
     '#value' => t('Save'),
   );
+  $form['actions']['cancel'] = array(
+    '#type' => 'markup',
+    '#value' => l(t('Cancel'), isset($_GET['destination']) ? $_GET['destination'] : '<front>'),
+  );
 
   return $form;
 }
 
-function sshkey_edit_public_key_form_submit($form, $form_state) {
-  $values = $form_state['values'];
-
-  $key = $values['key_object'];
-  $name = empty($values['name']) ? $key['comment'] : $values['name'];
-
-  sshkey_public_key_update($key['fingerprint'], $name);
-  drupal_goto();
+function sshkey_edit_form_validate($form, &$form_state) {
+  // Import the uploaded key file.
+  //if ($file = file_save_upload('key_upload', array(), FALSE, FILE_EXISTS_REPLACE)) {
+  //  $contents = @file_get_contents($file->filepath);
+  //  file_delete($file->filepath);
+  //  if ($contents === FALSE) {
+  //    drupal_set_message(t('The public key import failed, because the file %filename could not be read.', array('%filename' => $file->filename)), 'error');
+  //  }
+  //  else {
+  //    $form_state['values']['value'] = $contents;
+  //  }
+  //}
+
+  sshkey_validate($form_state['values'], $form, $form_state);
 }
 
-function sshkey_page_delete_public_key($key) {
-  return drupal_get_form('sshkey_delete_public_key_form', $key);
+function sshkey_edit_form_submit($form, &$form_state) {
+  $key = (object) $form_state['values'];
+  sshkey_save($key);
+  drupal_set_message(t('The SSH public key %title has been saved.', array('%title' => $form_state['values']['title'])));
 }
 
-function sshkey_delete_public_key_form($form_state, $key) {
-  $form['key_object'] = array(
-    '#type' => 'value',
-    '#value' => $key
-  );
+function sshkey_delete_form($form_state, $key) {
+  $form['#sshkey'] = $key;
 
-  $form['warning'] = array(
-    '#type' => 'item',
-    '#title' => t('Warning'),
-    '#value' => t('Your\'e about to delete a public key, effectively disabling access for any services that use it. Are you sure that you want to do this?'),
+  $form['key_id'] = array(
+    '#type' => 'value',
+    '#value' => $key->key_id,
   );
 
-  $destination = 'user/' . $key->uid . '/sshkeys';
-  if (isset($_REQUEST['destination'])) {
-    $destination = $_REQUEST['destination'];
-  }
-  $form['cancel'] = array(
-    '#type' => 'item',
-    '#title' => t('Cancel'),
-    '#value' => l('Click here to cancel the deletion of the key', $destination),
-  );
-  
-  $form['delete'] = array(
-    '#type' => 'submit',
-    '#value' => t('Delete'),
+  return confirm_form(
+    $form,
+    t('Are you sure you want to delete the public key %title?', array('%title' => $key->title)),
+    isset($_GET['destination']) ? $_GET['destination'] : '<front>',
+    '',
+    t('Delete'),
+    t('Cancel')
   );
-
-  return $form;
 }
 
-function sshkey_delete_public_key_form_submit($form, $form_state) {
-  $key = $form_state['values']['key_object'];
-  sshkey_public_key_delete($key['fingerprint']);
-  drupal_goto();
-}
\ No newline at end of file
+function sshkey_delete_form_submit($form, $form_state) {
+  sshkey_delete($form_state['values']['key_id']);
+  drupal_set_message(t('The SSH public key %title has been deleted.', array('%title' => $form['#sshkey']->title)));
+}
diff --git a/sshkey_public_key_list_page.tpl.php b/sshkey_public_key_list_page.tpl.php
deleted file mode 100644
index e957c7a..0000000
--- a/sshkey_public_key_list_page.tpl.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<div id="sshkey-public-key-wrapper">
-  <div class="public-key-list-table">
-    <?php print $table; ?>
-  </div>
-  <hr />
-  <div class="add-public-key-form">
-    <h3><?php print t('Add a public key'); ?></h3>
-    <?php print $form; ?>
-  </div>
-</div>
\ No newline at end of file
