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;
|
|
19
|
|
20 import java.io.ByteArrayOutputStream;
|
|
21 import java.io.File;
|
|
22 import java.io.FileInputStream;
|
|
23 import java.io.FileOutputStream;
|
|
24 import java.io.IOException;
|
|
25 import java.io.InputStream;
|
|
26 import java.security.KeyPair;
|
|
27 import java.security.PrivateKey;
|
|
28 import java.security.PublicKey;
|
|
29 import java.util.EventListener;
|
|
30 import java.util.List;
|
|
31
|
|
32 import com.five_ten_sg.connectbot.bean.PubkeyBean;
|
|
33 import com.five_ten_sg.connectbot.service.TerminalManager;
|
|
34 import com.five_ten_sg.connectbot.util.FileChooser;
|
|
35 import com.five_ten_sg.connectbot.util.FileChooserCallback;
|
|
36 import com.five_ten_sg.connectbot.util.PubkeyDatabase;
|
|
37 import com.five_ten_sg.connectbot.util.PubkeyUtils;
|
|
38 import android.app.AlertDialog;
|
|
39 import android.app.ListActivity;
|
|
40 import android.content.ComponentName;
|
|
41 import android.content.Context;
|
|
42 import android.content.DialogInterface;
|
|
43 import android.content.Intent;
|
|
44 import android.content.ServiceConnection;
|
|
45 import android.os.Bundle;
|
|
46 import android.os.Environment;
|
|
47 import android.os.IBinder;
|
|
48 import android.text.ClipboardManager;
|
|
49 import android.util.Log;
|
|
50 import android.view.ContextMenu;
|
|
51 import android.view.LayoutInflater;
|
|
52 import android.view.Menu;
|
|
53 import android.view.MenuItem;
|
|
54 import android.view.MenuItem.OnMenuItemClickListener;
|
|
55 import android.view.View;
|
|
56 import android.view.ViewGroup;
|
|
57 import android.widget.AdapterView;
|
|
58 import android.widget.AdapterView.OnItemClickListener;
|
|
59 import android.widget.ArrayAdapter;
|
|
60 import android.widget.EditText;
|
|
61 import android.widget.ImageView;
|
|
62 import android.widget.TableRow;
|
|
63 import android.widget.TextView;
|
|
64 import android.widget.Toast;
|
|
65
|
|
66 import com.trilead.ssh2.crypto.Base64;
|
|
67 import com.trilead.ssh2.crypto.PEMDecoder;
|
|
68 import com.trilead.ssh2.crypto.PEMStructure;
|
|
69
|
|
70 /**
|
|
71 * List public keys in database by nickname and describe their properties. Allow users to import,
|
|
72 * generate, rename, and delete key pairs.
|
|
73 *
|
|
74 * @author Kenny Root
|
|
75 */
|
|
76 public class PubkeyListActivity extends ListActivity implements EventListener, FileChooserCallback {
|
|
77
|
|
78 public final static String TAG = "ConnectBot.PubkeyListActivity";
|
|
79
|
|
80 private static final int MAX_KEYFILE_SIZE = 8192;
|
|
81 private static final int KEYTYPE_PUBLIC = 0;
|
|
82 private static final int KEYTYPE_PRIVATE = 1;
|
|
83
|
|
84 protected PubkeyDatabase pubkeydb;
|
|
85 private List<PubkeyBean> pubkeys;
|
|
86
|
|
87 protected ClipboardManager clipboard;
|
|
88
|
|
89 protected LayoutInflater inflater = null;
|
|
90
|
|
91 protected TerminalManager bound = null;
|
|
92
|
|
93 private MenuItem onstartToggle = null;
|
|
94 private MenuItem confirmUse = null;
|
|
95
|
|
96 private ServiceConnection connection = new ServiceConnection() {
|
|
97 public void onServiceConnected(ComponentName className, IBinder service) {
|
|
98 bound = ((TerminalManager.TerminalBinder) service).getService();
|
|
99 // update our listview binder to find the service
|
|
100 updateList();
|
|
101 }
|
|
102 public void onServiceDisconnected(ComponentName className) {
|
|
103 bound = null;
|
|
104 updateList();
|
|
105 }
|
|
106 };
|
|
107
|
|
108 @Override
|
|
109 public void onStart() {
|
|
110 super.onStart();
|
|
111 bindService(new Intent(this, TerminalManager.class), connection, Context.BIND_AUTO_CREATE);
|
|
112
|
|
113 if (pubkeydb == null)
|
|
114 pubkeydb = new PubkeyDatabase(this);
|
|
115 }
|
|
116
|
|
117 @Override
|
|
118 public void onStop() {
|
|
119 super.onStop();
|
|
120 unbindService(connection);
|
|
121
|
|
122 if (pubkeydb != null) {
|
|
123 pubkeydb.close();
|
|
124 pubkeydb = null;
|
|
125 }
|
|
126 }
|
|
127
|
|
128 @Override
|
|
129 public void onCreate(Bundle icicle) {
|
|
130 super.onCreate(icicle);
|
|
131 setContentView(R.layout.act_pubkeylist);
|
|
132 this.setTitle(String.format("%s: %s",
|
|
133 getResources().getText(R.string.app_name),
|
|
134 getResources().getText(R.string.title_pubkey_list)));
|
|
135 // connect with hosts database and populate list
|
|
136 pubkeydb = new PubkeyDatabase(this);
|
|
137 updateList();
|
|
138 registerForContextMenu(getListView());
|
|
139 getListView().setOnItemClickListener(new OnItemClickListener() {
|
|
140 public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
|
|
141 PubkeyBean pubkey = (PubkeyBean) getListView().getItemAtPosition(position);
|
|
142 boolean loaded = bound.isKeyLoaded(pubkey.getNickname());
|
|
143
|
|
144 // handle toggling key in-memory on/off
|
|
145 if (loaded) {
|
|
146 bound.removeKey(pubkey.getNickname());
|
|
147 updateList();
|
|
148 }
|
|
149 else {
|
|
150 handleAddKey(pubkey);
|
|
151 }
|
|
152 }
|
|
153 });
|
|
154 clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
|
|
155 inflater = LayoutInflater.from(this);
|
|
156 }
|
|
157
|
|
158 /**
|
|
159 * Read given file into memory as <code>byte[]</code>.
|
|
160 */
|
|
161 protected static byte[] readRaw(File file) throws Exception {
|
|
162 InputStream is = new FileInputStream(file);
|
|
163 ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
164 int bytesRead;
|
|
165 byte[] buffer = new byte[1024];
|
|
166
|
|
167 while ((bytesRead = is.read(buffer)) != -1) {
|
|
168 os.write(buffer, 0, bytesRead);
|
|
169 }
|
|
170
|
|
171 os.flush();
|
|
172 os.close();
|
|
173 is.close();
|
|
174 return os.toByteArray();
|
|
175 }
|
|
176
|
|
177 @Override
|
|
178 public boolean onCreateOptionsMenu(Menu menu) {
|
|
179 super.onCreateOptionsMenu(menu);
|
|
180 MenuItem generatekey = menu.add(R.string.pubkey_generate);
|
|
181 generatekey.setIcon(android.R.drawable.ic_menu_manage);
|
|
182 generatekey.setIntent(new Intent(PubkeyListActivity.this, GeneratePubkeyActivity.class));
|
|
183 MenuItem importkey = menu.add(R.string.pubkey_import);
|
|
184 importkey.setIcon(android.R.drawable.ic_menu_upload);
|
|
185 importkey.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
186 public boolean onMenuItemClick(MenuItem item) {
|
|
187 FileChooser.selectFile(PubkeyListActivity.this, PubkeyListActivity.this,
|
|
188 FileChooser.REQUEST_CODE_SELECT_FILE,
|
|
189 getString(R.string.file_chooser_select_file, getString(R.string.select_for_key_import)));
|
|
190 return true;
|
|
191 }
|
|
192 });
|
|
193 return true;
|
|
194 }
|
|
195
|
|
196 protected void handleAddKey(final PubkeyBean pubkey) {
|
|
197 if (pubkey.isEncrypted()) {
|
|
198 final View view = inflater.inflate(R.layout.dia_password, null);
|
|
199 final EditText passwordField = (EditText)view.findViewById(android.R.id.text1);
|
|
200 new AlertDialog.Builder(PubkeyListActivity.this)
|
|
201 .setView(view)
|
|
202 .setPositiveButton(R.string.pubkey_unlock, new DialogInterface.OnClickListener() {
|
|
203 public void onClick(DialogInterface dialog, int which) {
|
|
204 handleAddKey(pubkey, passwordField.getText().toString());
|
|
205 }
|
|
206 })
|
|
207 .setNegativeButton(android.R.string.cancel, null).create().show();
|
|
208 }
|
|
209 else {
|
|
210 handleAddKey(pubkey, null);
|
|
211 }
|
|
212 }
|
|
213
|
|
214 protected void handleAddKey(PubkeyBean keybean, String password) {
|
|
215 KeyPair pair = null;
|
|
216
|
|
217 if (PubkeyDatabase.KEY_TYPE_IMPORTED.equals(keybean.getType())) {
|
|
218 // load specific key using pem format
|
|
219 try {
|
|
220 pair = PEMDecoder.decode(new String(keybean.getPrivateKey()).toCharArray(), password);
|
|
221 }
|
|
222 catch (Exception e) {
|
|
223 String message = getResources().getString(R.string.pubkey_failed_add, keybean.getNickname());
|
|
224 Log.e(TAG, message, e);
|
|
225 Toast.makeText(PubkeyListActivity.this, message, Toast.LENGTH_LONG).show();
|
|
226 }
|
|
227 }
|
|
228 else {
|
|
229 // load using internal generated format
|
|
230 try {
|
|
231 PrivateKey privKey = PubkeyUtils.decodePrivate(keybean.getPrivateKey(), keybean.getType(), password);
|
|
232 PublicKey pubKey = PubkeyUtils.decodePublic(keybean.getPublicKey(), keybean.getType());
|
|
233 Log.d(TAG, "Unlocked key " + PubkeyUtils.formatKey(pubKey));
|
|
234 pair = new KeyPair(pubKey, privKey);
|
|
235 }
|
|
236 catch (Exception e) {
|
|
237 String message = getResources().getString(R.string.pubkey_failed_add, keybean.getNickname());
|
|
238 Log.e(TAG, message, e);
|
|
239 Toast.makeText(PubkeyListActivity.this, message, Toast.LENGTH_LONG).show();
|
|
240 return;
|
|
241 }
|
|
242 }
|
|
243
|
|
244 if (pair == null) return;
|
|
245
|
|
246 Log.d(TAG, String.format("Unlocked key '%s'", keybean.getNickname()));
|
|
247 // save this key in memory
|
|
248 bound.addKey(keybean, pair, true);
|
|
249 updateList();
|
|
250 }
|
|
251
|
|
252 @Override
|
|
253 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
|
254 // Create menu to handle deleting and editing pubkey
|
|
255 AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
|
|
256 final PubkeyBean pubkey = (PubkeyBean) getListView().getItemAtPosition(info.position);
|
|
257 menu.setHeaderTitle(pubkey.getNickname());
|
|
258 // TODO: option load/unload key from in-memory list
|
|
259 // prompt for password as needed for passworded keys
|
|
260 // cant change password or clipboard imported keys
|
|
261 final boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType());
|
|
262 final boolean loaded = bound.isKeyLoaded(pubkey.getNickname());
|
|
263 MenuItem load = menu.add(loaded ? R.string.pubkey_memory_unload : R.string.pubkey_memory_load);
|
|
264 load.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
265 public boolean onMenuItemClick(MenuItem item) {
|
|
266 if (loaded) {
|
|
267 bound.removeKey(pubkey.getNickname());
|
|
268 updateList();
|
|
269 }
|
|
270 else {
|
|
271 handleAddKey(pubkey);
|
|
272 //bound.addKey(nickname, trileadKey);
|
|
273 }
|
|
274
|
|
275 return true;
|
|
276 }
|
|
277 });
|
|
278 onstartToggle = menu.add(R.string.pubkey_load_on_start);
|
|
279 onstartToggle.setVisible(!pubkey.isEncrypted());
|
|
280 onstartToggle.setCheckable(true);
|
|
281 onstartToggle.setChecked(pubkey.isStartup());
|
|
282 onstartToggle.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
283 public boolean onMenuItemClick(MenuItem item) {
|
|
284 // toggle onstart status
|
|
285 pubkey.setStartup(!pubkey.isStartup());
|
|
286 pubkeydb.savePubkey(pubkey);
|
|
287 updateList();
|
|
288 return true;
|
|
289 }
|
|
290 });
|
|
291 MenuItem changePassword = menu.add(R.string.pubkey_change_password);
|
|
292 changePassword.setVisible(!imported);
|
|
293 changePassword.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
294 public boolean onMenuItemClick(MenuItem item) {
|
|
295 final View changePasswordView = inflater.inflate(R.layout.dia_changepassword, null, false);
|
|
296 ((TableRow)changePasswordView.findViewById(R.id.old_password_prompt))
|
|
297 .setVisibility(pubkey.isEncrypted() ? View.VISIBLE : View.GONE);
|
|
298 new AlertDialog.Builder(PubkeyListActivity.this)
|
|
299 .setView(changePasswordView)
|
|
300 .setPositiveButton(R.string.button_change, new DialogInterface.OnClickListener() {
|
|
301 public void onClick(DialogInterface dialog, int which) {
|
|
302 String oldPassword = ((EditText)changePasswordView.findViewById(R.id.old_password)).getText().toString();
|
|
303 String password1 = ((EditText)changePasswordView.findViewById(R.id.password1)).getText().toString();
|
|
304 String password2 = ((EditText)changePasswordView.findViewById(R.id.password2)).getText().toString();
|
|
305
|
|
306 if (!password1.equals(password2)) {
|
|
307 new AlertDialog.Builder(PubkeyListActivity.this)
|
|
308 .setMessage(R.string.alert_passwords_do_not_match_msg)
|
|
309 .setPositiveButton(android.R.string.ok, null)
|
|
310 .create().show();
|
|
311 return;
|
|
312 }
|
|
313
|
|
314 try {
|
|
315 if (!pubkey.changePassword(oldPassword, password1))
|
|
316 new AlertDialog.Builder(PubkeyListActivity.this)
|
|
317 .setMessage(R.string.alert_wrong_password_msg)
|
|
318 .setPositiveButton(android.R.string.ok, null)
|
|
319 .create().show();
|
|
320 else {
|
|
321 pubkeydb.savePubkey(pubkey);
|
|
322 updateList();
|
|
323 }
|
|
324 }
|
|
325 catch (Exception e) {
|
|
326 Log.e(TAG, "Could not change private key password", e);
|
|
327 new AlertDialog.Builder(PubkeyListActivity.this)
|
|
328 .setMessage(R.string.alert_key_corrupted_msg)
|
|
329 .setPositiveButton(android.R.string.ok, null)
|
|
330 .create().show();
|
|
331 }
|
|
332 }
|
|
333 })
|
|
334 .setNegativeButton(android.R.string.cancel, null).create().show();
|
|
335 return true;
|
|
336 }
|
|
337 });
|
|
338 confirmUse = menu.add(R.string.pubkey_confirm_use);
|
|
339 confirmUse.setCheckable(true);
|
|
340 confirmUse.setChecked(pubkey.isConfirmUse());
|
|
341 confirmUse.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
342 public boolean onMenuItemClick(MenuItem item) {
|
|
343 // toggle confirm use
|
|
344 pubkey.setConfirmUse(!pubkey.isConfirmUse());
|
|
345 pubkeydb.savePubkey(pubkey);
|
|
346 updateList();
|
|
347 return true;
|
|
348 }
|
|
349 });
|
|
350 MenuItem copyPublicToClipboard = menu.add(R.string.pubkey_copy_public);
|
|
351 copyPublicToClipboard.setVisible(!imported);
|
|
352 copyPublicToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
353 public boolean onMenuItemClick(MenuItem item) {
|
|
354 try {
|
|
355 PublicKey pk = PubkeyUtils.decodePublic(pubkey.getPublicKey(), pubkey.getType());
|
|
356 String openSSHPubkey = PubkeyUtils.convertToOpenSSHFormat(pk, pubkey.getNickname());
|
|
357 clipboard.setText(openSSHPubkey);
|
|
358 }
|
|
359 catch (Exception e) {
|
|
360 e.printStackTrace();
|
|
361 }
|
|
362
|
|
363 return true;
|
|
364 }
|
|
365 });
|
|
366 MenuItem exportPublic = menu.add(R.string.pubkey_export_public);
|
|
367 exportPublic.setVisible(!imported);
|
|
368 exportPublic.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
369 public boolean onMenuItemClick(MenuItem item) {
|
|
370 String keyString = PubkeyUtils.getPubkeyString(pubkey);
|
|
371
|
|
372 if (keyString != null)
|
|
373 saveKeyToFile(keyString, pubkey.getNickname(), KEYTYPE_PUBLIC);
|
|
374
|
|
375 return true;
|
|
376 }
|
|
377 });
|
|
378 MenuItem copyPrivateToClipboard = menu.add(R.string.pubkey_copy_private);
|
|
379 copyPrivateToClipboard.setVisible(!pubkey.isEncrypted() || imported);
|
|
380 copyPrivateToClipboard.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
381 public boolean onMenuItemClick(MenuItem item) {
|
|
382 String keyString = PubkeyUtils.getPrivkeyString(pubkey, null);
|
|
383
|
|
384 if (keyString != null)
|
|
385 clipboard.setText(keyString);
|
|
386
|
|
387 return true;
|
|
388 }
|
|
389 });
|
|
390 MenuItem exportPrivate = menu.add(R.string.pubkey_export_private);
|
|
391 exportPrivate.setVisible(!pubkey.isEncrypted() || imported);
|
|
392 exportPrivate.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
393 public boolean onMenuItemClick(MenuItem item) {
|
|
394 /* if (pubkey.isEncrypted()) {
|
|
395 final View view = inflater.inflate(R.layout.dia_password, null);
|
|
396 final EditText passwordField = (EditText)view.findViewById(android.R.id.text1);
|
|
397
|
|
398 new AlertDialog.Builder(PubkeyListActivity.this)
|
|
399 .setView(view)
|
|
400 .setPositiveButton(R.string.pubkey_unlock, new DialogInterface.OnClickListener() {
|
|
401 public void onClick(DialogInterface dialog, int which) {
|
|
402 String keyString = PubkeyUtils.getPrivkeyString(pubkey, passwordField.getText().toString());
|
|
403 if (keyString != null)
|
|
404 saveKeyToFile(keyString, pubkey.getNickname(), KEYTYPE_PRIVATE);
|
|
405 }
|
|
406 })
|
|
407 .setNegativeButton(android.R.string.cancel, null).create().show();
|
|
408 } else { */
|
|
409 String keyString = PubkeyUtils.getPrivkeyString(pubkey, null);
|
|
410
|
|
411 if (keyString != null)
|
|
412 saveKeyToFile(keyString, pubkey.getNickname(), KEYTYPE_PRIVATE);
|
|
413
|
|
414 // }
|
|
415 return true;
|
|
416 }
|
|
417 });
|
|
418 MenuItem delete = menu.add(R.string.pubkey_delete);
|
|
419 delete.setOnMenuItemClickListener(new OnMenuItemClickListener() {
|
|
420 public boolean onMenuItemClick(MenuItem item) {
|
|
421 // prompt user to make sure they really want this
|
|
422 new AlertDialog.Builder(PubkeyListActivity.this)
|
|
423 .setMessage(getString(R.string.delete_message, pubkey.getNickname()))
|
|
424 .setPositiveButton(R.string.delete_pos, new DialogInterface.OnClickListener() {
|
|
425 public void onClick(DialogInterface dialog, int which) {
|
|
426 // dont forget to remove from in-memory
|
|
427 if (loaded)
|
|
428 bound.removeKey(pubkey.getNickname());
|
|
429
|
|
430 // delete from backend database and update gui
|
|
431 pubkeydb.deletePubkey(pubkey);
|
|
432 updateList();
|
|
433 }
|
|
434 })
|
|
435 .setNegativeButton(R.string.delete_neg, null).create().show();
|
|
436 return true;
|
|
437 }
|
|
438 });
|
|
439 }
|
|
440
|
|
441 protected void updateList() {
|
|
442 if (pubkeydb == null) return;
|
|
443
|
|
444 pubkeys = pubkeydb.allPubkeys();
|
|
445 PubkeyAdapter adapter = new PubkeyAdapter(this, pubkeys);
|
|
446 this.setListAdapter(adapter);
|
|
447 }
|
|
448
|
|
449 @Override
|
|
450 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
|
451 super.onActivityResult(requestCode, resultCode, intent);
|
|
452
|
|
453 switch (requestCode) {
|
|
454 case FileChooser.REQUEST_CODE_SELECT_FILE:
|
|
455 if (resultCode == RESULT_OK && intent != null) {
|
|
456 File file = FileChooser.getSelectedFile(intent);
|
|
457
|
|
458 if (file != null)
|
|
459 readKeyFromFile(file);
|
|
460 }
|
|
461
|
|
462 break;
|
|
463 }
|
|
464 }
|
|
465
|
|
466 /**
|
|
467 * @param name
|
|
468 */
|
|
469 private void readKeyFromFile(File file) {
|
|
470 PubkeyBean pubkey = new PubkeyBean();
|
|
471 // find the exact file selected
|
|
472 pubkey.setNickname(file.getName());
|
|
473
|
|
474 if (file.length() > MAX_KEYFILE_SIZE) {
|
|
475 Toast.makeText(PubkeyListActivity.this,
|
|
476 R.string.pubkey_import_parse_problem,
|
|
477 Toast.LENGTH_LONG).show();
|
|
478 return;
|
|
479 }
|
|
480
|
|
481 // parse the actual key once to check if its encrypted
|
|
482 // then save original file contents into our database
|
|
483 try {
|
|
484 byte[] raw = readRaw(file);
|
|
485 String data = new String(raw);
|
|
486
|
|
487 if (data.startsWith(PubkeyUtils.PKCS8_START)) {
|
|
488 int start = data.indexOf(PubkeyUtils.PKCS8_START) + PubkeyUtils.PKCS8_START.length();
|
|
489 int end = data.indexOf(PubkeyUtils.PKCS8_END);
|
|
490
|
|
491 if (end > start) {
|
|
492 char[] encoded = data.substring(start, end - 1).toCharArray();
|
|
493 Log.d(TAG, "encoded: " + new String(encoded));
|
|
494 byte[] decoded = Base64.decode(encoded);
|
|
495 KeyPair kp = PubkeyUtils.recoverKeyPair(decoded);
|
|
496 pubkey.setType(kp.getPrivate().getAlgorithm());
|
|
497 pubkey.setPrivateKey(kp.getPrivate().getEncoded());
|
|
498 pubkey.setPublicKey(kp.getPublic().getEncoded());
|
|
499 }
|
|
500 else {
|
|
501 Log.e(TAG, "Problem parsing PKCS#8 file; corrupt?");
|
|
502 Toast.makeText(PubkeyListActivity.this,
|
|
503 R.string.pubkey_import_parse_problem,
|
|
504 Toast.LENGTH_LONG).show();
|
|
505 }
|
|
506 }
|
|
507 else {
|
|
508 PEMStructure struct = PEMDecoder.parsePEM(new String(raw).toCharArray());
|
|
509 pubkey.setEncrypted(PEMDecoder.isPEMEncrypted(struct));
|
|
510 pubkey.setType(PubkeyDatabase.KEY_TYPE_IMPORTED);
|
|
511 pubkey.setPrivateKey(raw);
|
|
512 }
|
|
513
|
|
514 // write new value into database
|
|
515 if (pubkeydb == null)
|
|
516 pubkeydb = new PubkeyDatabase(this);
|
|
517
|
|
518 pubkeydb.savePubkey(pubkey);
|
|
519 updateList();
|
|
520 }
|
|
521 catch (Exception e) {
|
|
522 Log.e(TAG, "Problem parsing imported private key", e);
|
|
523 Toast.makeText(PubkeyListActivity.this, R.string.pubkey_import_parse_problem, Toast.LENGTH_LONG).show();
|
|
524 }
|
|
525 }
|
|
526
|
|
527 private void saveKeyToFile(final String keyString, final String nickName, int keyType) {
|
|
528 final int titleId, messageId, successId, errorId;
|
|
529 final String errorString;
|
|
530
|
|
531 if (keyType == KEYTYPE_PRIVATE) {
|
|
532 titleId = R.string.pubkey_private_save_as;
|
|
533 messageId = R.string.pubkey_private_save_as_desc;
|
|
534 successId = R.string.pubkey_private_export_success;
|
|
535 errorId = R.string.pubkey_private_export_problem;
|
|
536 errorString = "Error exporting private key";
|
|
537 }
|
|
538 else {
|
|
539 titleId = R.string.pubkey_public_save_as;
|
|
540 messageId = R.string.pubkey_public_save_as_desc;
|
|
541 errorId = R.string.pubkey_public_export_problem;
|
|
542 successId = R.string.pubkey_public_export_success;
|
|
543 errorString = "Error exporting public key";
|
|
544 }
|
|
545
|
|
546 final String sdcard = Environment.getExternalStorageDirectory().toString();
|
|
547 final EditText fileName = new EditText(PubkeyListActivity.this);
|
|
548 fileName.setSingleLine();
|
|
549
|
|
550 if (nickName != null) {
|
|
551 if (keyType == KEYTYPE_PRIVATE)
|
|
552 fileName.setText(sdcard + "/" + nickName.trim());
|
|
553 else
|
|
554 fileName.setText(sdcard + "/" + nickName.trim() + ".pub");
|
|
555 }
|
|
556
|
|
557 new AlertDialog.Builder(PubkeyListActivity.this)
|
|
558 .setTitle(titleId)
|
|
559 .setMessage(messageId)
|
|
560 .setView(fileName)
|
|
561 .setPositiveButton(R.string.save, new DialogInterface.OnClickListener() {
|
|
562 public void onClick(DialogInterface dialog, int whichButton) {
|
|
563 File keyFile = new File(fileName.getText().toString());
|
|
564
|
|
565 if (!keyFile.exists()) {
|
|
566 try {
|
|
567 keyFile.createNewFile();
|
|
568 }
|
|
569 catch (IOException e) {
|
|
570 Log.e(TAG, errorString);
|
|
571 Toast.makeText(PubkeyListActivity.this,
|
|
572 errorId,
|
|
573 Toast.LENGTH_LONG).show();
|
|
574 return;
|
|
575 }
|
|
576 }
|
|
577
|
|
578 FileOutputStream fout = null;
|
|
579
|
|
580 try {
|
|
581 fout = new FileOutputStream(keyFile);
|
|
582 fout.write(keyString.getBytes(), 0, keyString.getBytes().length);
|
|
583 fout.flush();
|
|
584 }
|
|
585 catch (Exception e) {
|
|
586 Log.e(TAG, errorString);
|
|
587 Toast.makeText(PubkeyListActivity.this,
|
|
588 errorId,
|
|
589 Toast.LENGTH_LONG).show();
|
|
590 return;
|
|
591 }
|
|
592
|
|
593 Toast.makeText(PubkeyListActivity.this,
|
|
594 getResources().getString(successId, keyFile.getPath().toString()),
|
|
595 Toast.LENGTH_LONG).show();
|
|
596 }
|
|
597 }).setNegativeButton(android.R.string.cancel, null).create().show();
|
|
598 }
|
|
599
|
|
600 public void fileSelected(File f) {
|
|
601 Log.d(TAG, "File chooser returned " + f);
|
|
602 readKeyFromFile(f);
|
|
603 }
|
|
604
|
|
605 class PubkeyAdapter extends ArrayAdapter<PubkeyBean> {
|
|
606 private List<PubkeyBean> pubkeys;
|
|
607
|
|
608 class ViewHolder {
|
|
609 public TextView nickname;
|
|
610 public TextView caption;
|
|
611 public ImageView icon;
|
|
612 }
|
|
613
|
|
614 public PubkeyAdapter(Context context, List<PubkeyBean> pubkeys) {
|
|
615 super(context, R.layout.item_pubkey, pubkeys);
|
|
616 this.pubkeys = pubkeys;
|
|
617 }
|
|
618
|
|
619 @Override
|
|
620 public View getView(int position, View convertView, ViewGroup parent) {
|
|
621 ViewHolder holder;
|
|
622
|
|
623 if (convertView == null) {
|
|
624 convertView = inflater.inflate(R.layout.item_pubkey, null, false);
|
|
625 holder = new ViewHolder();
|
|
626 holder.nickname = (TextView) convertView.findViewById(android.R.id.text1);
|
|
627 holder.caption = (TextView) convertView.findViewById(android.R.id.text2);
|
|
628 holder.icon = (ImageView) convertView.findViewById(android.R.id.icon1);
|
|
629 convertView.setTag(holder);
|
|
630 }
|
|
631 else
|
|
632 holder = (ViewHolder) convertView.getTag();
|
|
633
|
|
634 PubkeyBean pubkey = pubkeys.get(position);
|
|
635 holder.nickname.setText(pubkey.getNickname());
|
|
636 boolean imported = PubkeyDatabase.KEY_TYPE_IMPORTED.equals(pubkey.getType());
|
|
637
|
|
638 if (imported) {
|
|
639 try {
|
|
640 PEMStructure struct = PEMDecoder.parsePEM(new String(pubkey.getPrivateKey()).toCharArray());
|
|
641 String type = (struct.pemType == PEMDecoder.PEM_RSA_PRIVATE_KEY) ? "RSA" : "DSA";
|
|
642 holder.caption.setText(String.format("%s unknown-bit", type));
|
|
643 }
|
|
644 catch (IOException e) {
|
|
645 Log.e(TAG, "Error decoding IMPORTED public key at " + pubkey.getId(), e);
|
|
646 }
|
|
647 }
|
|
648 else {
|
|
649 try {
|
|
650 holder.caption.setText(pubkey.getDescription());
|
|
651 }
|
|
652 catch (Exception e) {
|
|
653 Log.e(TAG, "Error decoding public key at " + pubkey.getId(), e);
|
|
654 holder.caption.setText(R.string.pubkey_unknown_format);
|
|
655 }
|
|
656 }
|
|
657
|
|
658 if (bound == null) {
|
|
659 holder.icon.setVisibility(View.GONE);
|
|
660 }
|
|
661 else {
|
|
662 holder.icon.setVisibility(View.VISIBLE);
|
|
663
|
|
664 if (bound.isKeyLoaded(pubkey.getNickname()))
|
|
665 holder.icon.setImageState(new int[] { android.R.attr.state_checked }, true);
|
|
666 else
|
|
667 holder.icon.setImageState(new int[] { }, true);
|
|
668 }
|
|
669
|
|
670 return convertView;
|
|
671 }
|
|
672 }
|
|
673 }
|