[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
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 namespace phpseclib3\Crypt\Common\Formats\Keys; 17 18 use phpseclib3\Common\Functions\Strings; 19 use phpseclib3\Crypt\AES; 20 use phpseclib3\Crypt\Random; 21 22 /** 23 * OpenSSH Formatted RSA Key Handler 24 * 25 * @author Jim Wigginton <terrafrost@php.net> 26 */ 27 abstract class OpenSSH 28 { 29 /** 30 * Default comment 31 * 32 * @var string 33 */ 34 protected static $comment = 'phpseclib-generated-key'; 35 36 /** 37 * Binary key flag 38 * 39 * @var bool 40 */ 41 protected static $binary = false; 42 43 /** 44 * Sets the default comment 45 * 46 * @param string $comment 47 */ 48 public static function setComment($comment) 49 { 50 self::$comment = str_replace(["\r", "\n"], '', $comment); 51 } 52 53 /** 54 * Break a public or private key down into its constituent components 55 * 56 * $type can be either ssh-dss or ssh-rsa 57 * 58 * @param string $key 59 * @param string $password 60 * @return array 61 */ 62 public static function load($key, $password = '') 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 (strpos($key, 'BEGIN OPENSSH PRIVATE KEY') !== false) { 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 list($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 switch ($ciphername) { 86 case 'none': 87 break; 88 case 'aes256-ctr': 89 if ($kdfname != 'bcrypt') { 90 throw new \RuntimeException('Only the bcrypt kdf is supported (' . $kdfname . ' encountered)'); 91 } 92 list($salt, $rounds) = Strings::unpackSSH2('sN', $kdfoptions); 93 $crypto = new AES('ctr'); 94 //$crypto->setKeyLength(256); 95 //$crypto->disablePadding(); 96 $crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32); 97 break; 98 default: 99 throw new \RuntimeException('The only supported cipherse are: none, aes256-ctr (' . $ciphername . ' is being used)'); 100 } 101 102 list($publicKey, $paddedKey) = Strings::unpackSSH2('ss', $key); 103 list($type) = Strings::unpackSSH2('s', $publicKey); 104 if (isset($crypto)) { 105 $paddedKey = $crypto->decrypt($paddedKey); 106 } 107 list($checkint1, $checkint2) = Strings::unpackSSH2('NN', $paddedKey); 108 // any leftover bytes in $paddedKey are for padding? but they should be sequential bytes. eg. 1, 2, 3, etc. 109 if ($checkint1 != $checkint2) { 110 throw new \RuntimeException('The two checkints do not match'); 111 } 112 self::checkType($type); 113 114 return compact('type', 'publicKey', 'paddedKey'); 115 } 116 117 $parts = explode(' ', $key, 3); 118 119 if (!isset($parts[1])) { 120 $key = base64_decode($parts[0]); 121 $comment = false; 122 } else { 123 $asciiType = $parts[0]; 124 self::checkType($parts[0]); 125 $key = base64_decode($parts[1]); 126 $comment = isset($parts[2]) ? $parts[2] : false; 127 } 128 if ($key === false) { 129 throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key)); 130 } 131 132 list($type) = Strings::unpackSSH2('s', $key); 133 self::checkType($type); 134 if (isset($asciiType) && $asciiType != $type) { 135 throw new \RuntimeException('Two different types of keys are claimed: ' . $asciiType . ' and ' . $type); 136 } 137 if (strlen($key) <= 4) { 138 throw new \UnexpectedValueException('Key appears to be malformed'); 139 } 140 141 $publicKey = $key; 142 143 return compact('type', 'publicKey', 'comment'); 144 } 145 146 /** 147 * Toggle between binary and printable keys 148 * 149 * Printable keys are what are generated by default. These are the ones that go in 150 * $HOME/.ssh/authorized_key. 151 * 152 * @param bool $enabled 153 */ 154 public static function setBinaryOutput($enabled) 155 { 156 self::$binary = $enabled; 157 } 158 159 /** 160 * Checks to see if the type is valid 161 * 162 * @param string $candidate 163 */ 164 private static function checkType($candidate) 165 { 166 if (!in_array($candidate, static::$types)) { 167 throw new \RuntimeException("The key type ($candidate) is not equal to: " . implode(',', static::$types)); 168 } 169 } 170 171 /** 172 * Wrap a private key appropriately 173 * 174 * @param string $publicKey 175 * @param string $privateKey 176 * @param string $password 177 * @param array $options 178 * @return string 179 */ 180 protected static function wrapPrivateKey($publicKey, $privateKey, $password, $options) 181 { 182 list(, $checkint) = unpack('N', Random::string(4)); 183 184 $comment = isset($options['comment']) ? $options['comment'] : self::$comment; 185 $paddedKey = Strings::packSSH2('NN', $checkint, $checkint) . 186 $privateKey . 187 Strings::packSSH2('s', $comment); 188 189 $usesEncryption = !empty($password) && is_string($password); 190 191 /* 192 from http://tools.ietf.org/html/rfc4253#section-6 : 193 194 Note that the length of the concatenation of 'packet_length', 195 'padding_length', 'payload', and 'random padding' MUST be a multiple 196 of the cipher block size or 8, whichever is larger. 197 */ 198 $blockSize = $usesEncryption ? 16 : 8; 199 $paddingLength = (($blockSize - 1) * strlen($paddedKey)) % $blockSize; 200 for ($i = 1; $i <= $paddingLength; $i++) { 201 $paddedKey .= chr($i); 202 } 203 if (!$usesEncryption) { 204 $key = Strings::packSSH2('sssNss', 'none', 'none', '', 1, $publicKey, $paddedKey); 205 } else { 206 $rounds = isset($options['rounds']) ? $options['rounds'] : 16; 207 $salt = Random::string(16); 208 $kdfoptions = Strings::packSSH2('sN', $salt, $rounds); 209 $crypto = new AES('ctr'); 210 $crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32); 211 $paddedKey = $crypto->encrypt($paddedKey); 212 $key = Strings::packSSH2('sssNss', 'aes256-ctr', 'bcrypt', $kdfoptions, 1, $publicKey, $paddedKey); 213 } 214 $key = "openssh-key-v1\0$key"; 215 216 return "-----BEGIN OPENSSH PRIVATE KEY-----\n" . 217 chunk_split(Strings::base64_encode($key), 70, "\n") . 218 "-----END OPENSSH PRIVATE KEY-----\n"; 219 } 220 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body