
org.xipki.ca.server.impl.cmp.CmpResponder Maven / Gradle / Ivy
/*
*
* Copyright (c) 2013 - 2017 Lijun Liao
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xipki.ca.server.impl.cmp;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.Date;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.cmp.ErrorMsgContent;
import org.bouncycastle.asn1.cmp.PKIBody;
import org.bouncycastle.asn1.cmp.PKIFailureInfo;
import org.bouncycastle.asn1.cmp.PKIFreeText;
import org.bouncycastle.asn1.cmp.PKIHeader;
import org.bouncycastle.asn1.cmp.PKIHeaderBuilder;
import org.bouncycastle.asn1.cmp.PKIMessage;
import org.bouncycastle.asn1.cmp.PKIStatus;
import org.bouncycastle.asn1.cmp.PKIStatusInfo;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.audit.AuditEvent;
import org.xipki.audit.AuditLevel;
import org.xipki.audit.AuditStatus;
import org.xipki.ca.server.impl.CaAuditConstants;
import org.xipki.ca.server.mgmt.api.CmpControl;
import org.xipki.ca.server.mgmt.api.RequestorInfo;
import org.xipki.cmp.CmpUtil;
import org.xipki.cmp.ProtectionResult;
import org.xipki.cmp.ProtectionVerificationResult;
import org.xipki.common.util.Base64;
import org.xipki.common.util.LogUtil;
import org.xipki.common.util.ParamUtil;
import org.xipki.common.util.RandomUtil;
import org.xipki.security.ConcurrentContentSigner;
import org.xipki.security.SecurityFactory;
import org.xipki.security.util.X509Util;
/**
* @author Lijun Liao
* @since 2.0.0
*/
abstract class CmpResponder {
private static final Logger LOG = LoggerFactory.getLogger(CmpResponder.class);
private static final int PVNO_CMP2000 = 2;
protected final SecurityFactory securityFactory;
private final SecureRandom random = new SecureRandom();
protected CmpResponder(final SecurityFactory securityFactory) {
this.securityFactory = ParamUtil.requireNonNull("securityFactory", securityFactory);
}
protected abstract ConcurrentContentSigner getSigner();
protected abstract GeneralName getSender();
protected abstract boolean intendsMe(GeneralName requestRecipient);
public boolean isOnService() {
try {
return getSigner() != null;
} catch (Exception ex) {
LogUtil.error(LOG, ex, "could not get responder signer");
return false;
}
}
/**
* @return never returns {@code null}.
*/
protected abstract CmpControl getCmpControl();
public abstract CmpRequestorInfo getRequestor(X500Name requestorSender);
public abstract CmpRequestorInfo getRequestor(X509Certificate requestorCert);
private CmpRequestorInfo getRequestor(final PKIHeader reqHeader) {
GeneralName requestSender = reqHeader.getSender();
if (requestSender.getTagNo() != GeneralName.directoryName) {
return null;
}
return getRequestor((X500Name) requestSender.getName());
} // method getRequestor
/**
* Processes the request and returns the response.
* @param request
* Original request. Will only be used for the storage. Could be{@code null}.
* @param requestor
* Requestor. Must not be {@code null}.
* @param transactionId
* Transaction id. Must not be {@code null}.
* @param pkiMessage
* PKI message. Must not be {@code null}.
* @param msgId
* Message id. Must not be {@code null}.
* @param event
* Audit event. Must not be {@code null}.
* @return the response
*/
protected abstract PKIMessage processPkiMessage0(PKIMessage request,
RequestorInfo requestor, ASN1OctetString transactionId,
GeneralPKIMessage pkiMessage, String msgId, AuditEvent event);
public PKIMessage processPkiMessage(final PKIMessage pkiMessage,
final X509Certificate tlsClientCert, final AuditEvent event) {
ParamUtil.requireNonNull("pkiMessage", pkiMessage);
ParamUtil.requireNonNull("event", event);
GeneralPKIMessage message = new GeneralPKIMessage(pkiMessage);
PKIHeader reqHeader = message.getHeader();
ASN1OctetString tid = reqHeader.getTransactionID();
String msgId = null;
if (event != null) {
msgId = RandomUtil.nextHexLong();
event.addEventData(CaAuditConstants.NAME_mid, msgId);
}
if (tid == null) {
byte[] randomBytes = randomTransactionId();
tid = new DEROctetString(randomBytes);
}
String tidStr = Base64.encodeToString(tid.getOctets());
event.addEventData(CaAuditConstants.NAME_tid, tidStr);
int reqPvno = reqHeader.getPvno().getValue().intValue();
if (reqPvno != PVNO_CMP2000) {
if (event != null) {
event.setLevel(AuditLevel.INFO);
event.setStatus(AuditStatus.FAILED);
event.addEventData(CaAuditConstants.NAME_message, "unsupproted version " + reqPvno);
}
return buildErrorPkiMessage(tid, reqHeader, PKIFailureInfo.unsupportedVersion, null);
}
CmpControl cmpControl = getCmpControl();
Integer failureCode = null;
String statusText = null;
Date messageTime = null;
if (reqHeader.getMessageTime() != null) {
try {
messageTime = reqHeader.getMessageTime().getDate();
} catch (ParseException ex) {
LogUtil.error(LOG, ex, "tid=" + tidStr + ": could not parse messageTime");
}
}
GeneralName recipient = reqHeader.getRecipient();
boolean intentMe = (recipient == null) ? true : intendsMe(recipient);
if (!intentMe) {
LOG.warn("tid={}: I am not the intended recipient, but '{}'", tid,
reqHeader.getRecipient());
failureCode = PKIFailureInfo.badRequest;
statusText = "I am not the intended recipient";
} else if (messageTime == null) {
if (cmpControl.isMessageTimeRequired()) {
failureCode = PKIFailureInfo.missingTimeStamp;
statusText = "missing time-stamp";
}
} else {
long messageTimeBias = cmpControl.messageTimeBias();
if (messageTimeBias < 0) {
messageTimeBias *= -1;
}
long msgTimeMs = messageTime.getTime();
long currentTimeMs = System.currentTimeMillis();
long bias = (msgTimeMs - currentTimeMs) / 1000L;
if (bias > messageTimeBias) {
failureCode = PKIFailureInfo.badTime;
statusText = "message time is in the future";
} else if (bias * -1 > messageTimeBias) {
failureCode = PKIFailureInfo.badTime;
statusText = "message too old";
}
}
if (failureCode != null) {
if (event != null) {
event.setLevel(AuditLevel.INFO);
event.setStatus(AuditStatus.FAILED);
event.addEventData(CaAuditConstants.NAME_message, statusText);
}
return buildErrorPkiMessage(tid, reqHeader, failureCode, statusText);
}
boolean isProtected = message.hasProtection();
CmpRequestorInfo requestor;
String errorStatus;
if (isProtected) {
try {
ProtectionVerificationResult verificationResult = verifyProtection(tidStr,
message, cmpControl);
ProtectionResult pr = verificationResult.protectionResult();
switch (pr) {
case VALID:
errorStatus = null;
break;
case INVALID:
errorStatus = "request is protected by signature but invalid";
break;
case NOT_SIGNATURE_BASED:
errorStatus = "request is not protected by signature";
break;
case SENDER_NOT_AUTHORIZED:
errorStatus =
"request is protected by signature but the requestor is not authorized";
break;
case SIGALGO_FORBIDDEN:
errorStatus = "request is protected by signature but the protection algorithm"
+ " is forbidden";
break;
default:
throw new RuntimeException(
"should not reach here, unknown ProtectionResult " + pr);
} // end switch
requestor = (CmpRequestorInfo) verificationResult.requestor();
} catch (Exception ex) {
LogUtil.error(LOG, ex, "tid=" + tidStr + ": could not verify the signature");
errorStatus = "request has invalid signature based protection";
requestor = null;
}
} else if (tlsClientCert != null) {
boolean authorized = false;
requestor = getRequestor(reqHeader);
if (requestor != null) {
if (tlsClientCert.equals(requestor.cert().cert())) {
authorized = true;
}
}
if (authorized) {
errorStatus = null;
} else {
LOG.warn("tid={}: not authorized requestor (TLS client '{}')", tid,
X509Util.getRfc4519Name(tlsClientCert.getSubjectX500Principal()));
errorStatus = "requestor (TLS client certificate) is not authorized";
}
} else {
errorStatus = "request has no protection";
requestor = null;
}
if (errorStatus != null) {
if (event != null) {
event.setLevel(AuditLevel.INFO);
event.setStatus(AuditStatus.FAILED);
event.addEventData(CaAuditConstants.NAME_message, errorStatus);
}
return buildErrorPkiMessage(tid, reqHeader, PKIFailureInfo.badMessageCheck,
errorStatus);
}
PKIMessage resp = processPkiMessage0(pkiMessage, requestor, tid, message, msgId, event);
if (isProtected) {
resp = addProtection(resp, event);
} else {
// protected by TLS connection
}
return resp;
} // method processPkiMessage
protected byte[] randomTransactionId() {
byte[] bytes = new byte[10];
random.nextBytes(bytes);
return bytes;
}
private ProtectionVerificationResult verifyProtection(final String tid,
final GeneralPKIMessage pkiMessage, final CmpControl cmpControl)
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();
AlgorithmIdentifier protectionAlg = header.getProtectionAlg();
if (!cmpControl.sigAlgoValidator().isAlgorithmPermitted(protectionAlg)) {
LOG.warn("SIG_ALGO_FORBIDDEN: {}",
pkiMessage.getHeader().getProtectionAlg().getAlgorithm().getId());
return new ProtectionVerificationResult(null, ProtectionResult.SIGALGO_FORBIDDEN);
}
CmpRequestorInfo requestor = getRequestor(header);
if (requestor == null) {
LOG.warn("tid={}: not authorized requestor '{}'", tid, header.getSender());
return new ProtectionVerificationResult(null, ProtectionResult.SENDER_NOT_AUTHORIZED);
}
ContentVerifierProvider verifierProvider = securityFactory.getContentVerifierProvider(
requestor.cert().cert());
if (verifierProvider == null) {
LOG.warn("tid={}: not authorized requestor '{}'", tid, header.getSender());
return new ProtectionVerificationResult(requestor,
ProtectionResult.SENDER_NOT_AUTHORIZED);
}
boolean signatureValid = protectedMsg.verify(verifierProvider);
return new ProtectionVerificationResult(requestor,
signatureValid ? ProtectionResult.VALID : ProtectionResult.INVALID);
} // method verifyProtection
private PKIMessage addProtection(final PKIMessage pkiMessage, final AuditEvent event) {
try {
return CmpUtil.addProtection(pkiMessage, getSigner(), getSender(),
getCmpControl().isSendResponderCert());
} catch (Exception ex) {
LogUtil.error(LOG, ex, "could not add protection to the PKI message");
PKIStatusInfo status = generateRejectionStatus(
PKIFailureInfo.systemFailure, "could not sign the PKIMessage");
event.setLevel(AuditLevel.ERROR);
event.setStatus(AuditStatus.FAILED);
event.addEventData(CaAuditConstants.NAME_message, "could not sign the PKIMessage");
PKIBody body = new PKIBody(PKIBody.TYPE_ERROR, new ErrorMsgContent(status));
return new PKIMessage(pkiMessage.getHeader(), body);
}
} // method addProtection
protected PKIMessage buildErrorPkiMessage(final ASN1OctetString tid,
final PKIHeader requestHeader, final int failureCode, final String statusText) {
GeneralName respRecipient = requestHeader.getSender();
PKIHeaderBuilder respHeader = new PKIHeaderBuilder(
requestHeader.getPvno().getValue().intValue(), getSender(), respRecipient);
respHeader.setMessageTime(new ASN1GeneralizedTime(new Date()));
if (tid != null) {
respHeader.setTransactionID(tid);
}
ASN1OctetString senderNonce = requestHeader.getSenderNonce();
if (senderNonce != null) {
respHeader.setRecipNonce(senderNonce);
}
PKIStatusInfo status = generateRejectionStatus(failureCode, statusText);
ErrorMsgContent error = new ErrorMsgContent(status);
PKIBody body = new PKIBody(PKIBody.TYPE_ERROR, error);
return new PKIMessage(respHeader.build(), body);
} // method buildErrorPkiMessage
protected PKIStatusInfo generateRejectionStatus(final Integer info,
final String errorMessage) {
return generateRejectionStatus(PKIStatus.rejection, info, errorMessage);
} // method generateCmpRejectionStatus
protected PKIStatusInfo generateRejectionStatus(final PKIStatus status, final Integer info,
final String errorMessage) {
PKIFreeText statusMessage = (errorMessage == null) ? null : new PKIFreeText(errorMessage);
PKIFailureInfo failureInfo = (info == null) ? null : new PKIFailureInfo(info);
return new PKIStatusInfo(status, statusMessage, failureInfo);
} // method generateCmpRejectionStatus
public X500Name getResponderSubject() {
GeneralName sender = getSender();
return (sender == null) ? null : (X500Name) sender.getName();
}
public X509Certificate getResponderCert() {
ConcurrentContentSigner signer = getSigner();
return (signer == null) ? null : signer.getCertificate();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy