blob: 163b924f9d1bc0a2d116d60d3a72606a9c2333f7 [file] [log] [blame]
package org.bouncycastle.tsp;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.asn1.cmp.PKIFailureInfo;
import org.bouncycastle.asn1.cmp.PKIFreeText;
import org.bouncycastle.asn1.cmp.PKIStatus;
import org.bouncycastle.asn1.cmp.PKIStatusInfo;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.tsp.TimeStampResp;
import org.bouncycastle.asn1.x509.Extensions;
/**
* Generator for RFC 3161 Time Stamp Responses.
* <p>
* New generate methods have been introduced to give people more control over what ends up in the message.
* Unfortunately it turns out that in some cases fields like statusString must be left out otherwise a an
* otherwise valid timestamp will be rejected.
* </p>
* If you're after the most control with generating a response use:
* <pre>
* TimeStampResponse tsResp;
*
* try
* {
* tsResp = tsRespGen.generateGrantedResponse(request, new BigInteger("23"), new Date());
* }
* catch (Exception e)
* {
* tsResp = tsRespGen.generateRejectedResponse(e);
* }
* </pre>
* The generate method does this, but provides a status string of "Operation Okay".
* <p>
* It should be pointed out that generateRejectedResponse() may also, on very rare occasions throw a TSPException.
* In the event that happens, there's a serious internal problem with your responder.
* </p>
*/
public class TimeStampResponseGenerator
{
int status;
ASN1EncodableVector statusStrings;
int failInfo;
private TimeStampTokenGenerator tokenGenerator;
private Set acceptedAlgorithms;
private Set acceptedPolicies;
private Set acceptedExtensions;
/**
*
* @param tokenGenerator
* @param acceptedAlgorithms a set of OIDs giving accepted algorithms.
*/
public TimeStampResponseGenerator(
TimeStampTokenGenerator tokenGenerator,
Set acceptedAlgorithms)
{
this(tokenGenerator, acceptedAlgorithms, null, null);
}
/**
*
* @param tokenGenerator
* @param acceptedAlgorithms a set of OIDs giving accepted algorithms.
* @param acceptedPolicies if non-null a set of policies OIDs we are willing to sign under.
*/
public TimeStampResponseGenerator(
TimeStampTokenGenerator tokenGenerator,
Set acceptedAlgorithms,
Set acceptedPolicies)
{
this(tokenGenerator, acceptedAlgorithms, acceptedPolicies, null);
}
/**
*
* @param tokenGenerator
* @param acceptedAlgorithms a set of OIDs giving accepted algorithms.
* @param acceptedPolicies if non-null a set of policies OIDs we are willing to sign under.
* @param acceptedExtensions if non-null a set of extensions OIDs we are willing to accept.
*/
public TimeStampResponseGenerator(
TimeStampTokenGenerator tokenGenerator,
Set acceptedAlgorithms,
Set acceptedPolicies,
Set acceptedExtensions)
{
this.tokenGenerator = tokenGenerator;
this.acceptedAlgorithms = convert(acceptedAlgorithms);
this.acceptedPolicies = convert(acceptedPolicies);
this.acceptedExtensions = convert(acceptedExtensions);
statusStrings = new ASN1EncodableVector();
}
private void addStatusString(String statusString)
{
statusStrings.add(new DERUTF8String(statusString));
}
private void setFailInfoField(int field)
{
failInfo = failInfo | field;
}
private PKIStatusInfo getPKIStatusInfo()
{
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new ASN1Integer(status));
if (statusStrings.size() > 0)
{
v.add(PKIFreeText.getInstance(new DERSequence(statusStrings)));
}
if (failInfo != 0)
{
DERBitString failInfoBitString = new FailInfo(failInfo);
v.add(failInfoBitString);
}
return PKIStatusInfo.getInstance(new DERSequence(v));
}
/**
* Return an appropriate TimeStampResponse.
* <p>
* If genTime is null a timeNotAvailable error response will be returned. Calling generate() is the
* equivalent of:
* <pre>
* TimeStampResponse tsResp;
*
* try
* {
* tsResp = tsRespGen.generateGrantedResponse(request, serialNumber, genTime, "Operation Okay");
* }
* catch (Exception e)
* {
* tsResp = tsRespGen.generateRejectedResponse(e);
* }
* </pre>
* @param request the request this response is for.
* @param serialNumber serial number for the response token.
* @param genTime generation time for the response token.
* @return a TimeStampResponse.
* @throws TSPException
*/
public TimeStampResponse generate(
TimeStampRequest request,
BigInteger serialNumber,
Date genTime)
throws TSPException
{
try
{
return this.generateGrantedResponse(request, serialNumber, genTime, "Operation Okay");
}
catch (Exception e)
{
return this.generateRejectedResponse(e);
}
}
/**
* Return a granted response, if the passed in request passes validation.
* <p>
* If genTime is null a timeNotAvailable or a validation exception occurs a TSPValidationException will
* be thrown. The parent TSPException will only occur on some sort of system failure.
* </p>
* @param request the request this response is for.
* @param serialNumber serial number for the response token.
* @param genTime generation time for the response token.
* @return the TimeStampResponse with a status of PKIStatus.GRANTED
* @throws TSPException on validation exception or internal error.
*/
public TimeStampResponse generateGrantedResponse(
TimeStampRequest request,
BigInteger serialNumber,
Date genTime)
throws TSPException
{
return generateGrantedResponse(request, serialNumber, genTime, null);
}
/**
* Return a granted response, if the passed in request passes validation with the passed in status string.
* <p>
* If genTime is null a timeNotAvailable or a validation exception occurs a TSPValidationException will
* be thrown. The parent TSPException will only occur on some sort of system failure.
* </p>
* @param request the request this response is for.
* @param serialNumber serial number for the response token.
* @param genTime generation time for the response token.
* @return the TimeStampResponse with a status of PKIStatus.GRANTED
* @throws TSPException on validation exception or internal error.
*/
public TimeStampResponse generateGrantedResponse(
TimeStampRequest request,
BigInteger serialNumber,
Date genTime,
String statusString)
throws TSPException
{
return generateGrantedResponse(request, serialNumber, genTime, statusString, null);
}
/**
* Return a granted response, if the passed in request passes validation with the passed in status string and extra extensions.
* <p>
* If genTime is null a timeNotAvailable or a validation exception occurs a TSPValidationException will
* be thrown. The parent TSPException will only occur on some sort of system failure.
* </p>
* @param request the request this response is for.
* @param serialNumber serial number for the response token.
* @param genTime generation time for the response token.
* @param additionalExtensions extra extensions to be added to the response token.
* @return the TimeStampResponse with a status of PKIStatus.GRANTED
* @throws TSPException on validation exception or internal error.
*/
public TimeStampResponse generateGrantedResponse(
TimeStampRequest request,
BigInteger serialNumber,
Date genTime,
String statusString,
Extensions additionalExtensions)
throws TSPException
{
if (genTime == null)
{
throw new TSPValidationException("The time source is not available.", PKIFailureInfo.timeNotAvailable);
}
request.validate(acceptedAlgorithms, acceptedPolicies, acceptedExtensions);
status = PKIStatus.GRANTED;
statusStrings = new ASN1EncodableVector();
if (statusString != null)
{
this.addStatusString(statusString);
}
PKIStatusInfo pkiStatusInfo = getPKIStatusInfo();
ContentInfo tstTokenContentInfo;
try
{
tstTokenContentInfo = tokenGenerator.generate(request, serialNumber, genTime, additionalExtensions).toCMSSignedData().toASN1Structure();
}
catch (TSPException e)
{
throw e;
}
catch (Exception e)
{
throw new TSPException(
"Timestamp token received cannot be converted to ContentInfo", e);
}
try
{
return new TimeStampResponse(new DLSequence(new ASN1Encodable[] { pkiStatusInfo.toASN1Primitive(), tstTokenContentInfo.toASN1Primitive() }));
}
catch (IOException e)
{
throw new TSPException("created badly formatted response!");
}
}
/**
* Generate a generic rejection response based on a TSPValidationException or
* an Exception. Exceptions which are not an instance of TSPValidationException
* will be treated as systemFailure. The return value of exception.getMessage() will
* be used as the status string for the response.
*
* @param exception the exception thrown on validating the request.
* @return a TimeStampResponse.
* @throws TSPException if a failure response cannot be generated.
*/
public TimeStampResponse generateRejectedResponse(Exception exception)
throws TSPException
{
if (exception instanceof TSPValidationException)
{
return generateFailResponse(PKIStatus.REJECTION, ((TSPValidationException)exception).getFailureCode(), exception.getMessage());
}
else
{
return generateFailResponse(PKIStatus.REJECTION, PKIFailureInfo.systemFailure, exception.getMessage());
}
}
/**
* Generate a non-granted TimeStampResponse with chosen status and FailInfoField.
*
* @param status the PKIStatus to set.
* @param failInfoField the FailInfoField to set.
* @param statusString an optional string describing the failure.
* @return a TimeStampResponse with a failInfoField and optional statusString
* @throws TSPException in case the response could not be created
*/
public TimeStampResponse generateFailResponse(int status, int failInfoField, String statusString)
throws TSPException
{
this.status = status;
this.statusStrings = new ASN1EncodableVector();
this.setFailInfoField(failInfoField);
if (statusString != null)
{
this.addStatusString(statusString);
}
PKIStatusInfo pkiStatusInfo = getPKIStatusInfo();
TimeStampResp resp = new TimeStampResp(pkiStatusInfo, null);
try
{
return new TimeStampResponse(resp);
}
catch (IOException e)
{
throw new TSPException("created badly formatted response!");
}
}
private Set convert(Set orig)
{
if (orig == null)
{
return orig;
}
Set con = new HashSet(orig.size());
for (Iterator it = orig.iterator(); it.hasNext();)
{
Object o = it.next();
if (o instanceof String)
{
con.add(new ASN1ObjectIdentifier((String)o));
}
else
{
con.add(o);
}
}
return con;
}
class FailInfo extends DERBitString
{
FailInfo(int failInfoValue)
{
super(getBytes(failInfoValue), getPadBits(failInfoValue));
}
}
}