Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -251,7 +251,7 @@ protected PGPSignatureGenerator initSignatureGenerator(
}

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

private int getPreferredHashAlgorithm(OpenPGPCertificate.OpenPGPComponentKey key)
Expand Down
134 changes: 125 additions & 9 deletions pg/src/main/java/org/bouncycastle/openpgp/api/OpenPGPCertificate.java
Original file line number Diff line number Diff line change
Expand Up @@ -804,17 +804,17 @@ private void processSubkey(OpenPGPSubkey subkey)
}

OpenPGPSignatureChains issuerChains = getAllSignatureChainsFor(issuer);
if (!issuerChains.chains.isEmpty())
if (issuerChains.chains.isEmpty())
{
subkeyChains.add(OpenPGPSignatureChain.direct(sig));
}
else
{
for (Iterator<OpenPGPSignatureChain> it2 = issuerChains.chains.iterator(); it2.hasNext(); )
{
subkeyChains.add(it2.next().plus(sig));
}
}
else
{
subkeyChains.add(new OpenPGPSignatureChain(OpenPGPSignatureChain.Link.create(sig)));
}
}
this.componentSignatureChains.put(subkey, subkeyChains);
}
Expand Down Expand Up @@ -2870,21 +2870,24 @@ public static class OpenPGPSignatureChain
implements Comparable<OpenPGPSignatureChain>, Iterable<OpenPGPSignatureChain.Link>
{
private final List<Link> chainLinks = new ArrayList<Link>();
private final OpenPGPPolicy policy;

private OpenPGPSignatureChain(Link rootLink)
private OpenPGPSignatureChain(Link rootLink, OpenPGPPolicy policy)
{
this.chainLinks.add(rootLink);
this.policy = policy;
}

private OpenPGPSignatureChain(List<Link> links)
private OpenPGPSignatureChain(List<Link> links, OpenPGPPolicy policy)
{
this.chainLinks.addAll(links);
this.policy = policy;
}

// copy constructor
private OpenPGPSignatureChain(OpenPGPSignatureChain copy)
{
this(copy.chainLinks);
this(copy.chainLinks, copy.policy);
}

/**
Expand Down Expand Up @@ -2958,7 +2961,7 @@ public OpenPGPSignatureChain plus(OpenPGPComponentSignature sig)
*/
public static OpenPGPSignatureChain direct(OpenPGPComponentSignature sig)
{
return new OpenPGPSignatureChain(Link.create(sig));
return new OpenPGPSignatureChain(Link.create(sig), sig.target.certificate.policy);
}

/**
Expand Down Expand Up @@ -3062,6 +3065,17 @@ public boolean isHardRevocation()
* @return most recent signature creation time
*/
public Date getSince()
{
return policy.getComponentSignatureEffectivenessEvaluator()
.getSignatureChainValidityPeriodBeginning(this);
}

/**
* Return the signature creation time of the most recent signature in the chain.
*
* @return most recent signature creation time in chain
*/
public Date getMostRecentLinkCreationTime()
{
Date latestDate = null;
for (Iterator it = chainLinks.iterator(); it.hasNext(); )
Expand All @@ -3076,6 +3090,19 @@ public Date getSince()
}
return latestDate;
}

/**
* Return the key creation time of the component signatures target key.
* In certificates composed of a primary key and subkeys, this is sufficient,
* since subkeys MUST NOT predate the primary key.
*
* @return most recent
*/
public Date getTargetKeyCreationTime()
{
return getLeafLinkTargetKey().getCreationTime();
}

// public Date getSince()
// {
// // Find most recent chain link
Expand Down Expand Up @@ -3400,6 +3427,16 @@ public OpenPGPComponentSignature getSignature()
{
return signature;
}

/**
* Return the issuer key of this link.
*
* @return issuer
*/
public OpenPGPComponentKey getIssuer()
{
return getSignature().getIssuer();
}
}

