Skip to content
This repository was archived by the owner on Apr 22, 2025. It is now read-only.

Commit 599fe3d

Browse files
Performance improvement for CryptoPrimitives (#214)
(cherry picked from commit 560aa5b) Signed-off-by: Simon Greatrix <simon.greatrix@setl.io> Co-authored-by: Simon Greatrix <simon@pippsford.com>
1 parent 0e4098d commit 599fe3d

File tree

1 file changed

+195
-53
lines changed

1 file changed

+195
-53
lines changed

src/main/java/org/hyperledger/fabric/sdk/security/CryptoPrimitives.java

100755100644
Lines changed: 195 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,15 @@
5555
import java.util.Properties;
5656
import java.util.Set;
5757
import java.util.concurrent.ConcurrentHashMap;
58+
import java.util.concurrent.ExecutionException;
59+
import java.util.concurrent.TimeUnit;
5860
import java.util.concurrent.atomic.AtomicBoolean;
5961
import javax.security.auth.x500.X500Principal;
6062
import javax.xml.bind.DatatypeConverter;
6163

64+
import com.google.common.cache.CacheBuilder;
65+
import com.google.common.cache.CacheLoader;
66+
import com.google.common.cache.LoadingCache;
6267
import org.apache.commons.io.FileUtils;
6368
import org.apache.commons.logging.Log;
6469
import org.apache.commons.logging.LogFactory;
@@ -80,7 +85,6 @@
8085
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
8186
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
8287
import org.bouncycastle.operator.ContentSigner;
83-
import org.bouncycastle.operator.OperatorCreationException;
8488
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
8589
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
8690
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
@@ -97,6 +101,7 @@
97101

98102
public class CryptoPrimitives implements CryptoSuite {
99103
private static final Log logger = LogFactory.getLog(CryptoPrimitives.class);
104+
private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
100105
private static final Config config = Config.getConfig();
101106
private static final boolean IS_TRACE_LEVEL = logger.isTraceEnabled();
102107

@@ -144,16 +149,10 @@ Provider setUpExplicitProvider(String securityProviderClassName) throws Instanti
144149
}
145150

146151
Class<?> aClass = Class.forName(securityProviderClassName);
147-
if (null == aClass) {
148-
throw new InstantiationException(format("Getting class for security provider %s returned null ", securityProviderClassName));
149-
}
150152
if (!Provider.class.isAssignableFrom(aClass)) {
151153
throw new InstantiationException(format("Class for security provider %s is not a Java security provider", aClass.getName()));
152154
}
153155
Provider securityProvider = (Provider) aClass.newInstance();
154-
if (securityProvider == null) {
155-
throw new InstantiationException(format("Creating instance of security %s returned null ", aClass.getName()));
156-
}
157156
return securityProvider;
158157
}
159158

