Mercurial > 510Connectbot
annotate src/com/five_ten_sg/connectbot/GeneratePubkeyActivity.java @ 392:7ac1a0b02f44
add test buffer changed when input stream runs dry, similar to async test
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Tue, 16 Sep 2014 16:22:41 -0700 |
parents | b40bc65fa09a |
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; | |
19 | |
20 import java.security.KeyPair; | |
21 import java.security.KeyPairGenerator; | |
22 import java.security.PrivateKey; | |
23 import java.security.PublicKey; | |
24 | |
25 import com.five_ten_sg.connectbot.bean.PubkeyBean; | |
26 import com.five_ten_sg.connectbot.util.EntropyDialog; | |
27 import com.five_ten_sg.connectbot.util.EntropyView; | |
28 import com.five_ten_sg.connectbot.util.OnEntropyGatheredListener; | |
29 import com.five_ten_sg.connectbot.util.PubkeyDatabase; | |
30 import com.five_ten_sg.connectbot.util.PubkeyUtils; | |
31 import android.app.Activity; | |
32 import android.app.Dialog; | |
33 import android.app.ProgressDialog; | |
34 import android.os.Bundle; | |
35 import android.text.Editable; | |
36 import android.text.TextWatcher; | |
37 import android.util.Log; | |
38 import android.view.LayoutInflater; | |
39 import android.view.View; | |
40 import android.view.View.OnClickListener; | |
41 import android.view.View.OnFocusChangeListener; | |
42 import android.widget.Button; | |
43 import android.widget.CheckBox; | |
44 import android.widget.EditText; | |
45 import android.widget.RadioGroup; | |
46 import android.widget.RadioGroup.OnCheckedChangeListener; | |
47 import android.widget.SeekBar; | |
48 import android.widget.SeekBar.OnSeekBarChangeListener; | |
49 | |
344
b40bc65fa09a
compensate for SecureRandom bug on older devices
Carl Byington <carl@five-ten-sg.com>
parents:
273
diff
changeset
|
50 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
|
51 import ch.ethz.ssh2.signature.ECDSASHA2Verify; |
0 | 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 | |
344
b40bc65fa09a
compensate for SecureRandom bug on older devices
Carl Byington <carl@five-ten-sg.com>
parents:
273
diff
changeset
|
241 SecureRandomFix random = new SecureRandomFix(); |
0 | 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 } |