mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-22 19:08:00 +01:00
Initial implementation of the new revoke-key command from SOP-07
This commit is contained in:
parent
556d1bee30
commit
37bbe8bb39
5 changed files with 163 additions and 1 deletions
|
@ -16,6 +16,7 @@ repositories {
|
|||
dependencies {
|
||||
implementation 'org.jetbrains:annotations:20.1.0'
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||
|
||||
// Logging
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||
import org.bouncycastle.bcpg.PublicKeyPacket;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.exception.WrongPassphraseException;
|
||||
import org.pgpainless.key.OpenPgpFingerprint;
|
||||
import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface;
|
||||
import org.pgpainless.key.util.KeyRingUtils;
|
||||
import org.pgpainless.key.util.RevocationAttributes;
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.RevokeKey;
|
||||
import sop.util.UTF8Util;
|
||||
|
||||
public class RevokeKeyImpl implements RevokeKey {
|
||||
|
||||
private final MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector();
|
||||
private boolean armor = true;
|
||||
|
||||
public RevokeKey noArmor() {
|
||||
this.armor = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the decryption password for the secret key.
|
||||
*
|
||||
* @param password password
|
||||
* @return builder instance
|
||||
* @throws sop.exception.SOPGPException.UnsupportedOption if the implementation does not support key passwords
|
||||
* @throws sop.exception.SOPGPException.PasswordNotHumanReadable if the password is not human-readable
|
||||
*/
|
||||
public RevokeKey withKeyPassword(byte[] password)
|
||||
throws SOPGPException.UnsupportedOption,
|
||||
SOPGPException.PasswordNotHumanReadable {
|
||||
String string;
|
||||
try {
|
||||
string = UTF8Util.decodeUTF8(password);
|
||||
} catch (CharacterCodingException e) {
|
||||
throw new SOPGPException.PasswordNotHumanReadable("Cannot UTF8-decode password.");
|
||||
}
|
||||
protector.addPassphrase(Passphrase.fromPassword(string));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Ready keys(InputStream keys) throws SOPGPException.BadData {
|
||||
PGPSecretKeyRingCollection secretKeyRings;
|
||||
try {
|
||||
secretKeyRings = PGPainless.readKeyRing().secretKeyRingCollection(keys);
|
||||
} catch (IOException e) {
|
||||
throw new SOPGPException.BadData("Cannot decode secret keys.", e);
|
||||
}
|
||||
for (PGPSecretKeyRing secretKeys : secretKeyRings) {
|
||||
protector.addSecretKey(secretKeys);
|
||||
}
|
||||
|
||||
final List<PGPPublicKeyRing> revocationCertificates = new ArrayList<>();
|
||||
for (PGPSecretKeyRing secretKeys : secretKeyRings) {
|
||||
SecretKeyRingEditorInterface editor = PGPainless.modifyKeyRing(secretKeys);
|
||||
try {
|
||||
RevocationAttributes revocationAttributes = RevocationAttributes.createKeyRevocation()
|
||||
.withReason(RevocationAttributes.Reason.KEY_RETIRED)
|
||||
.withoutDescription();
|
||||
if (secretKeys.getPublicKey().getVersion() == PublicKeyPacket.VERSION_6) {
|
||||
PGPPublicKeyRing revocation = editor.createMinimalRevocationCertificate(protector, revocationAttributes);
|
||||
revocationCertificates.add(revocation);
|
||||
} else {
|
||||
PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKeys);
|
||||
PGPSignature revocation = editor.createRevocation(protector, revocationAttributes);
|
||||
certificate = KeyRingUtils.injectCertification(certificate, revocation);
|
||||
revocationCertificates.add(certificate);
|
||||
}
|
||||
} catch (WrongPassphraseException e) {
|
||||
throw new SOPGPException.KeyIsProtected("Missing or wrong passphrase for key " + OpenPgpFingerprint.of(secretKeys), e);
|
||||
}
|
||||
catch (PGPException e) {
|
||||
throw new RuntimeException("Cannot generate revocation certificate.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return new Ready() {
|
||||
@Override
|
||||
public void writeTo(OutputStream outputStream) throws IOException {
|
||||
PGPPublicKeyRingCollection certificateCollection = new PGPPublicKeyRingCollection(revocationCertificates);
|
||||
if (armor) {
|
||||
ArmoredOutputStream out = ArmoredOutputStreamFactory.get(outputStream);
|
||||
certificateCollection.encode(out);
|
||||
out.close();
|
||||
} else {
|
||||
certificateCollection.encode(outputStream);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ import sop.operation.GenerateKey;
|
|||
import sop.operation.InlineSign;
|
||||
import sop.operation.InlineVerify;
|
||||
import sop.operation.ListProfiles;
|
||||
import sop.operation.RevokeKey;
|
||||
import sop.operation.Version;
|
||||
|
||||
/**
|
||||
|
@ -102,6 +103,11 @@ public class SOPImpl implements SOP {
|
|||
return new ListProfilesImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RevokeKey revokeKey() {
|
||||
return new RevokeKeyImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InlineDetach inlineDetach() {
|
||||
return new InlineDetachImpl();
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sop.testsuite.pgpainless.operation;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import sop.SOP;
|
||||
import sop.testsuite.operation.RevokeKeyTest;
|
||||
|
||||
public class PGPainlessRevokeKeyTest extends RevokeKeyTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideInstances") // from sop-java's RevokeKeyTest class
|
||||
@Override
|
||||
public void revokeUnprotectedKey(SOP sop) throws IOException {
|
||||
super.revokeUnprotectedKey(sop);
|
||||
|
||||
byte[] key = sop.generateKey().generate().getBytes();
|
||||
byte[] revokedKey = sop.revokeKey().keys(key).getBytes();
|
||||
|
||||
PGPKeyRing certificate = PGPainless.readKeyRing().keyRing(revokedKey);
|
||||
assertFalse(certificate instanceof PGPSecretKeyRing);
|
||||
assertTrue(certificate instanceof PGPPublicKeyRing);
|
||||
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(certificate);
|
||||
assertTrue(info.getRevocationState().isSoftRevocation());
|
||||
}
|
||||
}
|
|
@ -14,6 +14,6 @@ allprojects {
|
|||
logbackVersion = '1.2.11'
|
||||
mockitoVersion = '4.5.1'
|
||||
slf4jVersion = '1.7.36'
|
||||
sopJavaVersion = '6.1.0'
|
||||
sopJavaVersion = '7.0.0-SNAPSHOT'
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue