From 028d869168e3505d2bfeae1c86cb6980e8c083b4 Mon Sep 17 00:00:00 2001 From: Christian Hagau Date: Sun, 12 Nov 2017 00:00:00 +0000 Subject: [PATCH] Add tests for RSA, EdDSA & DSA keys in AuthenticationOperation --- .../AuthenticationOperationTest.java | 248 ++++++++++++++++-- .../resources/test-keys/authenticate_dsa.sec | Bin 0 -> 1925 bytes ...uthenticate.sec => authenticate_ecdsa.sec} | Bin .../test-keys/authenticate_eddsa.sec | Bin 0 -> 493 bytes .../resources/test-keys/authenticate_rsa.sec | Bin 0 -> 2541 bytes 5 files changed, 220 insertions(+), 28 deletions(-) create mode 100644 OpenKeychain/src/test/resources/test-keys/authenticate_dsa.sec rename OpenKeychain/src/test/resources/test-keys/{authenticate.sec => authenticate_ecdsa.sec} (100%) create mode 100644 OpenKeychain/src/test/resources/test-keys/authenticate_eddsa.sec create mode 100644 OpenKeychain/src/test/resources/test-keys/authenticate_rsa.sec 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/resources/test-keys/authenticate_dsa.sec b/OpenKeychain/src/test/resources/test-keys/authenticate_dsa.sec new file mode 100644 index 0000000000000000000000000000000000000000..7a2675f5368b6e289506fa9d033dbf66547ab2df GIT binary patch literal 1925 zcmV;02YUFG18D?W2sG9a2mpcw6W4pW&`c--wV*#dD2LE*Ks9; ztoGl7>>vNzvg`w=LB<3xB?cg}%h%cU?HlaL6FWzwJesrr16@a37qQA~&(FyVI~_>W z^TJSFuiMUL_bdOQz9N>g#;`wOUa>d}Ei(;4un`wA^tfVC~)7f-N zEDg+7w{D(+Pm9n~e&GP1*A7HH3ac02J=I|macgbRpmaOA{UeJwuAyK8>=_v zSAaSeoj8356S^fG4ZaRaGp~uZV-7fg09PHQkZ~5+sYD0cxSm&&&nXY? z@t>k3(RpQR2~eCbwpPub@_8#8*-Lw(Tpa^v;m%ziINH51;*DC|JA z1Y&GqXoy+_6A=mk8Uzao2nPZc3IY}Z0vP}Wf?5bP)&iaZ0-FH<3JDOu5Z9w4^rIT0 z1OT9}iQF+hr}ho&8Wu%oJqIHpsY`PJpGAZL)R!8YetLnPWl=wv`6KS1ww(iM1X>6* z))5E*o4s8rY&pxzKMwUIv%o8n9z)7 z<;4i#q-@u)WeO$wLHNa|#NvvS76Soz{L1r;YGr(I7-N-|jQPI4w5bT)0ci7h#e;LT zMLJ{V$(fF*IP18CQ{QJeoLi;Y50EY#)FU$f%< z(RC8ge#Q#Oe_vI)kn&1~;;e47ZEzXF3x-X~s1)ipFsj^V32`eH8-{^?QnIf&Nqz!b zZT2}hAcRWPju268H_2=@LYTWHfXv&gu+^LBRi}?#Hk|a7kX2*gb?N*1WGCwa5(*3p z0HEwEMMqtF?T|sNeNrfY2~+x(=0^wm1CMM(KM*H~UN2Z%bazWqjQTFnr!X4q4#qAK zr|1uDV$%Z#lUQ~G#dgqVc8vbM^H=zvr+~bL=9kLYx@GQthGL|s99zlS9hoVCWm+a9 zs0=@T75DCKh6CDfg4T)~9i$gJ`rY>gBlr#Pb<6Xr@&9C7Z!%9EfUg@6=NTE7>aBHo zfwnhU?gvWLZ>_sMr8Km{K`jp_<}7*f#W`&T3NGc&i#zlfuc& zs%Xa`eKn{e5v&1aEX(hezmO&G2mKJ{2>kgz-0n^2I9{QGlfAsR*GIvn9_J#NO%NjF zwSh-7h8e3F9d1mu+tV!i*o4Q(~AtnSCsuDU%r=g2m&r5<&Q4H zqeW?6QK@VsbLKMdzxzoD>H%-hYB{wM3N9_yT=m!l()ym@^kpi>J$IQ5P(8Bi#^-mn zYne++x_NC#pOf`v*>Bn7sdy4v)FaV4y=YF2+AKB#$7|*Y?H-=RQu_!QQ_0fScc%S0 z$i+T7J$8&kN;_>!^F!lT*Xmqzd2cioL&r=wO%5^Pw)(vWo_ LHO6z%@*Mb@K-iB- literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8e21c2f416761cb995b315788c1ed1057be2aafc GIT binary patch literal 493 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 D$3NY} literal 0 HcmV?d00001