319 lines
8.8 KiB
Java
319 lines
8.8 KiB
Java
/*
|
|
* ConnectBot: simple, powerful, open-source SSH client for Android
|
|
* Copyright 2007 Kenny Root, Jeffrey Sharkey
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package org.connectbot;
|
|
|
|
import java.security.KeyPair;
|
|
import java.security.KeyPairGenerator;
|
|
import java.security.PrivateKey;
|
|
import java.security.PublicKey;
|
|
import java.security.SecureRandom;
|
|
|
|
import org.connectbot.bean.PubkeyBean;
|
|
import org.connectbot.util.EntropyDialog;
|
|
import org.connectbot.util.EntropyView;
|
|
import org.connectbot.util.OnEntropyGatheredListener;
|
|
import org.connectbot.util.PubkeyDatabase;
|
|
import org.connectbot.util.PubkeyUtils;
|
|
|
|
import android.app.Activity;
|
|
import android.app.Dialog;
|
|
import android.app.ProgressDialog;
|
|
import android.content.Context;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.Message;
|
|
import android.text.Editable;
|
|
import android.text.TextWatcher;
|
|
import android.util.Log;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.View.OnClickListener;
|
|
import android.view.View.OnFocusChangeListener;
|
|
import android.widget.Button;
|
|
import android.widget.CheckBox;
|
|
import android.widget.EditText;
|
|
import android.widget.RadioGroup;
|
|
import android.widget.SeekBar;
|
|
import android.widget.RadioGroup.OnCheckedChangeListener;
|
|
import android.widget.SeekBar.OnSeekBarChangeListener;
|
|
|
|
public class GeneratePubkeyActivity extends Activity implements OnEntropyGatheredListener {
|
|
public final static String TAG = "ConnectBot.GeneratePubkeyActivity";
|
|
|
|
final static int DEFAULT_BITS = 1024;
|
|
|
|
private LayoutInflater inflater = null;
|
|
|
|
private EditText nickname;
|
|
private RadioGroup keyTypeGroup;
|
|
private SeekBar bitsSlider;
|
|
private EditText bitsText;
|
|
private CheckBox unlockAtStartup;
|
|
private CheckBox confirmUse;
|
|
private Button save;
|
|
private Dialog entropyDialog;
|
|
private ProgressDialog progress;
|
|
|
|
private EditText password1, password2;
|
|
|
|
private String keyType = PubkeyDatabase.KEY_TYPE_RSA;
|
|
private int minBits = 768;
|
|
private int bits = DEFAULT_BITS;
|
|
|
|
private byte[] entropy;
|
|
|
|
@Override
|
|
public void onCreate(Bundle icicle) {
|
|
super.onCreate(icicle);
|
|
|
|
setContentView(R.layout.act_generatepubkey);
|
|
|
|
nickname = (EditText) findViewById(R.id.nickname);
|
|
|
|
keyTypeGroup = (RadioGroup) findViewById(R.id.key_type);
|
|
|
|
bitsText = (EditText) findViewById(R.id.bits);
|
|
bitsSlider = (SeekBar) findViewById(R.id.bits_slider);
|
|
|
|
password1 = (EditText) findViewById(R.id.password1);
|
|
password2 = (EditText) findViewById(R.id.password2);
|
|
|
|
unlockAtStartup = (CheckBox) findViewById(R.id.unlock_at_startup);
|
|
|
|
confirmUse = (CheckBox) findViewById(R.id.confirm_use);
|
|
|
|
save = (Button) findViewById(R.id.save);
|
|
|
|
inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
|
|
|
nickname.addTextChangedListener(textChecker);
|
|
password1.addTextChangedListener(textChecker);
|
|
password2.addTextChangedListener(textChecker);
|
|
|
|
keyTypeGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
|
|
|
public void onCheckedChanged(RadioGroup group, int checkedId) {
|
|
if (checkedId == R.id.rsa) {
|
|
minBits = 768;
|
|
|
|
bitsSlider.setEnabled(true);
|
|
bitsSlider.setProgress(DEFAULT_BITS - minBits);
|
|
|
|
bitsText.setText(String.valueOf(DEFAULT_BITS));
|
|
bitsText.setEnabled(true);
|
|
|
|
keyType = PubkeyDatabase.KEY_TYPE_RSA;
|
|
} else if (checkedId == R.id.dsa) {
|
|
// DSA keys can only be 1024 bits
|
|
|
|
bitsSlider.setEnabled(false);
|
|
bitsSlider.setProgress(DEFAULT_BITS - minBits);
|
|
|
|
bitsText.setText(String.valueOf(DEFAULT_BITS));
|
|
bitsText.setEnabled(false);
|
|
|
|
keyType = PubkeyDatabase.KEY_TYPE_DSA;
|
|
}
|
|
}
|
|
});
|
|
|
|
bitsSlider.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
|
|
|
|
public void onProgressChanged(SeekBar seekBar, int progress,
|
|
boolean fromTouch) {
|
|
// Stay evenly divisible by 8 because it looks nicer to have
|
|
// 2048 than 2043 bits.
|
|
|
|
int leftover = progress % 8;
|
|
int ourProgress = progress;
|
|
|
|
if (leftover > 0)
|
|
ourProgress += 8 - leftover;
|
|
|
|
bits = minBits + ourProgress;
|
|
bitsText.setText(String.valueOf(bits));
|
|
}
|
|
|
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
|
// We don't care about the start.
|
|
}
|
|
|
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
|
// We don't care about the stop.
|
|
}
|
|
});
|
|
|
|
bitsText.setOnFocusChangeListener(new OnFocusChangeListener() {
|
|
public void onFocusChange(View v, boolean hasFocus) {
|
|
if (!hasFocus) {
|
|
try {
|
|
bits = Integer.parseInt(bitsText.getText().toString());
|
|
if (bits < minBits) {
|
|
bits = minBits;
|
|
bitsText.setText(String.valueOf(bits));
|
|
}
|
|
} catch (NumberFormatException nfe) {
|
|
bits = DEFAULT_BITS;
|
|
bitsText.setText(String.valueOf(bits));
|
|
}
|
|
|
|
bitsSlider.setProgress(bits - minBits);
|
|
}
|
|
}
|
|
});
|
|
|
|
save.setOnClickListener(new OnClickListener() {
|
|
public void onClick(View view) {
|
|
GeneratePubkeyActivity.this.save.setEnabled(false);
|
|
|
|
GeneratePubkeyActivity.this.startEntropyGather();
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
private void checkEntries() {
|
|
boolean allowSave = true;
|
|
|
|
if (!password1.getText().toString().equals(password2.getText().toString()))
|
|
allowSave = false;
|
|
|
|
if (nickname.getText().length() == 0)
|
|
allowSave = false;
|
|
|
|
save.setEnabled(allowSave);
|
|
}
|
|
|
|
private void startEntropyGather() {
|
|
final View entropyView = inflater.inflate(R.layout.dia_gatherentropy, null, false);
|
|
((EntropyView)entropyView.findViewById(R.id.entropy)).addOnEntropyGatheredListener(GeneratePubkeyActivity.this);
|
|
entropyDialog = new EntropyDialog(GeneratePubkeyActivity.this, entropyView);
|
|
entropyDialog.show();
|
|
}
|
|
|
|
public void onEntropyGathered(byte[] entropy) {
|
|
// For some reason the entropy dialog was aborted, exit activity
|
|
if (entropy == null) {
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
this.entropy = entropy.clone();
|
|
|
|
int numSetBits = 0;
|
|
for (int i = 0; i < 20; i++)
|
|
numSetBits += measureNumberOfSetBits(this.entropy[i]);
|
|
|
|
Log.d(TAG, "Entropy distribution=" + (int)(100.0 * numSetBits / 160.0) + "%");
|
|
|
|
Log.d(TAG, "entropy gathered; attemping to generate key...");
|
|
startKeyGen();
|
|
}
|
|
|
|
private void startKeyGen() {
|
|
progress = new ProgressDialog(GeneratePubkeyActivity.this);
|
|
progress.setMessage(GeneratePubkeyActivity.this.getResources().getText(R.string.pubkey_generating));
|
|
progress.setIndeterminate(true);
|
|
progress.setCancelable(false);
|
|
progress.show();
|
|
|
|
Thread keyGenThread = new Thread(mKeyGen);
|
|
keyGenThread.setName("KeyGen");
|
|
keyGenThread.start();
|
|
}
|
|
|
|
private Handler handler = new Handler() {
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
progress.dismiss();
|
|
GeneratePubkeyActivity.this.finish();
|
|
}
|
|
};
|
|
|
|
final private Runnable mKeyGen = new Runnable() {
|
|
public void run() {
|
|
try {
|
|
boolean encrypted = false;
|
|
|
|
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
|
|
|
random.setSeed(entropy);
|
|
|
|
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(keyType);
|
|
|
|
keyPairGen.initialize(bits, random);
|
|
|
|
KeyPair pair = keyPairGen.generateKeyPair();
|
|
PrivateKey priv = pair.getPrivate();
|
|
PublicKey pub = pair.getPublic();
|
|
|
|
String secret = password1.getText().toString();
|
|
if (secret.length() > 0)
|
|
encrypted = true;
|
|
|
|
Log.d(TAG, "private: " + PubkeyUtils.formatKey(priv));
|
|
Log.d(TAG, "public: " + PubkeyUtils.formatKey(pub));
|
|
|
|
PubkeyBean pubkey = new PubkeyBean();
|
|
pubkey.setNickname(nickname.getText().toString());
|
|
pubkey.setType(keyType);
|
|
pubkey.setPrivateKey(PubkeyUtils.getEncodedPrivate(priv, secret));
|
|
pubkey.setPublicKey(PubkeyUtils.getEncodedPublic(pub));
|
|
pubkey.setEncrypted(encrypted);
|
|
pubkey.setStartup(unlockAtStartup.isChecked());
|
|
pubkey.setConfirmUse(confirmUse.isChecked());
|
|
|
|
PubkeyDatabase pubkeydb = new PubkeyDatabase(GeneratePubkeyActivity.this);
|
|
pubkeydb.savePubkey(pubkey);
|
|
pubkeydb.close();
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Could not generate key pair");
|
|
|
|
e.printStackTrace();
|
|
}
|
|
|
|
handler.sendEmptyMessage(0);
|
|
}
|
|
|
|
};
|
|
|
|
final private TextWatcher textChecker = new TextWatcher() {
|
|
public void afterTextChanged(Editable s) {}
|
|
|
|
public void beforeTextChanged(CharSequence s, int start, int count,
|
|
int after) {}
|
|
|
|
public void onTextChanged(CharSequence s, int start, int before,
|
|
int count) {
|
|
checkEntries();
|
|
}
|
|
};
|
|
|
|
private int measureNumberOfSetBits(byte b) {
|
|
int numSetBits = 0;
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
if ((b & 1) == 1)
|
|
numSetBits++;
|
|
b >>= 1;
|
|
}
|
|
|
|
return numSetBits;
|
|
}
|
|
}
|