/*
*
* 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.commons.security;
import java.io.IOException;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.List;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.bouncycastle.asn1.crmf.POPOSigningKey;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cert.X509CRLHolder;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v2CRLBuilder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.cmp.CMPException;
import org.bouncycastle.cert.cmp.ProtectedPKIMessage;
import org.bouncycastle.cert.cmp.ProtectedPKIMessageBuilder;
import org.bouncycastle.cert.crmf.ProofOfPossessionSigningKeyBuilder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xipki.commons.common.util.LogUtil;
import org.xipki.commons.common.util.ParamUtil;
import org.xipki.commons.password.PasswordResolver;
import org.xipki.commons.security.exception.NoIdleSignerException;
import org.xipki.commons.security.exception.XiSecurityException;
import org.xipki.commons.security.util.AlgorithmUtil;
/**
* @author Lijun Liao
* @since 2.0.0
*/
public class DefaultConcurrentContentSigner implements ConcurrentContentSigner {
private static final Logger LOG = LoggerFactory.getLogger(DefaultConcurrentContentSigner.class);
private static final AtomicInteger NAME_INDEX = new AtomicInteger(1);
private static int defaultSignServiceTimeout = 10000; // 10 seconds
private final String name;
private final AlgorithmIdentifier algorithmIdentifier;
private final BlockingDeque<ContentSigner> idleSigners = new LinkedBlockingDeque<>();
private final BlockingDeque<ContentSigner> busySigners = new LinkedBlockingDeque<>();
private final PrivateKey privateKey;
private final AlgorithmCode algorithmCode;
private PublicKey publicKey;
private X509Certificate[] certificateChain;
private X509CertificateHolder[] certificateChainAsBcObjects;
static {
final String propKey = "org.xipki.commons.security.signservice.timeout";
String str = System.getProperty(propKey);
if (str != null) {
int vi = Integer.parseInt(str);
// valid value is between 0 and 60 seconds
if (vi < 0 || vi > 60 * 1000) {
LOG.error("invalid {}: {}", propKey, vi);
} else {
LOG.info("use {}: {}", propKey, vi);
defaultSignServiceTimeout = vi;
}
}
}
public DefaultConcurrentContentSigner(final List<ContentSigner> signers)
throws NoSuchAlgorithmException {
this(signers, null);
}
public DefaultConcurrentContentSigner(final List<ContentSigner> signers,
final PrivateKey privateKey) throws NoSuchAlgorithmException {
ParamUtil.requireNonEmpty("signers", signers);
this.algorithmIdentifier = signers.get(0).getAlgorithmIdentifier();
this.algorithmCode = AlgorithmUtil.getSignatureAlgorithmCode(algorithmIdentifier);
for (ContentSigner signer : signers) {
idleSigners.addLast(signer);
}
this.privateKey = privateKey;
this.name = "defaultSigner-" + NAME_INDEX.getAndIncrement();
}
public String getName() {
return name;
}
@Override
public AlgorithmCode getAlgorithmCode() {
return algorithmCode;
}
private ContentSigner borrowContentSigner() throws NoIdleSignerException {
return borrowContentSigner(defaultSignServiceTimeout);
}
/**
* @param timeout timeout in milliseconds, 0 for infinitely.
*/
private ContentSigner borrowContentSigner(final int soTimeout) throws NoIdleSignerException {
ContentSigner signer = null;
try {
signer = (soTimeout == 0) ? idleSigners.takeFirst()
: idleSigners.pollFirst(soTimeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) { // CHECKSTYLE:SKIP
}
if (signer == null) {
throw new NoIdleSignerException("no idle signer available");
}
busySigners.addLast(signer);
return signer;
}
private void returnContentSigner(final ContentSigner signer) {
ParamUtil.requireNonNull("signer", signer);
boolean isBusySigner = busySigners.remove(signer);
if (isBusySigner) {
idleSigners.addLast(signer);
} else {
final String msg =
"signer has not been borrowed before or has been returned more than once: "
+ signer;
LOG.error(msg);
throw new IllegalStateException(msg);
}
}
@Override
public void initialize(final String conf, final PasswordResolver passwordResolver)
throws XiSecurityException {
}
@Override
public PrivateKey getPrivateKey() {
return privateKey;
}
@Override
public void setCertificateChain(final X509Certificate[] certificateChain) {
if (certificateChain == null || certificateChain.length == 0) {
this.certificateChain = null;
this.certificateChainAsBcObjects = null;
return;
}
this.certificateChain = certificateChain;
setPublicKey(certificateChain[0].getPublicKey());
final int n = certificateChain.length;
this.certificateChainAsBcObjects = new X509CertificateHolder[n];
for (int i = 0; i < n; i++) {
X509Certificate cert = this.certificateChain[i];
try {
this.certificateChainAsBcObjects[i] = new X509CertificateHolder(cert.getEncoded());
} catch (CertificateEncodingException | IOException ex) {
throw new IllegalArgumentException(
String.format("%s occurred while parsing certificate at index %d: %s",
ex.getClass().getName(), i, ex.getMessage()), ex);
}
}
}
@Override
public PublicKey getPublicKey() {
return publicKey;
}
@Override
public void setPublicKey(final PublicKey publicKey) {
this.publicKey = publicKey;
}
@Override
public X509Certificate getCertificate() {
return (certificateChain != null && certificateChain.length > 0)
? certificateChain[0] : null;
}
@Override
public X509CertificateHolder getCertificateAsBcObject() {
return (certificateChainAsBcObjects != null && certificateChainAsBcObjects.length > 0)
? certificateChainAsBcObjects[0] : null;
}
@Override
public X509Certificate[] getCertificateChain() {
return certificateChain;
}
@Override
public X509CertificateHolder[] getCertificateChainAsBcObjects() {
return certificateChainAsBcObjects;
}
@Override
public boolean isHealthy() {
ContentSigner signer = null;
try {
signer = borrowContentSigner();
OutputStream stream = signer.getOutputStream();
stream.write(new byte[]{1, 2, 3, 4});
byte[] signature = signer.getSignature();
return signature != null && signature.length > 0;
} catch (Exception ex) {
LogUtil.error(LOG, ex);
return false;
} finally {
if (signer != null) {
returnContentSigner(signer);
}
}
}
@Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return algorithmIdentifier;
}
@Override
public void shutdown() {
}
@Override
public POPOSigningKey build(final ProofOfPossessionSigningKeyBuilder builder)
throws NoIdleSignerException {
ContentSigner contentSigner = borrowContentSigner();
try {
return builder.build(contentSigner);
} finally {
returnContentSigner(contentSigner);
}
}
@Override
public ProtectedPKIMessage build(final ProtectedPKIMessageBuilder builder)
throws NoIdleSignerException, CMPException {
ContentSigner contentSigner = borrowContentSigner();
try {
return builder.build(contentSigner);
} finally {
returnContentSigner(contentSigner);
}
}
@Override
public X509CRLHolder build(final X509v2CRLBuilder builder) throws NoIdleSignerException {
ContentSigner contentSigner = borrowContentSigner();
try {
return builder.build(contentSigner);
} finally {
returnContentSigner(contentSigner);
}
}
@Override
public X509CertificateHolder build(final X509v3CertificateBuilder builder)
throws NoIdleSignerException {
ContentSigner contentSigner = borrowContentSigner();
try {
return builder.build(contentSigner);
} finally {
returnContentSigner(contentSigner);
}
}
@Override
public OCSPReq build(final OCSPReqBuilder builder, final X509CertificateHolder[] chain)
throws NoIdleSignerException, OCSPException {
ContentSigner contentSigner = borrowContentSigner();
try {
return builder.build(contentSigner, chain);
} finally {
returnContentSigner(contentSigner);
}
}
@Override
public BasicOCSPResp build(final BasicOCSPRespBuilder builder,
final X509CertificateHolder[] chain, final Date producedAt)
throws NoIdleSignerException, OCSPException {
ContentSigner contentSigner = borrowContentSigner();
try {
return builder.build(contentSigner, chain, producedAt);
} finally {
returnContentSigner(contentSigner);
}
}
@Override
public PKCS10CertificationRequest build(final PKCS10CertificationRequestBuilder builder)
throws NoIdleSignerException {
ContentSigner contentSigner = borrowContentSigner();
try {
return builder.build(contentSigner);
} finally {
returnContentSigner(contentSigner);
}
}
@Override
public byte[] sign(final byte[] data) throws NoIdleSignerException, IOException {
ContentSigner contentSigner = borrowContentSigner();
try {
OutputStream signatureStream = contentSigner.getOutputStream();
signatureStream.write(data);
return contentSigner.getSignature();
} finally {
returnContentSigner(contentSigner);
}
}
}