/* * * 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.ca.client.impl; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Random; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1GeneralizedTime; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.cmp.CMPObjectIdentifiers; import org.bouncycastle.asn1.cmp.ErrorMsgContent; import org.bouncycastle.asn1.cmp.GenMsgContent; import org.bouncycastle.asn1.cmp.GenRepContent; import org.bouncycastle.asn1.cmp.InfoTypeAndValue; import org.bouncycastle.asn1.cmp.PKIBody; import org.bouncycastle.asn1.cmp.PKIFailureInfo; import org.bouncycastle.asn1.cmp.PKIHeader; import org.bouncycastle.asn1.cmp.PKIHeaderBuilder; import org.bouncycastle.asn1.cmp.PKIMessage; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.cert.cmp.CMPException; import org.bouncycastle.cert.cmp.GeneralPKIMessage; import org.bouncycastle.cert.cmp.ProtectedPKIMessage; import org.bouncycastle.operator.ContentVerifierProvider; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.util.encoders.Hex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xipki.commons.common.RequestResponseDebug; import org.xipki.commons.common.RequestResponsePair; import org.xipki.commons.common.util.CollectionUtil; import org.xipki.commons.common.util.ParamUtil; import org.xipki.commons.security.ConcurrentContentSigner; import org.xipki.commons.security.ObjectIdentifiers; import org.xipki.commons.security.SecurityFactory; import org.xipki.commons.security.exception.NoIdleSignerException; import org.xipki.commons.security.util.AlgorithmUtil; import org.xipki.commons.security.util.CmpFailureUtil; import org.xipki.pki.ca.client.api.PkiErrorException; import org.xipki.pki.ca.common.cmp.CmpUtf8Pairs; import org.xipki.pki.ca.common.cmp.CmpUtil; import org.xipki.pki.ca.common.cmp.PkiResponse; import org.xipki.pki.ca.common.cmp.ProtectionResult; import org.xipki.pki.ca.common.cmp.ProtectionVerificationResult; /** * @author Lijun Liao * @since 2.0.0 */ abstract class CmpRequestor { private static final Logger LOG = LoggerFactory.getLogger(CmpRequestor.class); protected final SecurityFactory securityFactory; protected boolean signRequest; private final Random random = new Random(); private final ConcurrentContentSigner requestor; private final GeneralName sender; private final CmpResponder responder; private final GeneralName recipient; private final X500Name recipientName; private boolean sendRequestorCert; public CmpRequestor(final X509Certificate requestorCert, final CmpResponder responder, final SecurityFactory securityFactory) { ParamUtil.requireNonNull("requestorCert", requestorCert); this.responder = ParamUtil.requireNonNull("responder", responder); this.securityFactory = ParamUtil.requireNonNull("securityFactory", securityFactory); this.requestor = null; this.signRequest = false; X500Name x500Name = X500Name.getInstance( requestorCert.getSubjectX500Principal().getEncoded()); this.sender = new GeneralName(x500Name); X500Name subject = X500Name.getInstance( responder.getCert().getSubjectX500Principal().getEncoded()); this.recipient = new GeneralName(subject); this.recipientName = subject; } public CmpRequestor(final ConcurrentContentSigner requestor, final CmpResponder responder, final SecurityFactory securityFactory) { this(requestor, responder, securityFactory, true); } public CmpRequestor(ConcurrentContentSigner requestor, final CmpResponder responder, final SecurityFactory securityFactory, final boolean signRequest) { this.requestor = ParamUtil.requireNonNull("requestor", requestor); if (requestor.getCertificate() == null) { throw new IllegalArgumentException("requestor without certificate is not allowed"); } this.responder = ParamUtil.requireNonNull("responder", responder); this.securityFactory = ParamUtil.requireNonNull("securityFactory", securityFactory); this.signRequest = signRequest; X500Name x500Name = X500Name.getInstance( requestor.getCertificate().getSubjectX500Principal().getEncoded()); this.sender = new GeneralName(x500Name); X500Name subject = X500Name.getInstance( responder.getCert().getSubjectX500Principal().getEncoded()); this.recipient = new GeneralName(subject); this.recipientName = subject; } protected abstract byte[] send(final byte[] request) throws IOException; protected PKIMessage sign(final PKIMessage request) throws CmpRequestorException { ParamUtil.requireNonNull("request", request); if (requestor == null) { throw new CmpRequestorException("no request signer is configured"); } try { return CmpUtil.addProtection(request, requestor, sender, sendRequestorCert); } catch (CMPException | NoIdleSignerException ex) { throw new CmpRequestorException("could not sign the request", ex); } } protected PkiResponse signAndSend(final PKIMessage request, final RequestResponseDebug debug) throws CmpRequestorException { ParamUtil.requireNonNull("request", request); PKIMessage tmpRequest = (signRequest) ? sign(request) : request; byte[] encodedRequest; try { encodedRequest = tmpRequest.getEncoded(); } catch (IOException ex) { LOG.error("could not encode the PKI request {}", tmpRequest); throw new CmpRequestorException(ex.getMessage(), ex); } RequestResponsePair reqResp = null; if (debug != null) { reqResp = new RequestResponsePair(); debug.add(reqResp); reqResp.setRequest(encodedRequest); } byte[] encodedResponse; try { encodedResponse = send(encodedRequest); } catch (IOException ex) { LOG.error("could not send the PKI request {} to server", tmpRequest); throw new CmpRequestorException("TRANSPORT_ERROR", ex); } if (reqResp != null) { reqResp.setResponse(encodedResponse); } GeneralPKIMessage response; try { response = new GeneralPKIMessage(encodedResponse); } catch (IOException ex) { LOG.error("could not decode the received PKI message: {}", Hex.toHexString(encodedResponse)); throw new CmpRequestorException(ex.getMessage(), ex); } PKIHeader respHeader = response.getHeader(); ASN1OctetString tid = respHeader.getTransactionID(); GeneralName rec = respHeader.getRecipient(); if (!sender.equals(rec)) { LOG.warn("tid={}: unknown CMP requestor '{}'", tid, rec); } PkiResponse ret = new PkiResponse(response); if (response.hasProtection()) { try { ProtectionVerificationResult verifyProtection = verifyProtection( Hex.toHexString(tid.getOctets()), response); ret.setProtectionVerificationResult(verifyProtection); } catch (InvalidKeyException | OperatorCreationException | CMPException ex) { throw new CmpRequestorException(ex.getMessage(), ex); } } else if (signRequest) { PKIBody respBody = response.getBody(); int bodyType = respBody.getType(); if (bodyType != PKIBody.TYPE_ERROR) { throw new CmpRequestorException("response is not signed"); } } return ret; } // method signAndSend protected ASN1Encodable extractGeneralRepContent(final PkiResponse response, final String expectedType) throws CmpRequestorException, PkiErrorException { ParamUtil.requireNonNull("response", response); ParamUtil.requireNonNull("expectedType", expectedType); return extractGeneralRepContent(response, expectedType, true); } private ASN1Encodable extractGeneralRepContent(final PkiResponse response, final String expectedType, final boolean requireProtectionCheck) throws CmpRequestorException, PkiErrorException { ParamUtil.requireNonNull("response", response); ParamUtil.requireNonNull("expectedType", expectedType); if (requireProtectionCheck) { checkProtection(response); } PKIBody respBody = response.getPkiMessage().getBody(); int bodyType = respBody.getType(); if (PKIBody.TYPE_ERROR == bodyType) { ErrorMsgContent content = ErrorMsgContent.getInstance(respBody.getContent()); throw new CmpRequestorException(CmpFailureUtil.formatPkiStatusInfo( content.getPKIStatusInfo())); } else if (PKIBody.TYPE_GEN_REP != bodyType) { throw new CmpRequestorException(String.format( "unknown PKI body type %s instead the expected [%s, %s]", bodyType, PKIBody.TYPE_GEN_REP, PKIBody.TYPE_ERROR)); } GenRepContent genRep = GenRepContent.getInstance(respBody.getContent()); InfoTypeAndValue[] itvs = genRep.toInfoTypeAndValueArray(); InfoTypeAndValue itv = null; if (itvs != null && itvs.length > 0) { for (InfoTypeAndValue entry : itvs) { if (expectedType.equals(entry.getInfoType().getId())) { itv = entry; break; } } } if (itv == null) { throw new CmpRequestorException("the response does not contain InfoTypeAndValue " + expectedType); } return itv.getInfoValue(); } // method extractGeneralRepContent protected ASN1Encodable extractXipkiActionRepContent(final PkiResponse response, final int action) throws CmpRequestorException, PkiErrorException { ParamUtil.requireNonNull("response", response); ASN1Encodable itvValue = extractGeneralRepContent(response, ObjectIdentifiers.id_xipki_cmp_cmpGenmsg.getId(), true); return extractXipkiActionContent(itvValue, action); } protected ASN1Encodable extractXipkiActionContent(final ASN1Encodable itvValue, final int action) throws CmpRequestorException { ParamUtil.requireNonNull("itvValue", itvValue); ASN1Sequence seq; try { seq = ASN1Sequence.getInstance(itvValue); } catch (IllegalArgumentException ex) { throw new CmpRequestorException("invalid syntax of the response"); } int size = seq.size(); if (size != 1 && size != 2) { throw new CmpRequestorException("invalid syntax of the response"); } int tmpAction; try { tmpAction = ASN1Integer.getInstance(seq.getObjectAt(0)).getPositiveValue().intValue(); } catch (IllegalArgumentException ex) { throw new CmpRequestorException("invalid syntax of the response"); } if (action != tmpAction) { throw new CmpRequestorException("received XiPKI action '" + tmpAction + "' instead the expected '" + action + "'"); } return (size == 1) ? null : seq.getObjectAt(1); } // method extractXipkiActionContent protected PKIHeader buildPkiHeader(final ASN1OctetString tid) { return buildPkiHeader(false, tid, (CmpUtf8Pairs) null, (InfoTypeAndValue[]) null); } protected PKIHeader buildPkiHeader(final boolean addImplictConfirm, final ASN1OctetString tid) { return buildPkiHeader(addImplictConfirm, tid, (CmpUtf8Pairs) null, (InfoTypeAndValue[]) null); } protected PKIHeader buildPkiHeader(final boolean addImplictConfirm, final ASN1OctetString tid, final CmpUtf8Pairs utf8Pairs, final InfoTypeAndValue... additionalGeneralInfos) { if (additionalGeneralInfos != null) { for (InfoTypeAndValue itv : additionalGeneralInfos) { ASN1ObjectIdentifier type = itv.getInfoType(); if (CMPObjectIdentifiers.it_implicitConfirm.equals(type)) { throw new IllegalArgumentException( "additionGeneralInfos contains not-permitted ITV implicitConfirm"); } if (CMPObjectIdentifiers.regInfo_utf8Pairs.equals(type)) { throw new IllegalArgumentException( "additionGeneralInfos contains not-permitted ITV utf8Pairs"); } } } PKIHeaderBuilder hdrBuilder = new PKIHeaderBuilder(PKIHeader.CMP_2000, sender, recipient); hdrBuilder.setMessageTime(new ASN1GeneralizedTime(new Date())); ASN1OctetString tmpTid = (tid == null) ? new DEROctetString(randomTransactionId()) : tid; hdrBuilder.setTransactionID(tmpTid); List<InfoTypeAndValue> itvs = new ArrayList<>(2); if (addImplictConfirm) { itvs.add(CmpUtil.getImplictConfirmGeneralInfo()); } if (utf8Pairs != null) { itvs.add(CmpUtil.buildInfoTypeAndValue(utf8Pairs)); } if (additionalGeneralInfos != null) { for (InfoTypeAndValue itv : additionalGeneralInfos) { if (itv != null) { itvs.add(itv); } } } if (CollectionUtil.isNonEmpty(itvs)) { hdrBuilder.setGeneralInfo(itvs.toArray(new InfoTypeAndValue[0])); } return hdrBuilder.build(); } // method buildPkiHeader protected PkiErrorException buildErrorResult(final ErrorMsgContent bodyContent) { ParamUtil.requireNonNull("bodyContent", bodyContent); org.xipki.pki.ca.common.cmp.PkiStatusInfo statusInfo = new org.xipki.pki.ca.common.cmp.PkiStatusInfo(bodyContent.getPKIStatusInfo()); return new PkiErrorException(statusInfo.getStatus(), statusInfo.getPkiFailureInfo(), statusInfo.getStatusMessage()); } private byte[] randomTransactionId() { byte[] tid = new byte[20]; random.nextBytes(tid); return tid; } private ProtectionVerificationResult verifyProtection(final String tid, final GeneralPKIMessage pkiMessage) throws CMPException, InvalidKeyException, OperatorCreationException { ProtectedPKIMessage protectedMsg = new ProtectedPKIMessage(pkiMessage); if (protectedMsg.hasPasswordBasedMacProtection()) { LOG.warn("NOT_SIGNAUTRE_BASED: " + pkiMessage.getHeader().getProtectionAlg().getAlgorithm().getId()); return new ProtectionVerificationResult(null, ProtectionResult.NOT_SIGNATURE_BASED); } PKIHeader header = protectedMsg.getHeader(); if (recipientName != null) { boolean authorizedResponder = true; if (header.getSender().getTagNo() != GeneralName.directoryName) { authorizedResponder = false; } else { X500Name msgSender = X500Name.getInstance(header.getSender().getName()); authorizedResponder = recipientName.equals(msgSender); } if (!authorizedResponder) { LOG.warn("tid={}: not authorized responder '{}'", tid, header.getSender()); return new ProtectionVerificationResult(null, ProtectionResult.SENDER_NOT_AUTHORIZED); } } AlgorithmIdentifier protectionAlgo = protectedMsg.getHeader().getProtectionAlg(); if (!responder.getSigAlgoValidator().isAlgorithmPermitted(protectionAlgo)) { String algoName; try { algoName = AlgorithmUtil.getSignatureAlgoName(protectionAlgo); } catch (NoSuchAlgorithmException ex) { algoName = protectionAlgo.getAlgorithm().getId(); } LOG.warn("tid={}: response protected by untrusted protection algorithm '{}'", tid, algoName); return new ProtectionVerificationResult(null, ProtectionResult.INVALID); } X509Certificate cert = responder.getCert(); ContentVerifierProvider verifierProvider = securityFactory.getContentVerifierProvider(cert); if (verifierProvider == null) { LOG.warn("tid={}: not authorized responder '{}'", tid, header.getSender()); return new ProtectionVerificationResult(cert, ProtectionResult.SENDER_NOT_AUTHORIZED); } boolean signatureValid = protectedMsg.verify(verifierProvider); ProtectionResult protRes = signatureValid ? ProtectionResult.VALID : ProtectionResult.INVALID; return new ProtectionVerificationResult(cert, protRes); } // method verifyProtection protected PKIMessage buildMessageWithXipkAction(final int action, final ASN1Encodable value) throws CmpRequestorException { PKIHeader header = buildPkiHeader(null); ASN1EncodableVector vec = new ASN1EncodableVector(); vec.add(new ASN1Integer(action)); if (value != null) { vec.add(value); } InfoTypeAndValue itv = new InfoTypeAndValue(ObjectIdentifiers.id_xipki_cmp_cmpGenmsg, new DERSequence(vec)); GenMsgContent genMsgContent = new GenMsgContent(itv); PKIBody body = new PKIBody(PKIBody.TYPE_GEN_MSG, genMsgContent); PKIMessage pkiMessage = new PKIMessage(header, body); return pkiMessage; } protected PKIMessage buildMessageWithGeneralMsgContent(final ASN1ObjectIdentifier type, final ASN1Encodable value) throws CmpRequestorException { ParamUtil.requireNonNull("type", type); PKIHeader header = buildPkiHeader(null); InfoTypeAndValue itv = (value != null) ? new InfoTypeAndValue(type, value) : new InfoTypeAndValue(type); GenMsgContent genMsgContent = new GenMsgContent(itv); PKIBody body = new PKIBody(PKIBody.TYPE_GEN_MSG, genMsgContent); PKIMessage pkiMessage = new PKIMessage(header, body); return pkiMessage; } protected void checkProtection(final PkiResponse response) throws PkiErrorException { ParamUtil.requireNonNull("response", response); if (!response.hasProtection()) { return; } ProtectionVerificationResult protectionVerificationResult = response.getProtectionVerificationResult(); if (protectionVerificationResult == null || protectionVerificationResult.getProtectionResult() != ProtectionResult.VALID) { throw new PkiErrorException(ClientErrorCode.PKISTATUS_RESPONSE_ERROR, PKIFailureInfo.badMessageCheck, "message check of the response failed"); } } public boolean isSendRequestorCert() { return sendRequestorCert; } public void setSendRequestorCert(final boolean sendRequestorCert) { this.sendRequestorCert = sendRequestorCert; } }