Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e0222af
OpenPGP Smart Card Support: Add support for message signing
vanitasvitae Sep 8, 2026
5e613d2
Reuse parseEphemeralKeyAndKeyEnc methods
vanitasvitae Sep 8, 2026
42bbca6
Direct conversion of JCE ECDH key without PGPPublicKey workaround
vanitasvitae Sep 8, 2026
d55fcc9
B1 - move PGPContentSignerBuilderProviderFactory into openpgp/api/ope…
vanitasvitae Sep 10, 2026
f339de7
B2 - Move build(sigType) into PGPExternalContentSignerBuilder to avoi…
vanitasvitae Sep 10, 2026
3b8cf61
Conventions: Curly Brackets
vanitasvitae Sep 10, 2026
2a87e19
Conventions: Wildcard imports
vanitasvitae Sep 10, 2026
dd2af32
Conventions: Uncommented code
vanitasvitae Sep 10, 2026
50eb8cb
Conventions: Readme with newline
vanitasvitae Sep 10, 2026
a8eb7c5
Conventions: Suppress println statement
vanitasvitae Sep 10, 2026
8e3fe9c
Conventions C6: Bring exceptions of decrypt/sign in line
vanitasvitae Sep 10, 2026
e89c278
ExternalContentSignerBuilder: Explain why we need two PGPDigestCalcul…
vanitasvitae Sep 10, 2026
6eff13a
ExternalContentSignerBuilder: Replace use of pqc DigestUtils with Mes…
vanitasvitae Sep 10, 2026
bef5f29
Reduce visibility of BcPublicKeyDataDecryptorFactory.decodePeerKey()
vanitasvitae Sep 10, 2026
3732edc
B3 - Fix NPE when no card holds the signing key
vanitasvitae Sep 10, 2026
dafad9b
B4 - BcPublicKeyDataDecryptorFactory: Throw PGPException for unsuppor…
vanitasvitae Sep 10, 2026
aba2fc8
B5 - fix off-by-one in shortened legacy identifier check
vanitasvitae Sep 10, 2026
bf6cedb
B5 - Add test for off-by-one error
vanitasvitae Sep 10, 2026
139199d
B6 - SupportedAlgorithms: Fix IllegalArgumentException for EC.matches…
vanitasvitae Sep 10, 2026
5df3c4d
B7 - Fix StringIndexOutOfBoundsException on toString() if SupportedAl…
vanitasvitae Sep 10, 2026
01bdbc6
B8 - OpenPGPSmartCardBackend: Do not shadow the converter in subclasses
vanitasvitae Sep 10, 2026
049daae
B9 - Shortened legacy fingerprints: Make identifier writer and matche…
vanitasvitae Sep 10, 2026
a2f1ba1
D1 - Replace BCECPublicKeyHelper, EDECPublicKeyConverter with KeyFact…
vanitasvitae Sep 10, 2026
057aa82
D2 - JceExternalPublicKeyDataDecryptorFactoryBuilder: Use helper.crea…
vanitasvitae Sep 10, 2026
cb86392
F1: openpgp.operator: Remove dependency on openpgp.api
vanitasvitae Sep 17, 2026
513bb96
F2: Do not throw unchecked PGPRuntimeOperationException during decrypt
vanitasvitae Sep 17, 2026
8e10966
F3: Implement proper OpenPGP digest OID lookup
vanitasvitae Sep 17, 2026
fb34495
F4: Fix signing with auth key PIN verification
vanitasvitae Sep 17, 2026
9af38e5
OpenPGPMessageProcessor: Add .setExceptionCallback()
vanitasvitae Sep 17, 2026
4e5a6fc
DecryptionTest: printStacktrace() exceptions
vanitasvitae Sep 17, 2026
ed06faa
F9: OpenPGPSmartCardBackend.convertPublicKey(): Detect algorithm by OID
vanitasvitae Sep 17, 2026
5973461
Add branches for Ed448, X448
vanitasvitae Sep 17, 2026
2b1b53f
Generalize convertPublicKey() methods
vanitasvitae Sep 17, 2026
d210c3c
SimulatorOpenPGPSmartCardBackend: Initialize with non-null JcaPGPKeyC…
vanitasvitae Sep 17, 2026
e9c0f90
Transform YubikeySmartCardBackendTest into general OpenPGPSmartCardBa…
vanitasvitae Sep 17, 2026
8a7be67
Reorganize tests
vanitasvitae Sep 17, 2026
660c41c
F10: SimulatorOpenPGPSmartCard: Use PublicKeyFactory.createKey(byte[]…
vanitasvitae Sep 17, 2026
fa2461c
Add missing package-info.java files
vanitasvitae Sep 17, 2026
3dc8e52
ExternalOpenPGPKeyUtils.fromCertificate(): Pass in implementation
vanitasvitae Sep 17, 2026
8c6d4ad
F6: Properly handle failing backends, add test for failing signature,…
vanitasvitae Sep 17, 2026
ef66b06
F7: BcPublicKeyDataDecryptorFactory: Add deprecated shim for getCrypt…
vanitasvitae Sep 17, 2026
9d5dba2
Remove notice about ElGamal, X448 from BcSmartCardPublicKeyDataDecryp…
vanitasvitae Sep 17, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.openpgp.operator.PGPContentSigner;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.PGPExternalContentSignerBuilder;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Strings;

Expand Down Expand Up @@ -90,7 +91,7 @@ public PGPSignatureGenerator(
}

/**
* Initialise the generator for signing.
* Initialize the generator for signing.
*
* @param signatureType type of signature
* @param key private signing key
Expand All @@ -105,7 +106,20 @@ public void init(
{
throw new PGPException("Illegal signature type 0xFF provided.");
}
contentSigner = contentSignerBuilder.build(signatureType, key);

if (contentSignerBuilder instanceof PGPExternalContentSignerBuilder)
{
contentSigner = ((PGPExternalContentSignerBuilder)contentSignerBuilder).build(signatureType);
}
else if (key != null)
{
contentSigner = contentSignerBuilder.build(signatureType, key);
}
else
{
throw new PGPException("Missing private key.");
}

sigOut = contentSigner.getOutputStream();
sigType = contentSigner.getType();
lastb = 0;
Expand All @@ -115,7 +129,7 @@ public void init(
throw new PGPException("key algorithm mismatch");
}

if (key.getPublicKeyPacket().getVersion() != version)
if (key != null && key.getPublicKeyPacket().getVersion() != version)
{
throw new PGPException("Key version mismatch.");
}
Expand Down
34 changes: 34 additions & 0 deletions pg/src/main/java/org/bouncycastle/openpgp/PGPUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
import org.bouncycastle.asn1.cryptlib.CryptlibObjectIdentifiers;
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
import org.bouncycastle.asn1.gnu.GNUObjectIdentifiers;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.sec.SECObjectIdentifiers;
import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.bcpg.BCPGInputStream;
Expand Down Expand Up @@ -83,6 +86,37 @@ public class PGPUtil
}
};

public static ASN1ObjectIdentifier getDigestIdentifier(int hashAlgorithmId)
throws PGPException
{
switch (hashAlgorithmId)
{
case MD5:
return PKCSObjectIdentifiers.md5;
case SHA1:
return X509ObjectIdentifiers.id_SHA1;
case SHA224:
return NISTObjectIdentifiers.id_sha224;
case SHA256:
return NISTObjectIdentifiers.id_sha256;
case SHA384:
return NISTObjectIdentifiers.id_sha384;
case SHA512:
return NISTObjectIdentifiers.id_sha512;
case SHA3_224:
return NISTObjectIdentifiers.id_sha3_224;
case SHA3_256:
return NISTObjectIdentifiers.id_sha3_256;
case SHA3_384:
return NISTObjectIdentifiers.id_sha3_384;
case SHA3_512:
return NISTObjectIdentifiers.id_sha3_512;
case RIPEMD160:
return TeleTrusTObjectIdentifiers.ripemd128;
}
throw new PGPException("unknown hash algorithm id: " + hashAlgorithmId);
}

/**
* Return an appropriate name for the hash algorithm represented by the passed
* in hash algorithm ID number.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.bouncycastle.bcpg.sig.PreferredAlgorithms;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.api.exception.InvalidSigningKeyException;
import org.bouncycastle.openpgp.api.exception.KeyPassphraseException;
import org.bouncycastle.openpgp.api.operator.PGPContentSignerBuilderProviderFactory;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider;

public class AbstractOpenPGPDocumentSignatureGenerator<T extends AbstractOpenPGPDocumentSignatureGenerator<T>>
{
Expand All @@ -23,6 +29,7 @@ public class AbstractOpenPGPDocumentSignatureGenerator<T extends AbstractOpenPGP
protected final List<OpenPGPKey.OpenPGPSecretKey> signingKeys = new ArrayList<OpenPGPKey.OpenPGPSecretKey>();
protected final List<SignatureParameters.Callback> signatureCallbacks = new ArrayList<SignatureParameters.Callback>();
protected final List<KeyPassphraseProvider> signingKeyPassphraseProviders = new ArrayList<KeyPassphraseProvider>();
protected final Set<PGPContentSignerBuilderProviderFactory> customContentSignerBuilderProviderFactories = new LinkedHashSet<PGPContentSignerBuilderProviderFactory>();

protected final KeyPassphraseProvider.DefaultKeyPassphraseProvider defaultKeyPassphraseProvider =
new KeyPassphraseProvider.DefaultKeyPassphraseProvider();
Expand Down Expand Up @@ -81,6 +88,18 @@ public T addKeyPassphrase(char[] passphrase)
return (T)this;
}

/**
* Add a custom {@link PGPContentSignerBuilderProviderFactory} for external key signing.
* This is useful to allow e.g. signing with keys stored on hardware tokens or smart cards.
* @param factory custom factory
* @return this
*/
public T addCustomPGPContentSignerBuilderProviderFactory(PGPContentSignerBuilderProviderFactory factory)
{
customContentSignerBuilderProviderFactories.add(factory);
return (T)this;
}

/**
* Add an {@link OpenPGPKey} for message signing.
* The {@link #signingKeySelector} is responsible for selecting one or more subkeys of the key to sign with.
Expand Down Expand Up @@ -243,15 +262,59 @@ protected PGPSignatureGenerator initSignatureGenerator(
throw new InvalidSigningKeyException(signingKey);
}

char[] passphrase = passphraseProvider.getKeyPassword(signingKey);
PGPKeyPair unlockedKey = signingKey.unlock(passphrase).getKeyPair();
if (unlockedKey == null)
if (signingKey.getPGPSecretKey().isExternalKey())
{
throw new KeyPassphraseException(signingKey, new PGPException("Cannot unlock secret key."));
PGPPublicKey publicKey = signingKey.getPGPPublicKey();
for (PGPContentSignerBuilderProviderFactory sigFac : customContentSignerBuilderProviderFactories)
{
PGPContentSignerBuilderProvider sigProv;
try
{
sigProv = sigFac.getPGPContentSignerBuilderProvider(
signingKey, passphraseProvider, parameters.getSignatureHashAlgorithmId());
if (sigProv == null)
{
// no matching card found
continue;
}
}
catch (PGPException e)
{
// No matching card found
continue;
}

PGPContentSignerBuilder contentSignerBuilder;
try
{
contentSignerBuilder = sigProv.get(publicKey);
}
catch (IllegalArgumentException e)
{
// Mismatched key
continue;
}

PGPSignatureGenerator sigGen = new PGPSignatureGenerator(contentSignerBuilder, publicKey);
sigGen.init(parameters.getSignatureType(), null);

return Utils.applyDefaultSubpackets(publicKey, parameters, parameters.getSignatureCreationTime(), null, sigGen);
}

throw new PGPException("Cannot initialize signature generator for external key " + signingKey.getKeyIdentifier());
}
else
{
char[] passphrase = passphraseProvider.getKeyPassword(signingKey);
PGPKeyPair unlockedKey = signingKey.unlock(passphrase).getKeyPair();
if (unlockedKey == null)
{
throw new KeyPassphraseException(signingKey, new PGPException("Cannot unlock secret key."));
}

return Utils.getPgpSignatureGenerator(implementation, signingKey.getPGPPublicKey(),
unlockedKey.getPrivateKey(), parameters, parameters.getSignatureCreationTime(), null);
return Utils.getPgpSignatureGenerator(implementation, signingKey.getPGPPublicKey(),
unlockedKey.getPrivateKey(), parameters, parameters.getSignatureCreationTime(), null);
}
}

private int getPreferredHashAlgorithm(OpenPGPCertificate.OpenPGPComponentKey key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,12 @@ void onException(PGPException e)
}
}

public OpenPGPMessageProcessor setExceptionCallback(PGPExceptionCallback callback)
{
this.configuration.exceptionCallback = callback;
return this;
}

/**
* Recover the session key of the given PKESK using the given secret key.
* <p>
Expand Down
10 changes: 10 additions & 0 deletions pg/src/main/java/org/bouncycastle/openpgp/api/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ static PGPSignatureGenerator getPgpSignatureGenerator(OpenPGPImplementation impl
publicKey);
sigGen.init(parameters.getSignatureType(), privateKey);

return applyDefaultSubpackets(publicKey, parameters, date, operation, sigGen);
}

static PGPSignatureGenerator applyDefaultSubpackets(PGPPublicKey publicKey,
SignatureParameters parameters,
Date date,
HashedSubpacketsOperation operation,
PGPSignatureGenerator sigGen)
throws PGPException
{
final PGPSignatureSubpacketGenerator hashedSubpackets = new PGPSignatureSubpacketGenerator();
hashedSubpackets.setIssuerFingerprint(true, publicKey);
if (date != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ public class KeyPassphraseException
{
private final Exception cause;

public KeyPassphraseException(OpenPGPCertificate.OpenPGPComponentKey key, String message, Exception cause)
{
super(key, message + "\n" + componentKeyErrorMessage(key, cause));
this.cause = cause;
}

public KeyPassphraseException(OpenPGPCertificate.OpenPGPComponentKey key, Exception cause)
{
super(key, componentKeyErrorMessage(key, cause));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ public JcaOpenPGPImplementation(Provider provider, SecureRandom secureRandom)
this.secureRandom = secureRandom;
}

public Provider getProvider()
{
return provider;
}

@Override
public PGPObjectFactory pgpObjectFactory(InputStream packetInputStream)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.bouncycastle.openpgp.api.operator;

import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.api.KeyPassphraseProvider;
import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilderProvider;

public interface PGPContentSignerBuilderProviderFactory
{
PGPContentSignerBuilderProvider getPGPContentSignerBuilderProvider(
OpenPGPKey.OpenPGPSecretKey secretKey,
KeyPassphraseProvider keyPassphraseProvider,
int hashAlgorithmId)
throws PGPException;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.bouncycastle.openpgp.api.operator.bc;

import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.api.OpenPGPKey;
Expand All @@ -17,7 +18,7 @@
* private-key operation by implementing {@link #getExternalKeyCryptoCallback()}.
* <p>
* Note that a secret key handled through this class need not actually be external: if the supplied key
* does carry usable software key material, {@link #getCryptoCallback()} returns the inherited software
* does carry usable software key material, {@link #getCryptoCallback(AsymmetricKeyParameter)} returns the inherited software
* callback so the (much cheaper) in-process path is used instead.
*/
public abstract class BcExternalPublicKeyDataDecryptorFactory
Expand Down Expand Up @@ -61,12 +62,12 @@ private static PGPKeyPair unlock(OpenPGPKey.OpenPGPSecretKey secretKey)
}

@Override
protected BcPublicKeyCryptoCallback getCryptoCallback()
protected BcPublicKeyCryptoCallback getCryptoCallback(AsymmetricKeyParameter privKey)
{
// if software key material is available we can skip the costly hardware round trip
if (!secretKey.getPGPSecretKey().isExternalKey())
{
return super.getCryptoCallback();
return super.getCryptoCallback(privKey);
}
return getExternalKeyCryptoCallback();
}
Expand Down
Loading