1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-06-24 12:34:50 +02:00
pgpainless/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditor.java

281 lines
12 KiB
Java
Raw Normal View History

2020-10-25 19:54:03 +01:00
/*
* Copyright 2020 Paul Schaub.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pgpainless.key.modification;
2020-10-23 16:44:21 +02:00
import java.util.ArrayList;
2020-10-25 20:43:09 +01:00
import java.util.Collections;
2020-10-23 16:44:21 +02:00
import java.util.Iterator;
import java.util.List;
2020-10-25 20:43:09 +01:00
import java.util.Map;
2020-10-23 16:44:21 +02:00
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
2020-10-23 16:44:21 +02:00
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
2020-10-23 16:44:21 +02:00
import org.bouncycastle.openpgp.PGPSignature;
2020-10-25 19:54:03 +01:00
import org.bouncycastle.openpgp.PGPSignatureGenerator;
2020-10-23 16:44:21 +02:00
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
2020-10-25 20:43:09 +01:00
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
2020-10-25 19:54:03 +01:00
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.pgpainless.algorithm.HashAlgorithm;
2020-10-23 16:44:21 +02:00
import org.pgpainless.algorithm.SignatureType;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.generation.KeySpec;
2020-10-23 16:44:21 +02:00
import org.pgpainless.key.protection.KeyRingProtectionSettings;
2020-10-25 20:43:09 +01:00
import org.pgpainless.key.protection.PassphraseMapKeyRingProtector;
import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector;
2020-10-23 16:44:21 +02:00
import org.pgpainless.key.protection.SecretKeyRingProtector;
2020-10-25 20:43:09 +01:00
import org.pgpainless.key.protection.UnprotectedKeysProtector;
import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider;
2020-10-25 19:54:03 +01:00
import org.pgpainless.key.util.OpenPgpKeyAttributeUtil;
import org.pgpainless.util.Passphrase;
public class KeyRingEditor implements KeyRingEditorInterface {
private PGPSecretKeyRing secretKeyRing;
public KeyRingEditor(PGPSecretKeyRing secretKeyRing) {
if (secretKeyRing == null) {
throw new NullPointerException("SecretKeyRing MUST NOT be null.");
}
this.secretKeyRing = secretKeyRing;
}
@Override
2020-10-23 16:44:21 +02:00
public KeyRingEditorInterface addUserId(String userId, SecretKeyRingProtector secretKeyRingProtector) throws PGPException {
userId = sanitizeUserId(userId);
2020-10-25 19:54:03 +01:00
Iterator<PGPSecretKey> secretKeys = secretKeyRing.getSecretKeys();
PGPSecretKey primarySecKey = secretKeys.next();
2020-10-23 16:44:21 +02:00
PGPPublicKey primaryPubKey = secretKeyRing.getPublicKey();
2020-10-25 19:54:03 +01:00
PGPPrivateKey privateKey = unlockSecretKey(primarySecKey, secretKeyRingProtector);
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
getPgpContentSignerBuilderForKey(primarySecKey));
2020-10-23 16:44:21 +02:00
signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.getCode(), privateKey);
PGPSignature userIdSignature = signatureGenerator.generateCertification(userId, primaryPubKey);
primaryPubKey = PGPPublicKey.addCertification(primaryPubKey,
userId, userIdSignature);
2020-10-25 19:54:03 +01:00
PGPDigestCalculator digestCalculator = new BcPGPDigestCalculatorProvider().get(
// TODO: Is SHA1 still a good choice?
// If not, what to use/how to make a proper choice?
HashAlgorithm.SHA1.getAlgorithmId());
2020-10-23 16:44:21 +02:00
// "reassemble" secret key ring with modified primary key
2020-10-25 19:54:03 +01:00
primarySecKey = new PGPSecretKey(privateKey, primaryPubKey, digestCalculator, true,
secretKeyRingProtector.getEncryptor(primaryPubKey.getKeyID()));
2020-10-23 16:44:21 +02:00
List<PGPSecretKey> secretKeyList = new ArrayList<>();
secretKeyList.add(primarySecKey);
while (secretKeys.hasNext()) {
secretKeyList.add(secretKeys.next());
}
secretKeyRing = new PGPSecretKeyRing(secretKeyList);
return this;
}
2020-10-25 19:54:03 +01:00
private static BcPGPContentSignerBuilder getPgpContentSignerBuilderForKey(PGPSecretKey secretKey) {
List<HashAlgorithm> preferredHashAlgorithms = OpenPgpKeyAttributeUtil.getPreferredHashAlgorithms(secretKey.getPublicKey());
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(preferredHashAlgorithms);
return new BcPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), hashAlgorithm.getAlgorithmId());
}
private static HashAlgorithm negotiateHashAlgorithm(List<HashAlgorithm> preferredHashAlgorithms) {
// TODO: Match our list of supported hash algorithms against the list, to determine the best suitable algo.
// For now we just take the first algorithm in the list and hope that BC has support for it.
return preferredHashAlgorithms.get(0);
}
2020-10-25 20:43:09 +01:00
// TODO: Move to utility class
2020-10-25 19:54:03 +01:00
private PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, SecretKeyRingProtector protector) throws PGPException {
PBESecretKeyDecryptor secretKeyDecryptor = protector.getDecryptor(secretKey.getKeyID());
2020-10-23 16:44:21 +02:00
PGPPrivateKey privateKey = secretKey.extractPrivateKey(secretKeyDecryptor);
return privateKey;
}
2020-10-25 20:43:09 +01:00
// TODO: Move to utility class?
2020-10-23 16:44:21 +02:00
private String sanitizeUserId(String userId) {
userId = userId.trim();
// TODO: Further research how to sanitize user IDs.
// eg. what about newlines?
return userId;
}
@Override
2020-10-25 19:54:03 +01:00
public KeyRingEditorInterface deleteUserId(String userId, SecretKeyRingProtector protector) {
return this;
}
@Override
2020-10-25 19:54:03 +01:00
public KeyRingEditorInterface addSubKey(KeySpec keySpec, SecretKeyRingProtector protector) {
return this;
}
@Override
2020-10-25 19:54:03 +01:00
public KeyRingEditorInterface deleteSubKey(OpenPgpV4Fingerprint fingerprint, SecretKeyRingProtector protector) {
return this;
}
@Override
2020-10-25 19:54:03 +01:00
public KeyRingEditorInterface deleteSubKey(long subKeyId, SecretKeyRingProtector protector) {
return this;
}
@Override
2020-10-25 19:54:03 +01:00
public KeyRingEditorInterface revokeSubKey(OpenPgpV4Fingerprint fingerprint, SecretKeyRingProtector protector) {
return this;
}
@Override
2020-10-25 19:54:03 +01:00
public KeyRingEditorInterface revokeSubKey(long subKeyId, SecretKeyRingProtector protector) {
return this;
}
2020-10-23 16:44:21 +02:00
@Override
public WithKeyRingEncryptionSettings changePassphraseFromOldPassphrase(@Nullable Passphrase oldPassphrase,
@Nonnull KeyRingProtectionSettings oldProtectionSettings) {
2020-10-25 20:43:09 +01:00
SecretKeyRingProtector protector = new PasswordBasedSecretKeyRingProtector(
oldProtectionSettings,
new SolitaryPassphraseProvider(oldPassphrase));
return new WithKeyRingEncryptionSettingsImpl(null, protector);
2020-10-23 16:44:21 +02:00
}
@Override
public WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase(@Nonnull Long keyId,
@Nullable Passphrase oldPassphrase,
@Nonnull KeyRingProtectionSettings oldProtectionSettings) {
2020-10-25 20:43:09 +01:00
Map<Long, Passphrase> passphraseMap = Collections.singletonMap(keyId, oldPassphrase);
SecretKeyRingProtector protector = new PassphraseMapKeyRingProtector(
passphraseMap, oldProtectionSettings, null);
return new WithKeyRingEncryptionSettingsImpl(keyId, protector);
2020-10-23 16:44:21 +02:00
}
@Override
public PGPSecretKeyRing done() {
return secretKeyRing;
}
2020-10-23 16:44:21 +02:00
2020-10-25 20:43:09 +01:00
private final class WithKeyRingEncryptionSettingsImpl implements WithKeyRingEncryptionSettings {
private final Long keyId;
// Protector to unlock the key with the old passphrase
private final SecretKeyRingProtector oldProtector;
private WithKeyRingEncryptionSettingsImpl(Long keyId, SecretKeyRingProtector oldProtector) {
this.keyId = keyId;
this.oldProtector = oldProtector;
}
2020-10-23 16:44:21 +02:00
@Override
public WithPassphrase withSecureDefaultSettings() {
return withCustomSettings(KeyRingProtectionSettings.secureDefaultSettings());
}
@Override
public WithPassphrase withCustomSettings(KeyRingProtectionSettings settings) {
2020-10-25 20:43:09 +01:00
return new WithPassphraseImpl(keyId, oldProtector, settings);
2020-10-23 16:44:21 +02:00
}
}
2020-10-25 20:43:09 +01:00
private final class WithPassphraseImpl implements WithPassphrase {
private final SecretKeyRingProtector oldProtector;
private final KeyRingProtectionSettings newProtectionSettings;
private final Long keyId;
private WithPassphraseImpl(Long keyId, SecretKeyRingProtector oldProtector, KeyRingProtectionSettings newProtectionSettings) {
this.keyId = keyId;
this.oldProtector = oldProtector;
this.newProtectionSettings = newProtectionSettings;
}
2020-10-23 16:44:21 +02:00
@Override
2020-10-25 20:43:09 +01:00
public KeyRingEditorInterface toNewPassphrase(Passphrase passphrase) throws PGPException {
SecretKeyRingProtector newProtector = new PasswordBasedSecretKeyRingProtector(
newProtectionSettings, new SolitaryPassphraseProvider(passphrase));
PGPSecretKeyRing secretKeys = changePassphrase(keyId, KeyRingEditor.this.secretKeyRing, oldProtector, newProtector);
KeyRingEditor.this.secretKeyRing = secretKeys;
2020-10-23 16:44:21 +02:00
return KeyRingEditor.this;
}
@Override
2020-10-25 20:43:09 +01:00
public KeyRingEditorInterface noPassphrase() throws PGPException {
SecretKeyRingProtector newProtector = new UnprotectedKeysProtector();
PGPSecretKeyRing secretKeys = changePassphrase(keyId, KeyRingEditor.this.secretKeyRing, oldProtector, newProtector);
KeyRingEditor.this.secretKeyRing = secretKeys;
2020-10-23 16:44:21 +02:00
return KeyRingEditor.this;
}
2020-10-25 20:43:09 +01:00
private PGPSecretKeyRing changePassphrase(Long keyId,
PGPSecretKeyRing secretKeys,
SecretKeyRingProtector oldProtector,
SecretKeyRingProtector newProtector) throws PGPException {
if (keyId == null) {
// change passphrase of whole key ring
List<PGPSecretKey> newlyEncryptedSecretKeys = new ArrayList<>();
Iterator<PGPSecretKey> secretKeyIterator = secretKeys.getSecretKeys();
while (secretKeyIterator.hasNext()) {
PGPSecretKey secretKey = secretKeyIterator.next();
PGPPrivateKey privateKey = unlockSecretKey(secretKey, oldProtector);
secretKey = lockPrivateKey(privateKey, secretKey.getPublicKey(), newProtector);
newlyEncryptedSecretKeys.add(secretKey);
}
return new PGPSecretKeyRing(newlyEncryptedSecretKeys);
} else {
// change passphrase of selected subkey only
List<PGPSecretKey> secretKeyList = new ArrayList<>();
Iterator<PGPSecretKey> secretKeyIterator = secretKeys.getSecretKeys();
while (secretKeyIterator.hasNext()) {
PGPSecretKey secretKey = secretKeyIterator.next();
if (secretKey.getPublicKey().getKeyID() == keyId) {
// Re-encrypt only the selected subkey
PGPPrivateKey privateKey = unlockSecretKey(secretKey, oldProtector);
secretKey = lockPrivateKey(privateKey, secretKey.getPublicKey(), newProtector);
}
secretKeyList.add(secretKey);
}
return new PGPSecretKeyRing(secretKeyList);
}
}
// TODO: Move to utility class
private PGPSecretKey lockPrivateKey(PGPPrivateKey privateKey, PGPPublicKey publicKey, SecretKeyRingProtector protector) throws PGPException {
PGPDigestCalculator checksumCalculator = new BcPGPDigestCalculatorProvider()
// TODO: Again, SHA1?
.get(HashAlgorithm.SHA1.getAlgorithmId());
PBESecretKeyEncryptor encryptor = protector.getEncryptor(publicKey.getKeyID());
PGPSecretKey secretKey = new PGPSecretKey(privateKey, publicKey, checksumCalculator, publicKey.isMasterKey(), encryptor);
return secretKey;
}
2020-10-23 16:44:21 +02:00
}
}