WIP: Start working on generifying key protection for argon2

This commit is contained in:
Paul Schaub 2023-06-20 14:05:41 +02:00
parent 600d5f49bb
commit 007e5a2bbe
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
14 changed files with 166 additions and 103 deletions

View File

@ -739,7 +739,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
@Override @Override
public WithPassphrase withSecureDefaultSettings() { public WithPassphrase withSecureDefaultSettings() {
return withCustomSettings(KeyRingProtectionSettings.secureDefaultSettings()); return withCustomSettings(KeyRingProtectionSettings.saltedAndIterated());
} }
@Override @Override

View File

@ -536,7 +536,7 @@ public interface SecretKeyRingEditorInterface {
*/ */
default WithKeyRingEncryptionSettings changePassphraseFromOldPassphrase( default WithKeyRingEncryptionSettings changePassphraseFromOldPassphrase(
@Nullable Passphrase oldPassphrase) { @Nullable Passphrase oldPassphrase) {
return changePassphraseFromOldPassphrase(oldPassphrase, KeyRingProtectionSettings.secureDefaultSettings()); return changePassphraseFromOldPassphrase(oldPassphrase, KeyRingProtectionSettings.saltedAndIterated());
} }
/** /**
@ -563,7 +563,7 @@ public interface SecretKeyRingEditorInterface {
default WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase( default WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase(
@Nonnull Long keyId, @Nonnull Long keyId,
@Nullable Passphrase oldPassphrase) { @Nullable Passphrase oldPassphrase) {
return changeSubKeyPassphraseFromOldPassphrase(keyId, oldPassphrase, KeyRingProtectionSettings.secureDefaultSettings()); return changeSubKeyPassphraseFromOldPassphrase(keyId, oldPassphrase, KeyRingProtectionSettings.saltedAndIterated());
} }
WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase( WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase(

View File

@ -0,0 +1,31 @@
package org.pgpainless.key.protection;
import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.pgpainless.algorithm.AEADAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.util.Passphrase;
public class Argon2AEAD implements KeyRingProtectionSettings {
private final S2K.Argon2Params parameters;
private final SymmetricKeyAlgorithm encryptionAlgorithm;
private final AEADAlgorithm aeadAlgorithm;
public Argon2AEAD() {
this(S2K.Argon2Params.universallyRecommendedParameters(), SymmetricKeyAlgorithm.AES_256, AEADAlgorithm.OCB);
}
public Argon2AEAD(S2K.Argon2Params parameters, SymmetricKeyAlgorithm encryptionAlgorithm, AEADAlgorithm aeadAlgorithm) {
this.parameters = parameters;
this.encryptionAlgorithm = encryptionAlgorithm;
this.aeadAlgorithm = aeadAlgorithm;
}
@Override
public PBESecretKeyEncryptor getEncryptor(Passphrase passphrase) throws PGPException {
// TODO: Implement.
return null;
}
}

View File

@ -14,7 +14,7 @@ import org.pgpainless.util.Passphrase;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
* Basic {@link SecretKeyRingProtector} implementation that respects the users {@link KeyRingProtectionSettings} when * Basic {@link SecretKeyRingProtector} implementation that respects the users {@link SaltedAndIteratedS2K} when
* encrypting keys. * encrypting keys.
*/ */
public class BaseSecretKeyRingProtector implements SecretKeyRingProtector { public class BaseSecretKeyRingProtector implements SecretKeyRingProtector {
@ -24,16 +24,16 @@ public class BaseSecretKeyRingProtector implements SecretKeyRingProtector {
/** /**
* Constructor that uses the given {@link SecretKeyPassphraseProvider} to retrieve passphrases and PGPainless' * Constructor that uses the given {@link SecretKeyPassphraseProvider} to retrieve passphrases and PGPainless'
* default {@link KeyRingProtectionSettings}. * default {@link SaltedAndIteratedS2K}.
* *
* @param passphraseProvider provider for passphrases * @param passphraseProvider provider for passphrases
*/ */
public BaseSecretKeyRingProtector(SecretKeyPassphraseProvider passphraseProvider) { public BaseSecretKeyRingProtector(SecretKeyPassphraseProvider passphraseProvider) {
this(passphraseProvider, KeyRingProtectionSettings.secureDefaultSettings()); this(passphraseProvider, KeyRingProtectionSettings.saltedAndIterated());
} }
/** /**
* Constructor that uses the given {@link SecretKeyPassphraseProvider} and {@link KeyRingProtectionSettings}. * Constructor that uses the given {@link SecretKeyPassphraseProvider} and {@link SaltedAndIteratedS2K}.
* *
* @param passphraseProvider provider for passphrases * @param passphraseProvider provider for passphrases
* @param protectionSettings protection settings * @param protectionSettings protection settings
@ -61,10 +61,6 @@ public class BaseSecretKeyRingProtector implements SecretKeyRingProtector {
public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException { public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException {
Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId); Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId);
return passphrase == null || passphrase.isEmpty() ? null : return passphrase == null || passphrase.isEmpty() ? null :
ImplementationFactory.getInstance().getPBESecretKeyEncryptor( protectionSettings.getEncryptor(passphrase);
protectionSettings.getEncryptionAlgorithm(),
protectionSettings.getHashAlgorithm(),
protectionSettings.getS2kCount(),
passphrase);
} }
} }

View File

@ -40,7 +40,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se
public CachingSecretKeyRingProtector(@Nullable SecretKeyPassphraseProvider missingPassphraseCallback) { public CachingSecretKeyRingProtector(@Nullable SecretKeyPassphraseProvider missingPassphraseCallback) {
this( this(
new HashMap<>(), new HashMap<>(),
KeyRingProtectionSettings.secureDefaultSettings(), KeyRingProtectionSettings.saltedAndIterated(),
missingPassphraseCallback missingPassphraseCallback
); );
} }

View File

@ -1,62 +1,12 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.protection; package org.pgpainless.key.protection;
import javax.annotation.Nonnull; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.util.Passphrase;
/** public interface KeyRingProtectionSettings {
* Secret key protection settings for iterated and salted S2K.
*/
public class KeyRingProtectionSettings {
private final SymmetricKeyAlgorithm encryptionAlgorithm;
private final HashAlgorithm hashAlgorithm;
private final int s2kCount;
/**
* Create a {@link KeyRingProtectionSettings} object using the given encryption algorithm, SHA1 and
* 65536 iterations.
*
* @param encryptionAlgorithm encryption algorithm
*/
public KeyRingProtectionSettings(@Nonnull SymmetricKeyAlgorithm encryptionAlgorithm) {
this(encryptionAlgorithm, HashAlgorithm.SHA1, 0x60); // Same s2kCount (encoded) as used in BC.
}
/**
* Constructor for custom salted and iterated S2K protection settings.
* The salt gets randomly chosen by the library each time.
*
* Note, that the s2kCount is the already encoded single-octet number.
*
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-3.7.1.3">Encoding Formula</a>
*
* @param encryptionAlgorithm encryption algorithm
* @param hashAlgorithm hash algorithm
* @param s2kCount encoded s2k iteration count
*/
public KeyRingProtectionSettings(@Nonnull SymmetricKeyAlgorithm encryptionAlgorithm, @Nonnull HashAlgorithm hashAlgorithm, int s2kCount) {
this.encryptionAlgorithm = validateEncryptionAlgorithm(encryptionAlgorithm);
this.hashAlgorithm = hashAlgorithm;
if (s2kCount < 1) {
throw new IllegalArgumentException("s2kCount cannot be less than 1.");
}
this.s2kCount = s2kCount;
}
private static SymmetricKeyAlgorithm validateEncryptionAlgorithm(SymmetricKeyAlgorithm encryptionAlgorithm) {
switch (encryptionAlgorithm) {
case NULL:
throw new IllegalArgumentException("Unencrypted is not allowed here!");
default:
return encryptionAlgorithm;
}
}
/** /**
* Secure default settings using {@link SymmetricKeyAlgorithm#AES_256}, {@link HashAlgorithm#SHA256} * Secure default settings using {@link SymmetricKeyAlgorithm#AES_256}, {@link HashAlgorithm#SHA256}
@ -64,36 +14,19 @@ public class KeyRingProtectionSettings {
* *
* @return secure protection settings * @return secure protection settings
*/ */
public static KeyRingProtectionSettings secureDefaultSettings() { static SaltedAndIteratedS2K saltedAndIterated() {
return new KeyRingProtectionSettings(SymmetricKeyAlgorithm.AES_256, HashAlgorithm.SHA256, 0x60); return new SaltedAndIteratedS2K(SymmetricKeyAlgorithm.AES_256, HashAlgorithm.SHA256, 0x60);
}
static KeyRingProtectionSettings argon2() {
return new Argon2AEAD();
} }
/** /**
* Return the encryption algorithm. * Return an {@link PBESecretKeyEncryptor} instance using these protection settings.
* *
* @return encryption algorithm * @param passphrase passphrase
* @return encryptor
*/ */
public @Nonnull SymmetricKeyAlgorithm getEncryptionAlgorithm() { PBESecretKeyEncryptor getEncryptor(Passphrase passphrase) throws PGPException;
return encryptionAlgorithm;
}
/**
* Return the hash algorithm.
*
* @return hash algorithm
*/
public @Nonnull HashAlgorithm getHashAlgorithm() {
return hashAlgorithm;
}
/**
* Return the (encoded!) s2k iteration count.
*
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-3.7.1.3">Encoding Formula</a>
*
* @return encoded s2k count
*/
public int getS2kCount() {
return s2kCount;
}
} }

View File

@ -16,7 +16,7 @@ import org.pgpainless.util.Passphrase;
/** /**
* Provides {@link PBESecretKeyDecryptor} and {@link PBESecretKeyEncryptor} objects while getting the passphrases * Provides {@link PBESecretKeyDecryptor} and {@link PBESecretKeyEncryptor} objects while getting the passphrases
* from a {@link SecretKeyPassphraseProvider} and using settings from an {@link KeyRingProtectionSettings}. * from a {@link SecretKeyPassphraseProvider} and using settings from an {@link SaltedAndIteratedS2K}.
*/ */
public class PasswordBasedSecretKeyRingProtector extends BaseSecretKeyRingProtector { public class PasswordBasedSecretKeyRingProtector extends BaseSecretKeyRingProtector {

View File

@ -0,0 +1,103 @@
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.key.protection;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.util.Passphrase;
/**
* Secret key protection settings for iterated and salted S2K.
*/
public class SaltedAndIteratedS2K implements KeyRingProtectionSettings {
private final SymmetricKeyAlgorithm encryptionAlgorithm;
private final HashAlgorithm hashAlgorithm;
private final int s2kCount;
/**
* Create a {@link SaltedAndIteratedS2K} object using the given encryption algorithm, SHA1 and
* 65536 iterations.
*
* @param encryptionAlgorithm encryption algorithm
*/
public SaltedAndIteratedS2K(@Nonnull SymmetricKeyAlgorithm encryptionAlgorithm) {
this(encryptionAlgorithm, HashAlgorithm.SHA1, 0x60); // Same s2kCount (encoded) as used in BC.
}
/**
* Constructor for custom salted and iterated S2K protection settings.
* The salt gets randomly chosen by the library each time.
*
* Note, that the s2kCount is the already encoded single-octet number.
*
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-3.7.1.3">Encoding Formula</a>
*
* @param encryptionAlgorithm encryption algorithm
* @param hashAlgorithm hash algorithm
* @param s2kCount encoded s2k iteration count
*/
public SaltedAndIteratedS2K(@Nonnull SymmetricKeyAlgorithm encryptionAlgorithm, @Nonnull HashAlgorithm hashAlgorithm, int s2kCount) {
this.encryptionAlgorithm = validateEncryptionAlgorithm(encryptionAlgorithm);
this.hashAlgorithm = hashAlgorithm;
if (s2kCount < 1) {
throw new IllegalArgumentException("s2kCount cannot be less than 1.");
}
this.s2kCount = s2kCount;
}
private static SymmetricKeyAlgorithm validateEncryptionAlgorithm(SymmetricKeyAlgorithm encryptionAlgorithm) {
switch (encryptionAlgorithm) {
case NULL:
throw new IllegalArgumentException("Unencrypted is not allowed here!");
default:
return encryptionAlgorithm;
}
}
/**
* Return the encryption algorithm.
*
* @return encryption algorithm
*/
public @Nonnull SymmetricKeyAlgorithm getEncryptionAlgorithm() {
return encryptionAlgorithm;
}
/**
* Return the hash algorithm.
*
* @return hash algorithm
*/
public @Nonnull HashAlgorithm getHashAlgorithm() {
return hashAlgorithm;
}
/**
* Return the (encoded!) s2k iteration count.
*
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-3.7.1.3">Encoding Formula</a>
*
* @return encoded s2k count
*/
public int getS2kCount() {
return s2kCount;
}
@Override
public PBESecretKeyEncryptor getEncryptor(Passphrase passphrase)
throws PGPException {
return ImplementationFactory.getInstance().getPBESecretKeyEncryptor(
getEncryptionAlgorithm(),
getHashAlgorithm(),
getS2kCount(),
passphrase);
}
}

View File

@ -72,7 +72,7 @@ public interface SecretKeyRingProtector {
static CachingSecretKeyRingProtector defaultSecretKeyRingProtector(SecretKeyPassphraseProvider missingPassphraseCallback) { static CachingSecretKeyRingProtector defaultSecretKeyRingProtector(SecretKeyPassphraseProvider missingPassphraseCallback) {
return new CachingSecretKeyRingProtector( return new CachingSecretKeyRingProtector(
new HashMap<>(), new HashMap<>(),
KeyRingProtectionSettings.secureDefaultSettings(), KeyRingProtectionSettings.saltedAndIterated(),
missingPassphraseCallback); missingPassphraseCallback);
} }
@ -163,6 +163,6 @@ public interface SecretKeyRingProtector {
* @return protector * @return protector
*/ */
static SecretKeyRingProtector fromPassphraseMap(@Nonnull Map<Long, Passphrase> passphraseMap) { static SecretKeyRingProtector fromPassphraseMap(@Nonnull Map<Long, Passphrase> passphraseMap) {
return new CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null); return new CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.saltedAndIterated(), null);
} }
} }

View File

@ -32,8 +32,8 @@ import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.protection.KeyRingProtectionSettings; import org.pgpainless.key.protection.KeyRingProtectionSettings;
import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector; import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector;
import org.pgpainless.key.protection.UnlockSecretKey; import org.pgpainless.key.protection.UnlockSecretKey;
import org.pgpainless.util.TestAllImplementations;
import org.pgpainless.util.Passphrase; import org.pgpainless.util.Passphrase;
import org.pgpainless.util.TestAllImplementations;
public class ChangeSecretKeyRingPassphraseTest { public class ChangeSecretKeyRingPassphraseTest {
@ -54,7 +54,7 @@ public class ChangeSecretKeyRingPassphraseTest {
PGPSecretKeyRing changedPassphraseKeyRing = secretKeys; PGPSecretKeyRing changedPassphraseKeyRing = secretKeys;
assertEquals(KeyRingProtectionSettings.secureDefaultSettings().getEncryptionAlgorithm().getAlgorithmId(), assertEquals(KeyRingProtectionSettings.saltedAndIterated().getEncryptionAlgorithm().getAlgorithmId(),
changedPassphraseKeyRing.getSecretKey().getKeyEncryptionAlgorithm()); changedPassphraseKeyRing.getSecretKey().getKeyEncryptionAlgorithm());
assertThrows(PGPException.class, () -> assertThrows(PGPException.class, () ->

View File

@ -15,6 +15,6 @@ public class InvalidProtectionSettingsTest {
@Test @Test
public void unencryptedKeyRingProtectionSettingsThrows() { public void unencryptedKeyRingProtectionSettingsThrows() {
assertThrows(IllegalArgumentException.class, () -> assertThrows(IllegalArgumentException.class, () ->
new KeyRingProtectionSettings(SymmetricKeyAlgorithm.NULL, HashAlgorithm.SHA256, 0x60)); new SaltedAndIteratedS2K(SymmetricKeyAlgorithm.NULL, HashAlgorithm.SHA256, 0x60));
} }
} }

View File

@ -27,7 +27,7 @@ public class PassphraseProtectedKeyTest {
* Protector that holds only the password of cryptie. * Protector that holds only the password of cryptie.
*/ */
private final PasswordBasedSecretKeyRingProtector protector = new PasswordBasedSecretKeyRingProtector( private final PasswordBasedSecretKeyRingProtector protector = new PasswordBasedSecretKeyRingProtector(
KeyRingProtectionSettings.secureDefaultSettings(), KeyRingProtectionSettings.saltedAndIterated(),
new SecretKeyPassphraseProvider() { new SecretKeyPassphraseProvider() {
@Nullable @Nullable
@Override @Override

View File

@ -106,7 +106,7 @@ public class SecretKeyRingProtectorTest {
Map<Long, Passphrase> passphraseMap = new ConcurrentHashMap<>(); Map<Long, Passphrase> passphraseMap = new ConcurrentHashMap<>();
passphraseMap.put(1L, Passphrase.emptyPassphrase()); passphraseMap.put(1L, Passphrase.emptyPassphrase());
CachingSecretKeyRingProtector protector = new CachingSecretKeyRingProtector(passphraseMap, CachingSecretKeyRingProtector protector = new CachingSecretKeyRingProtector(passphraseMap,
KeyRingProtectionSettings.secureDefaultSettings(), new SecretKeyPassphraseProvider() { KeyRingProtectionSettings.saltedAndIterated(), new SecretKeyPassphraseProvider() {
@Override @Override
public Passphrase getPassphraseFor(Long keyId) { public Passphrase getPassphraseFor(Long keyId) {
return Passphrase.fromPassword("missingP455w0rd"); return Passphrase.fromPassword("missingP455w0rd");

View File

@ -78,7 +78,7 @@ public class SymmetricEncryptionTest {
// Test public key decryption // Test public key decryption
PGPSecretKeyRingCollection decryptionKeys = TestKeys.getCryptieSecretKeyRingCollection(); PGPSecretKeyRingCollection decryptionKeys = TestKeys.getCryptieSecretKeyRingCollection();
SecretKeyRingProtector protector = new PasswordBasedSecretKeyRingProtector( SecretKeyRingProtector protector = new PasswordBasedSecretKeyRingProtector(
KeyRingProtectionSettings.secureDefaultSettings(), KeyRingProtectionSettings.saltedAndIterated(),
new SolitaryPassphraseProvider(Passphrase.fromPassword(TestKeys.CRYPTIE_PASSWORD))); new SolitaryPassphraseProvider(Passphrase.fromPassword(TestKeys.CRYPTIE_PASSWORD)));
decryptor = PGPainless.decryptAndOrVerify() decryptor = PGPainless.decryptAndOrVerify()
.onInputStream(new ByteArrayInputStream(ciphertext)) .onInputStream(new ByteArrayInputStream(ciphertext))