Initial implementation of the new revoke-key command from SOP-07

This commit is contained in:
Paul Schaub 2023-07-11 23:15:22 +02:00
parent 556d1bee30
commit 37bbe8bb39
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
5 changed files with 163 additions and 1 deletions

View File

@ -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

View File

@ -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);
}
}
};
}
}

View File

@ -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();

View File

@ -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());
}
}

View File

@ -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'
}
}