Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

High-Level OpenPGP API #1911

Draft
wants to merge 66 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
71a2253
WIP: Design high-level encryption API
vanitasvitae Oct 28, 2024
556647d
Add basic test for OpenPGPMessageGenerator
vanitasvitae Oct 28, 2024
4ff1425
Single class imports
vanitasvitae Oct 28, 2024
5219e19
Fix NPE for PBE and add static encryption test
vanitasvitae Oct 29, 2024
1b2bee8
Add support for compression
vanitasvitae Oct 29, 2024
1293742
Split up OpenPGPMessageGeneratorTest into static test
vanitasvitae Oct 29, 2024
9f376a8
WIP: SIgning
vanitasvitae Oct 29, 2024
22c388b
Rework OpenPGPMessageOutputStream and implement signing
vanitasvitae Oct 30, 2024
ad5fa41
Add MessageGeneratorTests to RegressionTests
vanitasvitae Oct 30, 2024
688d96a
Improve API
vanitasvitae Oct 30, 2024
08d3cf0
Add javadoc
vanitasvitae Oct 30, 2024
892d624
WIP: Start implementing API for certificate/key evaluation
vanitasvitae Nov 4, 2024
9d686a8
Work on signature verification
vanitasvitae Nov 5, 2024
a081791
Start work on interdimensionalsuperdatastructure
vanitasvitae Nov 6, 2024
68f7038
terminology
vanitasvitae Nov 6, 2024
b040d79
Document the idea behind the data structure
vanitasvitae Nov 6, 2024
9394b83
WIP
vanitasvitae Nov 6, 2024
3d75f7d
Add first test
vanitasvitae Nov 7, 2024
3896efc
Print chains
vanitasvitae Nov 7, 2024
38e9945
Further WIP
vanitasvitae Nov 7, 2024
3710e03
Testing
vanitasvitae Nov 8, 2024
ede197f
Integrate SortedSignatureCollection into Certificate
vanitasvitae Nov 11, 2024
154156d
Some workarounds for bugs
vanitasvitae Nov 12, 2024
364c0af
Dedicated signature verification exceptions
vanitasvitae Nov 13, 2024
49c2e80
Prototype key selection
vanitasvitae Nov 13, 2024
817e697
Move key inspection methods to key itself
vanitasvitae Nov 14, 2024
02c4efa
Integrate OpenPGPCertificate class with OpenPGPMessageGenerator
vanitasvitae Nov 14, 2024
45267b0
Test proper extraction of subpackets from direct or indirect signatures
vanitasvitae Nov 14, 2024
d7e6f3c
Shape API
vanitasvitae Nov 14, 2024
a39a673
OpenPGPKey: overwrite getPGPKeyRing()
vanitasvitae Nov 14, 2024
d74ff90
OpenPGPSignature: Expose getKeyIdentifiers()
vanitasvitae Nov 14, 2024
46190a7
Progress
vanitasvitae Nov 14, 2024
00d4bad
Implement toAsciiArmoredString()
vanitasvitae Nov 14, 2024
7e3a9cd
NotationRegistry: Use a set for notation names
vanitasvitae Nov 14, 2024
ceeba62
Preferences: Inspect certifying signatures only
vanitasvitae Nov 14, 2024
8483702
Rename CertificatePrinter -> DebugPrinter
vanitasvitae Nov 14, 2024
a17eae2
Prototype encryption algorithm negotiation
vanitasvitae Nov 14, 2024
a4840fc
Prototype OpenPGPMessageProcessor
vanitasvitae Nov 15, 2024
177ce9b
Test message processor
vanitasvitae Nov 15, 2024
f24e483
Rename OpenPGPNotationRegistry
vanitasvitae Nov 15, 2024
8cc5c91
BcPBEDataDecryptorFactory: fix constructor parameter type
vanitasvitae Nov 16, 2024
597c5c2
JcePBEKeyEncryptionMethodGenerator: Expose constructor taking Argon2 …
vanitasvitae Nov 16, 2024
18d7c15
Introduce OpenPGPImplementation classes
vanitasvitae Nov 16, 2024
d7b75d4
Fix method name
vanitasvitae Nov 16, 2024
69cadc1
get PGPObjectFactory from OpenPGPImplementation
vanitasvitae Nov 16, 2024
0716d02
Further integration of OpenPGPImplementation class
vanitasvitae Nov 16, 2024
ecd7051
Clean up API
vanitasvitae Nov 16, 2024
ea5cde9
Default to BcOpenPGPImplementation
vanitasvitae Nov 16, 2024
f651783
Use static instance of OpenPGPImplementation
vanitasvitae Nov 16, 2024
d13271c
Remove lies from PBEKeyEncryptionMethodGenerator javadoc
vanitasvitae Nov 16, 2024
9445344
Document the OpenPGPImplementation class and abstract providers
vanitasvitae Nov 16, 2024
dbdb421
Restructure and document exceptions
vanitasvitae Nov 16, 2024
b611ab2
Javadoc and cleanup
vanitasvitae Nov 16, 2024
fb31567
Clean up API for armor comments
vanitasvitae Nov 16, 2024
ecfb1c0
OpenPGPKey.getComponents(): Swap in secret key compoenents
vanitasvitae Nov 16, 2024
3a7f5df
Clean up API and do proper algorithm support negotiation
vanitasvitae Nov 18, 2024
28c717c
Optionally apply padding inside the SEIPD2 packet only
vanitasvitae Nov 18, 2024
4a54d01
Test signed SEIPD2 encrypted message generation
vanitasvitae Nov 18, 2024
ff48f35
Use proper signingKey method in tests
vanitasvitae Nov 18, 2024
517686f
Improve tests
vanitasvitae Nov 18, 2024
6eb4dc5
OpenPGPMessageProcessor: Do not crash on PGPOnePassSIgnatureList
vanitasvitae Nov 18, 2024
f4b727f
Add stub tests for signed messages
vanitasvitae Nov 18, 2024
d10f7cd
Improve OpenPGPMessageProcessor API
vanitasvitae Nov 19, 2024
6a71b4b
More tests for encrypting/decrypting messages
vanitasvitae Nov 19, 2024
6fcd2df
Move StackPassphraseCallback to test module
vanitasvitae Nov 19, 2024
a5f924c
Start working on nested OpenPGP streams
vanitasvitae Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions pg/src/main/java/org/bouncycastle/openpgp/KeyIdentifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public class KeyIdentifier
private final byte[] fingerprint;
private final long keyId;

