1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-19 12:00:55 +01:00

Allow Passphrase to generate SSH keypairs and extact public keys from private keys

Summary:
Ref T4587.

  - Add an option to generate a keypair.
  - Add an option to view the public keys for existing keypairs.

Test Plan:
  - Generated keypairs.
  - Viewed public keys.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: aran, epriestley

Maniphest Tasks: T4587

Differential Revision: https://secure.phabricator.com/D8515
This commit is contained in:
epriestley 2014-03-12 18:58:25 -07:00
parent 44fc671b3f
commit f950985cfd
12 changed files with 181 additions and 7 deletions

View file

@ -1024,6 +1024,7 @@ phutil_register_library_map(array(
'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php', 'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php',
'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php', 'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php',
'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php', 'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php',
'PassphraseCredentialPublicController' => 'applications/passphrase/controller/PassphraseCredentialPublicController.php',
'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php', 'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php',
'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php', 'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php',
'PassphraseCredentialSearchEngine' => 'applications/passphrase/query/PassphraseCredentialSearchEngine.php', 'PassphraseCredentialSearchEngine' => 'applications/passphrase/query/PassphraseCredentialSearchEngine.php',
@ -1032,6 +1033,7 @@ phutil_register_library_map(array(
'PassphraseCredentialTransactionQuery' => 'applications/passphrase/query/PassphraseCredentialTransactionQuery.php', 'PassphraseCredentialTransactionQuery' => 'applications/passphrase/query/PassphraseCredentialTransactionQuery.php',
'PassphraseCredentialType' => 'applications/passphrase/credentialtype/PassphraseCredentialType.php', 'PassphraseCredentialType' => 'applications/passphrase/credentialtype/PassphraseCredentialType.php',
'PassphraseCredentialTypePassword' => 'applications/passphrase/credentialtype/PassphraseCredentialTypePassword.php', 'PassphraseCredentialTypePassword' => 'applications/passphrase/credentialtype/PassphraseCredentialTypePassword.php',
'PassphraseCredentialTypeSSHGeneratedKey' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHGeneratedKey.php',
'PassphraseCredentialTypeSSHPrivateKey' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKey.php', 'PassphraseCredentialTypeSSHPrivateKey' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKey.php',
'PassphraseCredentialTypeSSHPrivateKeyFile' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKeyFile.php', 'PassphraseCredentialTypeSSHPrivateKeyFile' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKeyFile.php',
'PassphraseCredentialTypeSSHPrivateKeyText' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKeyText.php', 'PassphraseCredentialTypeSSHPrivateKeyText' => 'applications/passphrase/credentialtype/PassphraseCredentialTypeSSHPrivateKeyText.php',
@ -3676,6 +3678,7 @@ phutil_register_library_map(array(
0 => 'PassphraseController', 0 => 'PassphraseController',
1 => 'PhabricatorApplicationSearchResultsControllerInterface', 1 => 'PhabricatorApplicationSearchResultsControllerInterface',
), ),
'PassphraseCredentialPublicController' => 'PassphraseController',
'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PassphraseCredentialRevealController' => 'PassphraseController', 'PassphraseCredentialRevealController' => 'PassphraseController',
'PassphraseCredentialSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PassphraseCredentialSearchEngine' => 'PhabricatorApplicationSearchEngine',
@ -3684,6 +3687,7 @@ phutil_register_library_map(array(
'PassphraseCredentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PassphraseCredentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PassphraseCredentialType' => 'Phobject', 'PassphraseCredentialType' => 'Phobject',
'PassphraseCredentialTypePassword' => 'PassphraseCredentialType', 'PassphraseCredentialTypePassword' => 'PassphraseCredentialType',
'PassphraseCredentialTypeSSHGeneratedKey' => 'PassphraseCredentialTypeSSHPrivateKey',
'PassphraseCredentialTypeSSHPrivateKey' => 'PassphraseCredentialType', 'PassphraseCredentialTypeSSHPrivateKey' => 'PassphraseCredentialType',
'PassphraseCredentialTypeSSHPrivateKeyFile' => 'PassphraseCredentialTypeSSHPrivateKey', 'PassphraseCredentialTypeSSHPrivateKeyFile' => 'PassphraseCredentialTypeSSHPrivateKey',
'PassphraseCredentialTypeSSHPrivateKeyText' => 'PassphraseCredentialTypeSSHPrivateKey', 'PassphraseCredentialTypeSSHPrivateKeyText' => 'PassphraseCredentialTypeSSHPrivateKey',

View file

@ -40,6 +40,7 @@ final class PhabricatorApplicationPassphrase extends PhabricatorApplication {
'edit/(?:(?P<id>\d+)/)?' => 'PassphraseCredentialEditController', 'edit/(?:(?P<id>\d+)/)?' => 'PassphraseCredentialEditController',
'destroy/(?P<id>\d+)/' => 'PassphraseCredentialDestroyController', 'destroy/(?P<id>\d+)/' => 'PassphraseCredentialDestroyController',
'reveal/(?P<id>\d+)/' => 'PassphraseCredentialRevealController', 'reveal/(?P<id>\d+)/' => 'PassphraseCredentialRevealController',
'public/(?P<id>\d+)/' => 'PassphraseCredentialPublicController',
)); ));
} }

View file

@ -53,6 +53,10 @@ final class PassphraseCredentialEditController extends PassphraseController {
// Prefill username if provided. // Prefill username if provided.
$credential->setUsername($request->getStr('username')); $credential->setUsername($request->getStr('username'));
if (!$request->getStr('isInitialized')) {
$type->didInitializeNewCredential($viewer, $credential);
}
} }
$errors = array(); $errors = array();
@ -68,6 +72,16 @@ final class PassphraseCredentialEditController extends PassphraseController {
$bullet = "\xE2\x80\xA2"; $bullet = "\xE2\x80\xA2";
$v_secret = $credential->getSecretID() ? str_repeat($bullet, 32) : null; $v_secret = $credential->getSecretID() ? str_repeat($bullet, 32) : null;
if ($is_new && ($v_secret === null)) {
// If we're creating a new credential, the credential type may have
// populated the secret for us (for example, generated an SSH key). In
// this case,
try {
$v_secret = $credential->getSecret()->openEnvelope();
} catch (Exception $ex) {
// Ignore this.
}
}
$validation_exception = null; $validation_exception = null;
$errors = array(); $errors = array();
@ -205,6 +219,7 @@ final class PassphraseCredentialEditController extends PassphraseController {
} }
$form $form
->addHiddenInput('isInitialized', true)
->appendChild( ->appendChild(
id(new AphrontFormTextControl()) id(new AphrontFormTextControl())
->setName('name') ->setName('name')

View file

@ -0,0 +1,59 @@
<?php
final class PassphraseCredentialPublicController
extends PassphraseController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$credential = id(new PassphraseCredentialQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
))
->executeOne();
if (!$credential) {
return new Aphront404Response();
}
$type = PassphraseCredentialType::getTypeByConstant(
$credential->getCredentialType());
if (!$type) {
throw new Exception(pht('Credential has invalid type "%s"!', $type));
}
if (!$type->hasPublicKey()) {
throw new Exception(pht('Credential has no public key!'));
}
$view_uri = '/'.$credential->getMonogram();
$public_key = $type->getPublicKey($viewer, $credential);
$body = id(new PHUIFormLayoutView())
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Public Key'))
->setReadOnly(true)
->setValue($public_key));
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle(pht('Public Key (%s)', $credential->getMonogram()))
->appendChild($body)
->addCancelButton($view_uri, pht('Done'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View file

@ -36,15 +36,21 @@ final class PassphraseCredentialRevealController
id(new AphrontFormTextAreaControl()) id(new AphrontFormTextAreaControl())
->setLabel(pht('Plaintext')) ->setLabel(pht('Plaintext'))
->setReadOnly(true) ->setReadOnly(true)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setValue($credential->getSecret()->openEnvelope())); ->setValue($credential->getSecret()->openEnvelope()));
} else { } else {
$body = pht('This credential has no associated secret.'); $body = pht('This credential has no associated secret.');
} }
// NOTE: Disable workflow on the cancel button to reload the page so
// the viewer can see that their view was logged.
$dialog = id(new AphrontDialogView()) $dialog = id(new AphrontDialogView())
->setUser($viewer) ->setUser($viewer)
->setTitle(pht('Credential Secret')) ->setWidth(AphrontDialogView::WIDTH_FORM)
->setTitle(pht('Credential Secret (%s)', $credential->getMonogram()))
->appendChild($body) ->appendChild($body)
->setDisableWorkflowOnCancel(true)
->addCancelButton($view_uri, pht('Done')); ->addCancelButton($view_uri, pht('Done'));
$type_secret = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET; $type_secret = PassphraseCredentialTransaction::TYPE_LOOKEDATSECRET;

