@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 210 lines 6.6 kB view raw
1<?php 2 3/** 4 * Data structure representing a raw private key. 5 */ 6final class PhabricatorAuthSSHPrivateKey extends Phobject { 7 8 private $body; 9 private $passphrase; 10 11 private function __construct() { 12 // <internal> 13 } 14 15 public function setPassphrase(PhutilOpaqueEnvelope $passphrase) { 16 $this->passphrase = $passphrase; 17 return $this; 18 } 19 20 public function getPassphrase() { 21 return $this->passphrase; 22 } 23 24 public static function newFromRawKey(PhutilOpaqueEnvelope $entire_key) { 25 $key = new self(); 26 27 $key->body = $entire_key; 28 29 return $key; 30 } 31 32 public function getKeyBody() { 33 return $this->body; 34 } 35 36 public function newBarePrivateKey() { 37 if (!Filesystem::binaryExists('ssh-keygen')) { 38 throw new Exception( 39 pht( 40 'Analyzing or decrypting SSH keys requires the "ssh-keygen" binary, '. 41 'but it is not available in "$PATH". Make it available to work with '. 42 'SSH private keys.')); 43 } 44 45 $old_body = $this->body; 46 47 // Some versions of "ssh-keygen" are sensitive to trailing whitespace for 48 // some keys. Trim any trailing whitespace and replace it with a single 49 // newline. 50 $raw_body = $old_body->openEnvelope(); 51 $raw_body = rtrim($raw_body)."\n"; 52 $old_body = new PhutilOpaqueEnvelope($raw_body); 53 54 $tmp = $this->newTemporaryPrivateKeyFile($old_body); 55 56 // See T13454 for discussion of why this is so awkward. In broad strokes, 57 // we don't have a straightforward way to distinguish between keys with an 58 // invalid format and keys with a passphrase which we don't know. 59 60 // First, try to extract the public key from the file using the (possibly 61 // empty) passphrase we were given. If everything is in good shape, this 62 // should work. 63 64 $passphrase = $this->getPassphrase(); 65 if ($passphrase) { 66 list($err, $stdout, $stderr) = exec_manual( 67 'ssh-keygen -y -P %P -f %R', 68 $passphrase, 69 $tmp); 70 } else { 71 list($err, $stdout, $stderr) = exec_manual( 72 'ssh-keygen -y -P %s -f %R', 73 '', 74 $tmp); 75 } 76 77 // If that worked, the key is good and the (possibly empty) passphrase is 78 // correct. Strip the passphrase if we have one, then return the bare key. 79 80 if (!$err) { 81 if ($passphrase) { 82 execx( 83 'ssh-keygen -p -P %P -N %s -f %R', 84 $passphrase, 85 '', 86 $tmp); 87 88 $new_body = new PhutilOpaqueEnvelope(Filesystem::readFile($tmp)); 89 unset($tmp); 90 } else { 91 $new_body = $old_body; 92 } 93 94 return self::newFromRawKey($new_body); 95 } 96 97 // We were not able to extract the public key. Try to figure out why. The 98 // reasons we expect are: 99 // 100 // - We were given a passphrase, but the key has no passphrase. 101 // - We were given a passphrase, but the passphrase is wrong. 102 // - We were not given a passphrase, but the key has a passphrase. 103 // - The key format is invalid. 104 // 105 // Our ability to separate these cases varies a lot, particularly because 106 // some versions of "ssh-keygen" return very similar diagnostic messages 107 // for any error condition. Try our best. 108 109 if ($passphrase) { 110 // First, test for "we were given a passphrase, but the key has no 111 // passphrase", since this is a conclusive test. 112 list($err) = exec_manual( 113 'ssh-keygen -y -P %s -f %R', 114 '', 115 $tmp); 116 if (!$err) { 117 throw new PhabricatorAuthSSHPrivateKeySurplusPassphraseException( 118 pht( 119 'A passphrase was provided for this private key, but it does '. 120 'not require a passphrase. Check that you supplied the correct '. 121 'key, or omit the passphrase.')); 122 } 123 } 124 125 // We're out of conclusive tests, so try to guess why the error occurred. 126 // In some versions of "ssh-keygen", we get a usable diagnostic message. In 127 // other versions, not so much. 128 129 $reason_format = 'format'; 130 $reason_passphrase = 'passphrase'; 131 $reason_unknown = 'unknown'; 132 133 $patterns = array( 134 // macOS 10.14.6 135 '/incorrect passphrase supplied to decrypt private key/' 136 => $reason_passphrase, 137 138 // macOS 10.14.6 139 '/invalid format/' => $reason_format, 140 141 // Ubuntu 14 142 '/load failed/' => $reason_unknown, 143 ); 144 145 $reason = 'unknown'; 146 foreach ($patterns as $pattern => $pattern_reason) { 147 $ok = preg_match($pattern, $stderr); 148 149 if ($ok === false) { 150 throw new Exception( 151 pht( 152 'Pattern "%s" is not valid.', 153 $pattern)); 154 } 155 156 if ($ok) { 157 $reason = $pattern_reason; 158 break; 159 } 160 } 161 162 if ($reason === $reason_format) { 163 throw new PhabricatorAuthSSHPrivateKeyFormatException( 164 pht( 165 'This private key is not formatted correctly. Check that you '. 166 'have provided the complete text of a valid private key.')); 167 } 168 169 if ($reason === $reason_passphrase) { 170 if ($passphrase) { 171 throw new PhabricatorAuthSSHPrivateKeyIncorrectPassphraseException( 172 pht( 173 'This private key requires a passphrase, but the wrong '. 174 'passphrase was provided. Check that you supplied the correct '. 175 'key and passphrase.')); 176 } else { 177 throw new PhabricatorAuthSSHPrivateKeyIncorrectPassphraseException( 178 pht( 179 'This private key requires a passphrase, but no passphrase was '. 180 'provided. Check that you supplied the correct key, or provide '. 181 'the passphrase.')); 182 } 183 } 184 185 if ($passphrase) { 186 throw new PhabricatorAuthSSHPrivateKeyUnknownException( 187 pht( 188 'This private key could not be opened with the provided passphrase. '. 189 'This might mean that the passphrase is wrong or that the key is '. 190 'not formatted correctly. Check that you have supplied the '. 191 'complete text of a valid private key and the correct passphrase.')); 192 } else { 193 throw new PhabricatorAuthSSHPrivateKeyUnknownException( 194 pht( 195 'This private key could not be opened. This might mean that the '. 196 'key requires a passphrase, or might mean that the key is not '. 197 'formatted correctly. Check that you have supplied the complete '. 198 'text of a valid private key and the correct passphrase.')); 199 } 200 } 201 202 private function newTemporaryPrivateKeyFile(PhutilOpaqueEnvelope $key_body) { 203 $tmp = new TempFile(); 204 205 Filesystem::writeFile($tmp, $key_body->openEnvelope()); 206 207 return $tmp; 208 } 209 210}