/**
Expand Down Expand Up @@ -3646,4 +3683,83 @@ private void addSignaturesToChains(List<OpenPGPComponentSignature> signatures, O
chains.add(OpenPGPSignatureChain.direct(it.next()));
}
}

/**
* Delegate for component signature evaluation.
* The introduction of PQC in OpenPGP makes it desirable to allow for cleanup of historic binding signatures,
* since PQC signatures are rather large, so accumulating them can lead to very large certificates.
* The classic model of component signature evaluation evaluates the complete history of component binding
* signatures when evaluating the validity of a certificate component
* (see {@link #strictlyTemporallyConstrainedSignatureEvaluator()}).
* Removing old signatures with the classic evaluation model can lead to historic document- or certification
* signatures to suddenly become invalid.
* Therefore, we need a way to swap out the evaluation method by introducing this delegate, which can have
* different concrete implementations.
*/
public interface ComponentSignatureEvaluator {
Date getSignatureChainValidityPeriodBeginning(OpenPGPSignatureChain chain);
}

/**
* This {@link ComponentSignatureEvaluator} strictly constraints the temporal validity of component signatures.
* This behavior is consistent with most OpenPGP implementations, but might lead to "temporal holes".
* When evaluating the validity of a component at evaluation time N, we ignore all binding signatures
* made after N and check if the latest binding before N is not yet expired at N.
* Hard revocations at any time invalidate the component.
* Soft revocations only invalidate the component if they are made before N, not yet expired at N and not yet
* overwritten by a valid binding newer than the revocation.
* <p>
* The problem with this method of evaluation is, that it can lead to temporal holes when historic self signatures
* are removed from the certificate (e.g. in order to reduce its size).
* Removing all but the latest bindings will render the key invalid for document signatures made before the latest
* bindings.
* @return component signature evaluator consistent with legacy implementations
*
* @see <a href="https://sequoia-pgp.gitlab.io/openpgp-interoperability-test-suite/results.html#Temporary_validity">
* OpenPGP Interoperability Test Suite - Temporary validity</a>

*/
public static ComponentSignatureEvaluator strictlyTemporallyConstrainedSignatureEvaluator() {
return new ComponentSignatureEvaluator() {
@Override
public Date getSignatureChainValidityPeriodBeginning(OpenPGPSignatureChain chain) {
return chain.getMostRecentLinkCreationTime();
}
};
}

