Java tutorial
/* * * This file is part of the XiPKI project. * Copyright (c) 2014 - 2015 Lijun Liao * Author: 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.ca.client.impl; import java.io.IOException; import java.security.InvalidKeyException; 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.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.ca.client.api.PKIErrorException; import org.xipki.ca.common.cmp.CmpUtil; import org.xipki.ca.common.cmp.PKIResponse; import org.xipki.ca.common.cmp.ProtectionResult; import org.xipki.ca.common.cmp.ProtectionVerificationResult; import org.xipki.common.CmpUtf8Pairs; import org.xipki.common.ObjectIdentifiers; import org.xipki.common.ParamChecker; import org.xipki.common.RequestResponseDebug; import org.xipki.common.RequestResponsePair; import org.xipki.common.util.CollectionUtil; import org.xipki.common.util.SecurityUtil; import org.xipki.common.util.StringUtil; import org.xipki.common.util.X509Util; import org.xipki.security.api.ConcurrentContentSigner; import org.xipki.security.api.NoIdleSignerException; import org.xipki.security.api.SecurityFactory; /** * @author Lijun Liao */ public abstract class CmpRequestor { private static final Logger LOG = LoggerFactory.getLogger(CmpRequestor.class); private static GeneralName DUMMY_RECIPIENT = new GeneralName(new X500Name("CN=DUMMY")); private final Random random = new Random(); private final ConcurrentContentSigner requestor; private final GeneralName sender; private X509Certificate responderCert; private GeneralName recipient; private String c14nRecipientName; protected final SecurityFactory securityFactory; protected boolean signRequest; private boolean sendRequestorCert = false; public CmpRequestor(final X509Certificate requestorCert, final X509Certificate responderCert, final SecurityFactory securityFactory) { ParamChecker.assertNotNull("requestorCert", requestorCert); ParamChecker.assertNotNull("securityFactory", securityFactory); this.requestor = null; this.securityFactory = securityFactory; this.signRequest = false; X500Name x500Name = X500Name.getInstance(requestorCert.getSubjectX500Principal().getEncoded()); this.sender = new GeneralName(x500Name); if (responderCert != null) { setResponderCert(responderCert); } } public CmpRequestor(final ConcurrentContentSigner requestor, final X509Certificate responderCert, final SecurityFactory securityFactory) { this(requestor, responderCert, securityFactory, true); } public CmpRequestor(ConcurrentContentSigner requestor, final X509Certificate responderCert, final SecurityFactory securityFactory, final boolean signRequest) { ParamChecker.assertNotNull("requestor", requestor); ParamChecker.assertNotNull("securityFactory", securityFactory); this.requestor = requestor; this.securityFactory = securityFactory; this.signRequest = signRequest; X500Name x500Name = X500Name.getInstance(requestor.getCertificate().getSubjectX500Principal().getEncoded()); this.sender = new GeneralName(x500Name); if (responderCert != null) { setResponderCert(responderCert); } } private void setResponderCert(final X509Certificate responderCert) { ParamChecker.assertNotNull("responderCert", responderCert); this.responderCert = responderCert; X500Name subject = X500Name.getInstance(responderCert.getSubjectX500Principal().getEncoded()); this.recipient = new GeneralName(subject); this.c14nRecipientName = getSortedRFC4519Name(subject); } protected abstract byte[] send(final byte[] request) throws IOException; protected PKIMessage sign(final PKIMessage request) throws CmpRequestorException { if (requestor == null) { throw new CmpRequestorException("no request signer is configured"); } if (responderCert == null) { throw new CmpRequestorException("CMP responder is not configured"); } try { return CmpUtil.addProtection(request, requestor, sender, sendRequestorCert); } catch (CMPException | NoIdleSignerException e) { throw new CmpRequestorException("could not sign the request", e); } } protected PKIResponse signAndSend(final PKIMessage request, final RequestResponseDebug debug) throws CmpRequestorException { PKIMessage _request; if (signRequest) { _request = sign(request); } else { _request = request; } if (responderCert == null) { throw new CmpRequestorException("CMP responder is not configured"); } byte[] encodedRequest; try { encodedRequest = _request.getEncoded(); } catch (IOException e) { LOG.error("error while encode the PKI request {}", _request); throw new CmpRequestorException(e.getMessage(), e); } RequestResponsePair reqResp = null; if (debug != null) { reqResp = new RequestResponsePair(); debug.add(reqResp); reqResp.setRequest(encodedRequest); } byte[] encodedResponse; try { encodedResponse = send(encodedRequest); } catch (IOException e) { LOG.error("error while send the PKI request {} to server", _request); throw new CmpRequestorException("TRANSPORT_ERROR", e); } if (reqResp != null) { reqResp.setResponse(encodedResponse); } GeneralPKIMessage response; try { response = new GeneralPKIMessage(encodedResponse); } catch (IOException e) { if (LOG.isErrorEnabled()) { LOG.error("error while decode the received PKI message: {}", Hex.toHexString(encodedResponse)); } throw new CmpRequestorException(e.getMessage(), e); } PKIHeader respHeader = response.getHeader(); ASN1OctetString tid = respHeader.getTransactionID(); GeneralName recipient = respHeader.getRecipient(); if (sender.equals(recipient) == false) { LOG.warn("tid={}: unknown CMP requestor '{}'", tid, recipient); } PKIResponse ret = new PKIResponse(response); if (response.hasProtection()) { try { ProtectionVerificationResult verifyProtection = verifyProtection(Hex.toHexString(tid.getOctets()), response, responderCert); ret.setProtectionVerificationResult(verifyProtection); } catch (InvalidKeyException | OperatorCreationException | CMPException e) { throw new CmpRequestorException(e.getMessage(), e); } } 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; } protected ASN1Encodable extractGeneralRepContent(final PKIResponse response, final String exepectedType) throws CmpRequestorException, PKIErrorException { return extractGeneralRepContent(response, exepectedType, true); } protected ASN1Encodable extractXipkiActionRepContent(PKIResponse response, int action) throws CmpRequestorException, PKIErrorException { ASN1Encodable itvValue = extractGeneralRepContent(response, ObjectIdentifiers.id_xipki_cmp.getId(), true); return extractXipkiActionContent(itvValue, action); } protected ASN1Encodable extractXipkiActionContent(final ASN1Encodable itvValue, final int action) throws CmpRequestorException { ASN1Sequence seq; try { seq = ASN1Sequence.getInstance(itvValue); } catch (IllegalArgumentException e) { throw new CmpRequestorException("invalid syntax of the response"); } int n = seq.size(); if (n != 1 && n != 2) { throw new CmpRequestorException("invalid syntax of the response"); } int _action; try { _action = ASN1Integer.getInstance(seq.getObjectAt(0)).getPositiveValue().intValue(); } catch (IllegalArgumentException e) { throw new CmpRequestorException("invalid syntax of the response"); } if (action != _action) { throw new CmpRequestorException( "received XiPKI action '" + _action + "' instead the exceptected '" + action + "'"); } return (n == 1) ? null : seq.getObjectAt(1); } private ASN1Encodable extractGeneralRepContent(final PKIResponse response, final String exepectedType, final boolean requireProtectionCheck) throws CmpRequestorException, PKIErrorException { if (requireProtectionCheck) { checkProtection(response); } PKIBody respBody = response.getPkiMessage().getBody(); int bodyType = respBody.getType(); if (PKIBody.TYPE_ERROR == bodyType) { ErrorMsgContent content = (ErrorMsgContent) respBody.getContent(); throw new CmpRequestorException(SecurityUtil.formatPKIStatusInfo(content.getPKIStatusInfo())); } else if (PKIBody.TYPE_GEN_REP != bodyType) { throw new CmpRequestorException("unknown PKI body type " + bodyType + " instead the exceptected [" + PKIBody.TYPE_GEN_REP + ", " + PKIBody.TYPE_ERROR + "]"); } GenRepContent genRep = (GenRepContent) respBody.getContent(); InfoTypeAndValue[] itvs = genRep.toInfoTypeAndValueArray(); InfoTypeAndValue itv = null; if (itvs != null && itvs.length > 0) { for (InfoTypeAndValue _itv : itvs) { if (exepectedType.equals(_itv.getInfoType().getId())) { itv = _itv; break; } } } if (itv == null) { throw new CmpRequestorException("the response does not contain InfoTypeAndValue " + exepectedType); } return itv.getInfoValue(); } protected PKIHeader buildPKIHeader(final ASN1OctetString tid) { return buildPKIHeader(false, tid, null, (InfoTypeAndValue[]) null); } protected PKIHeader buildPKIHeader(final ASN1OctetString tid, final String username) { return buildPKIHeader(false, tid, username); } protected PKIHeader buildPKIHeader(final boolean addImplictConfirm, final ASN1OctetString tid, final String username) { CmpUtf8Pairs utf8Pairs = null; if (StringUtil.isNotBlank(username)) { utf8Pairs = new CmpUtf8Pairs(CmpUtf8Pairs.KEY_USER, username); } return buildPKIHeader(addImplictConfirm, tid, utf8Pairs, (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 unpermitted ITV implicitConfirm"); } if (CMPObjectIdentifiers.regInfo_utf8Pairs.equals(type)) { throw new IllegalArgumentException( "" + "additionGeneralInfos contains unpermitted ITV utf8Pairs"); } } } PKIHeaderBuilder hBuilder = new PKIHeaderBuilder(PKIHeader.CMP_2000, sender, recipient != null ? recipient : DUMMY_RECIPIENT); hBuilder.setMessageTime(new ASN1GeneralizedTime(new Date())); ASN1OctetString _tid; if (tid == null) { _tid = new DEROctetString(randomTransactionId()); } else { _tid = tid; } hBuilder.setTransactionID(_tid); 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.isNotEmpty(itvs)) { hBuilder.setGeneralInfo(itvs.toArray(new InfoTypeAndValue[0])); } return hBuilder.build(); } protected PKIErrorException buildErrorResult(final ErrorMsgContent bodyContent) { org.xipki.ca.common.cmp.PKIStatusInfo statusInfo = new org.xipki.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, final X509Certificate cert) throws CMPException, InvalidKeyException, OperatorCreationException { ProtectedPKIMessage pMsg = new ProtectedPKIMessage(pkiMessage); if (pMsg.hasPasswordBasedMacProtection()) { LOG.warn("NOT_SIGNAUTRE_BASED: " + pkiMessage.getHeader().getProtectionAlg().getAlgorithm().getId()); return new ProtectionVerificationResult(null, ProtectionResult.NOT_SIGNATURE_BASED); } PKIHeader h = pMsg.getHeader(); if (c14nRecipientName != null) { boolean authorizedResponder = true; if (h.getSender().getTagNo() != GeneralName.directoryName) { authorizedResponder = false; } else { String c14nMsgSender = getSortedRFC4519Name((X500Name) h.getSender().getName()); authorizedResponder = c14nRecipientName.equalsIgnoreCase(c14nMsgSender); } if (authorizedResponder == false) { LOG.warn("tid={}: not authorized responder '{}'", tid, h.getSender()); return new ProtectionVerificationResult(null, ProtectionResult.SENDER_NOT_AUTHORIZED); } } ContentVerifierProvider verifierProvider = securityFactory.getContentVerifierProvider(cert); if (verifierProvider == null) { LOG.warn("tid={}: not authorized responder '{}'", tid, h.getSender()); return new ProtectionVerificationResult(cert, ProtectionResult.SENDER_NOT_AUTHORIZED); } boolean signatureValid = pMsg.verify(verifierProvider); return new ProtectionVerificationResult(cert, signatureValid ? ProtectionResult.VALID : ProtectionResult.INVALID); } protected PKIMessage buildMessageWithXipkAction(final int action, final ASN1Encodable value) throws CmpRequestorException { PKIHeader header = buildPKIHeader(null); ASN1EncodableVector v = new ASN1EncodableVector(); v.add(new ASN1Integer(action)); if (value != null) { v.add(value); } InfoTypeAndValue itv = new InfoTypeAndValue(ObjectIdentifiers.id_xipki_cmp, new DERSequence(v)); 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 { PKIHeader header = buildPKIHeader(null); InfoTypeAndValue itv; if (value != null) { itv = new InfoTypeAndValue(type, value); } else { itv = 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 { ProtectionVerificationResult protectionVerificationResult = response.getProtectionVerificationResult(); if (response.hasProtection()) { 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; } private static String getSortedRFC4519Name(final X500Name name) { return X509Util.getRFC4519Name(X509Util.sortX509Name(name)); } }