mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-06-17 00:54:50 +02:00
WIP: Start working on generifying key protection for argon2
This commit is contained in:
parent
600d5f49bb
commit
007e5a2bbe
|
@ -739,7 +739,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
|||
|
||||
@Override
|
||||
public WithPassphrase withSecureDefaultSettings() {
|
||||
return withCustomSettings(KeyRingProtectionSettings.secureDefaultSettings());
|
||||
return withCustomSettings(KeyRingProtectionSettings.saltedAndIterated());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se
|
|||
public CachingSecretKeyRingProtector(@Nullable SecretKeyPassphraseProvider missingPassphraseCallback) {
|
||||
this(
|
||||
new HashMap<>(),
|
||||
KeyRingProtectionSettings.secureDefaultSettings(),
|
||||
KeyRingProtectionSettings.saltedAndIterated(),
|
||||
missingPassphraseCallback
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, () ->
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue