/*
*
* Copyright (c) 2013 - 2017 Lijun Liao
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
*
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the XiPKI software without
* disclosing the source code of your own applications.
*
* For more information, please contact Lijun Liao at this
* address: lijun.liao@gmail.com
*/
package org.xipki.pki.scep.message;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.CMSAttributes;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.cms.Time;
import org.bouncycastle.asn1.pkcs.CertificationRequest;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.SignerId;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.util.CollectionStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.pki.scep.exception.MessageDecodingException;
import org.xipki.pki.scep.transaction.FailInfo;
import org.xipki.pki.scep.transaction.MessageType;
import org.xipki.pki.scep.transaction.Nonce;
import org.xipki.pki.scep.transaction.PkiStatus;
import org.xipki.pki.scep.transaction.TransactionId;
import org.xipki.pki.scep.util.ScepUtil;
/**
* @author Lijun Liao
* @since 2.0.0
*/
public class DecodedPkiMessage extends PkiMessage {
private static final Logger LOG = LoggerFactory.getLogger(DecodedPkiMessage.class);
private static final Set<ASN1ObjectIdentifier> SCEP_ATTR_TYPES
= new HashSet<ASN1ObjectIdentifier>();
private X509Certificate signatureCert;
private ASN1ObjectIdentifier digestAlgorithm;
private ASN1ObjectIdentifier contentEncryptionAlgorithm;
private Boolean signatureValid;
private Boolean decryptionSuccessful;
private Date signingTime;
private String failureMessage;
static {
SCEP_ATTR_TYPES.add(ScepObjectIdentifiers.ID_FAILINFO);
SCEP_ATTR_TYPES.add(ScepObjectIdentifiers.ID_MESSAGE_TYPE);
SCEP_ATTR_TYPES.add(ScepObjectIdentifiers.ID_PKI_STATUS);
SCEP_ATTR_TYPES.add(ScepObjectIdentifiers.ID_RECIPIENT_NONCE);
SCEP_ATTR_TYPES.add(ScepObjectIdentifiers.ID_SENDER_NONCE);
SCEP_ATTR_TYPES.add(ScepObjectIdentifiers.ID_TRANSACTION_ID);
SCEP_ATTR_TYPES.add(CMSAttributes.signingTime);
}
public DecodedPkiMessage(final TransactionId transactionId, final MessageType messageType,
final Nonce senderNonce) {
super(transactionId, messageType, senderNonce);
}
public X509Certificate getSignatureCert() {
return signatureCert;
}
public void setSignatureCert(final X509Certificate signatureCert) {
this.signatureCert = signatureCert;
}
public void setDigestAlgorithm(final ASN1ObjectIdentifier digestAlgorithm) {
this.digestAlgorithm = digestAlgorithm;
}
public void setSignatureValid(final Boolean signatureValid) {
this.signatureValid = signatureValid;
}
public ASN1ObjectIdentifier getDigestAlgorithm() {
return digestAlgorithm;
}
public void setContentEncryptionAlgorithm(final ASN1ObjectIdentifier encryptionAlgorithm) {
this.contentEncryptionAlgorithm = encryptionAlgorithm;
}
public String getFailureMessage() {
return failureMessage;
}
public void setFailureMessage(final String failureMessage) {
this.failureMessage = failureMessage;
}
public ASN1ObjectIdentifier getContentEncryptionAlgorithm() {
return contentEncryptionAlgorithm;
}
public Boolean isDecryptionSuccessful() {
return decryptionSuccessful;
}
public void setDecryptionSuccessful(final Boolean decryptionSuccessful) {
this.decryptionSuccessful = decryptionSuccessful;
}
public Boolean isSignatureValid() {
return signatureValid;
}
public Date getSigningTime() {
return signingTime;
}
public void setSigningTime(final Date signingTime) {
this.signingTime = signingTime;
}
public static DecodedPkiMessage decode(final CMSSignedData pkiMessage,
final PrivateKey recipientKey, final X509Certificate recipientCert,
final CollectionStore<X509CertificateHolder> certStore)
throws MessageDecodingException {
EnvelopedDataDecryptorInstance decInstance = new EnvelopedDataDecryptorInstance(
recipientCert, recipientKey);
EnvelopedDataDecryptor recipient = new EnvelopedDataDecryptor(decInstance);
return decode(pkiMessage, recipient, certStore);
}
@SuppressWarnings("unchecked")
public static DecodedPkiMessage decode(final CMSSignedData pkiMessage,
final EnvelopedDataDecryptor recipient,
final CollectionStore<X509CertificateHolder> certStore)
throws MessageDecodingException {
ParamUtil.requireNonNull("pkiMessage", pkiMessage);
ParamUtil.requireNonNull("recipient", recipient);
SignerInformationStore signerStore = pkiMessage.getSignerInfos();
Collection<SignerInformation> signerInfos = signerStore.getSigners();
if (signerInfos.size() != 1) {
throw new MessageDecodingException(
"number of signerInfos is not 1, but " + signerInfos.size());
}
SignerInformation signerInfo = signerInfos.iterator().next();
SignerId sid = signerInfo.getSID();
Collection<?> signedDataCerts = null;
if (certStore != null) {
signedDataCerts = certStore.getMatches(sid);
}
if (signedDataCerts == null || signedDataCerts.isEmpty()) {
signedDataCerts = pkiMessage.getCertificates().getMatches(signerInfo.getSID());
}
if (signedDataCerts == null || signedDataCerts.size() != 1) {
throw new MessageDecodingException(
"could not find embedded certificate to verify the signature");
}
AttributeTable signedAttrs = signerInfo.getSignedAttributes();
if (signedAttrs == null) {
throw new MessageDecodingException("missing SCEP attributes");
}
Date signingTime = null;
// signingTime
ASN1Encodable attrValue = ScepUtil.getFirstAttrValue(signedAttrs,
CMSAttributes.signingTime);
if (attrValue != null) {
signingTime = Time.getInstance(attrValue).getDate();
}
// transactionId
String str = getPrintableStringAttrValue(signedAttrs,
ScepObjectIdentifiers.ID_TRANSACTION_ID);
if (str == null || str.isEmpty()) {
throw new MessageDecodingException("missing required SCEP attribute transactionId");
}
TransactionId transactionId = new TransactionId(str);
// messageType
Integer intValue = getIntegerPrintStringAttrValue(signedAttrs,
ScepObjectIdentifiers.ID_MESSAGE_TYPE);
if (intValue == null) {
throw new MessageDecodingException("tid " + transactionId.getId()
+ ": missing required SCEP attribute messageType");
}
MessageType messageType;
try {
messageType = MessageType.forValue(intValue);
} catch (IllegalArgumentException ex) {
throw new MessageDecodingException("tid " + transactionId.getId()
+ ": invalid messageType '" + intValue + "'");
}
// senderNonce
Nonce senderNonce = getNonceAttrValue(signedAttrs, ScepObjectIdentifiers.ID_SENDER_NONCE);
if (senderNonce == null) {
throw new MessageDecodingException("tid " + transactionId.getId()
+ ": missing required SCEP attribute senderNonce");
}
DecodedPkiMessage ret = new DecodedPkiMessage(transactionId, messageType, senderNonce);
if (signingTime != null) {
ret.setSigningTime(signingTime);
}
Nonce recipientNonce = null;
try {
recipientNonce = getNonceAttrValue(signedAttrs,
ScepObjectIdentifiers.ID_RECIPIENT_NONCE);
} catch (MessageDecodingException ex) {
ret.setFailureMessage("could not parse recipientNonce: " + ex.getMessage());
}
if (recipientNonce != null) {
ret.setRecipientNonce(recipientNonce);
}
PkiStatus pkiStatus = null;
FailInfo failInfo = null;
if (MessageType.CertRep == messageType) {
// pkiStatus
try {
intValue = getIntegerPrintStringAttrValue(signedAttrs,
ScepObjectIdentifiers.ID_PKI_STATUS);
} catch (MessageDecodingException ex) {
ret.setFailureMessage("could not parse pkiStatus: " + ex.getMessage());
return ret;
}
if (intValue == null) {
ret.setFailureMessage("missing required SCEP attribute pkiStatus");
return ret;
}
try {
pkiStatus = PkiStatus.forValue(intValue);
} catch (IllegalArgumentException ex) {
ret.setFailureMessage("invalid pkiStatus '" + intValue + "'");
return ret;
}
ret.setPkiStatus(pkiStatus);
// failureInfo
if (pkiStatus == PkiStatus.FAILURE) {
try {
intValue = getIntegerPrintStringAttrValue(signedAttrs,
ScepObjectIdentifiers.ID_FAILINFO);
} catch (MessageDecodingException ex) {
ret.setFailureMessage("could not parse failInfo: " + ex.getMessage());
return ret;
}
if (intValue == null) {
ret.setFailureMessage("missing required SCEP attribute failInfo");
return ret;
}
try {
failInfo = FailInfo.forValue(intValue);
} catch (IllegalArgumentException ex) {
ret.setFailureMessage("invalid failInfo '" + intValue + "'");
return ret;
}
ret.setFailInfo(failInfo);
} // end if(pkiStatus == PkiStatus.FAILURE)
} // end if (MessageType.CertRep == messageType)
// other signedAttributes
Attribute[] attrs = signedAttrs.toASN1Structure().getAttributes();
for (Attribute attr : attrs) {
ASN1ObjectIdentifier type = attr.getAttrType();
if (!SCEP_ATTR_TYPES.contains(type)) {
ret.addSignendAttribute(type, attr.getAttrValues().getObjectAt(0));
}
}
// unsignedAttributes
AttributeTable unsignedAttrs = signerInfo.getUnsignedAttributes();
attrs = (unsignedAttrs == null) ? null : unsignedAttrs.toASN1Structure().getAttributes();
if (attrs != null) {
for (Attribute attr : attrs) {
ASN1ObjectIdentifier type = attr.getAttrType();
ret.addUnsignendAttribute(type, attr.getAttrValues().getObjectAt(0));
}
}
ASN1ObjectIdentifier digestAlgOid = signerInfo.getDigestAlgorithmID().getAlgorithm();
ret.setDigestAlgorithm(digestAlgOid);
String sigAlgOid = signerInfo.getEncryptionAlgOID();
if (!PKCSObjectIdentifiers.rsaEncryption.getId().equals(sigAlgOid)) {
ASN1ObjectIdentifier tmpDigestAlgOid;
try {
tmpDigestAlgOid = ScepUtil.extractDigesetAlgorithmIdentifier(
signerInfo.getEncryptionAlgOID(), signerInfo.getEncryptionAlgParams());
} catch (Exception ex) {
final String msg =
"could not extract digest algorithm from signerInfo.signatureAlgorithm: "
+ ex.getMessage();
LOG.error(msg);
LOG.debug(msg, ex);
ret.setFailureMessage(msg);
return ret;
}
if (!digestAlgOid.equals(tmpDigestAlgOid)) {
ret.setFailureMessage("digestAlgorithm and encryptionAlgorithm do not use the"
+ " same digestAlgorithm");
return ret;
} // end if
} // end if
X509CertificateHolder tmpSignerCert =
(X509CertificateHolder) signedDataCerts.iterator().next();
X509Certificate signerCert;
try {
signerCert = ScepUtil.toX509Cert(tmpSignerCert.toASN1Structure());
} catch (CertificateException ex) {
final String msg = "could not construct X509Certificate: " + ex.getMessage();
LOG.error(msg);
LOG.debug(msg, ex);
ret.setFailureMessage(msg);
return ret;
}
ret.setSignatureCert(signerCert);
// validate the signature
SignerInformationVerifier verifier;
try {
verifier = new JcaSimpleSignerInfoVerifierBuilder().build(
signerCert.getPublicKey());
} catch (OperatorCreationException ex) {
final String msg = "could not build signature verifier: " + ex.getMessage();
LOG.error(msg);
LOG.debug(msg, ex);
ret.setFailureMessage(msg);
return ret;
}
boolean signatureValid;
try {
signatureValid = signerInfo.verify(verifier);
} catch (CMSException ex) {
final String msg = "could not verify the signature: " + ex.getMessage();
LOG.error(msg);
LOG.debug(msg, ex);
ret.setFailureMessage(msg);
return ret;
}
ret.setSignatureValid(signatureValid);
if (!signatureValid) {
return ret;
}
if (MessageType.CertRep == messageType
&& (pkiStatus == PkiStatus.FAILURE | pkiStatus == PkiStatus.PENDING)) {
return ret;
}
// MessageData
CMSTypedData signedContent = pkiMessage.getSignedContent();
ASN1ObjectIdentifier signedContentType = signedContent.getContentType();
if (!CMSObjectIdentifiers.envelopedData.equals(signedContentType)) {
// fall back: some SCEP client, such as JSCEP use id-data
if (!CMSObjectIdentifiers.data.equals(signedContentType)) {
ret.setFailureMessage("either id-envelopedData or id-data is excepted, but not '"
+ signedContentType.getId());
return ret;
}
}
CMSEnvelopedData envData;
try {
envData = new CMSEnvelopedData((byte[]) signedContent.getContent());
} catch (CMSException ex) {
final String msg = "could not create the CMSEnvelopedData: " + ex.getMessage();
LOG.error(msg);
LOG.debug(msg, ex);
ret.setFailureMessage(msg);
return ret;
}
ret.setContentEncryptionAlgorithm(envData.getContentEncryptionAlgorithm().getAlgorithm());
byte[] encodedMessageData;
try {
encodedMessageData = recipient.decrypt(envData);
} catch (MessageDecodingException ex) {
final String msg = "could not create the CMSEnvelopedData: " + ex.getMessage();
LOG.error(msg);
LOG.debug(msg, ex);
ret.setFailureMessage(msg);
ret.setDecryptionSuccessful(false);
return ret;
}
ret.setDecryptionSuccessful(true);
try {
if (MessageType.PKCSReq == messageType || MessageType.RenewalReq == messageType
|| MessageType.UpdateReq == messageType) {
CertificationRequest messageData =
CertificationRequest.getInstance(encodedMessageData);
ret.setMessageData(messageData);
} else if (MessageType.CertPoll == messageType) {
IssuerAndSubject messageData = IssuerAndSubject.getInstance(encodedMessageData);
ret.setMessageData(messageData);
} else if (MessageType.GetCert == messageType || MessageType.GetCRL == messageType) {
IssuerAndSerialNumber messageData =
IssuerAndSerialNumber.getInstance(encodedMessageData);
ret.setMessageData(messageData);
ret.setMessageData(messageData);
} else if (MessageType.CertRep == messageType) {
ContentInfo ci = ContentInfo.getInstance(encodedMessageData);
ret.setMessageData(ci);
} else {
throw new RuntimeException("should not reach here, unknown messageType "
+ messageType);
}
} catch (Exception ex) {
final String msg = "could not parse the messageData: " + ex.getMessage();
LOG.error(msg);
LOG.debug(msg, ex);
ret.setFailureMessage(msg);
return ret;
}
return ret;
} // method decode
private static String getPrintableStringAttrValue(final AttributeTable attrs,
final ASN1ObjectIdentifier type) throws MessageDecodingException {
ASN1Encodable value = ScepUtil.getFirstAttrValue(attrs, type);
if (value instanceof DERPrintableString) {
return ((DERPrintableString) value).getString();
} else if (value != null) {
throw new MessageDecodingException("the value of attribute " + type.getId()
+ " is not PrintableString");
} else {
return null;
}
}
private static Integer getIntegerPrintStringAttrValue(final AttributeTable attrs,
final ASN1ObjectIdentifier type) throws MessageDecodingException {
String str = getPrintableStringAttrValue(attrs, type);
if (str == null) {
return null;
}
try {
return Integer.parseInt(str);
} catch (NumberFormatException ex) {
throw new MessageDecodingException("invalid integer '" + str + "'");
}
}
private static Nonce getNonceAttrValue(final AttributeTable attrs,
final ASN1ObjectIdentifier type) throws MessageDecodingException {
ASN1Encodable value = ScepUtil.getFirstAttrValue(attrs, type);
if (value instanceof ASN1OctetString) {
byte[] bytes = ((ASN1OctetString) value).getOctets();
return new Nonce(bytes);
} else if (value != null) {
throw new MessageDecodingException("the value of attribute " + type.getId()
+ " is not OctetString");
} else {
return null;
}
}
}