.htaccess
1 year ago
JWK.php
1 year ago
OpenSSH.php
1 year ago
PKCS.php
1 year ago
PKCS1.php
1 year ago
PKCS8.php
1 year ago
PuTTY.php
1 year ago
index.html
1 year ago
web.config
1 year ago
OpenSSH.php
214 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * OpenSSH Key Handler |
| 5 | * |
| 6 | * PHP version 5 |
| 7 | * |
| 8 | * Place in $HOME/.ssh/authorized_keys |
| 9 | * |
| 10 | * @author Jim Wigginton <terrafrost@php.net> |
| 11 | * @copyright 2015 Jim Wigginton |
| 12 | * @license http://www.opensource.org/licenses/mit-license.html MIT License |
| 13 | * @link http://phpseclib.sourceforge.net |
| 14 | */ |
| 15 | |
| 16 | declare(strict_types=1); |
| 17 | |
| 18 | namespace phpseclib3\Crypt\Common\Formats\Keys; |
| 19 | |
| 20 | use phpseclib3\Common\Functions\Strings; |
| 21 | use phpseclib3\Crypt\AES; |
| 22 | use phpseclib3\Crypt\Random; |
| 23 | use phpseclib3\Exception\RuntimeException; |
| 24 | use phpseclib3\Exception\UnexpectedValueException; |
| 25 | |
| 26 | /** |
| 27 | * OpenSSH Formatted RSA Key Handler |
| 28 | * |
| 29 | * @author Jim Wigginton <terrafrost@php.net> |
| 30 | */ |
| 31 | abstract class OpenSSH |
| 32 | { |
| 33 | /** |
| 34 | * Default comment |
| 35 | * |
| 36 | * @var string |
| 37 | */ |
| 38 | protected static $comment = 'phpseclib-generated-key'; |
| 39 | |
| 40 | /** |
| 41 | * Binary key flag |
| 42 | * |
| 43 | * @var bool |
| 44 | */ |
| 45 | protected static $binary = false; |
| 46 | |
| 47 | /** |
| 48 | * Sets the default comment |
| 49 | */ |
| 50 | public static function setComment(string $comment): void |
| 51 | { |
| 52 | self::$comment = str_replace(["\r", "\n"], '', $comment); |
| 53 | } |
| 54 | |
| 55 | /** |
| 56 | * Break a public or private key down into its constituent components |
| 57 | * |
| 58 | * $type can be either ssh-dss or ssh-rsa |
| 59 | * |
| 60 | * @param string|array $key |
| 61 | */ |
| 62 | public static function load($key, ?string $password = null): array |
| 63 | { |
| 64 | if (!Strings::is_stringable($key)) { |
| 65 | throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key)); |
| 66 | } |
| 67 | |
| 68 | // key format is described here: |
| 69 | // https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD |
| 70 | |
| 71 | if (str_contains($key, 'BEGIN OPENSSH PRIVATE KEY')) { |
| 72 | $key = preg_replace('#(?:^-.*?-[\r\n]*$)|\s#ms', '', $key); |
| 73 | $key = Strings::base64_decode($key); |
| 74 | $magic = Strings::shift($key, 15); |
| 75 | if ($magic != "openssh-key-v1\0") { |
| 76 | throw new RuntimeException('Expected openssh-key-v1'); |
| 77 | } |
| 78 | [$ciphername, $kdfname, $kdfoptions, $numKeys] = Strings::unpackSSH2('sssN', $key); |
| 79 | if ($numKeys != 1) { |
| 80 | // if we wanted to support multiple keys we could update PublicKeyLoader to preview what the # of keys |
| 81 | // would be; it'd then call Common\Keys\OpenSSH.php::load() and get the paddedKey. it'd then pass |
| 82 | // that to the appropriate key loading parser $numKey times or something |
| 83 | throw new RuntimeException('Although the OpenSSH private key format supports multiple keys phpseclib does not'); |
| 84 | } |
| 85 | |
| 86 | switch ($ciphername) { |
| 87 | case 'none': |
| 88 | break; |
| 89 | case 'aes256-ctr': |
| 90 | if ($kdfname != 'bcrypt') { |
| 91 | throw new RuntimeException('Only the bcrypt kdf is supported (' . $kdfname . ' encountered)'); |
| 92 | } |
| 93 | [$salt, $rounds] = Strings::unpackSSH2('sN', $kdfoptions); |
| 94 | $crypto = new AES('ctr'); |
| 95 | //$crypto->setKeyLength(256); |
| 96 | //$crypto->disablePadding(); |
| 97 | $crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32); |
| 98 | break; |
| 99 | default: |
| 100 | throw new RuntimeException('The only supported cipherse are: none, aes256-ctr (' . $ciphername . ' is being used)'); |
| 101 | } |
| 102 | |
| 103 | [$publicKey, $paddedKey] = Strings::unpackSSH2('ss', $key); |
| 104 | [$type] = Strings::unpackSSH2('s', $publicKey); |
| 105 | if (isset($crypto)) { |
| 106 | $paddedKey = $crypto->decrypt($paddedKey); |
| 107 | } |
| 108 | [$checkint1, $checkint2] = Strings::unpackSSH2('NN', $paddedKey); |
| 109 | // any leftover bytes in $paddedKey are for padding? but they should be sequential bytes. eg. 1, 2, 3, etc. |
| 110 | if ($checkint1 != $checkint2) { |
| 111 | throw new RuntimeException('The two checkints do not match'); |
| 112 | } |
| 113 | self::checkType($type); |
| 114 | |
| 115 | return compact('type', 'publicKey', 'paddedKey'); |
| 116 | } |
| 117 | |
| 118 | $parts = explode(' ', $key, 3); |
| 119 | |
| 120 | if (!isset($parts[1])) { |
| 121 | $key = base64_decode($parts[0]); |
| 122 | $comment = false; |
| 123 | } else { |
| 124 | $asciiType = $parts[0]; |
| 125 | self::checkType($parts[0]); |
| 126 | $key = base64_decode($parts[1]); |
| 127 | $comment = $parts[2] ?? false; |
| 128 | } |
| 129 | if ($key === false) { |
| 130 | throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key)); |
| 131 | } |
| 132 | |
| 133 | [$type] = Strings::unpackSSH2('s', $key); |
| 134 | self::checkType($type); |
| 135 | if (isset($asciiType) && $asciiType != $type) { |
| 136 | throw new RuntimeException('Two different types of keys are claimed: ' . $asciiType . ' and ' . $type); |
| 137 | } |
| 138 | if (strlen($key) <= 4) { |
| 139 | throw new UnexpectedValueException('Key appears to be malformed'); |
| 140 | } |
| 141 | |
| 142 | $publicKey = $key; |
| 143 | |
| 144 | return compact('type', 'publicKey', 'comment'); |
| 145 | } |
| 146 | |
| 147 | /** |
| 148 | * Toggle between binary and printable keys |
| 149 | * |
| 150 | * Printable keys are what are generated by default. These are the ones that go in |
| 151 | * $HOME/.ssh/authorized_key. |
| 152 | */ |
| 153 | public static function setBinaryOutput(bool $enabled): void |
| 154 | { |
| 155 | self::$binary = $enabled; |
| 156 | } |
| 157 | |
| 158 | /** |
| 159 | * Checks to see if the type is valid |
| 160 | */ |
| 161 | private static function checkType(string $candidate): void |
| 162 | { |
| 163 | if (!in_array($candidate, static::$types)) { |
| 164 | throw new RuntimeException("The key type ($candidate) is not equal to: " . implode(',', static::$types)); |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | /** |
| 169 | * Wrap a private key appropriately |
| 170 | * |
| 171 | * @param string|false $password |
| 172 | */ |
| 173 | protected static function wrapPrivateKey(string $publicKey, string $privateKey, $password, array $options): string |
| 174 | { |
| 175 | [, $checkint] = unpack('N', Random::string(4)); |
| 176 | |
| 177 | $comment = $options['comment'] ?? self::$comment; |
| 178 | $paddedKey = Strings::packSSH2('NN', $checkint, $checkint) . |
| 179 | $privateKey . |
| 180 | Strings::packSSH2('s', $comment); |
| 181 | |
| 182 | $usesEncryption = !empty($password) && is_string($password); |
| 183 | |
| 184 | /* |
| 185 | from http://tools.ietf.org/html/rfc4253#section-6 : |
| 186 | |
| 187 | Note that the length of the concatenation of 'packet_length', |
| 188 | 'padding_length', 'payload', and 'random padding' MUST be a multiple |
| 189 | of the cipher block size or 8, whichever is larger. |
| 190 | */ |
| 191 | $blockSize = $usesEncryption ? 16 : 8; |
| 192 | $paddingLength = (($blockSize - 1) * strlen($paddedKey)) % $blockSize; |
| 193 | for ($i = 1; $i <= $paddingLength; $i++) { |
| 194 | $paddedKey .= chr($i); |
| 195 | } |
| 196 | if (!$usesEncryption) { |
| 197 | $key = Strings::packSSH2('sssNss', 'none', 'none', '', 1, $publicKey, $paddedKey); |
| 198 | } else { |
| 199 | $rounds = $options['rounds'] ?? 16; |
| 200 | $salt = Random::string(16); |
| 201 | $kdfoptions = Strings::packSSH2('sN', $salt, $rounds); |
| 202 | $crypto = new AES('ctr'); |
| 203 | $crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32); |
| 204 | $paddedKey = $crypto->encrypt($paddedKey); |
| 205 | $key = Strings::packSSH2('sssNss', 'aes256-ctr', 'bcrypt', $kdfoptions, 1, $publicKey, $paddedKey); |
| 206 | } |
| 207 | $key = "openssh-key-v1\0$key"; |
| 208 | |
| 209 | return "-----BEGIN OPENSSH PRIVATE KEY-----\n" . |
| 210 | chunk_split(Strings::base64_encode($key), 70, "\n") . |
| 211 | "-----END OPENSSH PRIVATE KEY-----\n"; |
| 212 | } |
| 213 | } |
| 214 |