Mercurial > 510Connectbot
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/five_ten_sg/connectbot/GeneratePubkeyActivity.java Thu May 22 10:41:19 2014 -0700 @@ -0,0 +1,321 @@ +/* + * 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 com.five_ten_sg.connectbot; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; + +import com.five_ten_sg.connectbot.bean.PubkeyBean; +import com.five_ten_sg.connectbot.util.EntropyDialog; +import com.five_ten_sg.connectbot.util.EntropyView; +import com.five_ten_sg.connectbot.util.OnEntropyGatheredListener; +import com.five_ten_sg.connectbot.util.PubkeyDatabase; +import com.five_ten_sg.connectbot.util.PubkeyUtils; +import android.app.Activity; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.os.Bundle; +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.RadioGroup.OnCheckedChangeListener; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; + +import com.trilead.ssh2.signature.ECDSASHA2Verify; + +public class GeneratePubkeyActivity extends Activity implements OnEntropyGatheredListener { + public final static String TAG = "ConnectBot.GeneratePubkeyActivity"; + + private static final int RSA_MINIMUM_BITS = 768; + final static int DEFAULT_BITS = 2048; + final static int DSA_BITS = 1024; + final static int[] ECDSA_SIZES = ECDSASHA2Verify.getCurveSizes(); + final static int ECDSA_DEFAULT_BITS = ECDSA_SIZES[0]; + + 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 = RSA_MINIMUM_BITS; + 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.from(this); + 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 = RSA_MINIMUM_BITS; + 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(DSA_BITS - minBits); + bitsText.setText(String.valueOf(DSA_BITS)); + bitsText.setEnabled(false); + keyType = PubkeyDatabase.KEY_TYPE_DSA; + } + else if (checkedId == R.id.ec) { + minBits = ECDSA_DEFAULT_BITS; + bitsSlider.setEnabled(true); + bitsSlider.setProgress(ECDSA_DEFAULT_BITS - minBits); + bitsText.setText(String.valueOf(ECDSA_DEFAULT_BITS)); + bitsText.setEnabled(true); + keyType = PubkeyDatabase.KEY_TYPE_EC; + } + } + }); + bitsSlider.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { + public void onProgressChanged(SeekBar seekBar, int progress, + boolean fromTouch) { + if (PubkeyDatabase.KEY_TYPE_EC.equals(keyType)) { + bits = getClosestFieldSize(progress + minBits); + seekBar.setProgress(bits - minBits); + } + else { + // Stay evenly divisible by 8 because it looks nicer to have + // 2048 than 2043 bits. + final int ourProgress = progress - (progress % 8); + 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) { + final boolean isEc = PubkeyDatabase.KEY_TYPE_EC.equals(keyType); + + try { + bits = Integer.parseInt(bitsText.getText().toString()); + + if (bits < minBits) { + bits = minBits; + bitsText.setText(String.valueOf(bits)); + } + + if (isEc) { + bits = getClosestFieldSize(bits); + } + } + catch (NumberFormatException nfe) { + bits = isEc ? ECDSA_DEFAULT_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(); + } + + final private Runnable mKeyGen = new Runnable() { + public void run() { + try { + boolean encrypted = false; + int tmpbits = bits; + + if (keyType == PubkeyDatabase.KEY_TYPE_DSA) + tmpbits = DSA_BITS; + + SecureRandom random = new SecureRandom(); + // Work around JVM bug + random.nextInt(); + random.setSeed(entropy); + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(keyType); + keyPairGen.initialize(tmpbits, 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(pub.getEncoded()); + 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(); + } + + GeneratePubkeyActivity.this.runOnUiThread(new Runnable() { + public void run() { + progress.dismiss(); + GeneratePubkeyActivity.this.finish(); + } + }); + } + }; + + 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; + } + + private int getClosestFieldSize(int bits) { + int outBits = ECDSA_DEFAULT_BITS; + int distance = Math.abs(bits - ECDSA_DEFAULT_BITS); + + for (int i = 1; i < ECDSA_SIZES.length; i++) { + int thisDistance = Math.abs(bits - ECDSA_SIZES[i]); + + if (thisDistance < distance) { + distance = thisDistance; + outBits = ECDSA_SIZES[i]; + } + } + + return outBits; + } +}