@@ -207,61 +206,51 @@ public Certificate bytesToCertificate(byte[] certBytes) throws CryptoException {
207206
* @param pemCertificate
208207
* @return
209208
*/
210-
211-
private X509Certificate getX509Certificate(byte[] pemCertificate) throws CryptoException {
209+
private X509Certificate getX509CertificateInternal(byte[] pemCertificate) throws CryptoException {
212210
X509Certificate ret = null;
213211
CryptoException rete = null;
214212

215213
List<Provider> providerList = new LinkedList<>(Arrays.asList(Security.getProviders()));
216-
if (SECURITY_PROVIDER != null) { //Add if overridden
214+
if (SECURITY_PROVIDER != null) {
215+
// Add if overridden. Note it is added to the end of the provider list so is only invoked if all other providers fail.
217216
providerList.add(SECURITY_PROVIDER);
218217
}
219-
try {
220-
providerList.add(BouncyCastleProvider.class.newInstance()); // bouncy castle is there always.
221-
} catch (Exception e) {
222-
logger.warn(e);
223218

224-
}
219+
providerList.add(BOUNCY_CASTLE_PROVIDER);
220+
225221
for (Provider provider : providerList) {
226222
try {
227223
if (null == provider) {
228-
continue;
224+
continue;
229225
}
230226
CertificateFactory certFactory = CertificateFactory.getInstance(CERTIFICATE_FORMAT, provider);
231-
if (null != certFactory) {
232-
try (ByteArrayInputStream bis = new ByteArrayInputStream(pemCertificate)) {
233-
Certificate certificate = certFactory.generateCertificate(bis);
234-
235-
if (certificate instanceof X509Certificate) {
236-
ret = (X509Certificate) certificate;
237-
rete = null;
238-
break;
239-
}
240-
}
227+
try (ByteArrayInputStream bis = new ByteArrayInputStream(pemCertificate)) {
228+
Certificate certificate = certFactory.generateCertificate(bis);
241229

230+
if (certificate instanceof X509Certificate) {
231+
ret = (X509Certificate) certificate;
232+
rete = null;
233+
break;
234+
}
242235
}
243236
} catch (Exception e) {
244-
245237
rete = new CryptoException(e.getMessage(), e);
246-
247238
}
248-
249239
}
250240

251241
if (null != rete) {
252-
253242
throw rete;
254-
255243
}
256244

257245
if (ret == null) {
258-
259246
logger.error("Could not convert pem bytes");
260-
261247
}
262248

263249
return ret;
250+
}
264251

252+
private X509Certificate getX509Certificate(byte[] pemCertificate) throws CryptoException {
253+
return getCertificateValue(pemCertificate).getX509();
265254
}
266255

267256
/**
@@ -309,20 +298,14 @@ public boolean verify(byte[] pemCertificate, String signatureAlgorithm, byte[] s
309298
}
310299
}
311300

312-
try {
313-
314-
X509Certificate certificate = getX509Certificate(pemCertificate);
301+
X509Certificate certificate = getValidCertificate(pemCertificate);
315302

303+
try {
316304
if (certificate != null) {
317-
318-
isVerified = validateCertificate(certificate);
319-
if (isVerified) { // only proceed if cert is trusted
320-
321-
Signature sig = Signature.getInstance(signatureAlgorithm);
322-
sig.initVerify(certificate);
323-
sig.update(plainText);
324-
isVerified = sig.verify(signature);
325-
}
305+
Signature sig = Signature.getInstance(signatureAlgorithm);
306+
sig.initVerify(certificate);
307+
sig.update(plainText);
308+
isVerified = sig.verify(signature);
326309
}
327310
} catch (InvalidKeyException e) {
328311
CryptoException ex = new CryptoException("Cannot verify signature. Error is: "
@@ -570,14 +553,8 @@ boolean validateCertificate(byte[] certPEM) {
570553
}
571554

572555
try {
573-
574-
X509Certificate certificate = getX509Certificate(certPEM);
575-
if (null == certificate) {
576-
throw new Exception("Certificate transformation returned null");
577-
}
578-
579-
return validateCertificate(certificate);
580-
} catch (Exception e) {
556+
return x509Cache.get(new CertKey(certPEM)).isValid();
557+
} catch (ExecutionException | CryptoException e) {
581558
logger.error("Cannot validate certificate. Error is: " + e.getMessage() + "\r\nCertificate (PEM, hex): "
582559
+ DatatypeConverter.printHexBinary(certPEM));
583560
return false;
@@ -1007,4 +984,169 @@ public byte[] certificateToDER(String certificatePEM) {
1007984
return content;
1008985
}
1009986

987+
988+
private static final long X509_RECHECK_MILLIS = TimeUnit.MINUTES.toMillis(30);
989+
990+
/**
991+
* Cache for key for X.509 certificates.
992+
*/
993+
private static class CertKey {
994+
/** Pre-generated hash code for performance. */
995+
final int hashCode;
996+
997+
/** The raw PEM data. */
998+
final byte[] pemData;
999+
1000+
1001+
/**
1002+
* New instance.
1003+
*
1004+
* @param pemData the PEM data
1005+
*/
1006+
CertKey(byte[] pemData) {
1007+
// defensive copy
1008+
this.pemData = pemData.clone();
1009+
1010+
// pre-calculate the hash code
1011+
hashCode = Arrays.hashCode(pemData);
1012+
}
1013+
1014+
1015+
@Override
1016+
public boolean equals(Object o) {
1017+
if (this == o) {
1018+
return true;
1019+
}
1020+
if (!(o instanceof CertKey)) {
1021+
return false;
1022+
}
1023+
CertKey certKey = (CertKey) o;
1024+
return hashCode == certKey.hashCode && Arrays.equals(pemData, certKey.pemData);
1025+
}
1026+
1027+
1028+
@Override
1029+
public int hashCode() {
1030+
return hashCode;
1031+
}
1032+
1033+
}
1034+
1035+
1036+
/** A cached X.509 certificate. */
1037+
private class CertValue {
1038+
/** Link back to the cache key. */
1039+
final CertKey key;
1040+
1041+
/** Indicator if the X.509 certificate is currently valid. Note: it may become valid at some point in the future. */
1042+
boolean isValid;
1043+
1044+
/** The next time this certificate will be checked, in milliseconds since the epoch. */
1045+
long nextCheckTime;
1046+
1047+
/** The certificate derived from the PEM data. */
1048+
X509Certificate x509;
1049+
1050+
1051+
/**
1052+
* New instance.
1053+
*
1054+
* @param key the cache key
1055+
*/
1056+
CertValue(CertKey key) {
1057+
this.key = key;
1058+
x509 = null;
1059+
isValid = false;
1060+
nextCheckTime = Long.MIN_VALUE;
1061+
}
1062+
1063+
1064+
/**
1065+
* Get the certificate if it is valid.
1066+
*
1067+
* @return the certificate, or null
1068+
*
1069+
* @throws CryptoException if the certificate cannot be parsed.
1070+
*/
1071+
public synchronized X509Certificate getValid() throws CryptoException {
1072+
return isValid() ? getX509() : null;
1073+
}
1074+
1075+
1076+
/**
1077+
* Get the X.509 certificate.
1078+
*
1079+
* @return the certificate
1080+
*
1081+
* @throws CryptoException if the PEM data cannot be parsed.
1082+
*/
1083+
public synchronized X509Certificate getX509() throws CryptoException {
1084+
if (x509 == null) {
1085+
// If the security providers change then something that could not be parsed could become parsable.
1086+
try {
1087+
x509 = getX509CertificateInternal(key.pemData);
1088+
} catch (CryptoException e) {
1089+
isValid = false;
1090+
nextCheckTime = System.currentTimeMillis() + X509_RECHECK_MILLIS;
1091+
logger.error("Unable to recover X.509 certificate from provided binary data", e);
1092+
throw e;
1093+
}
1094+
}
1095+
return x509;
1096+
}
1097+
1098+
1099+
/**
1100+
* Is the X.509 certificate currently valid?
1101+
*
1102+
* @return true if the certificate is valid
1103+
*
1104+
* @throws CryptoException if the certificate cannot be validated
1105+
*/
1106+
public synchronized boolean isValid() throws CryptoException {
1107+
// Only re-check if a reasonable amount of time has passed.
1108+
long now = System.currentTimeMillis();
1109+
if (nextCheckTime >= now) {
1110+
return isValid;
1111+
}
1112+
1113+
nextCheckTime = now + X509_RECHECK_MILLIS;
1114+
isValid = validateCertificate(getX509());
1115+
return isValid;
1116+
}
1117+
1118+
}
1119+
1120+
1121+
private final LoadingCache<CertKey, CertValue> x509Cache = CacheBuilder.newBuilder().maximumSize(1000).build(new CacheLoader<CertKey, CertValue>() {
1122+
@Override
1123+
public CertValue load(CertKey key) {
1124+
return new CertValue(key);
1125+
}
1126+
});
1127+
1128+
public void clearCertificateCache() {
1129+
x509Cache.invalidateAll();
1130+
}
1131+
1132+
private CertValue getCertificateValue(byte[] pemCertificate) throws CryptoException {
1133+
try {
1134+
return x509Cache.get(new CertKey(pemCertificate));
1135+
} catch (ExecutionException e) {
1136+
Throwable thrown = e.getCause();
1137+
if (thrown instanceof CryptoException) {
1138+
throw (CryptoException) thrown;
1139+
}
1140+
if (thrown instanceof Exception) {
1141+
throw new CryptoException("Error whilst processing certificate", (Exception) thrown);
1142+
}
1143+
throw new CryptoException("Error whilst processing certificate", e);
1144+
}
1145+
}
1146+
1147+
1148+
private X509Certificate getValidCertificate(byte[] pemCertificate) throws CryptoException {
1149+
return getCertificateValue(pemCertificate).getValid();
1150+
}
1151+
10101152
}

0 commit comments

Comments
 (0)