mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-23 03:17:58 +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 {
|
dependencies {
|
||||||
implementation 'org.jetbrains:annotations:20.1.0'
|
implementation 'org.jetbrains:annotations:20.1.0'
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
|
||||||
|
|
||||||
// Logging
|
// 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.InlineSign;
|
||||||
import sop.operation.InlineVerify;
|
import sop.operation.InlineVerify;
|
||||||
import sop.operation.ListProfiles;
|
import sop.operation.ListProfiles;
|
||||||
|
import sop.operation.RevokeKey;
|
||||||
import sop.operation.Version;
|
import sop.operation.Version;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,6 +103,11 @@ public class SOPImpl implements SOP {
|
||||||
return new ListProfilesImpl();
|
return new ListProfilesImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RevokeKey revokeKey() {
|
||||||
|
return new RevokeKeyImpl();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InlineDetach inlineDetach() {
|
public InlineDetach inlineDetach() {
|
||||||
return new InlineDetachImpl();
|
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'
|
logbackVersion = '1.2.11'
|
||||||
mockitoVersion = '4.5.1'
|
mockitoVersion = '4.5.1'
|
||||||
slf4jVersion = '1.7.36'
|
slf4jVersion = '1.7.36'
|
||||||
sopJavaVersion = '6.1.0'
|
sopJavaVersion = '7.0.0-SNAPSHOT'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue