package org.keysupport.bc.scvp;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bouncycastle.asn1.ASN1Enumerated;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1SequenceParser;
import org.bouncycastle.asn1.ASN1SetParser;
import org.bouncycastle.asn1.ASN1StreamParser;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.ASN1TaggedObjectParser;
import org.bouncycastle.asn1.ASN1UTCTime;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.Attributes;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.cms.ContentInfoParser;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.cms.SignerIdentifier;
import org.bouncycastle.asn1.cms.SignerInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Base64;
import org.keysupport.bc.scvp.asn1.CertChecks;
import org.keysupport.bc.scvp.asn1.SCVPRequest;
import org.keysupport.crypto.CipherEngine;
import org.keysupport.crypto.DigestEngine;
import org.keysupport.fpki.CommonPolicyRootCA;
import org.keysupport.util.DataUtil;
public class ExampleP7PEMSCVPClient {
private static final Logger log = Logger.getLogger(ExampleP7PEMSCVPClient.class.getPackage().getName());
private Provider jceProvider = null;
private byte[] fullRequest = null;
private byte[] fullResponse = null;
private final static int statusSuccess = 0;
private final static int statusMalformedPKC = 1;
private final static int statusMalformedAC = 2;
private final static int statusUnavailableValidationTime = 3;
private final static int statusReferenceCertHashFail = 4;
private final static int statusCertPathConstructFail = 5;
private final static int statusCertPathNotValid = 6;
private final static int statusCertPathNotValidNow = 7;
private final static int statusWantBackUnsatisfied = 8;
private final static String messageSuccess = new String("Success: all checks were performed successfully.");
private final static String messageMalformedPKC = new String("Failure: the public key certificate was malformed.");
private final static String messageMalformedAC = new String("Failure: the attribute certificate was malformed.");
private final static String messageUnavailableValidationTime = new String("Failure: historical data for the requested validation time is not available.");
private final static String messageReferenceCertHashFail = new String("Failure: the server could not locate the reference certificate or the referenced certificate did not match the hash value provided.");
private final static String messageCertPathConstructFail = new String("Failure: no certification path could be constructed.");
private final static String messageCertPathNotValid = new String("Failure: the constructed certification path is not valid with respect to the validation policy.");
private final static String messageCertPathNotValidNow = new String("Failure: the constructed certification path is not valid with respect to the validation policy, but a query at a later time may be successful.");
private final static String messageWantBackUnsatisfied = new String("Failure: all checks were performed successfully; however, one or more of the wantBacks could not be satisfied.");
public ExampleP7PEMSCVPClient(Provider jceProvider) {
this.jceProvider = jceProvider;
}
public static void usage() {
System.out.println("usage: java -jar SCVPAPI.jar <scvp_url> <PEM_Collection_filename> <polOID> <polOID> <polOID> <polOID> ...");
}
public static void main(String args[]) {
/*
* We are going to override the platform logger for
* this example and throw all messages to the console.
*/
log.setUseParentHandlers(false);
ConsoleHandler handler = new ConsoleHandler();
log.setLevel(Level.ALL);
handler.setLevel(Level.ALL);
log.addHandler(handler);
/*
* The intent is to change the provider for the cryptographic
* operations. I.e., a FIPS provider if needed. For now, we will use the
* BouncyCastle API since that is what we use for the ASN.1
*/
if (args.length <= 2) {
usage();
return;
}
Provider jceProvider = new BouncyCastleProvider();
Security.addProvider(jceProvider);
ExampleP7PEMSCVPClient client = new ExampleP7PEMSCVPClient(jceProvider);
String scvpUrl = args[0];
String certFile = args[1];
int i=2;
List<String> policyOids = new ArrayList<String>();
while (i != args.length) {
/*
* Syntax check for the OIDS
*/
try {
ASN1ObjectIdentifier id = new ASN1ObjectIdentifier(args[i]);
log.log(Level.INFO, "Including Policy: " + id.getId());
policyOids.add(id.toString());
} catch(IllegalArgumentException e) {
log.log(Level.SEVERE, "Invalid Policy OID:" + args[i] + ":" + e.getLocalizedMessage());
}
i++;
}
//X509Certificate endEntityCert;
try {
Collection<X509Certificate> certs = getCertsFromPEM(new FileInputStream(certFile));
log.log(Level.INFO, "Processing " + certs.size() + " certificates.");
for (X509Certificate endEntityCert: certs) {
if (client.validate(scvpUrl, endEntityCert, policyOids)) {
log.log(Level.INFO, "Certificate validated successfully.");
} else {
log.log(Level.INFO, "Certificate not valid.");
}
}
} catch (SCVPException e) {
log.log(Level.SEVERE, "PROB: There was a problem: " + e.getMessage());
} catch (CertificateException e) {
log.log(Level.SEVERE, "PROB: There was a problem with the certificate: " + e.getMessage());
} catch (FileNotFoundException e) {
log.log(Level.SEVERE, "PROB: No such file: " + certFile);
} catch (IOException e) {
log.log(Level.SEVERE, "PROB: Error parsing file: " + certFile);
}
}
public boolean validate(String scvpServer, X509Certificate endEntityCert, List<String> policyOids) throws SCVPException {
boolean certificateValid = false;
long start = System.currentTimeMillis();
ByteArrayInputStream bais;
Certificate eCert;
try {
bais = new ByteArrayInputStream(endEntityCert.getEncoded());
ASN1InputStream dis = new ASN1InputStream(bais);
ASN1Primitive dobj = dis.readObject();
dis.close();
eCert = Certificate.getInstance(dobj);
} catch (FileNotFoundException e) {
throw new SCVPException("Problem with client certificate", e);
} catch (IOException e) {
throw new SCVPException("Problem with client certificate", e);
} catch (CertificateException e) {
throw new SCVPException("Problem with client certificate", e);
}
log.log(Level.INFO, "Client Cert Subject & Serial:\t"
+ eCert.getSubject().toString() + ";"+ eCert.getSerialNumber().toString());
SCVPRequestBuilder builder = new SCVPRequestBuilder();
/*
* We are forming a delegated path validation request, and we are not
* going to ask for any wantBack(s). We are basically trusting the SCVP
* service to centrally validate our certificates.
*/
builder.addCertCheck(CertChecks.idStcBuildStatusCheckedPkcPath);
/*
* We can override policy, but our SCVP testing service makes use of the
* Common Policy Root CA as the Trust Anchor.
*/
Certificate ta = null;
try {
ta = Certificate.getInstance(CommonPolicyRootCA.getInstance().getCertificate().getEncoded());
} catch (CertificateException e1) {
e1.printStackTrace();
}
if (null != ta) {
builder.addTrustAnchor(ta);
}
//TODO: Add input for validation policy identifier
builder.setValidationPolRef(new ASN1ObjectIdentifier(
"1.3.6.1.5.5.7.19.1"), null);
//builder.addUserPolicy(new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.3.1"));
//for (String s: policyOids) {
// builder.addUserPolicy(new ASN1ObjectIdentifier(s));
//}
/*
* These are the additional RFC-5280 inputs:
*
* We do not allow wild-card policy assertions (InhibitAnyPolicy). We
* require the policy oids to be present in all certs within the path
* (RequireExplicitPolicy). We allow mapped policies in place of the
* explicitly defined inital policy set.
*/
//TODO: Add code to set the following inputs
//builder.setInhibitAnyPolicy(true);
//builder.setRequireExplicitPolicy(true);
//builder.setInhibitPolicyMapping(false);
/*
* This is the certificate we are validating
*/
builder.setCertReference(eCert);
/*
* This is based off of the GSA SCVP Request/Response Profile
*/
//TODO: Add Classes based off of the client profile document
builder.setRequestorName("URN:ValidationService:TEST:SCVPExample");
builder.setRequestorText("LOG;HI;MAJ;OTH;APP,https://github.com/grandamp/SCVPAPI/,-");
/*
* Adding a 16 byte nonce
*/
//TODO: Create an input for the nonce
builder.generateNonce(8);
/*
* Final assembly of the request.
*/
SCVPRequest req = builder.buildRequest();
//log.log(Level.FINE, "SCVPRequest:\n" + ASN1Dump.dumpAsString(req, true));
byte[] rawReq;
try {
rawReq = req.toASN1Primitive().getEncoded();
} catch (IOException e) {
throw new SCVPException("Problem with SCVP Request", e);
}
this.fullRequest = rawReq;
/*
* Send the request to the SCVP service...
*/
byte[] resp = sendSCVPRequestPOST(scvpServer, rawReq);
if (null != resp) {
this.fullResponse = resp;
certificateValid = validateSCVPResponse(resp);
} else {
log.log(Level.SEVERE, "Server did not return a CVResponse!");
}
log.log(Level.INFO, "Finished in " + (System.currentTimeMillis() - start) + " milliseconds.");
return certificateValid;
}
public boolean validateSCVPResponse(byte[] resp) throws SCVPException {
boolean certificateValid = false;
/*
* Now that we ca create a successful DPV request and receive a response
* from the service, we had better get to cracking on parsing the
* response and validating the signature!
*/
ASN1SequenceParser cmsSeqPar = null;
ContentInfoParser contentInfoParser = null;
ASN1ObjectIdentifier contentType = null;
if (resp != null) {
ASN1StreamParser streamParser = new ASN1StreamParser(resp);
Object object;
try {
object = streamParser.readObject();
} catch (IOException e) {
throw new SCVPException("Problem parsing response from server",
e);
}
if (object instanceof ASN1SequenceParser) {
cmsSeqPar = (ASN1SequenceParser) object;
try {
contentInfoParser = new ContentInfoParser(cmsSeqPar);
} catch (IOException e) {
throw new SCVPException("Problem parsing CMS ContentInfo",
e);
}
contentType = contentInfoParser.getContentType();
if (CMSObjectIdentifiers.signedData.equals(contentType)) {
try {
object = streamParser.readObject();
} catch (IOException e) {
throw new SCVPException(
"Problem parsing response from server", e);
}
if (object instanceof ASN1SequenceParser) {
/*
* Now that we confirmed this is CMS Signed data we are
* going to start parsing what we know without checking
* (not a good long term solution)
*/
ASN1SequenceParser cmsSdPar = (ASN1SequenceParser) object;
/*
* The following is for logging, but we may switch to
* decoding the response directly using a primitive, vs
* trying to use the decoders. Not certain if there is
* a bug, but the decoders interpret some of the data
* as BER and not DER :/
*/
ASN1Sequence ppResp = null;
try {
ppResp = (ASN1Sequence) ASN1Sequence.fromByteArray(resp);
} catch (IOException e) {
throw new SCVPException(
"Problem parsing response from server", e);
}
//log.log(Level.FINE, ASN1Dump.dumpAsString(ppResp, true));
/*
*
*/
// version CMSVersion
ASN1Integer sdv;
try {
sdv = (ASN1Integer) cmsSdPar.readObject();
} catch (IOException e) {
throw new SCVPException(
"Problem parsing CMS Version", e);
}
ASN1SetParser dASetPar;
AlgorithmIdentifier algId;
try {
dASetPar = (ASN1SetParser) cmsSdPar.readObject();
algId = AlgorithmIdentifier.getInstance(dASetPar
.readObject());
} catch (IOException e) {
throw new SCVPException(
"Problem parsing digest algorithm identifier",
e);
}
ASN1SequenceParser eCInfoPar;
ASN1ObjectIdentifier eContentType;
ASN1TaggedObjectParser eContent;
ASN1OctetString cVResponse;
try {
eCInfoPar = (ASN1SequenceParser) cmsSdPar
.readObject();
eContentType = (ASN1ObjectIdentifier) eCInfoPar
.readObject();
eContent = (ASN1TaggedObjectParser) eCInfoPar
.readObject();
cVResponse = (ASN1OctetString) eContent
.getObjectParser(0, true).toASN1Primitive();
} catch (IOException e) {
throw new SCVPException(
"Problem parsing EncapsulatedContentInfo",
e);
}
/*
* Digest the object bytes for signature validation
*/
byte[] cVRespBytes = cVResponse.getOctets();
byte[] digest = null;
/*
* Only support SHA-1/SHA-256/SHA-384. Die on validation
* otherwise.
*/
if (algId.getAlgorithm().equals(CipherEngine.SHA384)) {
/*
* SHA-384
*/
digest = DigestEngine.sHA384Sum(cVRespBytes,
jceProvider.getName());
} else if (algId.getAlgorithm().equals(
CipherEngine.SHA256)) {
/*
* SHA-256
*/
digest = DigestEngine.sHA256Sum(cVRespBytes,
jceProvider.getName());
} else if (algId.getAlgorithm().equals(
CipherEngine.SHA1)) {
/*
* SHA-1
*/
digest = DigestEngine.sHA1Sum(cVRespBytes,
jceProvider.getName());
} else {
throw new SCVPException(
"Unexpected Digest Algorithm: "
+ algId.getAlgorithm().getId());
}
ASN1TaggedObjectParser certSet;
Certificate cvSigner;
try {
certSet = (ASN1TaggedObjectParser) cmsSdPar
.readObject();
cvSigner = Certificate
.getInstance(certSet.getObjectParser(0,
true).toASN1Primitive());
} catch (IOException e) {
throw new SCVPException(
"Error parsing SCVP Signer in CMS", e);
}
ASN1SetParser sInfosPar;
SignerInfo sInfo;
try {
sInfosPar = (ASN1SetParser) cmsSdPar.readObject();
sInfo = SignerInfo.getInstance(sInfosPar
.readObject().toASN1Primitive());
} catch (IOException e) {
throw new SCVPException("Error parsing SignerInfo",
e);
}
SignerIdentifier sID = sInfo.getSID();
IssuerAndSerialNumber iSn = IssuerAndSerialNumber
.getInstance(sID);
if (iSn.equals(new IssuerAndSerialNumber(cvSigner))) {
/*
* To get here the signerInfo references the
* included signer and we will proceed to parse the
* SignerInfo, which includes the digest of (and
* reference to) a CVResponse, and the encrypted
* value (signature). Parse and validate the
* signature...
*/
AlgorithmIdentifier sIAlgId = sInfo
.getDigestAlgorithm();
Attributes sIAA = Attributes.getInstance(sInfo
.getAuthenticatedAttributes());
Attribute siContentType = null;
Attribute siSigningTime = null;
Attribute siMessageDigest = null;
for (Attribute a : sIAA.getAttributes()) {
if (a.getAttrType().equals(
new ASN1ObjectIdentifier(
"1.2.840.113549.1.9.3"))) {
siContentType = a;
}
if (a.getAttrType().equals(
new ASN1ObjectIdentifier(
"1.2.840.113549.1.9.5"))) {
siSigningTime = a;
}
if (a.getAttrType().equals(
new ASN1ObjectIdentifier(
"1.2.840.113549.1.9.4"))) {
siMessageDigest = a;
}
}
/*
* Make sure the SignerInfo has all that we expect,
* and lets validate the data.
*
* -ContentType: Make sure it is an SCVP Response
* -SigningTime: We use a nonce, ensure it was
* signed within the past minute -MessageDigest:
* This must match the digest of the CVResponse
*/
if (siContentType != null && siSigningTime != null
&& siMessageDigest != null) {
ASN1ObjectIdentifier siCT = (ASN1ObjectIdentifier) siContentType
.getAttrValues().getObjectAt(0);
if (siCT.equals(new ASN1ObjectIdentifier(
"1.2.840.113549.1.9.16.1.11"))) {
} else {
throw new SCVPException(
"Unexpected Content Type: "
+ siCT.getId());
}
Calendar currentTime = Calendar.getInstance();
ASN1UTCTime claimSignTime = (ASN1UTCTime) siSigningTime
.getAttrValues().getObjectAt(0);
Calendar signingTime = new GregorianCalendar();
try {
signingTime.setTime(claimSignTime
.getAdjustedDate());
} catch (ParseException e) {
throw new SCVPException(
"Error parsing SigningTime", e);
}
Calendar minBefore = new GregorianCalendar();
Calendar minAfter = new GregorianCalendar();
minBefore.add(Calendar.MINUTE, -1);
minAfter.add(Calendar.MINUTE, 1);
if (!(currentTime.before(minBefore) || currentTime
.after(minAfter))) {
} else {
throw new SCVPException(
"Unacceptable Signing Time: "
+ claimSignTime
.getAdjustedTime());
}
ASN1OctetString claimDigestOS = (ASN1OctetString) siMessageDigest
.getAttrValues().getObjectAt(0);
byte[] claimDigest = claimDigestOS.getOctets();
if (Arrays.areEqual(digest, claimDigest)) {
} else {
throw new SCVPException(
"SignerInfo Message Digest ("
+ DataUtil
.byteArrayToString(claimDigest)
+ ") does is not equal to actual digest ("
+ DataUtil
.byteArrayToString(digest)
+ ")");
}
} else {
throw new SCVPException(
"SignerInfo does not include requred Authenticated attributes");
}
AlgorithmIdentifier sigAlg = sInfo
.getDigestEncryptionAlgorithm();
byte[] sigBits = sInfo.getEncryptedDigest()
.getOctets();
String sigAlgName = CipherEngine
.getSigningAlgorithm(
sIAlgId.getAlgorithm(),
sigAlg.getAlgorithm());
Signature signature = null;
try {
signature = Signature.getInstance(sigAlgName,
jceProvider.getName());
} catch (NoSuchAlgorithmException
| NoSuchProviderException e) {
throw new SCVPException(
"Problem verifing signature", e);
}
InputStream in;
try {
in = new ByteArrayInputStream(
cvSigner.getEncoded());
} catch (IOException e) {
throw new SCVPException(
"Error parsing SCVP Signer Certificate",
e);
}
CertificateFactory cf;
X509Certificate cvSignerCert;
try {
cf = CertificateFactory.getInstance("X.509",
jceProvider.getName());
cvSignerCert = (X509Certificate) cf
.generateCertificate(in);
signature.initVerify(cvSignerCert);
} catch (InvalidKeyException e) {
throw new SCVPException(
"Problem parsing SCVP Signer public key",
e);
} catch (CertificateException e) {
throw new SCVPException(
"Problem parsing SCVP Signing certificate",
e);
} catch (NoSuchProviderException e) {
throw new SCVPException(
"Problem with JCE Provider", e);
}
try {
signature.update(sIAA.getEncoded());
} catch (SignatureException | IOException e) {
throw new SCVPException(
"Problem with SCVP Signature validation",
e);
}
boolean sigMatch = false;
try {
sigMatch = signature.verify(sigBits);
} catch (SignatureException e) {
throw new SCVPException(
"Invalid SCVP Signature: Signature Validation Failed",
e);
}
if (sigMatch) {
/*
* TODO: Validate that we trust the SCVP Signer
* certificate:
*
* To elaborate, while this code does validate the signature
* of the SCVP response, it does not verify the signer
* certificate is one that we "trust". Further, a large
* fault-tolerant SCVP service MAY have multiple SCVP signers.
* To specify explicit trust in those signers as a command
* line option, or as inputs to this code is counter-intuitive,
* as SCVP is intended to ease the burden of managing trust lists.
*
* So for this implementation, the SCVP signing certificate MUST chain
* to one specific trust anchor. There MUST be a policy on the SCVP
* service that supports validation of all SCVP signers encountered
* to that trust anchor. It is up to the implementor how often
* the SCVP signer is validated, vs. reliance on a cached CVResponse
* of the prior validation.
*
*/
/*
* Now we will process the CVResponse, verify
* the response from the request artifacts, and
* then return a result for human (or other IT
* Logic) consumption. We will render the
* CVResponse from the response bytes we
* digested (used for signature validation).
*/
ASN1StreamParser cvRespOs = new ASN1StreamParser(
cVRespBytes);
ASN1SequenceParser cvResp;
ASN1Integer cvResponseVersion;
ASN1Integer serverConfigurationID;
ASN1GeneralizedTime producedAt;
ASN1Sequence responseStatus;
ASN1Sequence respValidationPolicy = null;
ASN1TaggedObject requestRef = null;
ASN1Sequence requestorRef = null;
ASN1Sequence requestorName = null;
ASN1Sequence replyObjects = null;
ASN1OctetString respNonce = null;
ASN1OctetString serverContextInfo = null;
ASN1Sequence cvResponseExtensions = null;
ASN1OctetString requestorText = null;
try {
cvResp = (ASN1SequenceParser) cvRespOs
.readObject();
cvResponseVersion = ASN1Integer
.getInstance(cvResp.readObject());
serverConfigurationID = ASN1Integer
.getInstance(cvResp.readObject());
producedAt = ASN1GeneralizedTime
.getInstance(cvResp.readObject());
responseStatus = ASN1Sequence
.getInstance(cvResp.readObject());
ASN1Enumerated statusCode = ASN1Enumerated
.getInstance(responseStatus
.getObjectAt(0));
/*
* The remainder objects in this CVResponse
* are tagged and OPTIONAL.
*/
Object cvrObj;
while ((cvrObj = cvResp.readObject()) != null) {
ASN1TaggedObject atObjFp = (ASN1TaggedObject) ((ASN1TaggedObjectParser) cvrObj)
.toASN1Primitive();
switch (atObjFp.getTagNo()) {
case 0: {
respValidationPolicy = (ASN1Sequence) atObjFp
.getObject();
break;
}
case 1: {
requestRef = (ASN1TaggedObject) atObjFp
.getObject();
break;
}
case 2: {
requestorRef = (ASN1Sequence) atObjFp
.getObject();
break;
}
case 3: {
requestorName = (ASN1Sequence) atObjFp
.getObject();
break;
}
case 4: {
replyObjects = (ASN1Sequence) atObjFp
.getObject();
break;
}
case 5: {
respNonce = (ASN1OctetString) atObjFp
.getObject();
break;
}
case 6: {
serverContextInfo = (ASN1OctetString) atObjFp
.getObject();
break;
}
case 7: {
cvResponseExtensions = (ASN1Sequence) atObjFp
.getObject();
break;
}
case 8: {
requestorText = (ASN1OctetString) atObjFp
.getObject();
break;
}
default: {
throw new SCVPException(
"Unknown object encountered in CVResponse");
}
}
}
} catch (IOException e) {
throw new SCVPException(
"Error parsing CVResponse", e);
}
/*
* TODO: Decode the other objects, and match up
* to the request response objects to validate
* the response. I.e., requestRef, respNonce,
* etc...
*
* For now, we are only interested in the
* replyObjects to give us the certificate
* status. There is only one, because we only
* asked for one.
*/
if (replyObjects != null) {
//log.log(Level.FINE, ASN1Dump.dumpAsString(replyObjects, true));
/*
* Technically we have the single
* replyObject, so the following is the
* results of our hard work....
*/
/*
* Get the certificate
*/
Certificate eCertInRO = Certificate
.getInstance(((ASN1TaggedObject) replyObjects
.getObjectAt(0))
.getObject());
/*
* Get the statusCode
*/
ASN1Enumerated replyStatus = ASN1Enumerated
.getInstance(replyObjects
.getObjectAt(1));
/*
* Get the time of validation
*/
ASN1GeneralizedTime replyValTime = ASN1GeneralizedTime
.getInstance(replyObjects
.getObjectAt(2));
/*
* Get the reply checks
*
* This code only asked for one check, so it currently
* assumes that there will only be one ReplyCheck.
*
* This is not the proper way to do things...
*/
//ReplyChecks replyChecks;
//try {
// replyChecks = ReplyChecks.getInstance(replyObjects.getObjectAt(3));
//} catch (IOException e) {
// throw new SCVPException("Error decoding ReplyChecks: " + e.getLocalizedMessage(), e);
//}
//Enumeration<ReplyCheck> rcsEn = replyChecks.getValues();
//while (rcsEn.hasMoreElements()) {
// ReplyCheck replyCheck;
// try {
// replyCheck = ReplyCheck.getInstance(rcsEn.nextElement());
// } catch (IOException e) {
// throw new SCVPException("Error decoding ReplyCheck ", e);
// }
log.log(Level.INFO, "STATUS: " + replyStatus.getValue().intValue());
switch (replyStatus.getValue().intValue()) {
case statusSuccess: {
/*
* Bottom line, if the replyStatus is anything other than
* ReplyStatus.success, then the cert is invalid based on the
* policy...
*/
certificateValid = true;
log.log(Level.INFO, messageSuccess);
break;
}
case statusMalformedPKC: {
log.log(Level.INFO, messageMalformedPKC);
break;
}
case statusMalformedAC: {
log.log(Level.INFO, messageMalformedAC);
break;
}
case statusUnavailableValidationTime: {
log.log(Level.INFO, messageUnavailableValidationTime);
break;
}
case statusReferenceCertHashFail: {
log.log(Level.INFO, messageReferenceCertHashFail);
break;
}
case statusCertPathConstructFail: {
log.log(Level.INFO, messageCertPathConstructFail);
break;
}
case statusCertPathNotValid: {
log.log(Level.INFO, messageCertPathNotValid);
break;
}
case statusCertPathNotValidNow: {
log.log(Level.INFO, messageCertPathNotValidNow);
break;
}
case statusWantBackUnsatisfied: {
log.log(Level.INFO, messageWantBackUnsatisfied);
break;
}
default: {
log.log(Level.INFO, "Unknown validation error: " + replyStatus.getValue().intValue());
break;
}
}
//}
/*
* Get the reply wantBacks (although we
* asked for none)
*/
ASN1Sequence replyWantBacks = ASN1Sequence
.getInstance(replyObjects
.getObjectAt(4));
@SuppressWarnings("unchecked")
Enumeration<ASN1Sequence> rcWB = replyWantBacks
.getObjects();
int wbNum = 0;
while (rcWB.hasMoreElements()) {
ASN1Sequence replyWantBack = rcWB
.nextElement();
ASN1ObjectIdentifier wb = (ASN1ObjectIdentifier) replyWantBack
.getObjectAt(0);
ASN1Integer check = (ASN1Integer) replyWantBack
.getObjectAt(1);
wbNum++;
}
Object rcObj = replyObjects.getObjectAt(5);
/*
* Return our validation boolean
*/
} else {
throw new SCVPException(
"No ReplyObjects in CVResponse");
}
} else {
throw new SCVPException(
"Invalid SCVP Signature: Signature Validation Failed");
}
} else {
throw new SCVPException(
"The SignerIdentifier and Signing Certificate do not match");
}
} else {
throw new SCVPException(
"Response from the server is not a CMS message");
}
} else {
throw new SCVPException(
"Response from the server is not a CMS SignedData message");
}
} else {
throw new SCVPException(
"Response from the server is not a CMS SignedData message");
}
} else {
throw new SCVPException(
"Response from the server is not a CMS SignedData message");
}
return certificateValid;
}
/*
* This is not my preferable path... TODO: Replace transport with Apache
* HTTP client.
*/
public static byte[] sendSCVPRequestPOST(String postURL, byte[] req)
throws SCVPException {
byte[] resp = null;
try {
URL url = new URL(postURL);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setRequestMethod("POST");
con.setReadTimeout(120000);
con.setConnectTimeout(120000);
con.setAllowUserInteraction(false);
con.setUseCaches(false);
con.setDoOutput(true);
con.setDoInput(true);
con.setRequestProperty("Content-Type",
"application/scvp-cv-request");
con.setRequestProperty("Accept",
"application/scvp-cv-response");
OutputStream os = con.getOutputStream();
os.write(req);
os.close();
/*
* Lets make sure we are receiving an SCVP response...
*/
if (con.getResponseCode() == HttpURLConnection.HTTP_OK) {
if (con.getContentType().equalsIgnoreCase(
"application/scvp-cv-response")) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] chunk = new byte[4096];
int bytesRead;
InputStream stream = con.getInputStream();
while ((bytesRead = stream.read(chunk)) > 0) {
baos.write(chunk, 0, bytesRead);
}
resp = baos.toByteArray();
} else {
throw new SCVPException(
"Response from the server is not a CMS message");
}
} else {
//throw new SCVPException(
// "Received unexpected HTTP response code: " + con.getResponseCode());
return null;
}
} catch (IOException e) {
log.log(Level.SEVERE, e.getLocalizedMessage());
throw new SCVPException("Problem communicating with SCVP server", e);
}
return resp;
}
/**
* @return the fullRequest
*/
public byte[] getFullRequest() {
return fullRequest;
}
/**
* @return the fullResponse
*/
public byte[] getFullResponse() {
return fullResponse;
}
/**
* From: https://svn.cesecore.eu/svn/ejbca/tags/Rel_4_0_14/ejbca/src/java/org/ejbca/util/CertTools.java
*
* Reads a certificate in PEM-format from an InputStream. The stream may contain other things,
* the first certificate in the stream is read.
*
* @param certstream the input stream containing the certificate in PEM-format
* @return Ordered Collection of Certificate, first certificate first, or empty Collection
* @exception IOException if the stream cannot be read.
* @exception CertificateException if the stream does not contain a correct certificate.
*/
public static Collection<X509Certificate> getCertsFromPEM(InputStream certstream)
throws IOException, CertificateException {
ArrayList<X509Certificate> ret = new ArrayList<X509Certificate>();
String BEGIN_CERTIFICATE = "-----BEGIN CERTIFICATE-----";
String END_CERTIFICATE = "-----END CERTIFICATE-----";
String beginKeyTrust = "-----BEGIN TRUSTED CERTIFICATE-----";
String endKeyTrust = "-----END TRUSTED CERTIFICATE-----";
BufferedReader bufRdr = null;
ByteArrayOutputStream ostr = null;
PrintStream opstr = null;
try {
bufRdr = new BufferedReader(new InputStreamReader(certstream));
while (bufRdr.ready()) {
ostr = new ByteArrayOutputStream();
opstr = new PrintStream(ostr);
String temp;
while ((temp = bufRdr.readLine()) != null && !(temp.equals(BEGIN_CERTIFICATE) || temp.equals(beginKeyTrust))) {
continue;
}
if (temp == null) {
if (ret.isEmpty()) {
// There was no certificate in the file
throw new IOException("Error in " + certstream.toString() + ", missing " + BEGIN_CERTIFICATE + " boundary");
} else {
// There were certificates, but some blank lines or something in the end
// anyhow, the file has ended so we can break here.
break;
}
}
while ((temp = bufRdr.readLine()) != null && !(temp.equals(END_CERTIFICATE) || temp.equals(endKeyTrust))) {
opstr.print(temp);
}
if (temp == null) {
throw new IOException("Error in " + certstream.toString() + ", missing " + END_CERTIFICATE + " boundary");
}
opstr.close();
byte[] certbuf = Base64.decode(ostr.toByteArray());
ostr.close();
// Phweeew, were done, now decode the cert from file back to Certificate object
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certbuf));
ret.add(cert);
}
} finally {
if (bufRdr != null) {
bufRdr.close();
}
if (opstr != null) {
opstr.close();
}
if (ostr != null) {
ostr.close();
}
}
return ret;
} // getCertsFromPEM
}