/**
* This {@link ComponentSignatureEvaluator} allows for retroactive validation of historic document- or
* third-party signatures via new component binding signatures.
* Compared to the implementation in {@link #strictlyTemporallyConstrainedSignatureEvaluator()},
* this implementation prevents the issue of "temporal holes" and is therefore better suited for
* modern OpenPGP implementations where signatures are frequently cleaned up (e.g. PQC keys with
* large signatures).
* <p>
* This evaluator considers a component valid at time N iff
* <ul>
* <li>the latest binding signature exists and does not predate the component key itself</li>
* <li>the latest binding signature is not yet expired at N</li>
* <li>the component key was created before or at N</li>
* <li>if there is a soft-revocation created after the latest binding; the revocation is
* expired at N</li>
* <li>the component is not hard-revoked</li>
* </ul>
* This implementation ensures that when superseded binding signatures are removed from a certificate,
* historic document signatures remain valid.
* Note though, that this method may render the certificate valid for historic periods where the certificate
* was purposefully temporarily invalidated by expiring self-signatures.
*
* @return component signature history evaluator which performs a simplified evaluation, fixing temporal holes
* @see <a href="https://mailarchive.ietf.org/arch/msg/openpgp/kA4YtiP3j8LJUift1_D0mWIHVV0/">
* OpenPGP Mailing List - PQC requires urgent semantic cleanup</a>
*/
public static ComponentSignatureEvaluator retroactivelyTemporallyRevalidatingSignatureEvaluator() {
return new ComponentSignatureEvaluator() {
@Override
public Date getSignatureChainValidityPeriodBeginning(OpenPGPSignatureChain chain) {
return chain.getTargetKeyCreationTime();
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class OpenPGPDefaultPolicy
private int defaultDocumentSignatureHashAlgorithm = HashAlgorithmTags.SHA512;
private int defaultCertificationSignatureHashAlgorithm = HashAlgorithmTags.SHA512;
private int defaultSymmetricKeyAlgorithm = SymmetricKeyAlgorithmTags.AES_128;
private OpenPGPCertificate.ComponentSignatureEvaluator componentSignatureEvaluator;

public OpenPGPDefaultPolicy()
{
Expand Down Expand Up @@ -80,6 +81,13 @@ public OpenPGPDefaultPolicy()
acceptPublicKeyAlgorithm(PublicKeyAlgorithmTags.X448);
acceptPublicKeyAlgorithm(PublicKeyAlgorithmTags.Ed25519);
acceptPublicKeyAlgorithm(PublicKeyAlgorithmTags.Ed448);

/*
* Certificate component signature evaluation
*/
// Strictly constrain the temporal validity of component signatures during signature evaluation.
// This is consistent with legacy OpenPGP implementations.
applyStrictTemporalComponentSignatureValidityConstraints();
}

public OpenPGPDefaultPolicy rejectHashAlgorithm(int hashAlgorithmId)
Expand Down Expand Up @@ -212,6 +220,57 @@ public boolean isAcceptablePublicKeyStrength(int publicKeyAlgorithmId, int bitSt
return isAcceptable(publicKeyAlgorithmId, bitStrength, publicKeyMinimalBitStrengths);
}

@Override
public OpenPGPCertificate.ComponentSignatureEvaluator getComponentSignatureEffectivenessEvaluator() {
return componentSignatureEvaluator;
}

public OpenPGPDefaultPolicy setComponentSignatureEffectivenessEvaluator(
OpenPGPCertificate.ComponentSignatureEvaluator componentSignatureEvaluator)
{
this.componentSignatureEvaluator = componentSignatureEvaluator;
return this;
}

/**
* When evaluating a document signature or third-party certification issued at time t1,
* only consider component binding signatures made at t1 or prior and reject component
* binding signatures made at t2+.
* This behavior is consistent with OpenPGP implementations, but might break historical
* document signatures and third-party certifications if old component signatures are
* cleaned from the certificate (temporal holes).
* You can prevent temporal holes with {@link #allowRetroactiveComponentSignatureValidation()}.
* <p>
* This behavior is currently the default.
*
* @return policy
*/
public OpenPGPDefaultPolicy applyStrictTemporalComponentSignatureValidityConstraints()
{
return setComponentSignatureEffectivenessEvaluator(
OpenPGPCertificate.strictlyTemporallyConstrainedSignatureEvaluator());
}

/**
* When evaluating a document signature or third-party certification issued at time t1,
* also consider component binding signatures created at t2+.
* This behavior prevents historical document or certification signatures from breaking
* if older binding signatures are cleaned from the issuer certificate.
* Since PQC signatures may quickly blow up the certificate in size, it is desirable to
* clean up old signatures every once in a while and allowing retroactive validation of
* historic signatures via new component signatures prevents temporal holes.
* <p>
* Calling this will overwrite the - currently default - behavior from
* {@link #applyStrictTemporalComponentSignatureValidityConstraints()}.
*
* @return policy
*/
public OpenPGPDefaultPolicy allowRetroactiveComponentSignatureValidation()
{
return setComponentSignatureEffectivenessEvaluator(
OpenPGPCertificate.retroactivelyTemporallyRevalidatingSignatureEvaluator());
}

@Override
public OpenPGPNotationRegistry getNotationRegistry()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,11 @@ public void addKnownNotation(String notationName)
this.knownNotations.add(notationName);
}
}

/**
* The {@link OpenPGPCertificate.ComponentSignatureEvaluator} delegate defines, how component signatures on
* {@link OpenPGPCertificate OpenPGPCertificates} are being evaluated.
* @return delegate for component signature evaluation
*/
OpenPGPCertificate.ComponentSignatureEvaluator getComponentSignatureEffectivenessEvaluator();
}
Loading