comparison src/com/five_ten_sg/connectbot/GeneratePubkeyActivity.java @ 0:0ce5cc452d02

initial version
author Carl Byington <carl@five-ten-sg.com>
date Thu, 22 May 2014 10:41:19 -0700
parents
children 91a31873c42a
comparison
equal deleted inserted replaced
-1:000000000000 0:0ce5cc452d02
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 }