mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-22 19:08:00 +01:00
Adopt changes from SOP-Java and add test for using incapable keys
This commit is contained in:
parent
0b69e18715
commit
53df487e59
7 changed files with 137 additions and 41 deletions
|
@ -99,7 +99,7 @@ public class SignUsingPublicKeyBehaviorTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
|
||||
@ExpectSystemExitWithStatus(SOPGPException.KeyCannotSign.EXIT_CODE)
|
||||
public void testSignatureCreationAndVerification() throws IOException {
|
||||
originalSout = System.out;
|
||||
InputStream originalIn = System.in;
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.regex.Pattern;
|
|||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.bcpg.S2K;
|
||||
import org.bouncycastle.bcpg.sig.PrimaryUserID;
|
||||
import org.bouncycastle.bcpg.sig.RevocationReason;
|
||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||
|
@ -1039,6 +1040,32 @@ public class KeyRingInfo {
|
|||
return !getEncryptionSubkeys(purpose).isEmpty();
|
||||
}
|
||||
|
||||
public boolean isUsableForSigning() {
|
||||
List<PGPPublicKey> signingKeys = getSigningSubkeys();
|
||||
for (PGPPublicKey pk : signingKeys) {
|
||||
PGPSecretKey sk = getSecretKey(pk.getKeyID());
|
||||
if (sk == null) {
|
||||
// Missing secret key
|
||||
continue;
|
||||
}
|
||||
S2K s2K = sk.getS2K();
|
||||
// Unencrypted key
|
||||
if (s2K == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Secret key on smart-card
|
||||
int s2kType = s2K.getType();
|
||||
if (s2kType >= 100 && s2kType <= 110) {
|
||||
continue;
|
||||
}
|
||||
// protected secret key
|
||||
return true;
|
||||
}
|
||||
// No usable secret key found
|
||||
return false;
|
||||
}
|
||||
|
||||
private KeyAccessor getKeyAccessor(@Nullable String userId, long keyID) {
|
||||
if (getPublicKey(keyID) == null) {
|
||||
throw new NoSuchElementException("No subkey with key id " + Long.toHexString(keyID) + " found on this key.");
|
||||
|
|
|
@ -12,13 +12,19 @@ import java.io.OutputStream;
|
|||
import org.bouncycastle.openpgp.PGPUtil;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import sop.Ready;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.Dearmor;
|
||||
|
||||
public class DearmorImpl implements Dearmor {
|
||||
|
||||
@Override
|
||||
public Ready data(InputStream data) throws IOException {
|
||||
InputStream decoder = PGPUtil.getDecoderStream(data);
|
||||
InputStream decoder;
|
||||
try {
|
||||
decoder = PGPUtil.getDecoderStream(data);
|
||||
} catch (IOException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
return new Ready() {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.pgpainless.encryption_signing.ProducerOptions;
|
|||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.exception.KeyException;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import sop.MicAlg;
|
||||
|
@ -54,11 +55,15 @@ public class DetachedSignImpl implements DetachedSign {
|
|||
}
|
||||
|
||||
@Override
|
||||
public DetachedSign key(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
|
||||
public DetachedSign key(InputStream keyIn) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
|
||||
try {
|
||||
PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn);
|
||||
|
||||
for (PGPSecretKeyRing key : keys) {
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(key);
|
||||
if (!info.isUsableForSigning()) {
|
||||
throw new SOPGPException.KeyCannotSign("Key " + info.getFingerprint() + " does not have valid, signing capable subkeys.");
|
||||
}
|
||||
protector.addSecretKey(key);
|
||||
signingOptions.addDetachedSignature(protector, key, modeToSigType(mode));
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.pgpainless.encryption_signing.EncryptionOptions;
|
|||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.exception.KeyException;
|
||||
import org.pgpainless.exception.WrongPassphraseException;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import sop.Ready;
|
||||
|
@ -105,6 +106,8 @@ public class EncryptImpl implements Encrypt {
|
|||
.keyRingCollection(cert, false)
|
||||
.getPgpPublicKeyRingCollection();
|
||||
encryptionOptions.addRecipients(certificates);
|
||||
} catch (KeyException.UnacceptableEncryptionKeyException e) {
|
||||
throw new SOPGPException.CertCannotEncrypt(e.getMessage(), e);
|
||||
} catch (IOException | PGPException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
}
|
||||
|
|
|
@ -14,20 +14,17 @@ import java.util.List;
|
|||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.encryption_signing.EncryptionResult;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.encryption_signing.ProducerOptions;
|
||||
import org.pgpainless.encryption_signing.SigningOptions;
|
||||
import org.pgpainless.exception.KeyException;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.OpenPgpFingerprint;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import sop.MicAlg;
|
||||
import sop.ReadyWithResult;
|
||||
import sop.SigningResult;
|
||||
import sop.Ready;
|
||||
import sop.enums.InlineSignAs;
|
||||
import sop.exception.SOPGPException;
|
||||
import sop.operation.InlineSign;
|
||||
|
@ -38,6 +35,7 @@ public class InlineSignImpl implements InlineSign {
|
|||
private InlineSignAs mode = InlineSignAs.Binary;
|
||||
private final SigningOptions signingOptions = new SigningOptions();
|
||||
private final MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector();
|
||||
private final List<PGPSecretKeyRing> signingKeys = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public InlineSign mode(InlineSignAs mode) throws SOPGPException.UnsupportedOption {
|
||||
|
@ -52,17 +50,17 @@ public class InlineSignImpl implements InlineSign {
|
|||
}
|
||||
|
||||
@Override
|
||||
public InlineSign key(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
|
||||
public InlineSign key(InputStream keyIn) throws SOPGPException.KeyCannotSign, SOPGPException.BadData, IOException {
|
||||
try {
|
||||
PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn);
|
||||
|
||||
for (PGPSecretKeyRing key : keys) {
|
||||
protector.addSecretKey(key);
|
||||
if (mode == InlineSignAs.CleartextSigned) {
|
||||
signingOptions.addDetachedSignature(protector, key, DocumentSignatureType.BINARY_DOCUMENT);
|
||||
} else {
|
||||
signingOptions.addInlineSignature(protector, key, modeToSigType(mode));
|
||||
KeyRingInfo info = PGPainless.inspectKeyRing(key);
|
||||
if (!info.isUsableForSigning()) {
|
||||
throw new SOPGPException.KeyCannotSign("Key " + info.getFingerprint() + " does not have valid, signing capable subkeys.");
|
||||
}
|
||||
protector.addSecretKey(key);
|
||||
signingKeys.add(key);
|
||||
}
|
||||
} catch (PGPException | KeyException e) {
|
||||
throw new SOPGPException.BadData(e);
|
||||
|
@ -78,7 +76,20 @@ public class InlineSignImpl implements InlineSign {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ReadyWithResult<SigningResult> data(InputStream data) throws IOException, SOPGPException.ExpectedText {
|
||||
public Ready data(InputStream data) throws SOPGPException.KeyIsProtected, IOException, SOPGPException.ExpectedText {
|
||||
for (PGPSecretKeyRing key : signingKeys) {
|
||||
try {
|
||||
if (mode == InlineSignAs.CleartextSigned) {
|
||||
signingOptions.addDetachedSignature(protector, key, DocumentSignatureType.BINARY_DOCUMENT);
|
||||
} else {
|
||||
signingOptions.addInlineSignature(protector, key, modeToSigType(mode));
|
||||
}
|
||||
} catch (KeyException.UnacceptableSigningKeyException | KeyException.MissingSecretKeyException e) {
|
||||
throw new SOPGPException.KeyCannotSign("Key " + OpenPgpFingerprint.of(key) + " cannot sign.", e);
|
||||
} catch (PGPException e) {
|
||||
throw new SOPGPException.KeyIsProtected("Key " + OpenPgpFingerprint.of(key) + " cannot be unlocked.", e);
|
||||
}
|
||||
}
|
||||
|
||||
ProducerOptions producerOptions = ProducerOptions.sign(signingOptions);
|
||||
if (mode == InlineSignAs.CleartextSigned) {
|
||||
|
@ -88,9 +99,9 @@ public class InlineSignImpl implements InlineSign {
|
|||
producerOptions.setAsciiArmor(armor);
|
||||
}
|
||||
|
||||
return new ReadyWithResult<SigningResult>() {
|
||||
return new Ready() {
|
||||
@Override
|
||||
public SigningResult writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature {
|
||||
public void writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature {
|
||||
try {
|
||||
EncryptionStream signingStream = PGPainless.encryptAndOrSign()
|
||||
.onOutputStream(outputStream)
|
||||
|
@ -102,19 +113,9 @@ public class InlineSignImpl implements InlineSign {
|
|||
|
||||
Streams.pipeAll(data, signingStream);
|
||||
signingStream.close();
|
||||
EncryptionResult encryptionResult = signingStream.getResult();
|
||||
|
||||
// forget passphrases
|
||||
protector.clear();
|
||||
|
||||
List<PGPSignature> signatures = new ArrayList<>();
|
||||
for (SubkeyIdentifier key : encryptionResult.getDetachedSignatures().keySet()) {
|
||||
signatures.addAll(encryptionResult.getDetachedSignatures().get(key));
|
||||
}
|
||||
|
||||
return SigningResult.builder()
|
||||
.setMicAlg(micAlgFromSignatures(signatures))
|
||||
.build();
|
||||
} catch (PGPException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@ -122,19 +123,6 @@ public class InlineSignImpl implements InlineSign {
|
|||
};
|
||||
}
|
||||
|
||||
private MicAlg micAlgFromSignatures(Iterable<PGPSignature> signatures) {
|
||||
int algorithmId = 0;
|
||||
for (PGPSignature signature : signatures) {
|
||||
int sigAlg = signature.getHashAlgorithm();
|
||||
if (algorithmId == 0 || algorithmId == sigAlg) {
|
||||
algorithmId = sigAlg;
|
||||
} else {
|
||||
return MicAlg.empty();
|
||||
}
|
||||
}
|
||||
return algorithmId == 0 ? MicAlg.empty() : MicAlg.fromHashAlgorithmId(algorithmId);
|
||||
}
|
||||
|
||||
private static DocumentSignatureType modeToSigType(InlineSignAs mode) {
|
||||
return mode == InlineSignAs.Binary ? DocumentSignatureType.BINARY_DOCUMENT
|
||||
: DocumentSignatureType.CANONICAL_TEXT_DOCUMENT;
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package org.pgpainless.sop;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.KeyFlag;
|
||||
import org.pgpainless.key.generation.KeySpec;
|
||||
import org.pgpainless.key.generation.type.KeyType;
|
||||
import org.pgpainless.key.generation.type.ecc.EllipticCurve;
|
||||
import org.pgpainless.key.generation.type.eddsa.EdDSACurve;
|
||||
import org.pgpainless.util.ArmorUtils;
|
||||
import sop.SOP;
|
||||
import sop.exception.SOPGPException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class IncapableKeysTest {
|
||||
|
||||
private static byte[] nonSigningKey;
|
||||
private static byte[] nonEncryptionKey;
|
||||
private static byte[] nonSigningCert;
|
||||
private static byte[] nonEncryptionCert;
|
||||
|
||||
private static final SOP sop = new SOPImpl();
|
||||
|
||||
@BeforeAll
|
||||
public static void generateKeys() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
|
||||
PGPSecretKeyRing key = PGPainless.buildKeyRing()
|
||||
.addSubkey(KeySpec.getBuilder(KeyType.ECDH(EllipticCurve._P256), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
||||
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
|
||||
.addUserId("Non Signing <non@signing.key>")
|
||||
.build();
|
||||
nonSigningKey = ArmorUtils.toAsciiArmoredString(key).getBytes(StandardCharsets.UTF_8);
|
||||
nonSigningCert = sop.extractCert().key(nonSigningKey).getBytes();
|
||||
|
||||
key = PGPainless.buildKeyRing()
|
||||
.addSubkey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA))
|
||||
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
|
||||
.addUserId("Non Encryption <non@encryption.key>")
|
||||
.build();
|
||||
nonEncryptionKey = ArmorUtils.toAsciiArmoredString(key).getBytes(StandardCharsets.UTF_8);
|
||||
nonEncryptionCert = sop.extractCert().key(nonEncryptionKey).getBytes();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptionToNonEncryptionKeyFails() {
|
||||
assertThrows(SOPGPException.CertCannotEncrypt.class, () -> sop.encrypt().withCert(nonEncryptionCert));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signingWithNonSigningKeyFails() {
|
||||
assertThrows(SOPGPException.KeyCannotSign.class, () -> sop.sign().key(nonSigningKey));
|
||||
assertThrows(SOPGPException.KeyCannotSign.class, () -> sop.detachedSign().key(nonSigningKey));
|
||||
assertThrows(SOPGPException.KeyCannotSign.class, () -> sop.inlineSign().key(nonSigningKey));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue