diff options
Diffstat (limited to 'OpenKeychain/src/main/java')
7 files changed, 152 insertions, 24 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptData.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptData.java index c4e569d24..5d904331e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptData.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptData.java @@ -30,6 +30,7 @@ public class PgpSignEncryptData implements Parcelable { protected String mVersionHeader = null; protected boolean mEnableAsciiArmorOutput = false; + protected boolean mSshAuth = false; protected int mCompressionAlgorithm = CompressionAlgorithmTags.UNCOMPRESSED; protected long[] mEncryptionMasterKeyIds = null; protected Passphrase mSymmetricPassphrase = null; @@ -53,6 +54,7 @@ public class PgpSignEncryptData implements Parcelable { mVersionHeader = source.readString(); mEnableAsciiArmorOutput = source.readInt() == 1; + mSshAuth = source.readInt() == 1; mCompressionAlgorithm = source.readInt(); mEncryptionMasterKeyIds = source.createLongArray(); mSymmetricPassphrase = source.readParcelable(loader); @@ -78,6 +80,7 @@ public class PgpSignEncryptData implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeString(mVersionHeader); dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0); + dest.writeInt(mSshAuth ? 1 : 0); dest.writeInt(mCompressionAlgorithm); dest.writeLongArray(mEncryptionMasterKeyIds); dest.writeParcelable(mSymmetricPassphrase, 0); @@ -183,6 +186,10 @@ public class PgpSignEncryptData implements Parcelable { return mEnableAsciiArmorOutput; } + public boolean isSshAuth() { + return mSshAuth; + } + public String getVersionHeader() { return mVersionHeader; } @@ -197,6 +204,11 @@ public class PgpSignEncryptData implements Parcelable { return this; } + public PgpSignEncryptData setSshAuth(boolean sshAuth) { + mSshAuth = sshAuth; + return this; + } + public PgpSignEncryptData setCleartextSignature(boolean cleartextSignature) { this.mCleartextSignature = cleartextSignature; return this; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 7a1d99927..a855dcac8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -24,6 +24,7 @@ import android.net.Uri; import android.os.Parcelable; import android.support.annotation.NonNull; +import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.CompressionAlgorithmTags; @@ -58,6 +59,10 @@ import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.ProgressScaler; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import java.nio.ByteBuffer; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -165,6 +170,7 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa return executeInternal(input, cryptoInput, inputData, outputStream); } + /** * Signs and/or encrypts data based on parameters of class */ @@ -238,7 +244,10 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa } // Make sure we are allowed to sign here! - if (!signingKey.canSign()) { + if ((!signingKey.canSign() && !data.isSshAuth()) || + (!signingKey.canAuthenticate() && data.isSshAuth())) { + Log.w(Constants.TAG, "canSign " + signingKey.canSign() + " canAuthenticate " + + signingKey.canAuthenticate() + " isSshAuth "+ data.isSshAuth()); log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent); return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); } @@ -298,7 +307,11 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa // Use requested hash algo int requestedAlgorithm = data.getSignatureHashAlgorithm(); if (requestedAlgorithm == PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT) { - data.setSignatureHashAlgorithm(PgpSecurityConstants.DEFAULT_HASH_ALGORITHM); + if (data.isSshAuth()) { + data.setSignatureHashAlgorithm(HashAlgorithmTags.SHA1); + } else { + data.setSignatureHashAlgorithm(PgpSecurityConstants.DEFAULT_HASH_ALGORITHM); + } } } updateProgress(R.string.progress_preparing_streams, 2, 100); @@ -386,6 +399,7 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa ByteArrayOutputStream detachedByteOut = null; ArmoredOutputStream detachedArmorOut = null; BCPGOutputStream detachedBcpgOut = null; + MessageDigest raw_digest = null; long opTime, startTime = System.currentTimeMillis(); @@ -491,6 +505,28 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa } else if (enableSignature && data.isDetachedSignature()) { /* detached signature */ + // JMM - GROSS HACK + // + // The PGP stack doesn't do signatures of arbitary data: it wants + // to use its own structure for the message, so we have + // to be creative. We let the pgp stack do its stuff, but when + // it calls out to the NFC stack, we catch it and swap the + // message digest for own own. + // + // this means we have to check cyrptoInput for the correct + // hash ourselves on the 2nd trip through when the + // client resents the request. + // + + if (data.isSshAuth()) { + try { + raw_digest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + log.add(LogType.MSG_PSE_ERROR_IO, indent); + return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log); + } + } + updateProgress(R.string.progress_signing, 8, 100); log.add(LogType.MSG_PSE_SIGNING_DETACHED, indent); @@ -515,6 +551,9 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa while ((length = in.read(buffer)) > 0) { // no output stream is written, no changed to original data! + if (raw_digest != null) + raw_digest.update(buffer, 0, length); + signatureGenerator.update(buffer, 0, length); alreadyWritten += length; @@ -582,11 +621,37 @@ public class PgpSignEncryptOperation extends BaseOperation<PgpSignEncryptInputPa signatureGenerator.generate().encode(pOut); } } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) { - // this secret key diverts to a OpenPGP card, throw exception with hash that will be signed - log.add(LogType.MSG_PSE_PENDING_NFC, indent); - return new PgpSignEncryptResult(log, RequiredInputParcel.createSecurityTokenSignOperation( - signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(), - e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()), cryptoInput); + + // JMM - 2nd part of gross hack + // first swap the message digest for the raw one + // before this hits the NFC stack + + if (raw_digest != null) + e.hashToSign = raw_digest.digest(); + + // Test to see if we already have the answer for the hash + // as the test in encode() above was checking the wrong + // hash. + + byte [] answer = cryptoInput.getCryptoData().get(ByteBuffer.wrap(e.hashToSign)); + + if (raw_digest == null || answer == null) { + // We haven't done the hash yet, so kick it off to the NFC stack + + // this secret key diverts to a OpenPGP card, throw exception with hash that will be signed + + log.add(LogType.MSG_PSE_PENDING_NFC, indent); + return new PgpSignEncryptResult(log, RequiredInputParcel.createSecurityTokenSignOperation( + signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(), + e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()), cryptoInput); + } else if (raw_digest != null) { + // We've aready done the work, replace the output stream + // from the PGP signature with just the signature as we want just + // a raw signature + + detachedByteOut = new ByteArrayOutputStream(); + detachedByteOut.write(answer, 0, answer.length); + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java index 604a5a027..58c3a154e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java @@ -167,6 +167,23 @@ public class CachedPublicKeyRing extends KeyRing { } } + /** Returns the key id which should be used for auth. + * + * This method returns keys which are actually available (ie. secret available, and not stripped, + * revoked, or expired), hence only works on keyrings where a secret key is available! + * + */ + public long getSecretAuthId() throws PgpKeyNotFoundException { + try { + Object data = mProviderHelper.getGenericData(mUri, + KeyRings.HAS_AUTHENTICATE, + ProviderHelper.FIELD_TYPE_INTEGER); + return (Long) data; + } catch(ProviderHelper.NotFoundException e) { + throw new PgpKeyNotFoundException(e); + } + } + @Override public int getVerified() throws PgpKeyNotFoundException { try { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 8a5d09d7b..8f3e4c247 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -336,6 +336,8 @@ public class KeychainProvider extends ContentProvider { "kE." + Keys.KEY_ID + " AS " + KeyRings.HAS_ENCRYPT); projectionMap.put(KeyRings.HAS_SIGN, "kS." + Keys.KEY_ID + " AS " + KeyRings.HAS_SIGN); + projectionMap.put(KeyRings.HAS_AUTHENTICATE, + "kA." + Keys.KEY_ID + " AS " + KeyRings.HAS_AUTHENTICATE); projectionMap.put(KeyRings.HAS_CERTIFY, "kC." + Keys.KEY_ID + " AS " + KeyRings.HAS_CERTIFY); projectionMap.put(KeyRings.IS_EXPIRED, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index c85774ead..c5cbcbb71 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -199,7 +199,8 @@ public class OpenPgpService extends Service { } private Intent signImpl(Intent data, InputStream inputStream, - OutputStream outputStream, boolean cleartextSign) { + OutputStream outputStream, boolean cleartextSign, + boolean sshAuth) { try { boolean asciiArmor = cleartextSign || data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); @@ -209,6 +210,7 @@ public class OpenPgpService extends Service { .setCleartextSignature(cleartextSign) .setDetachedSignature(!cleartextSign) .setVersionHeader(null) + .setSshAuth(sshAuth) .setSignatureHashAlgorithm(PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT); @@ -227,9 +229,15 @@ public class OpenPgpService extends Service { // get first usable subkey capable of signing try { - long signSubKeyId = mProviderHelper.getCachedPublicKeyRing( + long subKeyId; + if (sshAuth) { + subKeyId = mProviderHelper.getCachedPublicKeyRing( + pgpData.getSignatureMasterKeyId()).getSecretAuthId(); + } else { + subKeyId = mProviderHelper.getCachedPublicKeyRing( pgpData.getSignatureMasterKeyId()).getSecretSignId(); - pgpData.setSignatureSubKeyId(signSubKeyId); + } + pgpData.setSignatureSubKeyId(subKeyId); } catch (PgpKeyNotFoundException e) { throw new Exception("signing subkey not found!", e); } @@ -941,15 +949,18 @@ public class OpenPgpService extends Service { return checkPermissionImpl(data); } case OpenPgpApi.ACTION_CLEARTEXT_SIGN: { - return signImpl(data, inputStream, outputStream, true); + return signImpl(data, inputStream, outputStream, true, false); } case OpenPgpApi.ACTION_SIGN: { // DEPRECATED: same as ACTION_CLEARTEXT_SIGN Log.w(Constants.TAG, "You are using a deprecated API call, please use ACTION_CLEARTEXT_SIGN instead of ACTION_SIGN!"); - return signImpl(data, inputStream, outputStream, true); + return signImpl(data, inputStream, outputStream, true, false); } case OpenPgpApi.ACTION_DETACHED_SIGN: { - return signImpl(data, inputStream, outputStream, false); + return signImpl(data, inputStream, outputStream, false, false); + } + case OpenPgpApi.ACTION_SSH_AUTH: { + return signImpl(data, inputStream, outputStream, false, true); } case OpenPgpApi.ACTION_ENCRYPT: { return encryptAndSignImpl(data, inputStream, outputStream, false); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java index bc1c42f7f..deee6366f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -513,9 +513,11 @@ public class SecurityTokenHelper { * @param hash the hash for signing * @return a big integer representing the MPI for the given hash */ - public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException { + public byte[] calculateSignature(byte[] hash, int hashAlgo, boolean useAuthKey) throws IOException { if (!mPw1ValidatedForSignature) { - verifyPin(0x81); // (Verify PW1 with mode 81 for signing) + // (Verify PW1 with mode 81 for signing) + // (Verify PW1 with mode 82 for auth) + verifyPin(useAuthKey ? 0x82 : 0x81); } // dsi, including Lc @@ -525,7 +527,7 @@ public class SecurityTokenHelper { switch (hashAlgo) { case HashAlgorithmTags.SHA1: if (hash.length != 20) { - throw new IOException("Bad hash length (" + hash.length + ", expected 10!"); + throw new IOException("Bad hash length (" + hash.length + ", expected 20!"); } dsi = "23" // Lc + "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes @@ -568,11 +570,24 @@ public class SecurityTokenHelper { throw new IOException("Not supported hash algo!"); } - // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37) - String apdu = - "002A9E9A" // CLA, INS, P1, P2 - + dsi // digital signature input - + "00"; // Le + String apdu; + + if (!useAuthKey) { + // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37) + apdu = + "002A9E9A" // CLA, INS, P1, P2 + + dsi // digital signature input + + "00"; // Le + + } else { + // Command APDU for INTERNAL AUTHENTICATE (page 55) + // This command doesn't take / + apdu = + "00880000" // CLA, INS, P1, P2 + + dsi // digital signature input, card does PKCS#1 + + "00"; // Le + + } String response = communicate(apdu); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 4d07025e6..4e7c06b69 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -200,10 +200,16 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity { break; } case SECURITY_TOKEN_SIGN: { - long tokenKeyId = KeyFormattingUtils.getKeyIdFromFingerprint( + long tokenSignKeyId = KeyFormattingUtils.getKeyIdFromFingerprint( mSecurityTokenHelper.getKeyFingerprint(KeyType.SIGN)); - if (tokenKeyId != mRequiredInput.getSubKeyId()) { + long tokenAuthKeyId = KeyFormattingUtils.getKeyIdFromFingerprint( + mSecurityTokenHelper.getKeyFingerprint(KeyType.AUTH)); + + long requiredKey = mRequiredInput.getSubKeyId(); + + + if ((tokenSignKeyId != requiredKey) && (tokenAuthKeyId != requiredKey)) { throw new IOException(getString(R.string.error_wrong_security_token)); } @@ -212,7 +218,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity { for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] hash = mRequiredInput.mInputData[i]; int algo = mRequiredInput.mSignAlgos[i]; - byte[] signedHash = mSecurityTokenHelper.calculateSignature(hash, algo); + byte[] signedHash = mSecurityTokenHelper.calculateSignature(hash, algo, requiredKey == tokenAuthKeyId); mInputParcel.addCryptoData(hash, signedHash); } break; |