Mercurial > 510Connectbot
annotate app/src/main/java/com/five_ten_sg/connectbot/util/PubkeyUtils.java @ 526:9a8862b87f6e stable-1.9.4-8
bump version
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Sat, 01 Jun 2024 10:27:22 -0600 |
parents | d29cce60f393 |
children |
rev | line source |
---|---|
0 | 1 /* |
2 * ConnectBot: simple, powerful, open-source SSH client for Android | |
3 * Copyright 2007 Kenny Root, Jeffrey Sharkey | |
4 * | |
5 * Licensed under the Apache License, Version 2.0 (the "License"); | |
6 * you may not use this file except in compliance with the License. | |
7 * You may obtain a copy of the License at | |
8 * | |
9 * http://www.apache.org/licenses/LICENSE-2.0 | |
10 * | |
11 * Unless required by applicable law or agreed to in writing, software | |
12 * distributed under the License is distributed on an "AS IS" BASIS, | |
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 * See the License for the specific language governing permissions and | |
15 * limitations under the License. | |
16 */ | |
17 | |
18 package com.five_ten_sg.connectbot.util; | |
19 | |
20 import java.io.IOException; | |
21 import java.math.BigInteger; | |
22 import java.security.AlgorithmParameters; | |
23 import java.security.InvalidAlgorithmParameterException; | |
24 import java.security.InvalidKeyException; | |
25 import java.security.Key; | |
26 import java.security.KeyFactory; | |
27 import java.security.KeyPair; | |
28 import java.security.MessageDigest; | |
29 import java.security.NoSuchAlgorithmException; | |
30 import java.security.PrivateKey; | |
31 import java.security.PublicKey; | |
32 import java.security.interfaces.DSAParams; | |
33 import java.security.interfaces.DSAPrivateKey; | |
34 import java.security.interfaces.DSAPublicKey; | |
35 import java.security.interfaces.ECPrivateKey; | |
36 import java.security.interfaces.ECPublicKey; | |
37 import java.security.interfaces.RSAPrivateCrtKey; | |
38 import java.security.interfaces.RSAPublicKey; | |
39 import java.security.spec.DSAPublicKeySpec; | |
40 import java.security.spec.ECParameterSpec; | |
41 import java.security.spec.ECPoint; | |
42 import java.security.spec.ECPublicKeySpec; | |
43 import java.security.spec.InvalidKeySpecException; | |
44 import java.security.spec.InvalidParameterSpecException; | |
45 import java.security.spec.KeySpec; | |
46 import java.security.spec.PKCS8EncodedKeySpec; | |
47 import java.security.spec.RSAPublicKeySpec; | |
48 import java.security.spec.X509EncodedKeySpec; | |
49 import java.util.Arrays; | |
50 | |
51 import javax.crypto.BadPaddingException; | |
52 import javax.crypto.Cipher; | |
53 import javax.crypto.EncryptedPrivateKeyInfo; | |
54 import javax.crypto.IllegalBlockSizeException; | |
55 import javax.crypto.NoSuchPaddingException; | |
56 import javax.crypto.SecretKeyFactory; | |
57 import javax.crypto.spec.PBEKeySpec; | |
58 import javax.crypto.spec.PBEParameterSpec; | |
59 import javax.crypto.spec.SecretKeySpec; | |
60 | |
61 import org.keyczar.jce.EcCore; | |
62 | |
63 import com.five_ten_sg.connectbot.bean.PubkeyBean; | |
64 import android.util.Log; | |
65 | |
273
91a31873c42a
start conversion from trilead to ganymed
Carl Byington <carl@five-ten-sg.com>
parents:
0
diff
changeset
|
66 import ch.ethz.ssh2.crypto.Base64; |
344
b40bc65fa09a
compensate for SecureRandom bug on older devices
Carl Byington <carl@five-ten-sg.com>
parents:
273
diff
changeset
|
67 import ch.ethz.ssh2.crypto.SecureRandomFix; |
273
91a31873c42a
start conversion from trilead to ganymed
Carl Byington <carl@five-ten-sg.com>
parents:
0
diff
changeset
|
68 import ch.ethz.ssh2.crypto.SimpleDERReader; |
91a31873c42a
start conversion from trilead to ganymed
Carl Byington <carl@five-ten-sg.com>
parents:
0
diff
changeset
|
69 import ch.ethz.ssh2.signature.DSASHA1Verify; |
91a31873c42a
start conversion from trilead to ganymed
Carl Byington <carl@five-ten-sg.com>
parents:
0
diff
changeset
|
70 import ch.ethz.ssh2.signature.ECDSASHA2Verify; |
91a31873c42a
start conversion from trilead to ganymed
Carl Byington <carl@five-ten-sg.com>
parents:
0
diff
changeset
|
71 import ch.ethz.ssh2.signature.RSASHA1Verify; |
0 | 72 |
73 public class PubkeyUtils { | |
74 private static final String TAG = "PubkeyUtils"; | |
75 | |
76 public static final String PKCS8_START = "-----BEGIN PRIVATE KEY-----"; | |
77 public static final String PKCS8_END = "-----END PRIVATE KEY-----"; | |
78 | |
79 // Size in bytes of salt to use. | |
80 private static final int SALT_SIZE = 8; | |
81 | |
82 // Number of iterations for password hashing. PKCS#5 recommends 1000 | |
83 private static final int ITERATIONS = 1000; | |
84 | |
85 // Cannot be instantiated | |
86 private PubkeyUtils() { | |
87 } | |
88 | |
89 public static String formatKey(Key key) { | |
90 String algo = key.getAlgorithm(); | |
91 String fmt = key.getFormat(); | |
92 byte[] encoded = key.getEncoded(); | |
93 return "Key[algorithm=" + algo + ", format=" + fmt + | |
94 ", bytes=" + encoded.length + "]"; | |
95 } | |
96 | |
97 public static byte[] sha256(byte[] data) throws NoSuchAlgorithmException { | |
98 return MessageDigest.getInstance("SHA-256").digest(data); | |
99 } | |
100 | |
101 public static byte[] cipher(int mode, byte[] data, byte[] secret) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { | |
102 SecretKeySpec secretKeySpec = new SecretKeySpec(sha256(secret), "AES"); | |
103 Cipher c = Cipher.getInstance("AES"); | |
104 c.init(mode, secretKeySpec); | |
105 return c.doFinal(data); | |
106 } | |
107 | |
108 public static byte[] encrypt(byte[] cleartext, String secret) throws Exception { | |
109 byte[] salt = new byte[SALT_SIZE]; | |
110 byte[] ciphertext = Encryptor.encrypt(salt, ITERATIONS, secret, cleartext); | |
111 byte[] complete = new byte[salt.length + ciphertext.length]; | |
112 System.arraycopy(salt, 0, complete, 0, salt.length); | |
113 System.arraycopy(ciphertext, 0, complete, salt.length, ciphertext.length); | |
114 Arrays.fill(salt, (byte) 0x00); | |
115 Arrays.fill(ciphertext, (byte) 0x00); | |
116 return complete; | |
117 } | |
118 | |
119 public static byte[] decrypt(byte[] saltAndCiphertext, String secret) throws Exception { | |
120 try { | |
121 byte[] salt = new byte[SALT_SIZE]; | |
122 byte[] ciphertext = new byte[saltAndCiphertext.length - salt.length]; | |
123 System.arraycopy(saltAndCiphertext, 0, salt, 0, salt.length); | |
124 System.arraycopy(saltAndCiphertext, salt.length, ciphertext, 0, ciphertext.length); | |
125 return Encryptor.decrypt(salt, ITERATIONS, secret, ciphertext); | |
126 } | |
127 catch (Exception e) { | |
128 Log.d("decrypt", "Could not decrypt with new method", e); | |
129 // We might be using the old encryption method. | |
130 return cipher(Cipher.DECRYPT_MODE, saltAndCiphertext, secret.getBytes()); | |
131 } | |
132 } | |
133 | |
134 public static byte[] getEncodedPrivate(PrivateKey pk, String secret) throws Exception { | |
135 final byte[] encoded = pk.getEncoded(); | |
136 | |
137 if (secret == null || secret.length() == 0) { | |
138 return encoded; | |
139 } | |
140 | |
141 return encrypt(pk.getEncoded(), secret); | |
142 } | |
143 | |
144 public static PrivateKey decodePrivate(byte[] encoded, String keyType) throws NoSuchAlgorithmException, InvalidKeySpecException { | |
145 PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(encoded); | |
146 KeyFactory kf = KeyFactory.getInstance(keyType); | |
147 return kf.generatePrivate(privKeySpec); | |
148 } | |
149 | |
150 public static PrivateKey decodePrivate(byte[] encoded, String keyType, String secret) throws Exception { | |
151 if (secret != null && secret.length() > 0) | |
152 return decodePrivate(decrypt(encoded, secret), keyType); | |
153 else | |
154 return decodePrivate(encoded, keyType); | |
155 } | |
156 | |
157 public static PublicKey decodePublic(byte[] encoded, String keyType) throws NoSuchAlgorithmException, InvalidKeySpecException { | |
158 X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encoded); | |
159 KeyFactory kf = KeyFactory.getInstance(keyType); | |
160 return kf.generatePublic(pubKeySpec); | |
161 } | |
162 | |
163 static String getAlgorithmForOid(String oid) throws NoSuchAlgorithmException { | |
164 if ("1.2.840.10045.2.1".equals(oid)) { | |
165 return "EC"; | |
166 } | |
167 else if ("1.2.840.113549.1.1.1".equals(oid)) { | |
168 return "RSA"; | |
169 } | |
170 else if ("1.2.840.10040.4.1".equals(oid)) { | |
171 return "DSA"; | |
172 } | |
173 else { | |
174 throw new NoSuchAlgorithmException("Unknown algorithm OID " + oid); | |
175 } | |
176 } | |
177 | |
178 static String getOidFromPkcs8Encoded(byte[] encoded) throws NoSuchAlgorithmException { | |
179 if (encoded == null) { | |
180 throw new NoSuchAlgorithmException("encoding is null"); | |
181 } | |
182 | |
183 try { | |
184 SimpleDERReader reader = new SimpleDERReader(encoded); | |
185 reader.resetInput(reader.readSequenceAsByteArray()); | |
186 reader.readInt(); | |
187 reader.resetInput(reader.readSequenceAsByteArray()); | |
188 return reader.readOid(); | |
189 } | |
190 catch (IOException e) { | |
191 Log.w(TAG, "Could not read OID", e); | |
192 throw new NoSuchAlgorithmException("Could not read key", e); | |
193 } | |
194 } | |
195 | |
196 public static KeyPair recoverKeyPair(byte[] encoded) throws NoSuchAlgorithmException, | |
197 InvalidKeySpecException { | |
198 final String algo = getAlgorithmForOid(getOidFromPkcs8Encoded(encoded)); | |
199 final KeySpec privKeySpec = new PKCS8EncodedKeySpec(encoded); | |
200 final KeyFactory kf = KeyFactory.getInstance(algo); | |
201 final PrivateKey priv = kf.generatePrivate(privKeySpec); | |
202 return new KeyPair(recoverPublicKey(kf, priv), priv); | |
203 } | |
204 | |
205 | |
206 static PublicKey recoverPublicKey(KeyFactory kf, PrivateKey priv) | |
207 throws NoSuchAlgorithmException, InvalidKeySpecException { | |
208 if (priv instanceof RSAPrivateCrtKey) { | |
209 RSAPrivateCrtKey rsaPriv = (RSAPrivateCrtKey) priv; | |
210 return kf.generatePublic(new RSAPublicKeySpec(rsaPriv.getModulus(), rsaPriv | |
211 .getPublicExponent())); | |
212 } | |
213 else if (priv instanceof DSAPrivateKey) { | |
214 DSAPrivateKey dsaPriv = (DSAPrivateKey) priv; | |
215 DSAParams params = dsaPriv.getParams(); | |
216 // Calculate public key Y | |
217 BigInteger y = params.getG().modPow(dsaPriv.getX(), params.getP()); | |
218 return kf.generatePublic(new DSAPublicKeySpec(y, params.getP(), params.getQ(), params | |
219 .getG())); | |
220 } | |
221 else if (priv instanceof ECPrivateKey) { | |
222 ECPrivateKey ecPriv = (ECPrivateKey) priv; | |
223 ECParameterSpec params = ecPriv.getParams(); | |
224 // Calculate public key Y | |
225 ECPoint generator = params.getGenerator(); | |
226 BigInteger[] wCoords = EcCore.multiplyPointA(new BigInteger[] { generator.getAffineX(), | |
227 generator.getAffineY() | |
228 }, ecPriv.getS(), params); | |
229 ECPoint w = new ECPoint(wCoords[0], wCoords[1]); | |
230 return kf.generatePublic(new ECPublicKeySpec(w, params)); | |
231 } | |
232 else { | |
233 throw new NoSuchAlgorithmException("Key type must be RSA, DSA, or EC"); | |
234 } | |
235 } | |
236 | |
237 /* | |
238 * OpenSSH compatibility methods | |
239 */ | |
240 | |
241 public static String convertToOpenSSHFormat(PublicKey pk, String origNickname) throws IOException, InvalidKeyException { | |
242 String nickname = origNickname; | |
243 | |
244 if (nickname == null) | |
245 nickname = "connectbot@android"; | |
246 | |
247 if (pk instanceof RSAPublicKey) { | |
248 String data = "ssh-rsa "; | |
249 data += String.valueOf(Base64.encode(RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey) pk))); | |
250 return data + " " + nickname; | |
251 } | |
252 else if (pk instanceof DSAPublicKey) { | |
253 String data = "ssh-dss "; | |
254 data += String.valueOf(Base64.encode(DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey) pk))); | |
255 return data + " " + nickname; | |
256 } | |
257 else if (pk instanceof ECPublicKey) { | |
258 ECPublicKey ecPub = (ECPublicKey) pk; | |
259 String keyType = ECDSASHA2Verify.getCurveName(ecPub.getParams().getCurve().getField().getFieldSize()); | |
260 String keyData = String.valueOf(Base64.encode(ECDSASHA2Verify.encodeSSHECDSAPublicKey(ecPub))); | |
261 return ECDSASHA2Verify.ECDSA_SHA2_PREFIX + keyType + " " + keyData + " " + nickname; | |
262 } | |
263 | |
264 throw new InvalidKeyException("Unknown key type"); | |
265 } | |
266 | |
267 /* | |
268 * OpenSSH compatibility methods | |
269 */ | |
270 | |
271 /** | |
272 * @param pair | |
273 * @return OpenSSH-encoded pubkey | |
274 */ | |
275 public static byte[] extractOpenSSHPublic(KeyPair pair) { | |
276 try { | |
277 PublicKey pubKey = pair.getPublic(); | |
278 | |
279 if (pubKey instanceof RSAPublicKey) { | |
280 return RSASHA1Verify.encodeSSHRSAPublicKey((RSAPublicKey) pair.getPublic()); | |
281 } | |
282 else if (pubKey instanceof DSAPublicKey) { | |
283 return DSASHA1Verify.encodeSSHDSAPublicKey((DSAPublicKey) pair.getPublic()); | |
284 } | |
285 else if (pubKey instanceof ECPublicKey) { | |
286 return ECDSASHA2Verify.encodeSSHECDSAPublicKey((ECPublicKey) pair.getPublic()); | |
287 } | |
288 else { | |
289 return null; | |
290 } | |
291 } | |
292 catch (IOException e) { | |
293 return null; | |
294 } | |
295 } | |
296 | |
297 public static String exportPEM(PrivateKey key, String secret) throws NoSuchAlgorithmException, InvalidParameterSpecException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, InvalidKeySpecException, IllegalBlockSizeException, IOException { | |
298 StringBuilder sb = new StringBuilder(); | |
299 byte[] data = key.getEncoded(); | |
300 sb.append(PKCS8_START); | |
301 sb.append('\n'); | |
302 | |
303 if (secret != null) { | |
304 byte[] salt = new byte[8]; | |
344
b40bc65fa09a
compensate for SecureRandom bug on older devices
Carl Byington <carl@five-ten-sg.com>
parents:
273
diff
changeset
|
305 SecureRandomFix random = new SecureRandomFix(); |
0 | 306 random.nextBytes(salt); |
307 PBEParameterSpec defParams = new PBEParameterSpec(salt, 1); | |
308 AlgorithmParameters params = AlgorithmParameters.getInstance(key.getAlgorithm()); | |
309 params.init(defParams); | |
310 PBEKeySpec pbeSpec = new PBEKeySpec(secret.toCharArray()); | |
311 SecretKeyFactory keyFact = SecretKeyFactory.getInstance(key.getAlgorithm()); | |
312 Cipher cipher = Cipher.getInstance(key.getAlgorithm()); | |
313 cipher.init(Cipher.WRAP_MODE, keyFact.generateSecret(pbeSpec), params); | |
314 byte[] wrappedKey = cipher.wrap(key); | |
315 EncryptedPrivateKeyInfo pinfo = new EncryptedPrivateKeyInfo(params, wrappedKey); | |
316 data = pinfo.getEncoded(); | |
317 sb.append("Proc-Type: 4,ENCRYPTED\n"); | |
318 sb.append("DEK-Info: DES-EDE3-CBC,"); | |
319 sb.append(encodeHex(salt)); | |
320 sb.append("\n\n"); | |
321 } | |
322 | |
323 int i = sb.length(); | |
324 sb.append(Base64.encode(data)); | |
325 | |
326 for (i += 63; i < sb.length(); i += 64) { | |
327 sb.insert(i, "\n"); | |
328 } | |
329 | |
330 sb.append('\n'); | |
331 sb.append(PKCS8_END); | |
332 sb.append('\n'); | |
333 return sb.toString(); | |
334 } | |
335 | |
336 private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', | |
337 '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' | |
338 }; | |
339 protected static String encodeHex(byte[] bytes) { | |
340 final char[] hex = new char[bytes.length * 2]; | |
341 int i = 0; | |
342 | |
343 for (byte b : bytes) { | |
344 hex[i++] = HEX_DIGITS[(b >> 4) & 0x0f]; | |
345 hex[i++] = HEX_DIGITS[b & 0x0f]; | |
346 } | |
347 | |
348 return String.valueOf(hex); | |
349 } | |
350 | |
351 public static String getPubkeyString(PubkeyBean pubkey) { | |
352 try { | |
353 PublicKey pk = decodePublic(pubkey.getPublicKey(), pubkey.getType()); | |
354 return convertToOpenSSHFormat(pk, pubkey.getNickname()); | |
355 } | |
356 catch (Exception e) { | |
357 e.printStackTrace(); | |
358 } | |
359 | |
360 return null; | |
361 } | |
362 | |
363 public static String getPrivkeyString(PubkeyBean pubkey, String passphrase) { | |
364 String data = null; | |
365 boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType()); | |
366 | |
367 if (imported) | |
368 try { | |
369 data = new String(pubkey.getPrivateKey()); | |
370 } | |
371 catch (Exception e) { | |
372 e.printStackTrace(); | |
373 } | |
374 else { | |
375 try { | |
376 PrivateKey pk = null; | |
377 | |
378 if (passphrase == null) | |
379 pk = decodePrivate(pubkey.getPrivateKey(), pubkey.getType()); | |
380 else | |
381 pk = decodePrivate(pubkey.getPrivateKey(), pubkey.getType(), passphrase); | |
382 | |
383 data = exportPEM(pk, passphrase); | |
384 } | |
385 catch (Exception e) { | |
386 e.printStackTrace(); | |
387 } | |
388 } | |
389 | |
390 return data; | |
391 } | |
392 } |