View file

@ -44,7 +44,7 @@ final class PassphraseCredentialViewController extends PassphraseController {
$crumbs->addTextCrumb('K'.$credential->getID()); $crumbs->addTextCrumb('K'.$credential->getID());
$header = $this->buildHeaderView($credential); $header = $this->buildHeaderView($credential);
$actions = $this->buildActionView($credential); $actions = $this->buildActionView($credential, $type);
$properties = $this->buildPropertyView($credential, $type, $actions); $properties = $this->buildPropertyView($credential, $type, $actions);
$box = id(new PHUIObjectBoxView()) $box = id(new PHUIObjectBoxView())
@ -78,7 +78,9 @@ final class PassphraseCredentialViewController extends PassphraseController {
return $header; return $header;
} }
private function buildActionView(PassphraseCredential $credential) { private function buildActionView(
PassphraseCredential $credential,
PassphraseCredentialType $type) {
$viewer = $this->getRequest()->getUser(); $viewer = $this->getRequest()->getUser();
$id = $credential->getID(); $id = $credential->getID();
@ -116,6 +118,15 @@ final class PassphraseCredentialViewController extends PassphraseController {
->setHref($this->getApplicationURI("reveal/{$id}/")) ->setHref($this->getApplicationURI("reveal/{$id}/"))
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setWorkflow(true)); ->setWorkflow(true));
if ($type->hasPublicKey()) {
$actions->addAction(
id(new PhabricatorActionView())
->setName(pht('Show Public Key'))
->setIcon('download-alt')
->setHref($this->getApplicationURI("public/{$id}/"))
->setWorkflow(true));
}
} }

