@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<?php
2
3/**
4 * Data structure representing a raw public key.
5 */
6final class PhabricatorAuthSSHPublicKey extends Phobject {
7
8 private $type;
9 private $body;
10 private $comment;
11
12 private function __construct() {
13 // <internal>
14 }
15
16 public static function newFromStoredKey(PhabricatorAuthSSHKey $key) {
17 $public_key = new PhabricatorAuthSSHPublicKey();
18 $public_key->type = $key->getKeyType();
19 $public_key->body = $key->getKeyBody();
20 $public_key->comment = $key->getKeyComment();
21
22 return $public_key;
23 }
24
25 public static function newFromRawKey($entire_key) {
26 $entire_key = trim($entire_key);
27 if (!strlen($entire_key)) {
28 throw new Exception(pht('No public key was provided.'));
29 }
30
31 $parts = str_replace("\n", '', $entire_key);
32
33 // The third field (the comment) can have spaces in it, so split this
34 // into a maximum of three parts.
35 $parts = preg_split('/\s+/', $parts, 3);
36
37 if (preg_match('/private\s*key/i', $entire_key)) {
38 // Try to give the user a better error message if it looks like
39 // they uploaded a private key.
40 throw new Exception(pht('Provide a public key, not a private key!'));
41 }
42
43 switch (count($parts)) {
44 case 1:
45 throw new Exception(
46 pht('Provided public key is not properly formatted.'));
47 case 2:
48 // Add an empty comment part.
49 $parts[] = '';
50 break;
51 case 3:
52 // This is the expected case.
53 break;
54 }
55
56 list($type, $body, $comment) = $parts;
57
58 // The only goal is to prevent user error by nonsense input.
59 // This is just a meaningful subset from 'ssh -Q key'.
60 $recognized_keys = array(
61 'ssh-dsa',
62 'ssh-dss',
63 'ssh-rsa',
64 'ssh-ed25519',
65 'sk-ssh-ed25519@openssh.com',
66 'sk-ecdsa-sha2-nistp256@openssh.com',
67 'ecdsa-sha2-nistp256',
68 'ecdsa-sha2-nistp384',
69 'ecdsa-sha2-nistp521',
70 );
71
72 if (!in_array($type, $recognized_keys)) {
73 $type_list = implode(', ', $recognized_keys);
74 throw new Exception(
75 pht(
76 'Public key type should be one of: %s',
77 $type_list));
78 }
79
80 $public_key = new PhabricatorAuthSSHPublicKey();
81 $public_key->type = $type;
82 $public_key->body = $body;
83 $public_key->comment = $comment;
84
85 return $public_key;
86 }
87
88 public function getType() {
89 return $this->type;
90 }
91
92 public function getBody() {
93 return $this->body;
94 }
95
96 public function getComment() {
97 return $this->comment;
98 }
99
100 public function getHash() {
101 $body = $this->getBody();
102 $body = trim($body);
103 $body = rtrim($body, '=');
104 return PhabricatorHash::digestForIndex($body);
105 }
106
107 public function getEntireKey() {
108 $key = $this->type.' '.$this->body;
109 if (strlen($this->comment)) {
110 $key = $key.' '.$this->comment;
111 }
112 return $key;
113 }
114
115 public function toPKCS8() {
116 $entire_key = $this->getEntireKey();
117 $cache_key = $this->getPKCS8CacheKey($entire_key);
118
119 $cache = PhabricatorCaches::getImmutableCache();
120 $pkcs8_key = $cache->getKey($cache_key);
121 if ($pkcs8_key) {
122 return $pkcs8_key;
123 }
124
125 $tmp = new TempFile();
126 Filesystem::writeFile($tmp, $this->getEntireKey());
127 try {
128 list($pkcs8_key) = execx(
129 'ssh-keygen -e -m PKCS8 -f %s',
130 $tmp);
131 } catch (CommandException $ex) {
132 unset($tmp);
133 throw new Exception(
134 pht(
135 'Failed to convert public key into PKCS8 format. If you are '.
136 'developing on OSX, you may be able to use `%s` '.
137 'to work around this issue. %s',
138 'bin/auth cache-pkcs8',
139 $ex->getMessage()),
140 0,
141 $ex);
142 }
143 unset($tmp);
144
145 $cache->setKey($cache_key, $pkcs8_key);
146
147 return $pkcs8_key;
148 }
149
150 public function forcePopulatePKCS8Cache($pkcs8_key) {
151 $entire_key = $this->getEntireKey();
152 $cache_key = $this->getPKCS8CacheKey($entire_key);
153
154 $cache = PhabricatorCaches::getImmutableCache();
155 $cache->setKey($cache_key, $pkcs8_key);
156 }
157
158 private function getPKCS8CacheKey($entire_key) {
159 return 'pkcs8:'.PhabricatorHash::digestForIndex($entire_key);
160 }
161
162}