26 public static function loadPublic($data)
28 if (!is_string($data)) {
29 throw new \InvalidArgumentException();
33 while (strpos($data,
' ') !==
false) {
34 $data = str_replace(
' ',
' ', $data);
37 $algos = \fpoirotte\pssht\Algorithms::factory();
40 $fields = explode(
' ', $data, 3);
41 if (count($fields) < 2) {
42 throw new \InvalidArgumentException($data);
44 $type = strtolower($fields[0]);
45 $cls = $algos->getClass(
'Key', $type);
49 while ($data !==
'') {
50 $pos = strcspn($data,
' "');
51 $skipped = substr($data, 0, $pos);
53 $data = (string) substr($data, $pos + 1);
56 throw new \InvalidArgumentException();
64 $pos = strpos($data,
'"');
66 throw new \InvalidArgumentException();
68 $data = (string) substr($data, $pos + 1);
71 throw new \InvalidArgumentException();
75 $fields = explode(
' ', $data, 3);
76 if (count($fields) < 2) {
77 throw new \InvalidArgumentException();
80 $type = strtolower($fields[0]);
81 $cls = $algos->getClass(
'Key', $type);
85 throw new \InvalidArgumentException();
88 $key = base64_decode($fields[1]);
90 throw new \InvalidArgumentException();
93 $decoder = new \fpoirotte\Pssht\Wire\Decoder();
94 $decoder->getBuffer()->push($key);
97 if ($decoder->decodeString() !== $type) {
98 throw new \InvalidArgumentException();
100 return $cls::unserialize($decoder);
103 public static function loadPrivate($data, $passphrase =
'')
105 if (!is_string($data)) {
106 throw new \InvalidArgumentException();
109 if (!is_string($passphrase)) {
110 throw new \InvalidArgumentException();
113 $key = openssl_pkey_get_private($data, $passphrase);
115 if ($key !==
false) {
116 $details = openssl_pkey_get_details($key);
119 if ($key ===
false || $details ===
false || $details[
'type'] === -1) {
127 return self::parseUnknown(
133 $algos = \fpoirotte\pssht\Algorithms::factory();
134 switch ($details[
'type']) {
135 case OPENSSL_KEYTYPE_EC:
142 return self::parseECDSA($data, $passphrase);
144 case OPENSSL_KEYTYPE_RSA:
145 $cls = $algos->getClass(
'Key',
'ssh-rsa');
147 gmp_init(bin2hex($details[
'rsa'][
'n']), 16),
148 gmp_init(bin2hex($details[
'rsa'][
'e']), 16),
149 gmp_init(bin2hex($details[
'rsa'][
'd']), 16)
152 case OPENSSL_KEYTYPE_DSA:
153 $cls = $algos->getClass(
'Key',
'ssh-dss');
155 gmp_init(bin2hex($details[
'dsa'][
'p']), 16),
156 gmp_init(bin2hex($details[
'dsa'][
'q']), 16),
157 gmp_init(bin2hex($details[
'dsa'][
'g']), 16),
158 gmp_init(bin2hex($details[
'dsa'][
'pub_key']), 16),
159 gmp_init(bin2hex($details[
'dsa'][
'priv_key']), 16)
163 throw new \InvalidArgumentException(
'Invalid key');
183 return self::parseOpensshPrivateKey($data, $passphrase);
203 if ($passphrase !==
'') {
204 throw new \RuntimeException();
208 $type =
'ssh-ed25519';
210 $header =
'-----BEGIN OPENSSH PRIVATE KEY-----';
211 $footer =
'-----END OPENSSH PRIVATE KEY-----';
213 if (strncmp($data, $header, strlen($header)) !== 0) {
214 throw new \InvalidArgumentException();
215 } elseif (substr($data, -strlen($footer)) !== $footer) {
216 throw new \InvalidArgumentException();
218 $key = base64_decode(substr($data, strlen($header), -strlen($footer)));
220 if (strncmp($key, static::AUTH_MAGIC, strlen(static::AUTH_MAGIC))) {
221 throw new \InvalidArgumentException();
224 $decoder = new \fpoirotte\Pssht\Wire\Decoder();
225 $decoder->getBuffer()->push(substr($key, strlen(static::AUTH_MAGIC)));
227 $ciphername = $decoder->decodeString();
230 if ($ciphername !==
'none') {
231 throw new \InvalidArgumentException();
234 $kdfname = $decoder->decodeString();
235 $kdfoptions = $decoder->decodeString();
237 $numKeys = $decoder->decodeUint32();
238 $publicKeys = array();
241 if ($numKeys <= 0 || $numKeys >= 0x80000000) {
242 throw new \InvalidArgumentException();
245 for ($i = 0; $i < $numKeys; $i++) {
246 $tmp = new \fpoirotte\Pssht\Wire\Decoder();
247 $tmp->getBuffer()->push($decoder->decodeString());
250 if ($tmp->decodeString() !== $type) {
254 $publicKeys[$i] = $tmp->decodeString();
257 $decoder->getBuffer()->push($decoder->decodeString());
260 if ($decoder->decodeUint32() !== $decoder->decodeUint32()) {
261 throw new \InvalidArgumentException();
265 if ($decoder->decodeString() !== $type) {
266 throw new \InvalidArgumentException();
270 $decoder->decodeString();
272 $secretKeys = array();
273 for ($i = 0; $i < $numKeys; $i++) {
274 $secretKeys[$i] = $decoder->decodeString();
276 $tmp->decodeString();
282 $pk = reset($publicKeys);
283 if (!isset($secretKeys[key($publicKeys)])) {
284 throw new \InvalidArgumentException();
286 $sk = $secretKeys[key($publicKeys)];
288 $algos = \fpoirotte\pssht\Algorithms::factory();
289 $cls = $algos->getClass(
'Key',
'ssh-ed25519');
291 return new $cls($pk, substr($sk, 0, 32));
294 private static function parseECDSA($data, $passphrase)
296 if ($passphrase !==
'') {
297 throw new \RuntimeException();
300 $key = str_replace(array(
"\r",
"\n"),
'', $data);
301 $header =
'-----BEGIN EC PRIVATE KEY-----';
302 $footer =
'-----END EC PRIVATE KEY-----';
303 if (strncmp($key, $header, strlen($header)) !== 0) {
304 throw new \InvalidArgumentException();
305 } elseif (substr($key, -strlen($footer)) !== $footer) {
306 throw new \InvalidArgumentException();
308 $key = base64_decode(substr($key, strlen($header), -strlen($footer)));
310 if ($key ===
false || strncmp($key,
"\x30\x77\x02\x01\x01\x04", 6) !== 0) {
311 throw new \InvalidArgumentException();
313 $key = substr($key, 6);
316 $privkey = gmp_init(bin2hex(substr($key, 1, $len)), 16);
317 $key = substr($key, $len + 1);
319 if ($key[0] !==
"\xA0" || $key[2] !==
"\x06") {
320 throw new \InvalidArgumentException();
323 if ($len + 2 !== ord($key[1])) {
324 throw new \InvalidArgumentException();
326 $oid = substr($key, 4, $len);
327 $key = substr($key, $len + 4);
329 if ($key[0] !==
"\xA1" || $key[2] !==
"\x03") {
330 throw new \InvalidArgumentException();
333 if ($len + 2 !== ord($key[1]) || strlen($key) !== $len + 4) {
334 throw new \InvalidArgumentException();
340 self::encodeOID(
'1.2.840.10045.3.1.7') =>
'nistp256',
341 self::encodeOID(
'1.3.132.0.34') =>
'nistp384',
342 self::encodeOID(
'1.3.132.0.35') =>
'nistp521',
344 if (!isset($curves[$oid])) {
345 throw new \InvalidArgumentException();
347 $curve = \fpoirotte\Pssht\ECC\Curve::getCurve($curves[$oid]);
348 $pubkey = \fpoirotte\Pssht\ECC\Point::unserialize(
350 ltrim(substr($key, 4),
"\x00")
352 $pubkey2 = $curve->getGenerator()->multiply($curve, $privkey);
354 if (gmp_strval($pubkey->x) !== gmp_strval($pubkey2->x) ||
355 gmp_strval($pubkey->y) !== gmp_strval($pubkey2->y)) {
356 throw new \InvalidArgumentException();
359 $algos = \fpoirotte\pssht\Algorithms::factory();
360 $cls = $algos->getClass(
'Key',
'ecdsa-sha2-' . $curves[$oid]);
361 return new $cls($pubkey, $privkey);
379 if (strspn($oid,
'1234567890.') !== strlen($oid)) {
380 throw new \InvalidArgumentException();
383 $parts = explode(
'.', trim($oid,
'.'));
384 $root = ((int) array_shift($parts)) * 40;
385 $root += (int) array_shift($parts);
388 foreach ($parts as $part) {
390 throw new \InvalidArgumentException();
394 if ($nb >= 0 && $nb < 128) {
399 $part = gmp_strval(gmp_init($part, 10), 2);
400 $len = (int) ((strlen($part) + 6) / 7);
401 $part = str_split(str_pad($part, $len * 7,
'0', STR_PAD_LEFT), 7);
402 foreach ($part as $index => $bits) {
403 $res .= chr((($index + 1 < $len) << 7) + bindec($bits));
static parseOpensshPrivateKey($data, $passphrase)
const AUTH_MAGIC
Magic value used to identity OpenSSH private keys.
static parseUnknown($data, $passphrase)