diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -10,6 +10,7 @@ '__library_version__' => 2, 'class' => array( 'AlmanacAuthorizedHost' => 'applications/almanac/storage/AlmanacAuthorizedHost.php', + 'AlmanacConduitUtil' => 'applications/almanac/util/AlmanacConduitUtil.php', 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php', 'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php', @@ -2784,6 +2785,7 @@ ), 'xmap' => array( 'AlmanacAuthorizedHost' => 'AlmanacDAO', + 'AlmanacConduitUtil' => 'Phobject', 'AlmanacDAO' => 'PhabricatorLiskDAO', 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', diff --git a/src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php b/src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php --- a/src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php +++ b/src/applications/almanac/management/AlmanacManagementRegisterWorkflow.php @@ -13,7 +13,7 @@ public function execute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); - if (Filesystem::pathExists($this->getHostPrivateKeyPath())) { + if (Filesystem::pathExists(AlmanacConduitUtil::getHostPrivateKeyPath())) { throw new Exception( 'This host already has a private key for Conduit access.'); } @@ -26,26 +26,14 @@ ->save(); Filesystem::writeFile( - $this->getHostPrivateKeyPath(), + AlmanacConduitUtil::getHostPrivateKeyPath(), $private_key); Filesystem::writeFile( - $this->getHostIDPath(), + AlmanacConduitUtil::getHostIDPath(), $host->getID()); $console->writeOut("Registered as authorized host %d.\n", $host->getID()); } - private function getHostPrivateKeyPath() { - $root = dirname(phutil_get_library_root('phabricator')); - $path = $root.'/conf/local/HOSTKEY'; - return $path; - } - - private function getHostIDPath() { - $root = dirname(phutil_get_library_root('phabricator')); - $path = $root.'/conf/local/HOSTID'; - return $path; - } - } diff --git a/src/applications/almanac/util/AlmanacConduitUtil.php b/src/applications/almanac/util/AlmanacConduitUtil.php new file mode 100644 --- /dev/null +++ b/src/applications/almanac/util/AlmanacConduitUtil.php @@ -0,0 +1,68 @@ +load($host_id); + + if ($authorized_host === null) { + return false; + } + + // We have to convert the SSH public key to the PEM format so we can + // verify the signature with OpenSSH. + $ssh_public_key = new TempFile(); + Filesystem::writeFile($ssh_public_key, $authorized_host->getPublicKey()); + + list($public_key, $stderr) = id(new ExecFuture( + 'ssh-keygen -e -f %s -m pkcs8', + $ssh_public_key))->resolvex(); + + unset($ssh_public_key); + + return openssl_verify( + self::getEncodedParameters($api_request), + base64_decode($signature), + $public_key) === 1; + } + + public static function signSignatureForRequest( + ConduitAPIRequest $api_request) { + + $signature = ''; + $result = openssl_sign( + self::getEncodedParameters($api_request), + $signature, + Filesystem::readFile(self::getHostPrivateKeyPath())); + if (!$result) { + throw new Exception('Unable to sign Conduit request with server key.'); + } + + $host_id = Filesystem::readFile(self::getHostIDPath()); + + return array((int)$host_id, base64_encode($signature)); + } + + public static function getHostPrivateKeyPath() { + $root = dirname(phutil_get_library_root('phabricator')); + $path = $root.'/conf/local/HOSTKEY'; + return $path; + } + + public static function getHostIDPath() { + $root = dirname(phutil_get_library_root('phabricator')); + $path = $root.'/conf/local/HOSTID'; + return $path; + } + + private static function getEncodedParameters(ConduitAPIRequest $api_request) { + $params = $api_request->getAllParameters(); + return json_encode($params); + } + +} diff --git a/src/applications/conduit/call/ConduitCall.php b/src/applications/conduit/call/ConduitCall.php --- a/src/applications/conduit/call/ConduitCall.php +++ b/src/applications/conduit/call/ConduitCall.php @@ -145,15 +145,22 @@ $params['__conduit__']['isProxied'] = true; if ($this->handler->shouldRequireAuthentication()) { - $client->callMethodSynchronous( - 'conduit.connect', - array( - 'client' => 'PhabricatorConduit', - 'clientVersion' => '1.0', - 'user' => $this->getUser()->getUserName(), - 'certificate' => $this->getUser()->getConduitCertificate(), - '__conduit__' => $params['__conduit__'], - )); + if ($user->isOmnipotent()) { + list($host_id, $signature) = + AlmanacConduitUtil::signSignatureForRequest($this->request); + + $client->setServerSignature($host_id, $signature); + } else { + $client->callMethodSynchronous( + 'conduit.connect', + array( + 'client' => 'PhabricatorConduit', + 'clientVersion' => '1.0', + 'user' => $this->getUser()->getUserName(), + 'certificate' => $this->getUser()->getConduitCertificate(), + '__conduit__' => $params['__conduit__'], + )); + } } return $client->callMethodSynchronous( diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -209,6 +209,25 @@ $request->getUser()); } + // handle cross-host auth + if (isset($metadata['hostID']) && isset($metadata['signature'])) { + if (AlmanacConduitUtil::verifySignature( + $metadata['hostID'], + $metadata['signature'], + $api_request)) { + + // Authenticated using server signature; therefore the user is + // the omnipotent user. + $api_request->setUser(PhabricatorUser::getOmnipotentUser()); + return null; + } else { + return array( + 'ERR-INVALID-AUTH', + 'Server signature is invalid.', + ); + } + } + // handle oauth $access_token = $request->getStr('access_token'); $method_scope = $metadata['scope'];