From 657b36dd0685a5d9248c674e8359188cd38db438 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 17 Nov 2014 13:11:52 -0800 Subject: [PATCH] Allow Phabricator to accept Conduit requests signed with an SSH key Summary: Ref T4209. Depends on D10402. This updates Conduit to support authenticating calls from other servers by signing the request parameters with the sending server's private key and verifying it with the public key stored in the database. Test Plan: - Made like 500 bad calls using the stuff in D10402. - Made a few valid calls using the stuff in D10402. Reviewers: hach-que, btrahan, #blessed_reviewers Reviewed By: btrahan, #blessed_reviewers Subscribers: epriestley, Korvin Maniphest Tasks: T6240, T4209 Differential Revision: https://secure.phabricator.com/D10401 --- .../sshkey/PhabricatorAuthSSHPublicKey.php | 22 ++++++ .../PhabricatorConduitAPIController.php | 78 +++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/src/applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php b/src/applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php index 6ed5ff20df..d32a1f84e7 100644 --- a/src/applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php +++ b/src/applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php @@ -99,4 +99,26 @@ final class PhabricatorAuthSSHPublicKey extends Phobject { return PhabricatorHash::digestForIndex($body); } + public function getEntireKey() { + $key = $this->type.' '.$this->body; + if (strlen($this->comment)) { + $key = $key.' '.$this->comment; + } + return $key; + } + + public function toPCKS8() { + + // TODO: Put a cache in front of this. + + $tmp = new TempFile(); + Filesystem::writeFile($tmp, $this->getEntireKey()); + list($pem_key) = execx( + 'ssh-keygen -e -m pcks8 -f %s', + $tmp); + unset($tmp); + + return $pem_key; + } + } diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php index 1a6080b0fe..9738237b82 100644 --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -209,6 +209,84 @@ final class PhabricatorConduitAPIController $request->getUser()); } + $auth_type = idx($metadata, 'auth.type'); + if ($auth_type === ConduitClient::AUTH_ASYMMETRIC) { + $host = idx($metadata, 'auth.host'); + if (!$host) { + return array( + 'ERR-INVALID-AUTH', + pht( + 'Request is missing required "auth.host" parameter.'), + ); + } + + // TODO: Validate that we are the host! + + $raw_key = idx($metadata, 'auth.key'); + $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($raw_key); + $ssl_public_key = $public_key->toPCKS8(); + + // First, verify the signature. + try { + $protocol_data = $metadata; + + // TODO: We should stop writing this into the protocol data when + // processing a request. + unset($protocol_data['scope']); + + ConduitClient::verifySignature( + $this->method, + $api_request->getAllParameters(), + $protocol_data, + $ssl_public_key); + } catch (Exception $ex) { + return array( + 'ERR-INVALID-AUTH', + pht( + 'Signature verification failure. %s', + $ex->getMessage()), + ); + } + + // If the signature is valid, find the user or device which is + // associated with this public key. + + $stored_key = id(new PhabricatorAuthSSHKeyQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withKeys(array($public_key)) + ->executeOne(); + if (!$stored_key) { + return array( + 'ERR-INVALID-AUTH', + pht( + 'No user or device is associated with that public key.'), + ); + } + + $object = $stored_key->getObject(); + + if ($object instanceof PhabricatorUser) { + $user = $object; + } else { + throw new Exception( + pht('Not Implemented: Would authenticate Almanac device.')); + } + + return $this->validateAuthenticatedUser( + $api_request, + $user); + } else if ($auth_type === null) { + // No specified authentication type, continue with other authentication + // methods below. + } else { + return array( + 'ERR-INVALID-AUTH', + pht( + 'Provided "auth.type" ("%s") is not recognized.', + $auth_type), + ); + } + // handle oauth $access_token = $request->getStr('access_token'); $method_scope = $metadata['scope'];