View file

@ -50,6 +50,23 @@ abstract class PassphraseCredentialType extends Phobject {
} }
public function didInitializeNewCredential(
PhabricatorUser $actor,
PassphraseCredential $credential) {
return $credential;
}
public function hasPublicKey() {
return false;
}
public function getPublicKey(
PhabricatorUser $viewer,
PassphraseCredential $credential) {
return null;
}
/* -( Passwords )---------------------------------------------------------- */ /* -( Passwords )---------------------------------------------------------- */

View file

@ -0,0 +1,36 @@
<?php
final class PassphraseCredentialTypeSSHGeneratedKey
extends PassphraseCredentialTypeSSHPrivateKey {
const CREDENTIAL_TYPE = 'ssh-generated-key';
public function getCredentialType() {
return self::CREDENTIAL_TYPE;
}
public function getCredentialTypeName() {
return pht('SSH Private Key (Generated)');
}
public function getCredentialTypeDescription() {
return pht('Generate an SSH keypair.');
}
public function getSecretLabel() {
return pht('Generated Key');
}
public function didInitializeNewCredential(
PhabricatorUser $actor,
PassphraseCredential $credential) {
$pair = PhabricatorSSHKeyGenerator::generateKeypair();
list($public_key, $private_key) = $pair;
$credential->attachSecret(new PhutilOpaqueEnvelope($private_key));
return $credential;
}
}

View file

@ -9,4 +9,20 @@ abstract class PassphraseCredentialTypeSSHPrivateKey
return self::PROVIDES_TYPE; return self::PROVIDES_TYPE;
} }
public function hasPublicKey() {
return true;
}
public function getPublicKey(
PhabricatorUser $viewer,
PassphraseCredential $credential) {
$key = PassphraseSSHKey::loadFromPHID($credential->getPHID(), $viewer);
$file = $key->getKeyfileEnvelope();
list($stdout) = execx('ssh-keygen -y -f %P', $file);
return $stdout;
}
} }

View file

@ -33,4 +33,9 @@ final class PassphraseCredentialTypeSSHPrivateKeyFile
return false; return false;
} }
public function hasPublicKey() {
// These have public keys, but they'd be cumbersome to extract.
return true;
}
} }

View file

@ -15,10 +15,10 @@ final class PassphraseSSHKey extends PassphraseAbstractKey {
public function getKeyfileEnvelope() { public function getKeyfileEnvelope() {
$credential = $this->requireCredential(); $credential = $this->requireCredential();
$text_type = PassphraseCredentialTypeSSHPrivateKeyText::CREDENTIAL_TYPE; $file_type = PassphraseCredentialTypeSSHPrivateKeyFile::CREDENTIAL_TYPE;
if ($credential->getCredentialType() == $text_type) { if ($credential->getCredentialType() != $file_type) {
// If the credential stores key text, write it out to a temporary file // If the credential does not store a file, write the key txt out to a
// so we can pass it to `ssh`. // temporary file so we can pass it to `ssh`.
if (!$this->keyFile) { if (!$this->keyFile) {
$temporary_file = new TempFile('passphrase-ssh-key'); $temporary_file = new TempFile('passphrase-ssh-key');

View file

@ -25,6 +25,10 @@ final class PassphraseCredential extends PassphraseDAO
->setEditPolicy($actor->getPHID()); ->setEditPolicy($actor->getPHID());
} }
public function getMonogram() {
return 'K'.$this->getID();
}
public function getConfiguration() { public function getConfiguration() {
return array( return array(
self::CONFIG_AUX_PHID => true, self::CONFIG_AUX_PHID => true,