diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index 4d01257cd..66b1af50a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -26,9 +26,10 @@ import java.security.interfaces.RSAPrivateCrtKey; import java.util.Date; import java.util.Map; +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; -import org.bouncycastle.openpgp.PGPAuthenticationSignatureGenerator; +import org.bouncycastle.openpgp.AuthenticationSignatureGenerator; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPSecretKey; @@ -37,13 +38,7 @@ import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter; -import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; -import org.bouncycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; -import org.bouncycastle.openpgp.operator.jcajce.SessionKeySecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.jcajce.*; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; @@ -219,8 +214,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { return true; } - private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, - Map signedHashes) { + private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, Map signedHashes) { if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) { // use synchronous "NFC based" SignerBuilder return new NfcSyncPGPContentSignerBuilder( @@ -253,17 +247,33 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { } } - public PGPAuthenticationSignatureGenerator getAuthenticationSignatureGenerator(int hashAlgorithm, - Map signedHashes) + + private PGPContentSignerBuilder getAuthenticationContentSignerBuilder(int hashAlgorithm, Map signedHashes) { + if (getAlgorithm() == PublicKeyAlgorithmTags.EDDSA) { + // content signer feeding the input directly into the signature engine, + // since EdDSA hashes the input anyway + return new EdDsaAuthenticationContentSignerBuilder( + mSecretKey.getPublicKey().getAlgorithm(), hashAlgorithm) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); + } else { + return getContentSignerBuilder(hashAlgorithm, signedHashes); + } + } + + public AuthenticationSignatureGenerator getAuthenticationSignatureGenerator(int hashAlgorithm, + Map signedHashes) throws PgpGeneralException { if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) { throw new PrivateKeyNotUnlockedException(); } - PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(hashAlgorithm, signedHashes); + PGPContentSignerBuilder contentSignerBuilder = + getAuthenticationContentSignerBuilder(hashAlgorithm, signedHashes); try { - PGPAuthenticationSignatureGenerator signatureGenerator = new PGPAuthenticationSignatureGenerator(contentSignerBuilder); + AuthenticationSignatureGenerator signatureGenerator = + new AuthenticationSignatureGenerator(contentSignerBuilder); signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, mPrivateKey); return signatureGenerator; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ssh/AuthenticationOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ssh/AuthenticationOperation.java index 174efc4da..5bef23b0d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ssh/AuthenticationOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ssh/AuthenticationOperation.java @@ -19,7 +19,7 @@ package org.sufficientlysecure.keychain.ssh; import android.content.Context; import android.support.annotation.NonNull; -import org.bouncycastle.openpgp.PGPAuthenticationSignatureGenerator; +import org.bouncycastle.openpgp.AuthenticationSignatureGenerator; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder; import org.sufficientlysecure.keychain.operations.BaseOperation; @@ -206,7 +206,7 @@ public class AuthenticationOperation extends BaseOperation } - PGPAuthenticationSignatureGenerator signatureGenerator; + AuthenticationSignatureGenerator signatureGenerator; try { signatureGenerator = authKey.getAuthenticationSignatureGenerator( hashAlgorithm, cryptoInput.getCryptoData()); @@ -218,7 +218,7 @@ public class AuthenticationOperation extends BaseOperation signatureGenerator.update(challenge, 0, challenge.length); try { - signature = signatureGenerator.generate().getSignature(); + signature = signatureGenerator.getSignature(); } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) { // this secret key diverts to a OpenPGP card, thus requires user interaction log.add(LogType.MSG_AUTH_PENDING_NFC, indent); diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java index 2a35a65c0..898e2a91d 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java @@ -20,6 +20,9 @@ package org.sufficientlysecure.keychain.operations; import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.jcajce.provider.asymmetric.eddsa.EdDSAEngine; +import org.bouncycastle.jcajce.provider.asymmetric.eddsa.spec.EdDSANamedCurveTable; +import org.bouncycastle.jcajce.provider.asymmetric.eddsa.spec.EdDSAParameterSpec; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.Assert; import org.junit.Before; @@ -42,6 +45,7 @@ import org.sufficientlysecure.keychain.support.KeyringTestingHelper; import org.sufficientlysecure.keychain.util.Passphrase; import java.io.PrintStream; +import java.security.MessageDigest; import java.security.PublicKey; import java.security.Security; import java.security.Signature; @@ -50,35 +54,84 @@ import java.util.ArrayList; @RunWith(KeychainTestRunner.class) public class AuthenticationOperationTest { - private static UncachedKeyRing mStaticRing; + private static UncachedKeyRing mStaticRingRsa; + private static UncachedKeyRing mStaticRingEcDsa; + private static UncachedKeyRing mStaticRingEdDsa; + private static UncachedKeyRing mStaticRingDsa; private static Passphrase mKeyPhrase; private static PrintStream oldShadowStream; + /* + private static void generateKeys() throws IOException { + PgpKeyOperation op = new PgpKeyOperation(null); + SaveKeyringParcel.Builder builder = SaveKeyringParcel.buildNewKeyringParcel(); + + builder.addSubkeyAdd(SaveKeyringParcel.SubkeyAdd.createSubkeyAdd( + SaveKeyringParcel.Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.CERTIFY_OTHER, 0L)); + builder.addSubkeyAdd(SaveKeyringParcel.SubkeyAdd.createSubkeyAdd( + SaveKeyringParcel.Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.AUTHENTICATION, 0L)); + builder.addUserId("blah"); + builder.setNewUnlock(ChangeUnlockParcel.createUnLockParcelForNewKey(new Passphrase("x"))); + + PgpEditKeyResult result = op.createSecretKeyRing(builder.build()); + new FileOutputStream("/tmp/authenticate_ecdsa.sec").write(result.getRing().getEncoded()); + + + op = new PgpKeyOperation(null); + builder = SaveKeyringParcel.buildNewKeyringParcel(); + + builder.addSubkeyAdd(SaveKeyringParcel.SubkeyAdd.createSubkeyAdd( + SaveKeyringParcel.Algorithm.EDDSA, 0, null, KeyFlags.CERTIFY_OTHER, 0L)); + builder.addSubkeyAdd(SaveKeyringParcel.SubkeyAdd.createSubkeyAdd( + SaveKeyringParcel.Algorithm.EDDSA, 0, null, KeyFlags.AUTHENTICATION, 0L)); + builder.addUserId("blah"); + builder.setNewUnlock(ChangeUnlockParcel.createUnLockParcelForNewKey(new Passphrase("x"))); + + result = op.createSecretKeyRing(builder.build()); + new FileOutputStream("/tmp/authenticate_eddsa.sec").write(result.getRing().getEncoded()); + + + op = new PgpKeyOperation(null); + builder = SaveKeyringParcel.buildNewKeyringParcel(); + + builder.addSubkeyAdd(SaveKeyringParcel.SubkeyAdd.createSubkeyAdd( + SaveKeyringParcel.Algorithm.RSA, 2048, null, KeyFlags.CERTIFY_OTHER, 0L)); + builder.addSubkeyAdd(SaveKeyringParcel.SubkeyAdd.createSubkeyAdd( + SaveKeyringParcel.Algorithm.RSA, 2048, null, KeyFlags.AUTHENTICATION, 0L)); + builder.addUserId("blah"); + builder.setNewUnlock(ChangeUnlockParcel.createUnLockParcelForNewKey(new Passphrase("x"))); + + result = op.createSecretKeyRing(builder.build()); + new FileOutputStream("/tmp/authenticate_rsa.sec").write(result.getRing().getEncoded()); + + + op = new PgpKeyOperation(null); + builder = SaveKeyringParcel.buildNewKeyringParcel(); + + builder.addSubkeyAdd(SaveKeyringParcel.SubkeyAdd.createSubkeyAdd( + SaveKeyringParcel.Algorithm.DSA, 2048, null, KeyFlags.CERTIFY_OTHER, 0L)); + builder.addSubkeyAdd(SaveKeyringParcel.SubkeyAdd.createSubkeyAdd( + SaveKeyringParcel.Algorithm.DSA, 2048, null, KeyFlags.AUTHENTICATION, 0L)); + builder.addUserId("blah"); + builder.setNewUnlock(ChangeUnlockParcel.createUnLockParcelForNewKey(new Passphrase("x"))); + + result = op.createSecretKeyRing(builder.build()); + new FileOutputStream("/tmp/authenticate_dsa.sec").write(result.getRing().getEncoded()); + } + */ + @BeforeClass public static void setUpOnce() throws Exception { Security.insertProviderAt(new BouncyCastleProvider(), 1); oldShadowStream = ShadowLog.stream; // ShadowLog.stream = System.out; - /* keyring generation: - PgpKeyOperation op = new PgpKeyOperation(null); - SaveKeyringParcel.Builder builder = SaveKeyringParcel.buildNewKeyringParcel(); - - builder.addSubkeyAdd(SubkeyAdd.createSubkeyAdd( - Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.CERTIFY_OTHER, 0L)); - builder.addSubkeyAdd(SubkeyAdd.createSubkeyAdd( - Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.AUTHENTICATION, 0L)); - builder.addUserId("blah"); - builder.setNewUnlock(ChangeUnlockParcel.createUnLockParcelForNewKey(new Passphrase("x"))); - - PgpEditKeyResult result = op.createSecretKeyRing(builder.build()); - new FileOutputStream("/tmp/authenticate.sec").write(result.getRing().getEncoded()); - */ - mKeyPhrase = new Passphrase("x"); - mStaticRing = KeyringTestingHelper.readRingFromResource("/test-keys/authenticate.sec"); - + mStaticRingRsa = KeyringTestingHelper.readRingFromResource("/test-keys/authenticate_rsa.sec"); + mStaticRingEcDsa = KeyringTestingHelper.readRingFromResource("/test-keys/authenticate_ecdsa.sec"); + mStaticRingEdDsa = KeyringTestingHelper.readRingFromResource("/test-keys/authenticate_eddsa.sec"); + mStaticRingDsa = KeyringTestingHelper.readRingFromResource("/test-keys/authenticate_dsa.sec"); } @Before @@ -89,21 +142,24 @@ public class AuthenticationOperationTest { // don't log verbosely here, we're not here to test imports ShadowLog.stream = oldShadowStream; - databaseInteractor.saveSecretKeyRing(mStaticRing); + databaseInteractor.saveSecretKeyRing(mStaticRingRsa); + databaseInteractor.saveSecretKeyRing(mStaticRingEcDsa); + databaseInteractor.saveSecretKeyRing(mStaticRingEdDsa); + databaseInteractor.saveSecretKeyRing(mStaticRingDsa); // ok NOW log verbosely! ShadowLog.stream = System.out; } @Test - public void testAuthenticate() throws Exception { + public void testAuthenticateRsa() throws Exception { byte[] challenge = "dies ist ein challenge ☭".getBytes(); byte[] signature; KeyRepository keyRepository = KeyRepository.create(RuntimeEnvironment.application); - long masterKeyId = mStaticRing.getMasterKeyId(); + long masterKeyId = mStaticRingRsa.getMasterKeyId(); Long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretAuthenticationId(); { // sign challenge @@ -115,10 +171,6 @@ public class AuthenticationOperationTest { authData.setAuthenticationSubKeyId(authSubKeyId); authData.setHashAlgorithm(HashAlgorithmTags.SHA512); -// ArrayList allowedKeyIds = new ArrayList<>(1); -// allowedKeyIds.add(mStaticRing.getMasterKeyId()); -// authData.setAllowedAuthenticationKeyIds(allowedKeyIds); - AuthenticationParcel authenticationParcel = AuthenticationParcel .createAuthenticationParcel(authData.build(), challenge); @@ -133,7 +185,53 @@ public class AuthenticationOperationTest { } { // verify signature CanonicalizedPublicKey canonicalizedPublicKey = keyRepository.getCanonicalizedPublicKeyRing(masterKeyId) - .getPublicKey(authSubKeyId); + .getPublicKey(authSubKeyId); + PublicKey publicKey = canonicalizedPublicKey.getJcaPublicKey(); + + Signature signatureVerifier = Signature.getInstance("SHA512withRSA"); + signatureVerifier.initVerify(publicKey); + signatureVerifier.update(challenge); + boolean isSignatureValid = signatureVerifier.verify(signature); + + Assert.assertTrue("signature must be valid", isSignatureValid); + } + } + + @Test + public void testAuthenticateEcDsa() throws Exception { + + byte[] challenge = "dies ist ein challenge ☭".getBytes(); + byte[] signature; + + KeyRepository keyRepository = KeyRepository.create(RuntimeEnvironment.application); + + long masterKeyId = mStaticRingEcDsa.getMasterKeyId(); + Long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretAuthenticationId(); + + { // sign challenge + AuthenticationOperation op = new AuthenticationOperation(RuntimeEnvironment.application, + keyRepository); + + AuthenticationData.Builder authData = AuthenticationData.builder(); + authData.setAuthenticationMasterKeyId(masterKeyId); + authData.setAuthenticationSubKeyId(authSubKeyId); + authData.setHashAlgorithm(HashAlgorithmTags.SHA512); + + AuthenticationParcel authenticationParcel = AuthenticationParcel + .createAuthenticationParcel(authData.build(), challenge); + + CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel(); + inputParcel = inputParcel.withPassphrase(mKeyPhrase); + + AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel); + + Assert.assertTrue("authentication must succeed", result.success()); + + signature = result.getSignature(); + } + { // verify signature + CanonicalizedPublicKey canonicalizedPublicKey = keyRepository.getCanonicalizedPublicKeyRing(masterKeyId) + .getPublicKey(authSubKeyId); PublicKey publicKey = canonicalizedPublicKey.getJcaPublicKey(); Signature signatureVerifier = Signature.getInstance("SHA512withECDSA"); @@ -145,6 +243,100 @@ public class AuthenticationOperationTest { } } + @Test + public void testAuthenticateEdDsa() throws Exception { + + byte[] challenge = "dies ist ein challenge ☭".getBytes(); + byte[] signature; + + KeyRepository keyRepository = KeyRepository.create(RuntimeEnvironment.application); + + long masterKeyId = mStaticRingEdDsa.getMasterKeyId(); + Long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretAuthenticationId(); + + { // sign challenge + AuthenticationOperation op = new AuthenticationOperation(RuntimeEnvironment.application, + keyRepository); + + AuthenticationData.Builder authData = AuthenticationData.builder(); + authData.setAuthenticationMasterKeyId(masterKeyId); + authData.setAuthenticationSubKeyId(authSubKeyId); + authData.setHashAlgorithm(HashAlgorithmTags.SHA512); + + AuthenticationParcel authenticationParcel = AuthenticationParcel + .createAuthenticationParcel(authData.build(), challenge); + + CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel(); + inputParcel = inputParcel.withPassphrase(mKeyPhrase); + + AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel); + + Assert.assertTrue("authentication must succeed", result.success()); + + signature = result.getSignature(); + } + { // verify signature + CanonicalizedPublicKey canonicalizedPublicKey = keyRepository.getCanonicalizedPublicKeyRing(masterKeyId) + .getPublicKey(authSubKeyId); + PublicKey publicKey = canonicalizedPublicKey.getJcaPublicKey(); + + EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519"); + Signature signatureVerifier = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); + signatureVerifier.setParameter(EdDSAEngine.ONE_SHOT_MODE); + signatureVerifier.initVerify(publicKey); + signatureVerifier.update(challenge); + boolean isSignatureValid = signatureVerifier.verify(signature); + + Assert.assertTrue("signature must be valid", isSignatureValid); + } + } + + @Test + public void testAuthenticateDsa() throws Exception { + + byte[] challenge = "dies ist ein challenge ☭".getBytes(); + byte[] signature; + + KeyRepository keyRepository = KeyRepository.create(RuntimeEnvironment.application); + + long masterKeyId = mStaticRingDsa.getMasterKeyId(); + Long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretAuthenticationId(); + + { // sign challenge + AuthenticationOperation op = new AuthenticationOperation(RuntimeEnvironment.application, + keyRepository); + + AuthenticationData.Builder authData = AuthenticationData.builder(); + authData.setAuthenticationMasterKeyId(masterKeyId); + authData.setAuthenticationSubKeyId(authSubKeyId); + authData.setHashAlgorithm(HashAlgorithmTags.SHA256); + + AuthenticationParcel authenticationParcel = AuthenticationParcel + .createAuthenticationParcel(authData.build(), challenge); + + CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel(); + inputParcel = inputParcel.withPassphrase(mKeyPhrase); + + AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel); + + Assert.assertTrue("authentication must succeed", result.success()); + + signature = result.getSignature(); + } + { // verify signature + CanonicalizedPublicKey canonicalizedPublicKey = keyRepository.getCanonicalizedPublicKeyRing(masterKeyId) + .getPublicKey(authSubKeyId); + PublicKey publicKey = canonicalizedPublicKey.getJcaPublicKey(); + + Signature signatureVerifier = Signature.getInstance("SHA256withDSA"); + signatureVerifier.initVerify(publicKey); + signatureVerifier.update(challenge); + boolean isSignatureValid = signatureVerifier.verify(signature); + + Assert.assertTrue("signature must be valid", isSignatureValid); + } + } + @Test public void testAccessControl() throws Exception { @@ -152,7 +344,7 @@ public class AuthenticationOperationTest { KeyRepository keyRepository = KeyRepository.create(RuntimeEnvironment.application); - long masterKeyId = mStaticRing.getMasterKeyId(); + long masterKeyId = mStaticRingEcDsa.getMasterKeyId(); Long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretAuthenticationId(); { // sign challenge - should succeed with selected key allowed @@ -165,7 +357,7 @@ public class AuthenticationOperationTest { authData.setHashAlgorithm(HashAlgorithmTags.SHA512); ArrayList allowedKeyIds = new ArrayList<>(1); - allowedKeyIds.add(mStaticRing.getMasterKeyId()); + allowedKeyIds.add(mStaticRingEcDsa.getMasterKeyId()); authData.setAllowedAuthenticationKeyIds(allowedKeyIds); AuthenticationParcel authenticationParcel = AuthenticationParcel diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ssh/SshPublicKeyTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ssh/SshPublicKeyTest.java index e7003b6e6..088105ffc 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ssh/SshPublicKeyTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/ssh/SshPublicKeyTest.java @@ -42,7 +42,7 @@ import java.security.Security; @RunWith(KeychainTestRunner.class) public class SshPublicKeyTest { - private static UncachedKeyRing mStaticRing; + private static UncachedKeyRing mStaticRingEcDsa; private static Passphrase mKeyPhrase; private static PrintStream oldShadowStream; @@ -53,23 +53,8 @@ public class SshPublicKeyTest { oldShadowStream = ShadowLog.stream; // ShadowLog.stream = System.out; - /* keyring generation: - PgpKeyOperation op = new PgpKeyOperation(null); - SaveKeyringParcel.Builder builder = SaveKeyringParcel.buildNewKeyringParcel(); - - builder.addSubkeyAdd(SubkeyAdd.createSubkeyAdd( - Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.CERTIFY_OTHER, 0L)); - builder.addSubkeyAdd(SubkeyAdd.createSubkeyAdd( - Algorithm.ECDSA, 0, SaveKeyringParcel.Curve.NIST_P256, KeyFlags.AUTHENTICATION, 0L)); - builder.addUserId("blah"); - builder.setNewUnlock(ChangeUnlockParcel.createUnLockParcelForNewKey(new Passphrase("x"))); - - PgpEditKeyResult result = op.createSecretKeyRing(builder.build()); - new FileOutputStream("/tmp/authenticate.sec").write(result.getRing().getEncoded()); - */ - mKeyPhrase = new Passphrase("x"); - mStaticRing = KeyringTestingHelper.readRingFromResource("/test-keys/authenticate.sec"); + mStaticRingEcDsa = KeyringTestingHelper.readRingFromResource("/test-keys/authenticate_ecdsa.sec"); } @Before @@ -80,7 +65,7 @@ public class SshPublicKeyTest { // don't log verbosely here, we're not here to test imports ShadowLog.stream = oldShadowStream; - databaseInteractor.saveSecretKeyRing(mStaticRing); + databaseInteractor.saveSecretKeyRing(mStaticRingEcDsa); // ok NOW log verbosely! ShadowLog.stream = System.out; @@ -90,7 +75,7 @@ public class SshPublicKeyTest { public void testECDSA() throws Exception { KeyRepository keyRepository = KeyRepository.create(RuntimeEnvironment.application); - long masterKeyId = mStaticRing.getMasterKeyId(); + long masterKeyId = mStaticRingEcDsa.getMasterKeyId(); long authSubKeyId = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretAuthenticationId(); CanonicalizedPublicKey canonicalizedPublicKey = keyRepository.getCanonicalizedPublicKeyRing(masterKeyId) .getPublicKey(authSubKeyId); diff --git a/OpenKeychain/src/test/resources/test-keys/authenticate_dsa.sec b/OpenKeychain/src/test/resources/test-keys/authenticate_dsa.sec new file mode 100644 index 000000000..7a2675f53 Binary files /dev/null and b/OpenKeychain/src/test/resources/test-keys/authenticate_dsa.sec differ diff --git a/OpenKeychain/src/test/resources/test-keys/authenticate.sec b/OpenKeychain/src/test/resources/test-keys/authenticate_ecdsa.sec similarity index 100% rename from OpenKeychain/src/test/resources/test-keys/authenticate.sec rename to OpenKeychain/src/test/resources/test-keys/authenticate_ecdsa.sec diff --git a/OpenKeychain/src/test/resources/test-keys/authenticate_eddsa.sec b/OpenKeychain/src/test/resources/test-keys/authenticate_eddsa.sec new file mode 100644 index 000000000..8e21c2f41 Binary files /dev/null and b/OpenKeychain/src/test/resources/test-keys/authenticate_eddsa.sec differ diff --git a/OpenKeychain/src/test/resources/test-keys/authenticate_rsa.sec b/OpenKeychain/src/test/resources/test-keys/authenticate_rsa.sec new file mode 100644 index 000000000..4243ae737 Binary files /dev/null and b/OpenKeychain/src/test/resources/test-keys/authenticate_rsa.sec differ diff --git a/libkeychain/src/main/java/org/bouncycastle/openpgp/AuthenticationSignatureGenerator.java b/libkeychain/src/main/java/org/bouncycastle/openpgp/AuthenticationSignatureGenerator.java new file mode 100644 index 000000000..99889550c --- /dev/null +++ b/libkeychain/src/main/java/org/bouncycastle/openpgp/AuthenticationSignatureGenerator.java @@ -0,0 +1,86 @@ +package org.bouncycastle.openpgp; + +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.openpgp.operator.PGPContentSigner; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Generator for authentication signatures. + */ +public class AuthenticationSignatureGenerator { + private OutputStream sigOut; + private PGPContentSignerBuilder contentSignerBuilder; + private PGPContentSigner contentSigner; + private int sigType; + + /** + * Create a signature generator built on the passed in contentSignerBuilder. + * + * @param contentSignerBuilder builder to produce PGPContentSigner objects for generating signatures. + */ + public AuthenticationSignatureGenerator(PGPContentSignerBuilder contentSignerBuilder) { + this.contentSignerBuilder = contentSignerBuilder; + } + + /** + * Initialise the generator for signing. + * + * @param signatureType + * @param key + * @throws PGPException + */ + public void init(int signatureType, PGPPrivateKey key) throws PGPException { + contentSigner = contentSignerBuilder.build(signatureType, key); + sigOut = contentSigner.getOutputStream(); + sigType = contentSigner.getType(); + } + + public void update(byte b) { + byteUpdate(b); + } + + public void update(byte[] b) { + update(b, 0, b.length); + } + + public void update(byte[] b, int off, int len) { + blockUpdate(b, off, len); + } + + private void byteUpdate(byte b) { + try { + sigOut.write(b); + } catch (IOException e) { + throw new PGPRuntimeOperationException(e.getMessage(), e); + } + } + + private void blockUpdate(byte[] block, int off, int len) { + try { + sigOut.write(block, off, len); + } catch (IOException e) { + throw new PGPRuntimeOperationException(e.getMessage(), e); + } + } + + /** + * Return the signature. + * + * @return byte[] + * @throws PGPException + */ + public byte[] getSignature() throws PGPException { + if (contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN + || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_GENERAL + || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.EDDSA + || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.ECDSA + || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.DSA) { + return contentSigner.getSignature(); + } else { + throw new UnsupportedOperationException("Unsupported algorithm"); + } + } +} diff --git a/libkeychain/src/main/java/org/bouncycastle/openpgp/PGPAuthenticationSignatureGenerator.java b/libkeychain/src/main/java/org/bouncycastle/openpgp/PGPAuthenticationSignatureGenerator.java deleted file mode 100644 index 1ee235642..000000000 --- a/libkeychain/src/main/java/org/bouncycastle/openpgp/PGPAuthenticationSignatureGenerator.java +++ /dev/null @@ -1,201 +0,0 @@ -package org.bouncycastle.openpgp; - -import org.bouncycastle.bcpg.MPInteger; -import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; -import org.bouncycastle.bcpg.SignaturePacket; -import org.bouncycastle.bcpg.SignatureSubpacket; -import org.bouncycastle.openpgp.operator.PGPContentSigner; -import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; -import org.bouncycastle.util.BigIntegers; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.math.BigInteger; - -/** - * Generator for PGP Signatures. - */ -public class PGPAuthenticationSignatureGenerator -{ - private OutputStream sigOut; - private PGPContentSignerBuilder contentSignerBuilder; - private PGPContentSigner contentSigner; - private int sigType; - private byte lastb; - private int providedKeyAlgorithm = -1; - - /** - * Create a signature generator built on the passed in contentSignerBuilder. - * - * @param contentSignerBuilder builder to produce PGPContentSigner objects for generating signatures. - */ - public PGPAuthenticationSignatureGenerator( - PGPContentSignerBuilder contentSignerBuilder) - { - this.contentSignerBuilder = contentSignerBuilder; - } - - /** - * Initialise the generator for signing. - * - * @param signatureType - * @param key - * @throws PGPException - */ - public void init( - int signatureType, - PGPPrivateKey key) - throws PGPException - { - contentSigner = contentSignerBuilder.build(signatureType, key); - sigOut = contentSigner.getOutputStream(); - sigType = contentSigner.getType(); - lastb = 0; - - if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm()) - { - throw new PGPException("key algorithm mismatch"); - } - } - - public void update( - byte b) - { - if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT) - { - if (b == '\r') - { - byteUpdate((byte)'\r'); - byteUpdate((byte)'\n'); - } - else if (b == '\n') - { - if (lastb != '\r') - { - byteUpdate((byte)'\r'); - byteUpdate((byte)'\n'); - } - } - else - { - byteUpdate(b); - } - - lastb = b; - } - else - { - byteUpdate(b); - } - } - - public void update( - byte[] b) - { - this.update(b, 0, b.length); - } - - public void update( - byte[] b, - int off, - int len) - { - if (sigType == PGPSignature.CANONICAL_TEXT_DOCUMENT) - { - int finish = off + len; - - for (int i = off; i != finish; i++) - { - this.update(b[i]); - } - } - else - { - blockUpdate(b, off, len); - } - } - - private void byteUpdate(byte b) - { - try - { - sigOut.write(b); - } - catch (IOException e) - { - throw new PGPRuntimeOperationException(e.getMessage(), e); - } - } - - private void blockUpdate(byte[] block, int off, int len) - { - try - { - sigOut.write(block, off, len); - } - catch (IOException e) - { - throw new PGPRuntimeOperationException(e.getMessage(), e); - } - } - - /** - * Return a signature object containing the current signature state. - * - * @return PGPSignature - * @throws PGPException - */ - public PGPSignature generate() - throws PGPException - { - MPInteger[] sigValues; - ByteArrayOutputStream sOut = new ByteArrayOutputStream(); - SignatureSubpacket[] hPkts, unhPkts; - hPkts = new SignatureSubpacket[0]; - unhPkts = new SignatureSubpacket[0]; - - try - { - ByteArrayOutputStream hOut = new ByteArrayOutputStream(); - byte[] data = hOut.toByteArray(); - sOut.write(data); - } - catch (IOException e) - { - throw new PGPException("exception encoding hashed data.", e); - } - - byte[] trailer = sOut.toByteArray(); - - blockUpdate(trailer, 0, trailer.length); - - if (contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_SIGN - || contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.RSA_GENERAL) // an RSA signature - { - sigValues = new MPInteger[1]; - sigValues[0] = new MPInteger(new BigInteger(1, contentSigner.getSignature())); - } - else if (contentSigner.getKeyAlgorithm() == PublicKeyAlgorithmTags.EDDSA) - { - byte[] sig = contentSigner.getSignature(); - - sigValues = new MPInteger[2]; - - sigValues[0] = new MPInteger(BigIntegers.fromUnsignedByteArray(sig, 0, 32)); - sigValues[1] = new MPInteger(BigIntegers.fromUnsignedByteArray(sig, 32, 32)); - } - else - { - sigValues = PGPUtil.dsaSigToMpi(contentSigner.getSignature()); - } - - byte[] digest = contentSigner.getDigest(); - byte[] fingerPrint = new byte[2]; - - fingerPrint[0] = digest[0]; - fingerPrint[1] = digest[1]; - - return new PGPSignature(new SignaturePacket(sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(), contentSigner.getHashAlgorithm(), hPkts, unhPkts, fingerPrint, sigValues)); - } -} diff --git a/libkeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/EdDsaAuthenticationContentSignerBuilder.java b/libkeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/EdDsaAuthenticationContentSignerBuilder.java new file mode 100644 index 000000000..91289b478 --- /dev/null +++ b/libkeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/EdDsaAuthenticationContentSignerBuilder.java @@ -0,0 +1,101 @@ +package org.bouncycastle.openpgp.operator.jcajce; + +import org.bouncycastle.jcajce.provider.asymmetric.eddsa.EdDSAEngine; +import org.bouncycastle.jcajce.provider.asymmetric.eddsa.spec.EdDSANamedCurveTable; +import org.bouncycastle.jcajce.provider.asymmetric.eddsa.spec.EdDSAParameterSpec; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPRuntimeOperationException; +import org.bouncycastle.openpgp.operator.PGPContentSigner; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.security.*; + +public class EdDsaAuthenticationContentSignerBuilder implements PGPContentSignerBuilder { + private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter(); + private int hashAlgorithm; + private int keyAlgorithm; + + public EdDsaAuthenticationContentSignerBuilder(int keyAlgorithm, int hashAlgorithm) { + this.keyAlgorithm = keyAlgorithm; + this.hashAlgorithm = hashAlgorithm; + } + + public EdDsaAuthenticationContentSignerBuilder setProvider(Provider provider) { + keyConverter.setProvider(provider); + return this; + } + + public EdDsaAuthenticationContentSignerBuilder setProvider(String providerName) { + keyConverter.setProvider(providerName); + return this; + } + + private Signature createSignature() throws NoSuchAlgorithmException { + EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName("Ed25519"); + return new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm())); + } + + public PGPContentSigner build(final int signatureType, final long keyID, final PrivateKey privateKey) + throws PGPException { + Signature signatureEdDsa; + try { + signatureEdDsa = createSignature(); + } catch (NoSuchAlgorithmException e) { + throw new PGPException("unable to create Signature.", e); + } + final Signature signature = signatureEdDsa; + + final ByteArrayOutputStream dataOutputStream = new ByteArrayOutputStream(); + + try { + signature.initSign(privateKey); + } catch (InvalidKeyException e) { + throw new PGPException("invalid key.", e); + } + + return new PGPContentSigner() { + public int getType() { + return signatureType; + } + + public int getHashAlgorithm() { + return hashAlgorithm; + } + + public int getKeyAlgorithm() { + return keyAlgorithm; + } + + public long getKeyID() { + return keyID; + } + + public OutputStream getOutputStream() { + return new SignatureOutputStream(signature); + } + + public byte[] getSignature() { + try { + return signature.sign(); + } catch (SignatureException e) { + throw new PGPRuntimeOperationException("Unable to create signature: " + e.getMessage(), e); + } + } + + public byte[] getDigest() { + return null; + } + }; + } + + public PGPContentSigner build(final int signatureType, PGPPrivateKey privateKey) throws PGPException { + if (privateKey instanceof JcaPGPPrivateKey) { + return build(signatureType, privateKey.getKeyID(), ((JcaPGPPrivateKey) privateKey).getPrivateKey()); + } else { + return build(signatureType, privateKey.getKeyID(), keyConverter.getPrivateKey(privateKey)); + } + } +}