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
public WithPassphrase withSecureDefaultSettings() {
return withCustomSettings(KeyRingProtectionSettings.secureDefaultSettings());
return withCustomSettings(KeyRingProtectionSettings.saltedAndIterated());
}
@Override

View File

@ -536,7 +536,7 @@ public interface SecretKeyRingEditorInterface {
*/
default WithKeyRingEncryptionSettings changePassphraseFromOldPassphrase(
@Nullable Passphrase oldPassphrase) {
return changePassphraseFromOldPassphrase(oldPassphrase, KeyRingProtectionSettings.secureDefaultSettings());
return changePassphraseFromOldPassphrase(oldPassphrase, KeyRingProtectionSettings.saltedAndIterated());
}
/**
@ -563,7 +563,7 @@ public interface SecretKeyRingEditorInterface {
default WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase(
@Nonnull Long keyId,
@Nullable Passphrase oldPassphrase) {
return changeSubKeyPassphraseFromOldPassphrase(keyId, oldPassphrase, KeyRingProtectionSettings.secureDefaultSettings());
return changeSubKeyPassphraseFromOldPassphrase(keyId, oldPassphrase, KeyRingProtectionSettings.saltedAndIterated());
}
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;
/**
* Basic {@link SecretKeyRingProtector} implementation that respects the users {@link KeyRingProtectionSettings} when
* Basic {@link SecretKeyRingProtector} implementation that respects the users {@link SaltedAndIteratedS2K} when
* encrypting keys.
*/
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'
* default {@link KeyRingProtectionSettings}.
* default {@link SaltedAndIteratedS2K}.
*
* @param passphraseProvider provider for passphrases
*/
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 protectionSettings protection settings
@ -61,10 +61,6 @@ public class BaseSecretKeyRingProtector implements SecretKeyRingProtector {
public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException {
Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId);
return passphrase == null || passphrase.isEmpty() ? null :
ImplementationFactory.getInstance().getPBESecretKeyEncryptor(
protectionSettings.getEncryptionAlgorithm(),
protectionSettings.getHashAlgorithm(),
protectionSettings.getS2kCount(),
passphrase);
protectionSettings.getEncryptor(passphrase);
}
}

View File

@ -40,7 +40,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se
public CachingSecretKeyRingProtector(@Nullable SecretKeyPassphraseProvider missingPassphraseCallback) {
this(
new HashMap<>(),
KeyRingProtectionSettings.secureDefaultSettings(),
KeyRingProtectionSettings.saltedAndIterated(),
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;
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.util.Passphrase;
/**
* 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;
}
}
public interface KeyRingProtectionSettings {
/**
* Secure default settings using {@link SymmetricKeyAlgorithm#AES_256}, {@link HashAlgorithm#SHA256}
@ -64,36 +14,19 @@ public class KeyRingProtectionSettings {
*
* @return secure protection settings
*/
public static KeyRingProtectionSettings secureDefaultSettings() {
return new KeyRingProtectionSettings(SymmetricKeyAlgorithm.AES_256, HashAlgorithm.SHA256, 0x60);
static SaltedAndIteratedS2K saltedAndIterated() {
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() {
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;
}
PBESecretKeyEncryptor getEncryptor(Passphrase passphrase) throws PGPException;
}

View File

@ -16,7 +16,7 @@ import org.pgpainless.util.Passphrase;
/**
* 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 {

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) {
return new CachingSecretKeyRingProtector(
new HashMap<>(),
KeyRingProtectionSettings.secureDefaultSettings(),
KeyRingProtectionSettings.saltedAndIterated(),
missingPassphraseCallback);
}
@ -163,6 +163,6 @@ public interface SecretKeyRingProtector {
* @return protector
*/
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.PasswordBasedSecretKeyRingProtector;
import org.pgpainless.key.protection.UnlockSecretKey;
import org.pgpainless.util.TestAllImplementations;
import org.pgpainless.util.Passphrase;
import org.pgpainless.util.TestAllImplementations;
public class ChangeSecretKeyRingPassphraseTest {
@ -54,7 +54,7 @@ public class ChangeSecretKeyRingPassphraseTest {
PGPSecretKeyRing changedPassphraseKeyRing = secretKeys;
assertEquals(KeyRingProtectionSettings.secureDefaultSettings().getEncryptionAlgorithm().getAlgorithmId(),
assertEquals(KeyRingProtectionSettings.saltedAndIterated().getEncryptionAlgorithm().getAlgorithmId(),
changedPassphraseKeyRing.getSecretKey().getKeyEncryptionAlgorithm());
assertThrows(PGPException.class, () ->

View File

@ -15,6 +15,6 @@ public class InvalidProtectionSettingsTest {
@Test
public void unencryptedKeyRingProtectionSettingsThrows() {
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.
*/
private final PasswordBasedSecretKeyRingProtector protector = new PasswordBasedSecretKeyRingProtector(
KeyRingProtectionSettings.secureDefaultSettings(),
KeyRingProtectionSettings.saltedAndIterated(),
new SecretKeyPassphraseProvider() {
@Nullable
@Override

View File

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

View File

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