public KeyIdentifier(String hexEncoded)
{
this(Hex.decode(hexEncoded));
}

/**
* Create a new {@link KeyIdentifier} based on a keys fingerprint.
* For fingerprints matching the format of a v4, v5 or v6 key, the constructor will
Expand Down Expand Up @@ -263,6 +268,26 @@ public boolean isWildcard()
return keyId == 0L && fingerprint.length == 0;
}

public static boolean matches(List<KeyIdentifier> identifiers, KeyIdentifier identifier, boolean explicit)
{
for (KeyIdentifier candidate : identifiers)
{
if (!explicit && candidate.isWildcard())
{
return true;
}

if (candidate.getFingerprint() != null &&
Arrays.constantTimeAreEqual(candidate.getFingerprint(), identifier.getFingerprint()))
{
return true;
}

return candidate.getKeyId() == identifier.getKeyId();
}
return false;
}

/**
* Return true, if any of the {@link KeyIdentifier KeyIdentifiers} in the {@code identifiers} list
* matches the given {@link PGPPublicKey}.
Expand Down Expand Up @@ -327,6 +352,33 @@ public static boolean matches(List<KeyIdentifier> identifiers,
return false;
}

@Override
public boolean equals(Object obj) {
if (obj == null)
{
return false;
}
if (this == obj)
{
return true;
}
if (!(obj instanceof KeyIdentifier))
{
return false;
}
KeyIdentifier other = (KeyIdentifier) obj;
if (getFingerprint() != null && other.getFingerprint() != null)
{
return Arrays.constantTimeAreEqual(getFingerprint(), other.getFingerprint());
}
return getKeyId() == other.getKeyId();
}

@Override
public int hashCode() {
return (int) getKeyId();
}

public String toString()
{
if (isWildcard())
Expand Down
33 changes: 33 additions & 0 deletions pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.bouncycastle.bcpg.TrustPacket;
import org.bouncycastle.bcpg.sig.IssuerFingerprint;
import org.bouncycastle.bcpg.sig.IssuerKeyID;
import org.bouncycastle.bcpg.sig.RevocationReason;
import org.bouncycastle.bcpg.sig.RevocationReasonTags;
import org.bouncycastle.math.ec.rfc8032.Ed25519;
import org.bouncycastle.math.ec.rfc8032.Ed448;
import org.bouncycastle.openpgp.operator.PGPContentVerifier;
Expand Down Expand Up @@ -897,6 +899,37 @@ public static boolean isCertification(int signatureType)
|| PGPSignature.POSITIVE_CERTIFICATION == signatureType;
}

public static boolean isRevocation(int signatureType)
{
return PGPSignature.KEY_REVOCATION == signatureType
|| PGPSignature.CERTIFICATION_REVOCATION == signatureType
|| PGPSignature.SUBKEY_REVOCATION == signatureType;
}

public boolean isHardRevocation()
{
if (!isRevocation(getSignatureType()))
{
return false; // no revocation
}

if (!hasSubpackets())
{
return true; // consider missing subpackets (and therefore missing reason) as hard revocation
}

// only consider reasons from the hashed packet area
RevocationReason reason = getHashedSubPackets() != null ?
getHashedSubPackets().getRevocationReason() : null;
if (reason == null)
{
return true; // missing reason packet is hard
}

return reason.getRevocationReason() == RevocationReasonTags.NO_REASON // No reason is hard
|| reason.getRevocationReason() == RevocationReasonTags.KEY_COMPROMISED; // key compromise is hard
}

/**
* Return true, if the cryptographic signature encoding of the two signatures match.
* @param sig1 first signature
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.bouncycastle.openpgp;

public class PGPSignatureException
extends PGPException
{
public PGPSignatureException(String message)
{
super(message);
}

public PGPSignatureException(String message, Exception cause)
{
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package org.bouncycastle.openpgp.api;

import java.io.IOException;
import java.io.InputStream;

/**
* Implementation of {@link InputStream} that withholds a number of bytes ({@link #CIRCULAR_BUFFER_SIZE} by
* default) from the original message until the message has been processed completely.
* This is done in order to minimize the risk of emitting unauthenticated plaintext, while being somewhat
* resource-efficient.
* The number of bytes to withhold can be configured.
*/
public class AuthenticatingInputStream
extends InputStream
{
private static final int CIRCULAR_BUFFER_SIZE = 1024 * 1024 * 32; // 32 MiB

private final byte[] circularBuffer;
private int lastWrittenPos = 0;
private int bufReadPos = 0;
private final InputStream in;
private boolean closed = false;

public AuthenticatingInputStream(InputStream in)
{
this(in, CIRCULAR_BUFFER_SIZE);
}

public AuthenticatingInputStream(InputStream in, int bufferSize)
{
if (bufferSize <= 0)
{
throw new IllegalArgumentException("Buffer size cannot be null nor negative.");
}
this.circularBuffer = new byte[bufferSize];
this.in = in;
}

private void fill()
throws IOException
{
if (closed)
{
return;
}

// readerPos - 1 % buf.len
int lastAvailPos = (circularBuffer.length + bufReadPos - 1) % circularBuffer.length;
int read;
if (lastWrittenPos < lastAvailPos)
{
read = in.read(circularBuffer, lastWrittenPos, lastAvailPos - lastWrittenPos);
}
else
{
read = in.read(circularBuffer, lastWrittenPos, circularBuffer.length - lastWrittenPos);
if (read >= 0)
{
lastWrittenPos += read;
}
read = in.read(circularBuffer, 0, lastAvailPos);
}

if (read >= 0)
{
lastWrittenPos += read;
}
else
{
close();
}

lastWrittenPos %= circularBuffer.length;
}

@Override
public void close()
throws IOException
{
if (!closed)
{
closed = true;
System.out.println("Close");
in.close();
}
}

@Override
public int read()
throws IOException
{
fill();
if (bufReadPos == lastWrittenPos)
{
return -1;
}
int i = circularBuffer[bufReadPos++];
bufReadPos %= circularBuffer.length;
return i;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package org.bouncycastle.openpgp.api;

import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptorBuilderProvider;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilderProvider;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.bc.BcSessionKeyDataDecryptorFactory;

import java.io.InputStream;

public class BcOpenPGPImplementation
extends OpenPGPImplementation
{
@Override
public PGPObjectFactory pgpObjectFactory(InputStream packetInputStream)
{
return new BcPGPObjectFactory(packetInputStream);
}

@Override
public PGPContentVerifierBuilderProvider pgpContentVerifierBuilderProvider()
{
return new BcPGPContentVerifierBuilderProvider();
}

@Override
public PBESecretKeyDecryptorBuilderProvider pbeSecretKeyDecryptorBuilderProvider()
{
return new BcPBESecretKeyDecryptorBuilderProvider();
}

@Override
public PGPDataEncryptorBuilder pgpDataEncryptorBuilder(int symmetricKeyAlgorithm)
{
return new BcPGPDataEncryptorBuilder(symmetricKeyAlgorithm);
}

@Override
public PublicKeyKeyEncryptionMethodGenerator publicKeyKeyEncryptionMethodGenerator(PGPPublicKey encryptionSubkey)
{
return new BcPublicKeyKeyEncryptionMethodGenerator(encryptionSubkey);
}

@Override
public PBEKeyEncryptionMethodGenerator pbeKeyEncryptionMethodGenerator(char[] messagePassphrase)
{
return new BcPBEKeyEncryptionMethodGenerator(messagePassphrase);
}

@Override
public PBEKeyEncryptionMethodGenerator pbeKeyEncryptionMethodGenerator(char[] messagePassphrase, S2K.Argon2Params argon2Params)
{
return new BcPBEKeyEncryptionMethodGenerator(messagePassphrase, argon2Params);
}

@Override
public PGPContentSignerBuilder pgpContentSignerBuilder(int publicKeyAlgorithm, int hashAlgorithm)
{
return new BcPGPContentSignerBuilder(publicKeyAlgorithm, hashAlgorithm);
}

@Override
public PBEDataDecryptorFactory pbeDataDecryptorFactory(char[] messagePassphrase)
throws PGPException
{
return new BcPBEDataDecryptorFactory(messagePassphrase, pgpDigestCalculatorProvider());
}

@Override
public SessionKeyDataDecryptorFactory sessionKeyDataDecryptorFactory(PGPSessionKey sessionKey)
{
return new BcSessionKeyDataDecryptorFactory(sessionKey);
}

@Override
public PublicKeyDataDecryptorFactory publicKeyDataDecryptorFactory(PGPPrivateKey decryptionKey)
{
return new BcPublicKeyDataDecryptorFactory(decryptionKey);
}

@Override
public PGPDigestCalculatorProvider pgpDigestCalculatorProvider()
throws PGPException
{
return new BcPGPDigestCalculatorProvider();
}
}
Loading