Merge pull request #2217 from hagau/cleanup_auth_sig_gen
Fix EdDSA signature generation for authentication & clean up authentication signature generator
This commit is contained in:
@@ -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<ByteBuffer,byte[]> signedHashes) {
|
||||
private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo, Map<ByteBuffer, byte[]> 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<ByteBuffer, byte[]> signedHashes)
|
||||
|
||||
private PGPContentSignerBuilder getAuthenticationContentSignerBuilder(int hashAlgorithm, Map<ByteBuffer,
|
||||
byte[]> 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<ByteBuffer, byte[]> 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;
|
||||
|
||||
@@ -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<AuthenticationParcel>
|
||||
|
||||
}
|
||||
|
||||
PGPAuthenticationSignatureGenerator signatureGenerator;
|
||||
AuthenticationSignatureGenerator signatureGenerator;
|
||||
try {
|
||||
signatureGenerator = authKey.getAuthenticationSignatureGenerator(
|
||||
hashAlgorithm, cryptoInput.getCryptoData());
|
||||
@@ -218,7 +218,7 @@ public class AuthenticationOperation extends BaseOperation<AuthenticationParcel>
|
||||
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);
|
||||
|
||||
@@ -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<Long> 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<Long> allowedKeyIds = new ArrayList<>(1);
|
||||
allowedKeyIds.add(mStaticRing.getMasterKeyId());
|
||||
allowedKeyIds.add(mStaticRingEcDsa.getMasterKeyId());
|
||||
authData.setAllowedAuthenticationKeyIds(allowedKeyIds);
|
||||
|
||||
AuthenticationParcel authenticationParcel = AuthenticationParcel
|
||||
|
||||
@@ -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);
|
||||
|
||||
BIN
OpenKeychain/src/test/resources/test-keys/authenticate_dsa.sec
Normal file
BIN
OpenKeychain/src/test/resources/test-keys/authenticate_dsa.sec
Normal file
Binary file not shown.
BIN
OpenKeychain/src/test/resources/test-keys/authenticate_eddsa.sec
Normal file
BIN
OpenKeychain/src/test/resources/test-keys/authenticate_eddsa.sec
Normal file
Binary file not shown.
BIN
OpenKeychain/src/test/resources/test-keys/authenticate_rsa.sec
Normal file
BIN
OpenKeychain/src/test/resources/test-keys/authenticate_rsa.sec
Normal file
Binary file not shown.
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user