/*
*
* 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.ocsp.client.shell;
import java.math.BigInteger;
import java.net.URL;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.karaf.shell.api.action.Completion;
import org.apache.karaf.shell.api.action.Option;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.cert.AttributeCertificateIssuer;
import org.bouncycastle.cert.X509AttributeCertificateHolder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.xipki.commons.common.RequestResponseDebug;
import org.xipki.commons.common.RequestResponsePair;
import org.xipki.commons.common.util.BigIntegerRange;
import org.xipki.commons.common.util.CollectionUtil;
import org.xipki.commons.common.util.IoUtil;
import org.xipki.commons.common.util.StringUtil;
import org.xipki.commons.console.karaf.IllegalCmdParamException;
import org.xipki.commons.console.karaf.completer.FilePathCompleter;
import org.xipki.commons.security.HashAlgoType;
import org.xipki.commons.security.IssuerHash;
import org.xipki.commons.security.ObjectIdentifiers;
import org.xipki.commons.security.util.X509Util;
import org.xipki.pki.ocsp.client.api.RequestOptions;
/**
* @author Lijun Liao
* @since 2.0.0
*/
public abstract class BaseOcspStatusCommandSupport extends OcspStatusCommandSupport {
protected static final Map<ASN1ObjectIdentifier, String> EXTENSION_OIDNAME_MAP
= new HashMap<>();
@Option(name = "--verbose", aliases = "-v",
description = "show status verbosely")
protected Boolean verbose = Boolean.FALSE;
@Option(name = "--resp-issuer",
description = "certificate file of the responder's issuer")
@Completion(FilePathCompleter.class)
private String respIssuerFile;
@Option(name = "--url",
description = "OCSP responder URL")
private String serverUrl;
@Option(name = "--req-out",
description = "where to save the request")
@Completion(FilePathCompleter.class)
private String reqout;
@Option(name = "--resp-out",
description = "where to save the response")
@Completion(FilePathCompleter.class)
private String respout;
@Option(name = "--hex",
description = "serial number without prefix is hex number")
private Boolean hex = Boolean.FALSE;
@Option(name = "--serial", aliases = "-s",
description = "comma-separated serial numbers or ranges (like 1,3,6-10)\n"
+ "(at least one of serial and cert must be specified)")
private String serialNumberList;
@Option(name = "--cert", aliases = "-c",
multiValued = true,
description = "certificate\n"
+ "(multi-valued)")
@Completion(FilePathCompleter.class)
private List<String> certFiles;
@Option(name = "--ac",
description = "the certificates are attribute certificates")
@Completion(FilePathCompleter.class)
private Boolean isAttrCert = Boolean.FALSE;
static {
EXTENSION_OIDNAME_MAP.put(OCSPObjectIdentifiers.id_pkix_ocsp_archive_cutoff,
"ArchiveCutoff");
EXTENSION_OIDNAME_MAP.put(OCSPObjectIdentifiers.id_pkix_ocsp_crl, "CrlID");
EXTENSION_OIDNAME_MAP.put(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, "Nonce");
EXTENSION_OIDNAME_MAP.put(ObjectIdentifiers.id_pkix_ocsp_extendedRevoke, "ExtendedRevoke");
}
protected abstract void checkParameters(@Nullable X509Certificate respIssuer,
@NonNull List<BigInteger> serialNumbers, @Nullable Map<BigInteger, byte[]> encodedCerts)
throws Exception;
protected abstract Object processResponse(@NonNull OCSPResp response,
@Nullable X509Certificate respIssuer, @NonNull IssuerHash issuerHash,
@NonNull List<BigInteger> serialNumbers, @Nullable Map<BigInteger, byte[]> encodedCerts)
throws Exception;
@Override
protected final Object doExecute() throws Exception {
if (StringUtil.isBlank(serialNumberList) && isEmpty(certFiles)) {
throw new IllegalCmdParamException("Neither serialNumbers nor certFiles is set");
}
X509Certificate issuerCert = X509Util.parseCert(issuerCertFile);
Map<BigInteger, byte[]> encodedCerts = null;
List<BigInteger> sns = new LinkedList<>();
if (isNotEmpty(certFiles)) {
encodedCerts = new HashMap<>(certFiles.size());
String ocspUrl = null;
X500Name issuerX500Name = null;
if (isAttrCert) {
issuerX500Name = X500Name.getInstance(
issuerCert.getSubjectX500Principal().getEncoded());
}
for (String certFile : certFiles) {
BigInteger sn;
List<String> ocspUrls;
if (isAttrCert) {
X509AttributeCertificateHolder cert =
new X509AttributeCertificateHolder(IoUtil.read(certFile));
// no signature validation
AttributeCertificateIssuer reqIssuer = cert.getIssuer();
if (reqIssuer != null && issuerX500Name != null) {
X500Name reqIssuerName = reqIssuer.getNames()[0];
if (!issuerX500Name.equals(reqIssuerName)) {
throw new IllegalCmdParamException("certificate " + certFile
+ " is not issued by the given issuer");
}
}
ocspUrls = extractOcspUrls(cert);
sn = cert.getSerialNumber();
} else {
X509Certificate cert = X509Util.parseCert(certFile);
if (!X509Util.issues(issuerCert, cert)) {
throw new IllegalCmdParamException(
"certificate " + certFile + " is not issued by the given issuer");
}
ocspUrls = extractOcspUrls(cert);
sn = cert.getSerialNumber();
}
if (isBlank(serverUrl)) {
if (CollectionUtil.isEmpty(ocspUrls)) {
throw new IllegalCmdParamException("could not extract OCSP responder URL");
} else {
String url = ocspUrls.get(0);
if (ocspUrl != null && !ocspUrl.equals(url)) {
throw new IllegalCmdParamException("given certificates have different"
+ " OCSP responder URL in certificate");
} else {
ocspUrl = url;
}
}
} // end if
sns.add(sn);
byte[] encodedCert = IoUtil.read(certFile);
encodedCerts.put(sn, encodedCert);
} // end for
if (isBlank(serverUrl)) {
serverUrl = ocspUrl;
}
} else {
StringTokenizer st = new StringTokenizer(serialNumberList, ", ");
while (st.hasMoreTokens()) {
String token = st.nextToken();
StringTokenizer st2 = new StringTokenizer(token, "-");
BigInteger from = toBigInt(st2.nextToken(), hex);
BigInteger to = st2.hasMoreTokens() ? toBigInt(st2.nextToken(), hex) : null;
if (to == null) {
sns.add(from);
} else {
BigIntegerRange range = new BigIntegerRange(from, to);
if (range.getDiff().compareTo(BigInteger.valueOf(10)) > 0) {
throw new IllegalCmdParamException("to many serial numbers");
}
BigInteger sn = range.getFrom();
while (range.isInRange(sn)) {
sns.add(sn);
sn = sn.add(BigInteger.ONE);
}
}
}
}
if (isBlank(serverUrl)) {
throw new IllegalCmdParamException("could not get URL for the OCSP responder");
}
X509Certificate respIssuer = null;
if (respIssuerFile != null) {
respIssuer = X509Util.parseCert(IoUtil.expandFilepath(respIssuerFile));
}
URL serverUrlObj = new URL(serverUrl);
RequestOptions options = getRequestOptions();
checkParameters(respIssuer, sns, encodedCerts);
boolean saveReq = isNotBlank(reqout);
boolean saveResp = isNotBlank(respout);
RequestResponseDebug debug = null;
if (saveReq || saveResp) {
debug = new RequestResponseDebug();
}
IssuerHash issuerHash = new IssuerHash(
HashAlgoType.getNonNullHashAlgoType(options.getHashAlgorithmId()),
Certificate.getInstance(issuerCert.getEncoded()));
OCSPResp response;
try {
response = requestor.ask(issuerCert, sns.toArray(new BigInteger[0]), serverUrlObj,
options, debug);
} finally {
if (debug != null && debug.size() > 0) {
RequestResponsePair reqResp = debug.get(0);
if (saveReq) {
byte[] bytes = reqResp.getRequest();
if (bytes != null) {
IoUtil.save(reqout, bytes);
}
}
if (saveResp) {
byte[] bytes = reqResp.getResponse();
if (bytes != null) {
IoUtil.save(respout, bytes);
}
}
} // end if
} // end finally
return processResponse(response, respIssuer, issuerHash, sns, encodedCerts);
} // method doExecute
public static List<String> extractOcspUrls(final X509Certificate cert)
throws CertificateEncodingException {
byte[] extValue = X509Util.getCoreExtValue(cert, Extension.authorityInfoAccess);
if (extValue == null) {
return Collections.emptyList();
}
AuthorityInformationAccess aia = AuthorityInformationAccess.getInstance(extValue);
return extractOcspUrls(aia);
}
public static List<String> extractOcspUrls(final X509AttributeCertificateHolder cert)
throws CertificateEncodingException {
byte[] extValue = X509Util.getCoreExtValue(cert, Extension.authorityInfoAccess);
if (extValue == null) {
return Collections.emptyList();
}
AuthorityInformationAccess aia = AuthorityInformationAccess.getInstance(extValue);
return extractOcspUrls(aia);
}
public static List<String> extractOcspUrls(final AuthorityInformationAccess aia)
throws CertificateEncodingException {
AccessDescription[] accessDescriptions = aia.getAccessDescriptions();
List<AccessDescription> ocspAccessDescriptions = new LinkedList<>();
for (AccessDescription accessDescription : accessDescriptions) {
if (accessDescription.getAccessMethod().equals(X509ObjectIdentifiers.id_ad_ocsp)) {
ocspAccessDescriptions.add(accessDescription);
}
}
final int n = ocspAccessDescriptions.size();
List<String> ocspUris = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
GeneralName accessLocation = ocspAccessDescriptions.get(i).getAccessLocation();
if (accessLocation.getTagNo() == GeneralName.uniformResourceIdentifier) {
String ocspUri = ((ASN1String) accessLocation.getName()).getString();
ocspUris.add(ocspUri);
}
}
return ocspUris;
}
}