/* * * 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.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Date; import java.util.LinkedList; import java.util.List; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1ObjectIdentifier; 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.SignedData; import org.bouncycastle.asn1.cms.Time; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.cert.X509CertificateHolder; 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.util.ScepUtil; /** * @author Lijun Liao * @since 2.0.0 */ public class DecodedNextCaMessage { private static final Logger LOG = LoggerFactory.getLogger(DecodedNextCaMessage.class); private AuthorityCertStore authorityCertStore; private X509Certificate signatureCert; private ASN1ObjectIdentifier digestAlgorithm; private Boolean signatureValid; private Date signingTime; private String failureMessage; public DecodedNextCaMessage() { } public AuthorityCertStore getAuthorityCertStore() { return authorityCertStore; } public void setAuthorityCertStore(final AuthorityCertStore authorityCertStore) { this.authorityCertStore = authorityCertStore; } 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 String getFailureMessage() { return failureMessage; } public void setFailureMessage(final String failureMessage) { this.failureMessage = failureMessage; } public Boolean isSignatureValid() { return signatureValid; } public Date getSigningTime() { return signingTime; } public void setSigningTime(final Date signingTime) { this.signingTime = signingTime; } @SuppressWarnings("unchecked") public static DecodedNextCaMessage decode(final CMSSignedData pkiMessage, final CollectionStore<X509CertificateHolder> certStore) throws MessageDecodingException { ParamUtil.requireNonNull("pkiMessage", pkiMessage); 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 signed attributes"); } Date signingTime = null; // signingTime ASN1Encodable attrValue = ScepUtil.getFirstAttrValue(signedAttrs, CMSAttributes.signingTime); if (attrValue != null) { signingTime = Time.getInstance(attrValue).getDate(); } DecodedNextCaMessage ret = new DecodedNextCaMessage(); if (signingTime != null) { ret.setSigningTime(signingTime); } 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 X509CertificateHolder tmpSignerCert = (X509CertificateHolder) signedDataCerts.iterator().next(); X509Certificate signerCert; try { signerCert = ScepUtil.toX509Cert(tmpSignerCert.toASN1Structure()); } catch (CertificateException ex) { final String msg = "could not construct X509CertificateObject: " + 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; } // MessageData CMSTypedData signedContent = pkiMessage.getSignedContent(); ASN1ObjectIdentifier signedContentType = signedContent.getContentType(); if (!CMSObjectIdentifiers.signedData.equals(signedContentType)) { // fall back: some SCEP client use id-data if (!CMSObjectIdentifiers.data.equals(signedContentType)) { ret.setFailureMessage("either id-signedData or id-data is excepted, but not '" + signedContentType.getId()); return ret; } } ContentInfo contentInfo = ContentInfo.getInstance((byte[]) signedContent.getContent()); SignedData signedData = SignedData.getInstance(contentInfo.getContent()); List<X509Certificate> certs; try { certs = ScepUtil.getCertsFromSignedData(signedData); } catch (CertificateException ex) { final String msg = "could not extract Certificates from the message: " + ex.getMessage(); LOG.error(msg); LOG.debug(msg, ex); ret.setFailureMessage(msg); return ret; } final int n = certs.size(); X509Certificate caCert = null; List<X509Certificate> raCerts = new LinkedList<X509Certificate>(); for (int i = 0; i < n; i++) { X509Certificate cert = certs.get(i); if (cert.getBasicConstraints() > -1) { if (caCert != null) { final String msg = "multiple CA certificates is returned, but exactly 1 is expected"; LOG.error(msg); ret.setFailureMessage(msg); return ret; } caCert = cert; } else { raCerts.add(cert); } } // end for if (caCert == null) { final String msg = "no CA certificate is returned"; LOG.error(msg); ret.setFailureMessage(msg); return ret; } X509Certificate[] locaRaCerts; if (raCerts.isEmpty()) { locaRaCerts = null; } else { locaRaCerts = raCerts.toArray(new X509Certificate[0]); } AuthorityCertStore authorityCertStore = AuthorityCertStore.getInstance(caCert, locaRaCerts); ret.setAuthorityCertStore(authorityCertStore); return ret; } // method decode }