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 @@ -1316,6 +1316,7 @@ 'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php', 'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php', 'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php', + 'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php', 'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php', 'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php', 'PhabricatorAuthManagementRecoverWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php', @@ -4429,6 +4430,7 @@ 'PhabricatorAuthLinkController' => 'PhabricatorAuthController', 'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthLoginController' => 'PhabricatorAuthController', + 'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRecoverWorkflow' => 'PhabricatorAuthManagementWorkflow', diff --git a/src/applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php b/src/applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php @@ -0,0 +1,96 @@ +setName('cache-pkcs8') + ->setExamples('**cache-pkcs8** --public __keyfile__ --pkcs8 __keyfile__') + ->setSynopsis( + pht( + 'Cache the PKCS8 format of a public key. When developing on OSX, '. + 'this can be used to work around issues with ssh-keygen. Use '. + '`ssh-keygen -e -m PKCS8 -f key.pub` to generate a PKCS8 key to '. + 'feed to this command.')) + ->setArguments( + array( + array( + 'name' => 'public', + 'param' => 'keyfile', + 'help' => pht('Path to public keyfile.'), + ), + array( + 'name' => 'pkcs8', + 'param' => 'keyfile', + 'help' => pht('Path to corresponding PKCS8 key.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); + + $public_keyfile = $args->getArg('public'); + if (!strlen($public_keyfile)) { + throw new PhutilArgumentUsageException( + pht( + 'You must specify the path to a public keyfile with --public.')); + } + + if (!Filesystem::pathExists($public_keyfile)) { + throw new PhutilArgumentUsageException( + pht( + 'Specified public keyfile "%s" does not exist!', + $public_keyfile)); + } + + $public_key = Filesystem::readFile($public_keyfile); + + $pkcs8_keyfile = $args->getArg('pkcs8'); + if (!strlen($pkcs8_keyfile)) { + throw new PhutilArgumentUsageException( + pht( + 'You must specify the path to a pkcs8 keyfile with --pkc8s.')); + } + + if (!Filesystem::pathExists($pkcs8_keyfile)) { + throw new PhutilArgumentUsageException( + pht( + 'Specified pkcs8 keyfile "%s" does not exist!', + $pkcs8_keyfile)); + } + + $pkcs8_key = Filesystem::readFile($pkcs8_keyfile); + + $warning = pht( + 'Adding a PKCS8 keyfile to the cache can be very dangerous. If the '. + 'PKCS8 file really encodes a different public key than the one '. + 'specified, an attacker could use it to gain unautorized access.'. + "\n\n". + 'Generally, you should use this option only in a development '. + 'environment where ssh-keygen is broken and it is inconvenient to '. + 'fix it, and only if you are certain you understand the risks. You '. + 'should never cache a PKCS8 file you did not generate yourself.'); + + $console->writeOut( + "%s\n", + phutil_console_wrap($warning)); + + $prompt = pht('Really trust this PKCS8 keyfile?'); + if (!phutil_console_confirm($prompt)) { + throw new PhutilArgumentUsageException( + pht('Aborted workflow.')); + } + + $key = PhabricatorAuthSSHPublicKey::newFromRawKey($public_key); + $key->forcePopulatePKCS8Cache($pkcs8_key); + + $console->writeOut( + "%s\n", + pht('Cached PKCS8 key for public key.')); + + return 0; + } + +} diff --git a/src/applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php b/src/applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php --- a/src/applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php +++ b/src/applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php @@ -108,17 +108,48 @@ } public function toPKCS8() { + $entire_key = $this->getEntireKey(); + $cache_key = $this->getPKCS8CacheKey($entire_key); - // TODO: Put a cache in front of this. + $cache = PhabricatorCaches::getImmutableCache(); + $pkcs8_key = $cache->getKey($cache_key); + if ($pkcs8_key) { + return $pkcs8_key; + } $tmp = new TempFile(); Filesystem::writeFile($tmp, $this->getEntireKey()); - list($pem_key) = execx( - 'ssh-keygen -e -m PKCS8 -f %s', - $tmp); + try { + list($pkcs8_key) = execx( + 'ssh-keygen -e -m PKCS8 -f %s', + $tmp); + } catch (CommandException $ex) { + unset($tmp); + throw new PhutilProxyException( + pht( + 'Failed to convert public key into PKCS8 format. If you are '. + 'developing on OSX, you may be able to use `bin/auth cache-pkcs8` '. + 'to work around this issue. %s', + $ex->getMessage()), + $ex); + } unset($tmp); - return $pem_key; + $cache->setKey($cache_key, $pkcs8_key); + + return $pkcs8_key; + } + + public function forcePopulatePKCS8Cache($pkcs8_key) { + $entire_key = $this->getEntireKey(); + $cache_key = $this->getPKCS8CacheKey($entire_key); + + $cache = PhabricatorCaches::getImmutableCache(); + $cache->setKey($cache_key, $pkcs8_key); + } + + private function getPKCS8CacheKey($entire_key) { + return 'pkcs8:'.PhabricatorHash::digestForIndex($entire_key); } }