mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-23 04:42:06 +01:00
Progress
This commit is contained in:
parent
04ada88188
commit
3f09fa0cc7
9 changed files with 163 additions and 103 deletions
|
@ -59,6 +59,10 @@ public abstract class ImplementationFactory {
|
||||||
|
|
||||||
public abstract PBESecretKeyDecryptor getPBESecretKeyDecryptor(Passphrase passphrase) throws PGPException;
|
public abstract PBESecretKeyDecryptor getPBESecretKeyDecryptor(Passphrase passphrase) throws PGPException;
|
||||||
|
|
||||||
|
public PGPDigestCalculator getV4FingerprintCalculator() throws PGPException {
|
||||||
|
return getPGPDigestCalculator(HashAlgorithm.SHA1);
|
||||||
|
}
|
||||||
|
|
||||||
public PGPDigestCalculator getPGPDigestCalculator(HashAlgorithm algorithm) throws PGPException {
|
public PGPDigestCalculator getPGPDigestCalculator(HashAlgorithm algorithm) throws PGPException {
|
||||||
return getPGPDigestCalculator(algorithm.getAlgorithmId());
|
return getPGPDigestCalculator(algorithm.getAlgorithmId());
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ import org.pgpainless.algorithm.SignatureType;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
import org.pgpainless.key.generation.type.KeyType;
|
import org.pgpainless.key.generation.type.KeyType;
|
||||||
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
import org.pgpainless.key.protection.UnlockSecretKey;
|
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||||
import org.pgpainless.provider.ProviderFactory;
|
import org.pgpainless.provider.ProviderFactory;
|
||||||
import org.pgpainless.signature.subpackets.SignatureSubpacketGeneratorUtil;
|
import org.pgpainless.signature.subpackets.SignatureSubpacketGeneratorUtil;
|
||||||
|
@ -52,13 +53,13 @@ public class KeyRingBuilder implements KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
private final Charset UTF8 = Charset.forName("UTF-8");
|
private final Charset UTF8 = Charset.forName("UTF-8");
|
||||||
|
|
||||||
private PGPSignatureGenerator signatureGenerator;
|
private PGPSignatureGenerator signatureGenerator;
|
||||||
private PGPDigestCalculator digestCalculator;
|
private PGPDigestCalculator keyFingerprintCalculator;
|
||||||
private PBESecretKeyEncryptor secretKeyEncryptor;
|
private PBESecretKeyEncryptor secretKeyEncryptor;
|
||||||
|
|
||||||
private KeySpec primaryKeySpec;
|
private KeySpec primaryKeySpec;
|
||||||
private final List<KeySpec> subkeySpecs = new ArrayList<>();
|
private final List<KeySpec> subkeySpecs = new ArrayList<>();
|
||||||
private final Set<String> userIds = new LinkedHashSet<>();
|
private final Set<String> userIds = new LinkedHashSet<>();
|
||||||
private Passphrase passphrase = null;
|
private Passphrase passphrase = Passphrase.emptyPassphrase();
|
||||||
private Date expirationDate = null;
|
private Date expirationDate = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -126,13 +127,11 @@ public class KeyRingBuilder implements KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
if (userIds.isEmpty()) {
|
if (userIds.isEmpty()) {
|
||||||
throw new IllegalStateException("At least one user-id is required.");
|
throw new IllegalStateException("At least one user-id is required.");
|
||||||
}
|
}
|
||||||
digestCalculator = buildDigestCalculator();
|
keyFingerprintCalculator = ImplementationFactory.getInstance().getV4FingerprintCalculator();
|
||||||
secretKeyEncryptor = buildSecretKeyEncryptor();
|
secretKeyEncryptor = buildSecretKeyEncryptor();
|
||||||
PBESecretKeyDecryptor secretKeyDecryptor = buildSecretKeyDecryptor();
|
PBESecretKeyDecryptor secretKeyDecryptor = buildSecretKeyDecryptor();
|
||||||
|
|
||||||
if (passphrase != null) {
|
passphrase.clear();
|
||||||
passphrase.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate Primary Key
|
// Generate Primary Key
|
||||||
PGPKeyPair certKey = generateKeyPair(primaryKeySpec);
|
PGPKeyPair certKey = generateKeyPair(primaryKeySpec);
|
||||||
|
@ -171,8 +170,7 @@ public class KeyRingBuilder implements KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
|
|
||||||
// "reassemble" secret key ring with modified primary key
|
// "reassemble" secret key ring with modified primary key
|
||||||
PGPSecretKey primarySecKey = new PGPSecretKey(
|
PGPSecretKey primarySecKey = new PGPSecretKey(
|
||||||
privateKey,
|
privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor);
|
||||||
primaryPubKey, digestCalculator, true, secretKeyEncryptor);
|
|
||||||
List<PGPSecretKey> secretKeyList = new ArrayList<>();
|
List<PGPSecretKey> secretKeyList = new ArrayList<>();
|
||||||
secretKeyList.add(primarySecKey);
|
secretKeyList.add(primarySecKey);
|
||||||
while (secretKeys.hasNext()) {
|
while (secretKeys.hasNext()) {
|
||||||
|
@ -190,7 +188,7 @@ public class KeyRingBuilder implements KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
String primaryUserId = userIds.iterator().next();
|
String primaryUserId = userIds.iterator().next();
|
||||||
return new PGPKeyRingGenerator(
|
return new PGPKeyRingGenerator(
|
||||||
SignatureType.POSITIVE_CERTIFICATION.getCode(), certKey,
|
SignatureType.POSITIVE_CERTIFICATION.getCode(), certKey,
|
||||||
primaryUserId, digestCalculator,
|
primaryUserId, keyFingerprintCalculator,
|
||||||
hashedSubPackets, null, signer, secretKeyEncryptor);
|
hashedSubPackets, null, signer, secretKeyEncryptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,22 +234,20 @@ public class KeyRingBuilder implements KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
private PBESecretKeyEncryptor buildSecretKeyEncryptor() {
|
private PBESecretKeyEncryptor buildSecretKeyEncryptor() {
|
||||||
SymmetricKeyAlgorithm keyEncryptionAlgorithm = PGPainless.getPolicy().getSymmetricKeyEncryptionAlgorithmPolicy()
|
SymmetricKeyAlgorithm keyEncryptionAlgorithm = PGPainless.getPolicy().getSymmetricKeyEncryptionAlgorithmPolicy()
|
||||||
.getDefaultSymmetricKeyAlgorithm();
|
.getDefaultSymmetricKeyAlgorithm();
|
||||||
PBESecretKeyEncryptor encryptor = passphrase == null || passphrase.isEmpty() ?
|
if (!passphrase.isValid()) {
|
||||||
null : // unencrypted key pair, otherwise AES-256 encrypted
|
throw new IllegalStateException("Passphrase was cleared.");
|
||||||
|
}
|
||||||
|
return passphrase.isEmpty() ? null : // unencrypted key pair, otherwise AES-256 encrypted
|
||||||
ImplementationFactory.getInstance().getPBESecretKeyEncryptor(
|
ImplementationFactory.getInstance().getPBESecretKeyEncryptor(
|
||||||
keyEncryptionAlgorithm, digestCalculator, passphrase);
|
keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase);
|
||||||
return encryptor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PBESecretKeyDecryptor buildSecretKeyDecryptor() throws PGPException {
|
private PBESecretKeyDecryptor buildSecretKeyDecryptor() throws PGPException {
|
||||||
PBESecretKeyDecryptor decryptor = passphrase == null || passphrase.isEmpty() ?
|
if (!passphrase.isValid()) {
|
||||||
null :
|
throw new IllegalStateException("Passphrase was cleared.");
|
||||||
|
}
|
||||||
|
return passphrase.isEmpty() ? null :
|
||||||
ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase);
|
ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase);
|
||||||
return decryptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PGPDigestCalculator buildDigestCalculator() throws PGPException {
|
|
||||||
return ImplementationFactory.getInstance().getPGPDigestCalculator(HashAlgorithm.SHA1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PGPKeyPair generateKeyPair(KeySpec spec)
|
public static PGPKeyPair generateKeyPair(KeySpec spec)
|
||||||
|
@ -269,4 +265,30 @@ public class KeyRingBuilder implements KeyRingBuilderInterface<KeyRingBuilder> {
|
||||||
PGPKeyPair pgpKeyPair = ImplementationFactory.getInstance().getPGPKeyPair(type.getAlgorithm(), keyPair, new Date());
|
PGPKeyPair pgpKeyPair = ImplementationFactory.getInstance().getPGPKeyPair(type.getAlgorithm(), keyPair, new Date());
|
||||||
return pgpKeyPair;
|
return pgpKeyPair;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PGPSecretKey generatePGPSecretKey(KeySpec keySpec, @Nonnull Passphrase passphrase, boolean isPrimary)
|
||||||
|
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||||
|
PGPDigestCalculator keyFingerprintCalculator = ImplementationFactory.getInstance()
|
||||||
|
.getV4FingerprintCalculator();
|
||||||
|
PGPKeyPair keyPair = generateKeyPair(keySpec);
|
||||||
|
SecretKeyRingProtector protector;
|
||||||
|
|
||||||
|
synchronized (passphrase.lock) {
|
||||||
|
if (!passphrase.isValid()) {
|
||||||
|
throw new IllegalStateException("Passphrase has been cleared.");
|
||||||
|
}
|
||||||
|
if (!passphrase.isEmpty()) {
|
||||||
|
protector = SecretKeyRingProtector.unlockSingleKeyWith(passphrase, keyPair.getKeyID());
|
||||||
|
} else {
|
||||||
|
protector = SecretKeyRingProtector.unprotectedKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PGPSecretKey(
|
||||||
|
keyPair.getPrivateKey(),
|
||||||
|
keyPair.getPublicKey(),
|
||||||
|
keyFingerprintCalculator,
|
||||||
|
isPrimary,
|
||||||
|
protector.getEncryptor(keyPair.getKeyID()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,6 @@ import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.KeyFlag;
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||||
import org.pgpainless.algorithm.SignatureType;
|
import org.pgpainless.algorithm.SignatureType;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
import org.pgpainless.key.OpenPgpFingerprint;
|
import org.pgpainless.key.OpenPgpFingerprint;
|
||||||
import org.pgpainless.key.generation.KeyRingBuilder;
|
import org.pgpainless.key.generation.KeyRingBuilder;
|
||||||
|
@ -182,28 +181,27 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
||||||
KeyFlag keyFlag,
|
KeyFlag keyFlag,
|
||||||
KeyFlag... additionalKeyFlags) throws PGPException, IOException {
|
KeyFlag... additionalKeyFlags) throws PGPException, IOException {
|
||||||
KeyFlag[] flags = concat(keyFlag, additionalKeyFlags);
|
KeyFlag[] flags = concat(keyFlag, additionalKeyFlags);
|
||||||
SignatureSubpacketsUtil.assureKeyCanCarryFlags(PublicKeyAlgorithm.fromId(subkey.getPublicKey().getAlgorithm()));
|
PublicKeyAlgorithm subkeyAlgorithm = PublicKeyAlgorithm.fromId(subkey.getPublicKey().getAlgorithm());
|
||||||
|
SignatureSubpacketsUtil.assureKeyCanCarryFlags(subkeyAlgorithm);
|
||||||
boolean isSigningKey = CollectionUtils.contains(flags, KeyFlag.SIGN_DATA) ||
|
|
||||||
CollectionUtils.contains(flags, KeyFlag.CERTIFY_OTHER);
|
|
||||||
if (!isSigningKey) {
|
|
||||||
return addSubKey(subkey.getPublicKey(),
|
|
||||||
bindingSignatureCallback,
|
|
||||||
primaryKeyProtector,
|
|
||||||
keyFlag,
|
|
||||||
additionalKeyFlags);
|
|
||||||
}
|
|
||||||
|
|
||||||
PGPSecretKey primaryKey = secretKeyRing.getSecretKey();
|
PGPSecretKey primaryKey = secretKeyRing.getSecretKey();
|
||||||
SubkeyBindingSignatureBuilder bindingSigBuilder = new SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector);
|
SubkeyBindingSignatureBuilder bindingSigBuilder =
|
||||||
|
new SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector);
|
||||||
|
|
||||||
bindingSigBuilder.applyCallback(bindingSignatureCallback);
|
bindingSigBuilder.applyCallback(bindingSignatureCallback);
|
||||||
bindingSigBuilder.getHashedSubpackets().setKeyFlags(flags);
|
bindingSigBuilder.getHashedSubpackets().setKeyFlags(flags);
|
||||||
|
|
||||||
PrimaryKeyBindingSignatureBuilder backSigBuilder = new PrimaryKeyBindingSignatureBuilder(subkey, subkeyProtector);
|
boolean isSigningKey = CollectionUtils.contains(flags, KeyFlag.SIGN_DATA) ||
|
||||||
backSigBuilder.applyCallback(backSignatureCallback);
|
CollectionUtils.contains(flags, KeyFlag.CERTIFY_OTHER);
|
||||||
PGPSignature backSig = backSigBuilder.build(primaryKey.getPublicKey());
|
if (isSigningKey) {
|
||||||
|
// Add embedded back-signature made by subkey over primary key
|
||||||
|
PrimaryKeyBindingSignatureBuilder backSigBuilder =
|
||||||
|
new PrimaryKeyBindingSignatureBuilder(subkey, subkeyProtector);
|
||||||
|
backSigBuilder.applyCallback(backSignatureCallback);
|
||||||
|
PGPSignature backSig = backSigBuilder.build(primaryKey.getPublicKey());
|
||||||
|
bindingSigBuilder.getHashedSubpackets().addEmbeddedSignature(backSig);
|
||||||
|
}
|
||||||
|
|
||||||
bindingSigBuilder.getHashedSubpackets().addEmbeddedSignature(backSig);
|
|
||||||
PGPSignature bindingSig = bindingSigBuilder.build(subkey.getPublicKey());
|
PGPSignature bindingSig = bindingSigBuilder.build(subkey.getPublicKey());
|
||||||
subkey = KeyRingUtils.secretKeyPlusSignature(subkey, bindingSig);
|
subkey = KeyRingUtils.secretKeyPlusSignature(subkey, bindingSig);
|
||||||
secretKeyRing = KeyRingUtils.secretKeysPlusSecretKey(secretKeyRing, subkey);
|
secretKeyRing = KeyRingUtils.secretKeysPlusSecretKey(secretKeyRing, subkey);
|
||||||
|
@ -211,54 +209,10 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public SecretKeyRingEditorInterface addSubKey(PGPPublicKey subkey,
|
|
||||||
SelfSignatureSubpackets.Callback bindingSignatureCallback,
|
|
||||||
SecretKeyRingProtector primaryKeyProtector,
|
|
||||||
KeyFlag keyFlag,
|
|
||||||
KeyFlag... additionalKeyFlags) throws PGPException {
|
|
||||||
KeyFlag[] flags = concat(keyFlag, additionalKeyFlags);
|
|
||||||
boolean isSigningKey = CollectionUtils.contains(flags, KeyFlag.SIGN_DATA) ||
|
|
||||||
CollectionUtils.contains(flags, KeyFlag.CERTIFY_OTHER);
|
|
||||||
if (isSigningKey) {
|
|
||||||
throw new IllegalArgumentException("Cannot bind a signing capable subkey without access to the secret subkey.\n" +
|
|
||||||
"Please use addSubKey(PGPSecretKey secretSubKey, [...]) instead.");
|
|
||||||
}
|
|
||||||
|
|
||||||
PGPSignature bindingSignature = createSubkeyBindingSignature(subkey, bindingSignatureCallback, primaryKeyProtector, flags);
|
|
||||||
subkey = PGPPublicKey.addCertification(subkey, bindingSignature);
|
|
||||||
|
|
||||||
secretKeyRing = KeyRingUtils.secretKeysPlusPublicKey(secretKeyRing, subkey);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PGPSignature createSubkeyBindingSignature(PGPPublicKey subkey,
|
|
||||||
SelfSignatureSubpackets.Callback bindingSignatureCallback,
|
|
||||||
SecretKeyRingProtector primaryKeyProtector,
|
|
||||||
KeyFlag... keyFlags) throws PGPException {
|
|
||||||
PGPSecretKey primaryKey = secretKeyRing.getSecretKey();
|
|
||||||
SubkeyBindingSignatureBuilder builder = new SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector);
|
|
||||||
builder.applyCallback(bindingSignatureCallback);
|
|
||||||
builder.getHashedSubpackets().setKeyFlags(keyFlags);
|
|
||||||
|
|
||||||
PGPSignature signature = builder.build(subkey);
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PGPSecretKey generateSubKey(@Nonnull KeySpec keySpec,
|
private PGPSecretKey generateSubKey(@Nonnull KeySpec keySpec,
|
||||||
@Nonnull Passphrase subKeyPassphrase)
|
@Nonnull Passphrase subKeyPassphrase)
|
||||||
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
|
||||||
PGPDigestCalculator checksumCalculator = ImplementationFactory.getInstance()
|
return KeyRingBuilder.generatePGPSecretKey(keySpec, subKeyPassphrase, false);
|
||||||
.getPGPDigestCalculator(defaultDigestHashAlgorithm);
|
|
||||||
|
|
||||||
PBESecretKeyEncryptor subKeyEncryptor = subKeyPassphrase.isEmpty() ? null :
|
|
||||||
ImplementationFactory.getInstance().getPBESecretKeyEncryptor(SymmetricKeyAlgorithm.AES_256, subKeyPassphrase);
|
|
||||||
|
|
||||||
PGPKeyPair keyPair = KeyRingBuilder.generateKeyPair(keySpec);
|
|
||||||
PGPSecretKey secretKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
|
|
||||||
checksumCalculator, false, subKeyEncryptor);
|
|
||||||
return secretKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -12,7 +12,6 @@ import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
@ -59,7 +58,7 @@ public interface SecretKeyRingEditorInterface {
|
||||||
* @return the builder
|
* @return the builder
|
||||||
*/
|
*/
|
||||||
SecretKeyRingEditorInterface addSubKey(@Nonnull KeySpec keySpec,
|
SecretKeyRingEditorInterface addSubKey(@Nonnull KeySpec keySpec,
|
||||||
@Nonnull Passphrase subKeyPassphrase,
|
@Nullable Passphrase subKeyPassphrase,
|
||||||
SecretKeyRingProtector secretKeyRingProtector)
|
SecretKeyRingProtector secretKeyRingProtector)
|
||||||
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException;
|
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException;
|
||||||
|
|
||||||
|
@ -78,12 +77,6 @@ public interface SecretKeyRingEditorInterface {
|
||||||
KeyFlag keyFlag,
|
KeyFlag keyFlag,
|
||||||
KeyFlag... additionalKeyFlags) throws PGPException, IOException;
|
KeyFlag... additionalKeyFlags) throws PGPException, IOException;
|
||||||
|
|
||||||
SecretKeyRingEditorInterface addSubKey(PGPPublicKey subkey,
|
|
||||||
SelfSignatureSubpackets.Callback bindingSignatureCallback,
|
|
||||||
SecretKeyRingProtector primaryKeyProtector,
|
|
||||||
KeyFlag keyFlag,
|
|
||||||
KeyFlag... additionalKeyFlags) throws PGPException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revoke the key ring.
|
* Revoke the key ring.
|
||||||
* The revocation will be a hard revocation, rendering the whole key invalid for any past or future signatures.
|
* The revocation will be a hard revocation, rendering the whole key invalid for any past or future signatures.
|
||||||
|
|
|
@ -56,7 +56,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se
|
||||||
* @param keyId id of the key
|
* @param keyId id of the key
|
||||||
* @param passphrase passphrase
|
* @param passphrase passphrase
|
||||||
*/
|
*/
|
||||||
public void addPassphrase(@Nonnull Long keyId, @Nullable Passphrase passphrase) {
|
public void addPassphrase(@Nonnull Long keyId, @Nonnull Passphrase passphrase) {
|
||||||
this.cache.put(keyId, passphrase);
|
this.cache.put(keyId, passphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se
|
||||||
* @param keyRing key ring
|
* @param keyRing key ring
|
||||||
* @param passphrase passphrase
|
* @param passphrase passphrase
|
||||||
*/
|
*/
|
||||||
public void addPassphrase(@Nonnull PGPKeyRing keyRing, @Nullable Passphrase passphrase) {
|
public void addPassphrase(@Nonnull PGPKeyRing keyRing, @Nonnull Passphrase passphrase) {
|
||||||
Iterator<PGPPublicKey> keys = keyRing.getPublicKeys();
|
Iterator<PGPPublicKey> keys = keyRing.getPublicKeys();
|
||||||
while (keys.hasNext()) {
|
while (keys.hasNext()) {
|
||||||
PGPPublicKey publicKey = keys.next();
|
PGPPublicKey publicKey = keys.next();
|
||||||
|
@ -80,11 +80,11 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se
|
||||||
* @param key key
|
* @param key key
|
||||||
* @param passphrase passphrase
|
* @param passphrase passphrase
|
||||||
*/
|
*/
|
||||||
public void addPassphrase(@Nonnull PGPPublicKey key, @Nullable Passphrase passphrase) {
|
public void addPassphrase(@Nonnull PGPPublicKey key, @Nonnull Passphrase passphrase) {
|
||||||
addPassphrase(key.getKeyID(), passphrase);
|
addPassphrase(key.getKeyID(), passphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPassphrase(@Nonnull OpenPgpFingerprint fingerprint, @Nullable Passphrase passphrase) {
|
public void addPassphrase(@Nonnull OpenPgpFingerprint fingerprint, @Nonnull Passphrase passphrase) {
|
||||||
addPassphrase(fingerprint.getKeyId(), passphrase);
|
addPassphrase(fingerprint.getKeyId(), passphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,9 +95,10 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se
|
||||||
* @param keyId id of the key
|
* @param keyId id of the key
|
||||||
*/
|
*/
|
||||||
public void forgetPassphrase(@Nonnull Long keyId) {
|
public void forgetPassphrase(@Nonnull Long keyId) {
|
||||||
Passphrase passphrase = cache.get(keyId);
|
Passphrase passphrase = cache.remove(keyId);
|
||||||
passphrase.clear();
|
if (passphrase != null) {
|
||||||
cache.remove(keyId);
|
passphrase.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -140,12 +141,13 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(Long keyId) {
|
||||||
return cache.containsKey(keyId);
|
Passphrase passphrase = cache.get(keyId);
|
||||||
|
return passphrase != null && passphrase.isValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphraseFor(Long keyId) {
|
public boolean hasPassphraseFor(Long keyId) {
|
||||||
return cache.containsKey(keyId);
|
return hasPassphrase(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -64,12 +64,16 @@ public class PasswordBasedSecretKeyRingProtector implements SecretKeyRingProtect
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PasswordBasedSecretKeyRingProtector forKey(PGPSecretKey key, Passphrase passphrase) {
|
public static PasswordBasedSecretKeyRingProtector forKey(PGPSecretKey key, Passphrase passphrase) {
|
||||||
|
return forKeyId(key.getPublicKey().getKeyID(), passphrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PasswordBasedSecretKeyRingProtector forKeyId(long singleKeyId, Passphrase passphrase) {
|
||||||
KeyRingProtectionSettings protectionSettings = KeyRingProtectionSettings.secureDefaultSettings();
|
KeyRingProtectionSettings protectionSettings = KeyRingProtectionSettings.secureDefaultSettings();
|
||||||
SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() {
|
SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() {
|
||||||
@Override
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@Override
|
||||||
public Passphrase getPassphraseFor(Long keyId) {
|
public Passphrase getPassphraseFor(Long keyId) {
|
||||||
if (key.getKeyID() == keyId) {
|
if (keyId == singleKeyId) {
|
||||||
return passphrase;
|
return passphrase;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -77,7 +81,7 @@ public class PasswordBasedSecretKeyRingProtector implements SecretKeyRingProtect
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(Long keyId) {
|
||||||
return keyId == key.getKeyID();
|
return keyId == singleKeyId;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return new PasswordBasedSecretKeyRingProtector(protectionSettings, passphraseProvider);
|
return new PasswordBasedSecretKeyRingProtector(protectionSettings, passphraseProvider);
|
||||||
|
|
|
@ -98,6 +98,10 @@ public interface SecretKeyRingProtector {
|
||||||
return PasswordBasedSecretKeyRingProtector.forKey(key, passphrase);
|
return PasswordBasedSecretKeyRingProtector.forKey(key, passphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SecretKeyRingProtector unlockSingleKeyWith(Passphrase passphrase, long keyId) {
|
||||||
|
return PasswordBasedSecretKeyRingProtector.forKeyId(keyId, passphrase);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protector for unprotected keys.
|
* Protector for unprotected keys.
|
||||||
* This protector returns null for all {@link #getEncryptor(Long)}/{@link #getDecryptor(Long)} calls,
|
* This protector returns null for all {@link #getEncryptor(Long)}/{@link #getDecryptor(Long)} calls,
|
||||||
|
|
|
@ -10,7 +10,7 @@ import java.util.Arrays;
|
||||||
|
|
||||||
public class Passphrase {
|
public class Passphrase {
|
||||||
|
|
||||||
private final Object lock = new Object();
|
public final Object lock = new Object();
|
||||||
|
|
||||||
private final char[] chars;
|
private final char[] chars;
|
||||||
private boolean valid = true;
|
private boolean valid = true;
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package org.pgpainless.key.modification;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.bouncycastle.bcpg.sig.NotationData;
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.junit.JUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.algorithm.EncryptionPurpose;
|
||||||
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
import org.pgpainless.key.generation.KeyRingBuilder;
|
||||||
|
import org.pgpainless.key.generation.KeySpec;
|
||||||
|
import org.pgpainless.key.generation.type.KeyType;
|
||||||
|
import org.pgpainless.key.generation.type.xdh.XDHSpec;
|
||||||
|
import org.pgpainless.key.info.KeyRingInfo;
|
||||||
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
|
import org.pgpainless.signature.subpackets.SelfSignatureSubpackets;
|
||||||
|
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||||
|
import org.pgpainless.util.Passphrase;
|
||||||
|
|
||||||
|
public class AddSubkeyWithModifiedBindingSignatureSubpackets {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void bindEncryptionSubkeyAndModifyBindingSignatureHashedSubpackets() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||||
|
SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys();
|
||||||
|
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
|
||||||
|
.modernKeyRing("Alice <alice@pgpainless.org>", null);
|
||||||
|
KeyRingInfo before = PGPainless.inspectKeyRing(secretKeys);
|
||||||
|
|
||||||
|
PGPSecretKey secretSubkey = KeyRingBuilder.generatePGPSecretKey(
|
||||||
|
KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_COMMS).build(),
|
||||||
|
Passphrase.emptyPassphrase(), false);
|
||||||
|
|
||||||
|
secretKeys = PGPainless.modifyKeyRing(secretKeys)
|
||||||
|
.addSubKey(secretSubkey, new SelfSignatureSubpackets.Callback() {
|
||||||
|
@Override
|
||||||
|
public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) {
|
||||||
|
hashedSubpackets.setKeyExpirationTime(true, 1000);
|
||||||
|
hashedSubpackets.addNotationData(false, "test@test.test", "test");
|
||||||
|
}
|
||||||
|
}, null, SecretKeyRingProtector.unprotectedKeys(), protector, KeyFlag.ENCRYPT_COMMS)
|
||||||
|
.done();
|
||||||
|
|
||||||
|
KeyRingInfo after = PGPainless.inspectKeyRing(secretKeys);
|
||||||
|
|
||||||
|
List<PGPPublicKey> encryptionKeys = after.getEncryptionSubkeys(EncryptionPurpose.COMMUNICATIONS);
|
||||||
|
encryptionKeys.removeAll(before.getEncryptionSubkeys(EncryptionPurpose.COMMUNICATIONS));
|
||||||
|
assertFalse(encryptionKeys.isEmpty());
|
||||||
|
assertEquals(1, encryptionKeys.size());
|
||||||
|
|
||||||
|
PGPPublicKey newKey = encryptionKeys.get(0);
|
||||||
|
JUtils.assertEquals(new Date().getTime() + 1000 * 1000, after.getSubkeyExpirationDate(new OpenPgpV4Fingerprint(newKey)).getTime(), 2000);
|
||||||
|
assertTrue(newKey.getSignatures().hasNext());
|
||||||
|
PGPSignature binding = newKey.getSignatures().next();
|
||||||
|
List<NotationData> notations = SignatureSubpacketsUtil.getHashedNotationData(binding);
|
||||||
|
assertEquals(1, notations.size());
|
||||||
|
assertEquals("test@test.test", notations.get(0).getNotationName());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue