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.security.KeyPair;
|
|
21 import java.security.KeyPairGenerator;
|
|
22 import java.security.PrivateKey;
|
|
23 import java.security.PublicKey;
|
|
24 import java.security.SecureRandom;
|
|
25
|
|
26 import com.five_ten_sg.connectbot.bean.PubkeyBean;
|
|
27 import com.five_ten_sg.connectbot.util.EntropyDialog;
|
|
28 import com.five_ten_sg.connectbot.util.EntropyView;
|
|
29 import com.five_ten_sg.connectbot.util.OnEntropyGatheredListener;
|
|
30 import com.five_ten_sg.connectbot.util.PubkeyDatabase;
|
|
31 import com.five_ten_sg.connectbot.util.PubkeyUtils;
|
|
32 import android.app.Activity;
|
|
33 import android.app.Dialog;
|
|
34 import android.app.ProgressDialog;
|
|
35 import android.os.Bundle;
|
|
36 import android.text.Editable;
|
|
37 import android.text.TextWatcher;
|
|
38 import android.util.Log;
|
|
39 import android.view.LayoutInflater;
|
|
40 import android.view.View;
|
|
41 import android.view.View.OnClickListener;
|
|
42 import android.view.View.OnFocusChangeListener;
|
|
43 import android.widget.Button;
|
|
44 import android.widget.CheckBox;
|
|
45 import android.widget.EditText;
|
|
46 import android.widget.RadioGroup;
|
|
47 import android.widget.RadioGroup.OnCheckedChangeListener;
|
|
48 import android.widget.SeekBar;
|
|
49 import android.widget.SeekBar.OnSeekBarChangeListener;
|
|
50
|
|
51 import com.trilead.ssh2.signature.ECDSASHA2Verify;
|
|
52
|
|
53 public class GeneratePubkeyActivity extends Activity implements OnEntropyGatheredListener {
|
|
54 public final static String TAG = "ConnectBot.GeneratePubkeyActivity";
|
|
55
|
|
56 private static final int RSA_MINIMUM_BITS = 768;
|
|
57 final static int DEFAULT_BITS = 2048;
|
|
58 final static int DSA_BITS = 1024;
|
|
59 final static int[] ECDSA_SIZES = ECDSASHA2Verify.getCurveSizes();
|
|
60 final static int ECDSA_DEFAULT_BITS = ECDSA_SIZES[0];
|
|
61
|
|
62 private LayoutInflater inflater = null;
|
|
63
|
|
64 private EditText nickname;
|
|
65 private RadioGroup keyTypeGroup;
|
|
66 private SeekBar bitsSlider;
|
|
67 private EditText bitsText;
|
|
68 private CheckBox unlockAtStartup;
|
|
69 private CheckBox confirmUse;
|
|
70 private Button save;
|
|
71 private Dialog entropyDialog;
|
|
72 private ProgressDialog progress;
|
|
73
|
|
74 private EditText password1, password2;
|
|
75
|
|
76 private String keyType = PubkeyDatabase.KEY_TYPE_RSA;
|
|
77 private int minBits = RSA_MINIMUM_BITS;
|
|
78 private int bits = DEFAULT_BITS;
|
|
79
|
|
80 private byte[] entropy;
|
|
81
|
|
82 @Override
|
|
83 public void onCreate(Bundle icicle) {
|
|
84 super.onCreate(icicle);
|
|
85 setContentView(R.layout.act_generatepubkey);
|
|
86 nickname = (EditText) findViewById(R.id.nickname);
|
|
87 keyTypeGroup = (RadioGroup) findViewById(R.id.key_type);
|
|
88 bitsText = (EditText) findViewById(R.id.bits);
|
|
89 bitsSlider = (SeekBar) findViewById(R.id.bits_slider);
|
|
90 password1 = (EditText) findViewById(R.id.password1);
|
|
91 password2 = (EditText) findViewById(R.id.password2);
|
|
92 unlockAtStartup = (CheckBox) findViewById(R.id.unlock_at_startup);
|
|
93 confirmUse = (CheckBox) findViewById(R.id.confirm_use);
|
|
94 save = (Button) findViewById(R.id.save);
|
|
95 inflater = LayoutInflater.from(this);
|
|
96 nickname.addTextChangedListener(textChecker);
|
|
97 password1.addTextChangedListener(textChecker);
|
|
98 password2.addTextChangedListener(textChecker);
|
|
99 keyTypeGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
|
100 public void onCheckedChanged(RadioGroup group, int checkedId) {
|
|
101 if (checkedId == R.id.rsa) {
|
|
102 minBits = RSA_MINIMUM_BITS;
|
|
103 bitsSlider.setEnabled(true);
|
|
104 bitsSlider.setProgress(DEFAULT_BITS - minBits);
|
|
105 bitsText.setText(String.valueOf(DEFAULT_BITS));
|
|
106 bitsText.setEnabled(true);
|
|
107 keyType = PubkeyDatabase.KEY_TYPE_RSA;
|
|
108 }
|
|
109 else if (checkedId == R.id.dsa) {
|
|
110 // DSA keys can only be 1024 bits
|
|
111 bitsSlider.setEnabled(false);
|
|
112 bitsSlider.setProgress(DSA_BITS - minBits);
|
|
113 bitsText.setText(String.valueOf(DSA_BITS));
|
|
114 bitsText.setEnabled(false);
|
|
115 keyType = PubkeyDatabase.KEY_TYPE_DSA;
|
|
116 }
|
|
117 else if (checkedId == R.id.ec) {
|
|
118 minBits = ECDSA_DEFAULT_BITS;
|
|
119 bitsSlider.setEnabled(true);
|
|
120 bitsSlider.setProgress(ECDSA_DEFAULT_BITS - minBits);
|
|
121 bitsText.setText(String.valueOf(ECDSA_DEFAULT_BITS));
|
|
122 bitsText.setEnabled(true);
|
|
123 keyType = PubkeyDatabase.KEY_TYPE_EC;
|
|
124 }
|
|
125 }
|
|
126 });
|
|
127 bitsSlider.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
|
|
128 public void onProgressChanged(SeekBar seekBar, int progress,
|
|
129 boolean fromTouch) {
|
|
130 if (PubkeyDatabase.KEY_TYPE_EC.equals(keyType)) {
|
|
131 bits = getClosestFieldSize(progress + minBits);
|
|
132 seekBar.setProgress(bits - minBits);
|
|
133 }
|
|
134 else {
|
|
135 // Stay evenly divisible by 8 because it looks nicer to have
|
|
136 // 2048 than 2043 bits.
|
|
137 final int ourProgress = progress - (progress % 8);
|
|
138 bits = minBits + ourProgress;
|
|
139 }
|
|
140
|
|
141 bitsText.setText(String.valueOf(bits));
|
|
142 }
|
|
143 public void onStartTrackingTouch(SeekBar seekBar) {
|
|
144 // We don't care about the start.
|
|
145 }
|
|
146 public void onStopTrackingTouch(SeekBar seekBar) {
|
|
147 // We don't care about the stop.
|
|
148 }
|
|
149 });
|
|
150 bitsText.setOnFocusChangeListener(new OnFocusChangeListener() {
|
|
151 public void onFocusChange(View v, boolean hasFocus) {
|
|
152 if (!hasFocus) {
|
|
153 final boolean isEc = PubkeyDatabase.KEY_TYPE_EC.equals(keyType);
|
|
154
|
|
155 try {
|
|
156 bits = Integer.parseInt(bitsText.getText().toString());
|
|
157
|
|
158 if (bits < minBits) {
|
|
159 bits = minBits;
|
|
160 bitsText.setText(String.valueOf(bits));
|
|
161 }
|
|
162
|
|
163 if (isEc) {
|
|
164 bits = getClosestFieldSize(bits);
|
|
165 }
|
|
166 }
|
|
167 catch (NumberFormatException nfe) {
|
|
168 bits = isEc ? ECDSA_DEFAULT_BITS : DEFAULT_BITS;
|
|
169 bitsText.setText(String.valueOf(bits));
|
|
170 }
|
|
171
|
|
172 bitsSlider.setProgress(bits - minBits);
|
|
173 }
|
|
174 }
|
|
175 });
|
|
176 save.setOnClickListener(new OnClickListener() {
|
|
177 public void onClick(View view) {
|
|
178 GeneratePubkeyActivity.this.save.setEnabled(false);
|
|
179 GeneratePubkeyActivity.this.startEntropyGather();
|
|
180 }
|
|
181 });
|
|
182 }
|
|
183
|
|
184 private void checkEntries() {
|
|
185 boolean allowSave = true;
|
|
186
|
|
187 if (!password1.getText().toString().equals(password2.getText().toString()))
|
|
188 allowSave = false;
|
|
189
|
|
190 if (nickname.getText().length() == 0)
|
|
191 allowSave = false;
|
|
192
|
|
193 save.setEnabled(allowSave);
|
|
194 }
|
|
195
|
|
196 private void startEntropyGather() {
|
|
197 final View entropyView = inflater.inflate(R.layout.dia_gatherentropy, null, false);
|
|
198 ((EntropyView)entropyView.findViewById(R.id.entropy)).addOnEntropyGatheredListener(GeneratePubkeyActivity.this);
|
|
199 entropyDialog = new EntropyDialog(GeneratePubkeyActivity.this, entropyView);
|
|
200 entropyDialog.show();
|
|
201 }
|
|
202
|
|
203 public void onEntropyGathered(byte[] entropy) {
|
|
204 // For some reason the entropy dialog was aborted, exit activity
|
|
205 if (entropy == null) {
|
|
206 finish();
|
|
207 return;
|
|
208 }
|
|
209
|
|
210 this.entropy = entropy.clone();
|
|
211 int numSetBits = 0;
|
|
212
|
|
213 for (int i = 0; i < 20; i++)
|
|
214 numSetBits += measureNumberOfSetBits(this.entropy[i]);
|
|
215
|
|
216 Log.d(TAG, "Entropy distribution=" + (int)(100.0 * numSetBits / 160.0) + "%");
|
|
217 Log.d(TAG, "entropy gathered; attemping to generate key...");
|
|
218 startKeyGen();
|
|
219 }
|
|
220
|
|
221 private void startKeyGen() {
|
|
222 progress = new ProgressDialog(GeneratePubkeyActivity.this);
|
|
223 progress.setMessage(GeneratePubkeyActivity.this.getResources().getText(R.string.pubkey_generating));
|
|
224 progress.setIndeterminate(true);
|
|
225 progress.setCancelable(false);
|
|
226 progress.show();
|
|
227 Thread keyGenThread = new Thread(mKeyGen);
|
|
228 keyGenThread.setName("KeyGen");
|
|
229 keyGenThread.start();
|
|
230 }
|
|
231
|
|
232 final private Runnable mKeyGen = new Runnable() {
|
|
233 public void run() {
|
|
234 try {
|
|
235 boolean encrypted = false;
|
|
236 int tmpbits = bits;
|
|
237
|
|
238 if (keyType == PubkeyDatabase.KEY_TYPE_DSA)
|
|
239 tmpbits = DSA_BITS;
|
|
240
|
|
241 SecureRandom random = new SecureRandom();
|
|
242 // Work around JVM bug
|
|
243 random.nextInt();
|
|
244 random.setSeed(entropy);
|
|
245 KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(keyType);
|
|
246 keyPairGen.initialize(tmpbits, random);
|
|
247 KeyPair pair = keyPairGen.generateKeyPair();
|
|
248 PrivateKey priv = pair.getPrivate();
|
|
249 PublicKey pub = pair.getPublic();
|
|
250 String secret = password1.getText().toString();
|
|
251
|
|
252 if (secret.length() > 0)
|
|
253 encrypted = true;
|
|
254
|
|
255 Log.d(TAG, "private: " + PubkeyUtils.formatKey(priv));
|
|
256 Log.d(TAG, "public: " + PubkeyUtils.formatKey(pub));
|
|
257 PubkeyBean pubkey = new PubkeyBean();
|
|
258 pubkey.setNickname(nickname.getText().toString());
|
|
259 pubkey.setType(keyType);
|
|
260 pubkey.setPrivateKey(PubkeyUtils.getEncodedPrivate(priv, secret));
|
|
261 pubkey.setPublicKey(pub.getEncoded());
|
|
262 pubkey.setEncrypted(encrypted);
|
|
263 pubkey.setStartup(unlockAtStartup.isChecked());
|
|
264 pubkey.setConfirmUse(confirmUse.isChecked());
|
|
265 PubkeyDatabase pubkeydb = new PubkeyDatabase(GeneratePubkeyActivity.this);
|
|
266 pubkeydb.savePubkey(pubkey);
|
|
267 pubkeydb.close();
|
|
268 }
|
|
269 catch (Exception e) {
|
|
270 Log.e(TAG, "Could not generate key pair");
|
|
271 e.printStackTrace();
|
|
272 }
|
|
273
|
|
274 GeneratePubkeyActivity.this.runOnUiThread(new Runnable() {
|
|
275 public void run() {
|
|
276 progress.dismiss();
|
|
277 GeneratePubkeyActivity.this.finish();
|
|
278 }
|
|
279 });
|
|
280 }
|
|
281 };
|
|
282
|
|
283 final private TextWatcher textChecker = new TextWatcher() {
|
|
284 public void afterTextChanged(Editable s) {}
|
|
285 public void beforeTextChanged(CharSequence s, int start, int count,
|
|
286 int after) {}
|
|
287 public void onTextChanged(CharSequence s, int start, int before,
|
|
288 int count) {
|
|
289 checkEntries();
|
|
290 }
|
|
291 };
|
|
292
|
|
293 private int measureNumberOfSetBits(byte b) {
|
|
294 int numSetBits = 0;
|
|
295
|
|
296 for (int i = 0; i < 8; i++) {
|
|
297 if ((b & 1) == 1)
|
|
298 numSetBits++;
|
|
299
|
|
300 b >>= 1;
|
|
301 }
|
|
302
|
|
303 return numSetBits;
|
|
304 }
|
|
305
|
|
306 private int getClosestFieldSize(int bits) {
|
|
307 int outBits = ECDSA_DEFAULT_BITS;
|
|
308 int distance = Math.abs(bits - ECDSA_DEFAULT_BITS);
|
|
309
|
|
310 for (int i = 1; i < ECDSA_SIZES.length; i++) {
|
|
311 int thisDistance = Math.abs(bits - ECDSA_SIZES[i]);
|
|
312
|
|
313 if (thisDistance < distance) {
|
|
314 distance = thisDistance;
|
|
315 outBits = ECDSA_SIZES[i];
|
|
316 }
|
|
317 }
|
|
318
|
|
319 return outBits;
|
|
320 }
|
|
321 }
|