diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/exception/BaseException.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/exception/BaseException.java new file mode 100644 index 0000000..0a1433d --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/exception/BaseException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016 DTCC, Fujitsu Australia Software Technology, IBM - All Rights Reserved. + * + * 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 com.chinaunicom.mall.ebtp.common.crypto.exception; + +public class BaseException extends Exception { + + private static final long serialVersionUID = 1L; + + public BaseException(String message, Throwable parent) { + super(message, parent); + } + + public BaseException(String message) { + super(message); + } + + public BaseException(Throwable t) { + super(t); + } + +} diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/exception/CryptoException.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/exception/CryptoException.java new file mode 100644 index 0000000..f5c323a --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/exception/CryptoException.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016 DTCC, Fujitsu Australia Software Technology - All Rights Reserved. + * + * 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 com.chinaunicom.mall.ebtp.common.crypto.exception; + +public class CryptoException extends BaseException { + + private static final long serialVersionUID = 1L; + + public CryptoException(String message, Exception parent) { + super(message, parent); + } + + public CryptoException(String message) { + super(message); + } + +} diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/exception/InvalidArgumentException.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/exception/InvalidArgumentException.java new file mode 100644 index 0000000..5dac406 --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/exception/InvalidArgumentException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 DTCC, Fujitsu Australia Software Technology - All Rights Reserved. + * + * 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 com.chinaunicom.mall.ebtp.common.crypto.exception; + +/* +Exception that is thrown from Chaincode + */ +public class InvalidArgumentException extends BaseException { + private static final long serialVersionUID = -6094512275378074427L; + + public InvalidArgumentException(String message, Exception parent) { + super(message, parent); + } + + public InvalidArgumentException(String message) { + super(message); + } + + public InvalidArgumentException(Throwable t) { + super(t); + } +} diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/helper/Config.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/helper/Config.java new file mode 100644 index 0000000..88623a0 --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/helper/Config.java @@ -0,0 +1,607 @@ +/* + * Copyright 2016, 2017 IBM, DTCC, Fujitsu Australia Software Technology, IBM - All Rights Reserved. + * + * 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 com.chinaunicom.mall.ebtp.common.crypto.helper; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import static java.lang.String.format; + +/** + * Config allows for a global config of the toolkit. Central location for all + * toolkit configuration defaults. Has a local config file that can override any + * property defaults. Config file can be relocated via a system property + * "org.hyperledger.fabric.sdk.configuration". Any property can be overridden + * with environment variable and then overridden + * with a java system property. Property hierarchy goes System property + * overrides environment variable which overrides config file for default values specified here. + */ + +public class Config { + private static final Log logger = LogFactory.getLog(Config.class); + + private static final String DEFAULT_CONFIG = "config.properties"; + public static final String ORG_HYPERLEDGER_FABRIC_SDK_CONFIGURATION = "org.hyperledger.fabric.sdk.configuration"; + + private static final String DEFAULT_NULL = "\0DEFAULT_NULL\0".intern(); // Used to set value to NULL since null won't work. + /** + * Timeout settings + **/ + public static final String PROPOSAL_WAIT_TIME = "org.hyperledger.fabric.sdk.proposal.wait.time"; + public static final String CHANNEL_CONFIG_WAIT_TIME = "org.hyperledger.fabric.sdk.channelconfig.wait_time"; + public static final String TRANSACTION_CLEANUP_UP_TIMEOUT_WAIT_TIME = "org.hyperledger.fabric.sdk.client.transaction_cleanup_up_timeout_wait_time"; + public static final String ORDERER_RETRY_WAIT_TIME = "org.hyperledger.fabric.sdk.orderer_retry.wait_time"; + public static final String ORDERER_WAIT_TIME = "org.hyperledger.fabric.sdk.orderer.ordererWaitTimeMilliSecs"; + public static final String PEER_EVENT_REGISTRATION_WAIT_TIME = "org.hyperledger.fabric.sdk.peer.eventRegistration.wait_time"; + public static final String PEER_EVENT_RETRY_WAIT_TIME = "org.hyperledger.fabric.sdk.peer.retry_wait_time"; + public static final String PEER_EVENT_RECONNECTION_WARNING_RATE = "org.hyperledger.fabric.sdk.peer.reconnection_warning_rate"; + public static final String GENESISBLOCK_WAIT_TIME = "org.hyperledger.fabric.sdk.channel.genesisblock_wait_time"; + /** + * Crypto configuration settings -- settings should not be changed. + **/ + public static final String DEFAULT_CRYPTO_SUITE_FACTORY = "org.hyperledger.fabric.sdk.crypto.default_crypto_suite_factory"; + public static final String SECURITY_LEVEL = "org.hyperledger.fabric.sdk.security_level"; + public static final String SECURITY_PROVIDER_CLASS_NAME = "org.hyperledger.fabric.sdk.security_provider_class_name"; + public static final String SECURITY_CURVE_MAPPING = "org.hyperledger.fabric.sdk.security_curve_mapping"; + public static final String HASH_ALGORITHM = "org.hyperledger.fabric.sdk.hash_algorithm"; + public static final String ASYMMETRIC_KEY_TYPE = "org.hyperledger.fabric.sdk.crypto.asymmetric_key_type"; + public static final String CERTIFICATE_FORMAT = "org.hyperledger.fabric.sdk.crypto.certificate_format"; + public static final String SIGNATURE_ALGORITHM = "org.hyperledger.fabric.sdk.crypto.default_signature_algorithm"; + /** + * Logging settings + **/ + public static final String MAX_LOG_STRING_LENGTH = "org.hyperledger.fabric.sdk.log.stringlengthmax"; + public static final String EXTRALOGLEVEL = "org.hyperledger.fabric.sdk.log.extraloglevel"; // ORG_HYPERLEDGER_FABRIC_SDK_LOG_EXTRALOGLEVEL + public static final String LOGGERLEVEL = "org.hyperledger.fabric.sdk.loglevel"; // ORG_HYPERLEDGER_FABRIC_SDK_LOGLEVEL=TRACE,DEBUG + public static final String DIAGNOTISTIC_FILE_DIRECTORY = "org.hyperledger.fabric.sdk.diagnosticFileDir"; //ORG_HYPERLEDGER_FABRIC_SDK_DIAGNOSTICFILEDIR + + /** + * Connections settings + */ + + public static final String CONN_SSL_PROVIDER = "org.hyperledger.fabric.sdk.connections.ssl.sslProvider"; + public static final String CONN_SSL_NEGTYPE = "org.hyperledger.fabric.sdk.connections.ssl.negotiationType"; + + /** + * Default HFClient thread executor settings. + */ + + public static final String CLIENT_THREAD_EXECUTOR_COREPOOLSIZE = "org.hyperledger.fabric.sdk.client.thread_executor_corepoolsize"; + public static final String CLIENT_THREAD_EXECUTOR_MAXIMUMPOOLSIZE = "org.hyperledger.fabric.sdk.client.thread_executor_maximumpoolsize"; + public static final String CLIENT_THREAD_EXECUTOR_KEEPALIVETIME = "org.hyperledger.fabric.sdk.client.thread_executor_keepalivetime"; + public static final String CLIENT_THREAD_EXECUTOR_KEEPALIVETIMEUNIT = "org.hyperledger.fabric.sdk.client.thread_executor_keepalivetimeunit"; + + /** + * Miscellaneous settings + **/ + public static final String PROPOSAL_CONSISTENCY_VALIDATION = "org.hyperledger.fabric.sdk.proposal.consistency_validation"; + + public static final String SERVICE_DISCOVER_FREQ_SECONDS = "org.hyperledger.fabric.sdk.service_discovery.frequency_sec"; + public static final String SERVICE_DISCOVER_WAIT_TIME = "org.hyperledger.fabric.sdk.service_discovery.discovery_wait_time"; + public static final String SERVICE_DISCOVER_AS_LOCALHOST = "org.hyperledger.fabric.sdk.service_discovery.as_localhost"; + + public static final String LIFECYCLE_CHAINCODE_ENDORSEMENT_PLUGIN = "org.hyperledger.fabric.sdk.lifecycle.chaincode_endorsement_plugin"; //ORG_HYPERLEDGER_FABRIC_SDK_LIFECYCLE_CHAINCODE_ENDORSEMENT_PLUGIN + + public static final String LIFECYCLE_CHAINCODE_VALIDATION_PLUGIN = "org.hyperledger.fabric.sdk.lifecycle.chaincode_validation_plugin"; //ORG_HYPERLEDGER_FABRIC_SDK_LIFECYCLE_CHAINCODE_VALIDATION_PLUGIN + public static final String LIFECYCLE_INITREQUIREDDEFAULT = "org.hyperledger.fabric.sdk.lifecycle.initRequiredDefault"; //ORG_HYPERLEDGER_FABRIC_SDK_LIFECYCLE_INITREQUIREDDEFAULT + + private static Config config; + private static final Properties sdkProperties = new Properties(); + private static final AtomicLong count = new AtomicLong(0); + + //Provides a unique id for logging to identify a specific instance. + public String getNextID() { + return "" + count.incrementAndGet(); + } + + private Config() { + File loadFile; + FileInputStream configProps; + + try { + loadFile = new File(System.getProperty(ORG_HYPERLEDGER_FABRIC_SDK_CONFIGURATION, DEFAULT_CONFIG)) + .getAbsoluteFile(); + logger.debug(format("Loading configuration from %s and it is present: %b", loadFile.toString(), + loadFile.exists())); + configProps = new FileInputStream(loadFile); + sdkProperties.load(configProps); + + } catch (IOException e) { + logger.warn(format("Failed to load any configuration from: %s. Using toolkit defaults", + DEFAULT_CONFIG)); + } finally { + + // Default values + /** + * Timeout settings + **/ + defaultProperty(PROPOSAL_WAIT_TIME, "120000"); + defaultProperty(CHANNEL_CONFIG_WAIT_TIME, "15000"); + defaultProperty(ORDERER_RETRY_WAIT_TIME, "200"); + defaultProperty(ORDERER_WAIT_TIME, "10000"); + defaultProperty(PEER_EVENT_REGISTRATION_WAIT_TIME, "5000"); + defaultProperty(PEER_EVENT_RETRY_WAIT_TIME, "500"); + defaultProperty(GENESISBLOCK_WAIT_TIME, "5000"); + /** + * This will NOT complete any transaction futures time out and must be kept WELL above any expected future timeout + * for transactions sent to the Orderer. For internal cleanup only. + */ + + defaultProperty(TRANSACTION_CLEANUP_UP_TIMEOUT_WAIT_TIME, "600000"); //10 min. + + /** + * Crypto configuration settings + **/ + defaultProperty(DEFAULT_CRYPTO_SUITE_FACTORY, "org.hyperledger.fabric.sdk.security.HLSDKJCryptoSuiteFactory"); + defaultProperty(SECURITY_LEVEL, "256"); + defaultProperty(SECURITY_PROVIDER_CLASS_NAME, BouncyCastleProvider.class.getName()); + defaultProperty(SECURITY_CURVE_MAPPING, "256=secp256r1:384=secp384r1"); + defaultProperty(HASH_ALGORITHM, "SHA2"); + defaultProperty(ASYMMETRIC_KEY_TYPE, "EC"); + + defaultProperty(CERTIFICATE_FORMAT, "X.509"); + defaultProperty(SIGNATURE_ALGORITHM, "SHA256withECDSA"); + + /** + * Connection defaults + */ + + defaultProperty(CONN_SSL_PROVIDER, "openSSL"); + defaultProperty(CONN_SSL_NEGTYPE, "TLS"); + + /** + * Default HFClient thread executor settings. + */ + + defaultProperty(CLIENT_THREAD_EXECUTOR_COREPOOLSIZE, "0"); + defaultProperty(CLIENT_THREAD_EXECUTOR_MAXIMUMPOOLSIZE, "" + Integer.MAX_VALUE); + defaultProperty(CLIENT_THREAD_EXECUTOR_KEEPALIVETIME, "" + "60"); + defaultProperty(CLIENT_THREAD_EXECUTOR_KEEPALIVETIMEUNIT, "SECONDS"); + + /** + * Logging settings + **/ + defaultProperty(MAX_LOG_STRING_LENGTH, "64"); + defaultProperty(EXTRALOGLEVEL, "0"); + defaultProperty(LOGGERLEVEL, null); + defaultProperty(DIAGNOTISTIC_FILE_DIRECTORY, null); + /** + * Miscellaneous settings + */ + defaultProperty(PROPOSAL_CONSISTENCY_VALIDATION, "true"); + defaultProperty(PEER_EVENT_RECONNECTION_WARNING_RATE, "50"); + + defaultProperty(SERVICE_DISCOVER_FREQ_SECONDS, "120"); + defaultProperty(SERVICE_DISCOVER_WAIT_TIME, "5000"); + defaultProperty(SERVICE_DISCOVER_AS_LOCALHOST, "false"); + defaultProperty(LIFECYCLE_CHAINCODE_ENDORSEMENT_PLUGIN, DEFAULT_NULL); + defaultProperty(LIFECYCLE_CHAINCODE_VALIDATION_PLUGIN, DEFAULT_NULL); + defaultProperty(LIFECYCLE_INITREQUIREDDEFAULT, DEFAULT_NULL); + + final String inLogLevel = sdkProperties.getProperty(LOGGERLEVEL); + + if (null != inLogLevel) { + + + + } + + } + + } + + /** + * getConfig return back singleton for SDK configuration. + * + * @return Global configuration + */ + public static Config getConfig() { + if (null == config) { + config = new Config(); + } + return config; + + } + + /** + * getProperty return back property for the given value. + * + * @param property + * @return String value for the property + */ + private String getProperty(String property) { + + if (!sdkProperties.containsKey(property)) { + logger.warn(format("No configuration value found for '%s'", property)); + } + String ret = sdkProperties.getProperty(property); + if (DEFAULT_NULL.equals(ret)) { + ret = null; + } + return ret; + } + + private static void defaultProperty(String key, String value) { + + String ret = System.getProperty(key); + if (ret != null) { + sdkProperties.put(key, ret); + } else { + String envKey = key.toUpperCase().replaceAll("\\.", "_"); + ret = System.getenv(envKey); + if (null != ret) { + sdkProperties.put(key, ret); + } else { + if (null == sdkProperties.getProperty(key) && value != null) { + sdkProperties.put(key, value); + } + + } + + } + } + + /** + * Get the configured security level. The value determines the elliptic curve used to generate keys. + * + * @return the security level. + */ + public int getSecurityLevel() { + + return Integer.parseInt(getProperty(SECURITY_LEVEL)); + + } + + /** + * Get the configured security provider. + * This is the security provider used for the default SDK crypto suite factory. + * + * @return the security provider. + */ + public String getSecurityProviderClassName() { + return getProperty(SECURITY_PROVIDER_CLASS_NAME); + } + + /** + * Get the name of the configured hash algorithm, used for digital signatures. + * + * @return the hash algorithm name. + */ + public String getHashAlgorithm() { + return getProperty(HASH_ALGORITHM); + + } + + /** + * The default ssl provider for grpc connection + * + * @return The default ssl provider for grpc connection + */ + public String getDefaultSSLProvider() { + return getProperty(CONN_SSL_PROVIDER); + + } + + /** + * The default ssl negotiation type + * + * @return The default ssl negotiation type + */ + + public String getDefaultSSLNegotiationType() { + return getProperty(CONN_SSL_NEGTYPE); + + } + + private Map curveMapping = null; + + /** + * Get a mapping from strength to curve desired. + * + * @return mapping from strength to curve name to use. + */ + public Map getSecurityCurveMapping() { + + if (curveMapping == null) { + + curveMapping = parseSecurityCurveMappings(getProperty(SECURITY_CURVE_MAPPING)); + } + + return Collections.unmodifiableMap(curveMapping); + } + + public static Map parseSecurityCurveMappings(final String property) { + Map lcurveMapping = new HashMap<>(8); + + if (property != null && !property.isEmpty()) { //empty will be caught later. + + String[] cmaps = property.split("[ \t]*:[ \t]*"); + for (String mape : cmaps) { + + String[] ep = mape.split("[ \t]*=[ \t]*"); + if (ep.length != 2) { + logger.warn(format("Bad curve mapping for %s in property %s", mape, SECURITY_CURVE_MAPPING)); + continue; + } + + try { + int parseInt = Integer.parseInt(ep[0]); + lcurveMapping.put(parseInt, ep[1]); + } catch (NumberFormatException e) { + logger.warn(format("Bad curve mapping. Integer needed for strength %s for %s in property %s", + ep[0], mape, SECURITY_CURVE_MAPPING)); + } + + } + + } + return lcurveMapping; + } + + /** + * Get the timeout for a single proposal request to endorser. + * + * @return the timeout in milliseconds. + */ + public long getProposalWaitTime() { + return Long.parseLong(getProperty(PROPOSAL_WAIT_TIME)); + } + + /** + * Get the configured time to wait for genesis block. + * + * @return time in milliseconds. + */ + public long getGenesisBlockWaitTime() { + return Long.parseLong(getProperty(GENESISBLOCK_WAIT_TIME)); + } + + /** + * Time to wait for channel to be configured. + * + * @return + */ + public long getChannelConfigWaitTime() { + return Long.parseLong(getProperty(CHANNEL_CONFIG_WAIT_TIME)); + } + + /** + * Time to wait before retrying an operation. + * + * @return + */ + public long getOrdererRetryWaitTime() { + return Long.parseLong(getProperty(ORDERER_RETRY_WAIT_TIME)); + } + + public long getOrdererWaitTime() { + return Long.parseLong(getProperty(ORDERER_WAIT_TIME)); + } + + /** + * getPeerEventRegistrationWaitTime + * + * @return time in milliseconds to wait for peer eventing service to wait for event registration + */ + public long getPeerEventRegistrationWaitTime() { + return Long.parseLong(getProperty(PEER_EVENT_REGISTRATION_WAIT_TIME)); + } + + /** + * getPeerEventRegistrationWaitTime + * + * @return time in milliseconds to wait for peer eventing service to wait for event registration + */ + public long getPeerRetryWaitTime() { + return Long.parseLong(getProperty(PEER_EVENT_RETRY_WAIT_TIME)); + } + + public long getPeerEventReconnectionWarningRate() { + return Long.parseLong(getProperty(PEER_EVENT_RECONNECTION_WARNING_RATE)); + } + + /** + * How often serviced discovery is preformed in seconds. + * + * @return + */ + public int getServiceDiscoveryFreqSeconds() { + return Integer.parseInt(getProperty(SERVICE_DISCOVER_FREQ_SECONDS)); + } + + /** + * Time to wait for service discovery to complete. + * + * @return + */ + public int getServiceDiscoveryWaitTime() { + return Integer.parseInt(getProperty(SERVICE_DISCOVER_WAIT_TIME)); + } + + public boolean discoverAsLocalhost() { + return Boolean.parseBoolean(getProperty(SERVICE_DISCOVER_AS_LOCALHOST)); + } + + public String getAsymmetricKeyType() { + return getProperty(ASYMMETRIC_KEY_TYPE); + } + + public String getCertificateFormat() { + return getProperty(CERTIFICATE_FORMAT); + } + + public String getSignatureAlgorithm() { + return getProperty(SIGNATURE_ALGORITHM); + } + + public String getDefaultCryptoSuiteFactory() { + return getProperty(DEFAULT_CRYPTO_SUITE_FACTORY); + } + + public int maxLogStringLength() { + return Integer.parseInt(getProperty(MAX_LOG_STRING_LENGTH)); + } + + /** + * getProposalConsistencyValidation determine if validation of the proposals should + * be done before sending to the orderer. + * + * @return if true proposals will be checked they are consistent with each other before sending to the Orderer + */ + + public boolean getProposalConsistencyValidation() { + return Boolean.parseBoolean(getProperty(PROPOSAL_CONSISTENCY_VALIDATION)); + + } + + private int extraLogLevel = -1; + + public boolean extraLogLevel(int val) { + if (extraLogLevel == -1) { + extraLogLevel = Integer.parseInt(getProperty(EXTRALOGLEVEL)); + } + + return val <= extraLogLevel; + + } + + DiagnosticFileDumper diagnosticFileDumper = null; + + /** + * The directory where diagnostic dumps are to be place, null if none should be done. + * + * @return The directory where diagnostic dumps are to be place, null if none should be done. + */ + + public DiagnosticFileDumper getDiagnosticFileDumper() { + + if (diagnosticFileDumper != null) { + return diagnosticFileDumper; + } + + String dd = sdkProperties.getProperty(DIAGNOTISTIC_FILE_DIRECTORY); + + if (dd != null) { + + diagnosticFileDumper = DiagnosticFileDumper.configInstance(new File(dd)); + + } + + return diagnosticFileDumper; + } + + /** + * This does NOT trigger futures time out and must be kept WELL above any expected future timeout + * for transactions sent to the Orderer + * + * @return + */ + public long getTransactionListenerCleanUpTimeout() { + return Long.parseLong(getProperty(TRANSACTION_CLEANUP_UP_TIMEOUT_WAIT_TIME)); + } + + /** + * The number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set + * + * @return The number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set + */ + + public int getClientThreadExecutorCorePoolSize() { + return Integer.parseInt(getProperty(CLIENT_THREAD_EXECUTOR_COREPOOLSIZE)); + } + + /** + * maximumPoolSize the maximum number of threads to allow in the pool + * + * @return maximumPoolSize the maximum number of threads to allow in the pool + */ + public int getClientThreadExecutorMaxiumPoolSize() { + return Integer.parseInt(getProperty(CLIENT_THREAD_EXECUTOR_MAXIMUMPOOLSIZE)); + } + + /** + * keepAliveTime when the number of threads is greater than + * the core, this is the maximum time that excess idle threads + * will wait for new tasks before terminating. + * + * @return The keep alive time. + */ + + public long getClientThreadExecutorKeepAliveTime() { + return Long.parseLong(getProperty(CLIENT_THREAD_EXECUTOR_KEEPALIVETIME)); + } + + /** + * the time unit for the argument + * + * @return + */ + + public TimeUnit getClientThreadExecutorKeepAliveTimeUnit() { + + return TimeUnit.valueOf(getProperty(CLIENT_THREAD_EXECUTOR_KEEPALIVETIMEUNIT)); + } + + /** + * The default chaincode Endorsement policy plugin + *

+ * This should never need setting + * + * @return The default chaincode Endorsement policy plugin + */ + + public String getDefaultChaincodeEndorsementPlugin() { + return getProperty(LIFECYCLE_CHAINCODE_ENDORSEMENT_PLUGIN); + } + + /** + * The default chaincode validation plugin + * This should never need setting. + * + * @return The default chaincode validation plugin + */ + public String getDefaultChaincodeValidationPlugin() { + return getProperty(LIFECYCLE_CHAINCODE_VALIDATION_PLUGIN); + } + + /** + * Whether require init method in chaincode to be run. + * The default will return null which will not set the Fabric protobuf value which then sets false. False is the Fabric + * default. + * + * @return The default setting for initRequired in chaincode approve for my org and commit chaincode definition. + */ + public Boolean getLifecycleInitRequiredDefault() { + + String property = getProperty(LIFECYCLE_INITREQUIREDDEFAULT); + if (property != null) { + + return Boolean.parseBoolean(property); + + } + + return null; + } +} \ No newline at end of file diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/helper/DiagnosticFileDumper.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/helper/DiagnosticFileDumper.java new file mode 100644 index 0000000..1a0ca0e --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/helper/DiagnosticFileDumper.java @@ -0,0 +1,204 @@ +/* + * + * Copyright 2016,2017 DTCC, Fujitsu Australia Software Technology, IBM - All Rights Reserved. + * + * 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 com.chinaunicom.mall.ebtp.common.crypto.helper; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.CompletionHandler; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.TimeZone; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Dumps files for diagnostic purposes + */ + +public class DiagnosticFileDumper implements Runnable { + + private final File directory; + private final String dirAbsolutePath; + private final String pid; + // private static final Log logger = LogFactory.getLog(DiagnosticFileDumper.class); + private static Thread thread; + private final BlockingQueue queEntries = new LinkedBlockingQueue<>(); + + private static DiagnosticFileDumper singleInstance = null; + private static final AtomicInteger counter = new AtomicInteger(0); + + private DiagnosticFileDumper(File directory) { + + this.directory = directory; + this.dirAbsolutePath = directory == null ? null : directory.getAbsolutePath(); + this.pid = getPID() + ""; + + } + + static DiagnosticFileDumper configInstance(File directory) { + + if (singleInstance == null) { + singleInstance = new DiagnosticFileDumper(directory); + thread = new Thread(singleInstance); + thread.setName("DiagnosticFileDumper"); + thread.setDaemon(true); + thread.start(); + + } + + return singleInstance; + + } + + public String createDiagnosticProtobufFile(byte[] byteString) { + + return createDiagnosticFile(byteString, "protobuf_", "proto"); + + } + + private boolean cantWrite() { + return null == directory || !directory.exists() || !directory.isDirectory() || !directory.canWrite(); + } + + public String createDiagnosticFile(byte[] bytes) { + + return createDiagnosticFile(bytes, null, null); + + } + + public String createDiagnosticTarFile(byte[] bytes) { + + return createDiagnosticFile(bytes, null, "tgz"); + + } + + public String createDiagnosticFile(String msg) { + + return createDiagnosticFile(msg.getBytes(StandardCharsets.UTF_8), null, null); + + } + + public String createDiagnosticFile(byte[] bytes, String prefix, String ext) { + String fileName = ""; + if (cantWrite()) { + return "Missing dump directory or can not write: " + (dirAbsolutePath); + } + if (null != bytes) { + if (null == prefix) { + prefix = "diagnostic_"; + } + if (null == ext) { + ext = "bin"; + } + + SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss_SSS"); + dateFormatGmt.setTimeZone(TimeZone.getTimeZone("UTC")); + fileName = prefix + dateFormatGmt.format(new Date()) + "P" + pid + "_" + Thread.currentThread().getId() + + "_" + counter.addAndGet(1) + "." + ext; + fileName = fileName.replaceAll("\\:", "-"); // colon is bad for windows. + + new QueEntry(fileName, bytes); //Add to Que let process by async thread. + + } + return fileName; + + } + + @Override + public void run() { + + while (true) { + + try { + final LinkedList entries = new LinkedList<>(); + + entries.add(this.queEntries.take()); // wait on one. + this.queEntries.drainTo(entries); //got one, see if there are more. + + if (cantWrite()) { + return; //IF the directory is missing just assume user does not want diagnostic files created anymore. + } + + entries.forEach(queEntry -> { + + try { + final AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get(dirAbsolutePath, queEntry.fileName), + StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + + channel.write(ByteBuffer.wrap(queEntry.dataBytes), 0, null, new CompletionHandler() { + @Override + public void completed(Integer result, Object attachment) { + try { + channel.close(); + } catch (IOException e) { + //best effort + } + } + + @Override + public void failed(Throwable exc, Object attachment) { + + try { + channel.close(); + } catch (IOException e) { + //best effort. + } + + } + }); + + } catch (IOException e) { + //best effort. + } + + }); + } catch (InterruptedException e) { + // best effort + } + + } + + } + + class QueEntry { + final String fileName; + final byte[] dataBytes; + + QueEntry(String fileName, byte[] dataBytes) { + this.fileName = fileName; + this.dataBytes = dataBytes; + + queEntries.add(this); + + } + + } + + private static long getPID() { + String processName = + java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); + return Long.parseLong(processName.split("@")[0]); + } + +} diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/helper/Utils.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/helper/Utils.java new file mode 100644 index 0000000..8de802c --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/helper/Utils.java @@ -0,0 +1,406 @@ +/* + * Copyright 2017 DTCC, Fujitsu Australia Software Technology, IBM - All Rights Reserved. + * + * 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 com.chinaunicom.mall.ebtp.common.crypto.helper; + +import com.chinaunicom.mall.ebtp.common.crypto.helper.Config; +import com.google.protobuf.ByteString; +import com.google.protobuf.Timestamp; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA3Digest; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; + +import java.io.*; +import java.net.URI; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.SecureRandom; +import java.time.Instant; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.commons.codec.binary.Hex.encodeHexString; + +public final class Utils { + private static final Log logger = LogFactory.getLog(Utils.class); + + private static final boolean TRACE_ENABED = logger.isTraceEnabled(); + private static final Config config = Config.getConfig(); + private static final int MAX_LOG_STRING_LENGTH = config.maxLogStringLength(); + + /** + * Generate parameter hash for the given chaincode path,func and args + * + * @param path Chaincode path + * @param func Chaincode function name + * @param args List of arguments + * @return hash of path, func and args + */ + public static String generateParameterHash(String path, String func, List args) { + logger.debug(format("GenerateParameterHash : path=%s, func=%s, args=%s", path, func, args)); + + // Append the arguments + StringBuilder param = new StringBuilder(path); + param.append(func); + args.forEach(param::append); + + // Compute the hash + + return Hex.toHexString(hash(param.toString().getBytes(UTF_8), new SHA3Digest())); + } + + /** + * Generate hash of a chaincode directory + * + * @param rootDir Root directory + * @param chaincodeDir Channel code directory + * @param hash Previous hash (if any) + * @return hash of the directory + * @throws IOException + */ + public static String generateDirectoryHash(String rootDir, String chaincodeDir, String hash) throws IOException { + // Generate the project directory + Path projectPath; + if (rootDir == null) { + projectPath = Paths.get(chaincodeDir); + } else { + projectPath = Paths.get(rootDir, chaincodeDir); + } + + File dir = projectPath.toFile(); + if (!dir.exists() || !dir.isDirectory()) { + throw new IOException(format("The chaincode path \"%s\" is invalid", projectPath)); + } + + StringBuilder hashBuilder = new StringBuilder(hash); + Files.walk(projectPath) + .sorted(Comparator.naturalOrder()) + .filter(Files::isRegularFile) + .map(Path::toFile) + .forEach(file -> { + try { + byte[] buf = readFile(file); + byte[] toHash = Arrays.concatenate(buf, hashBuilder.toString().getBytes(UTF_8)); + hashBuilder.setLength(0); + hashBuilder.append(Hex.toHexString(hash(toHash, new SHA3Digest()))); + } catch (IOException ex) { + throw new RuntimeException(format("Error while reading file %s", file.getAbsolutePath()), ex); + } + }); + + // If original hash and final hash are the same, it indicates that no new contents were found + if (hashBuilder.toString().equals(hash)) { + throw new IOException(format("The chaincode directory \"%s\" has no files", projectPath)); + } + return hashBuilder.toString(); + } + + /** + * Compress the contents of given directory using Tar and Gzip to an in-memory byte array. + * + * @param sourceDirectory the source directory. + * @param pathPrefix a path to be prepended to every file name in the .tar.gz output, or {@code null} if no prefix is required. + * @param chaincodeMetaInf + * @return the compressed directory contents. + * @throws IOException + */ + public static byte[] generateTarGz(File sourceDirectory, String pathPrefix, File chaincodeMetaInf) throws IOException { + logger.trace(format("generateTarGz: sourceDirectory: %s, pathPrefix: %s, chaincodeMetaInf: %s", + sourceDirectory == null ? "null" : sourceDirectory.getAbsolutePath(), pathPrefix, + chaincodeMetaInf == null ? "null" : chaincodeMetaInf.getAbsolutePath())); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(500000); + + String sourcePath = sourceDirectory.getAbsolutePath(); + +// try (TarArchiveOutputStream archiveOutputStream = new TarArchiveOutputStream(new GzipCompressorOutputStream(bos))) { +// archiveOutputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); +// +// Collection childrenFiles = org.apache.commons.io.FileUtils.listFiles(sourceDirectory, null, true); +// +// ArchiveEntry archiveEntry; +// for (File childFile : childrenFiles) { +// String childPath = childFile.getAbsolutePath(); +// String relativePath = childPath.substring((sourcePath.length() + 1)); +// +// if (pathPrefix != null) { +// relativePath = Utils.combinePaths(pathPrefix, relativePath); +// } +// +// relativePath = FilenameUtils.separatorsToUnix(relativePath); +// +// if (TRACE_ENABED) { +// logger.trace(format("generateTarGz: Adding '%s' entry from source '%s' to archive.", relativePath, childFile.getAbsolutePath())); +// } +// +// archiveEntry = new TarArchiveEntry(childFile, relativePath); +// archiveOutputStream.putArchiveEntry(archiveEntry); +// +// try (FileInputStream fileInputStream = new FileInputStream(childFile)) { +// IOUtils.copy(fileInputStream, archiveOutputStream); +// } finally { +// archiveOutputStream.closeArchiveEntry(); +// } +// +// } +// +// if (null != chaincodeMetaInf) { +// childrenFiles = org.apache.commons.io.FileUtils.listFiles(chaincodeMetaInf, null, true); +// +// final URI metabase = chaincodeMetaInf.toURI(); +// +// for (File childFile : childrenFiles) { +// +// final String relativePath = Paths.get("META-INF", metabase.relativize(childFile.toURI()).getPath()).toString(); +// +// if (TRACE_ENABED) { +// logger.trace(format("generateTarGz: Adding '%s' entry from source '%s' to archive.", relativePath, childFile.getAbsolutePath())); +// } +// +// archiveEntry = new TarArchiveEntry(childFile, relativePath); +// archiveOutputStream.putArchiveEntry(archiveEntry); +// +// try (FileInputStream fileInputStream = new FileInputStream(childFile)) { +// IOUtils.copy(fileInputStream, archiveOutputStream); +// } finally { +// archiveOutputStream.closeArchiveEntry(); +// } +// +// } +// +// } +// } + + return bos.toByteArray(); + } + + /** + * Read the contents a file. + * + * @param input source file to read. + * @return contents of the file. + * @throws IOException + */ + public static byte[] readFile(File input) throws IOException { + return Files.readAllBytes(Paths.get(input.getAbsolutePath())); + } + + /** + * Generate a v4 UUID + * + * @return String representation of {@link UUID} + */ + public static String generateUUID() { + return UUID.randomUUID().toString(); + } + + /** + * Create a new {@link Timestamp} instance based on the current time + * + * @return timestamp + */ + public static Timestamp generateTimestamp() { + Instant time = Instant.now(); + return Timestamp.newBuilder().setSeconds(time.getEpochSecond()) + .setNanos(time.getNano()).build(); + } + + /** + * Delete a file or directory + * + * @param file {@link File} representing file or directory + * @throws IOException + */ + public static void deleteFileOrDirectory(File file) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + Path rootPath = Paths.get(file.getAbsolutePath()); + + Files.walk(rootPath, FileVisitOption.FOLLOW_LINKS) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } else { + file.delete(); + } + } else { + throw new RuntimeException("File or directory does not exist"); + } + } + + /** + * Generate hash of the given input using the given Digest. + * + * @param input input data. + * @param digest the digest to use for hashing + * @return hashed data. + */ + public static byte[] hash(byte[] input, Digest digest) { + byte[] retValue = new byte[digest.getDigestSize()]; + digest.update(input, 0, input.length); + digest.doFinal(retValue, 0); + return retValue; + } + + /** + * Combine two or more paths + * + * @param first parent directory path + * @param other children + * @return combined path + */ + public static String combinePaths(String first, String... other) { + return Paths.get(first, other).toString(); + } + + /** + * Read a file from classpath + * + * @param fileName + * @return byte[] data + * @throws IOException + */ + public static byte[] readFileFromClasspath(String fileName) throws IOException { + try (InputStream is = Utils.class.getClassLoader().getResourceAsStream(fileName)) { + return IOUtils.toByteArray(is); + } + } + + public static Properties parseGrpcUrl(String url) { + if (isNullOrEmpty(url)) { + throw new RuntimeException("URL cannot be null or empty"); + } + + Properties props = new Properties(); + Pattern p = Pattern.compile("([^:]+)[:]//([^:]+)[:]([0-9]+)", Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(url); + if (m.matches()) { + props.setProperty("protocol", m.group(1)); + props.setProperty("host", m.group(2)); + props.setProperty("port", m.group(3)); + + String protocol = props.getProperty("protocol"); + if (!"grpc".equals(protocol) && !"grpcs".equals(protocol)) { + throw new RuntimeException(format("Invalid protocol expected grpc or grpcs and found %s.", protocol)); + } + } else { + throw new RuntimeException("URL must be of the format protocol://host:port. Found: '" + url + "'"); + } + + // TODO: allow all possible formats of the URL + return props; + } + + /** + * Check if the strings Grpc url is valid + * + * @param url + * @return Return the exception that indicates the error or null if ok. + */ + public static Exception checkGrpcUrl(String url) { + try { + + parseGrpcUrl(url); + return null; + + } catch (Exception e) { + return e; + } + } + + /** + * Check if a string is null or empty. + * + * @param url the string to test. + * @return {@code true} if the string is null or empty; otherwise {@code false}. + */ + public static boolean isNullOrEmpty(String url) { + return url == null || url.isEmpty(); + } + + /** + * Makes logging strings which can be long or with unprintable characters be logged and trimmed. + * + * @param string Unsafe string too long + * @return returns a string which does not have unprintable characters and trimmed in length. + */ + public static String logString(final String string) { + if (string == null || string.length() == 0) { + return string; + } + + String ret = string.replaceAll("[^\\p{Print}]", "?"); + + ret = ret.substring(0, Math.min(ret.length(), MAX_LOG_STRING_LENGTH)) + (ret.length() > MAX_LOG_STRING_LENGTH ? "..." : ""); + + return ret; + + } + + private static final int NONONCE_LENGTH = 24; + + private static final SecureRandom RANDOM = new SecureRandom(); + + public static byte[] generateNonce() { + + byte[] values = new byte[NONONCE_LENGTH]; + RANDOM.nextBytes(values); + + return values; + } + + public static String toHexString(ByteString byteString) { + if (byteString == null) { + return null; + } + + return encodeHexString(byteString.toByteArray()); + + } + + public static String toHexString(byte[] bytes) { + if (bytes == null) { + return null; + } + + return encodeHexString(bytes); + + } + + public static String toHexString(String bytes) { + if (bytes == null) { + return null; + } + + return encodeHexString(bytes.getBytes(UTF_8)); + + } + + /** + * Private constructor to prevent instantiation. + */ + private Utils() { + } + +} diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/security/CryptoPrimitives.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/security/CryptoPrimitives.java new file mode 100644 index 0000000..ebd2ebd --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/security/CryptoPrimitives.java @@ -0,0 +1,977 @@ +/* + * Copyright 2016, 2017 DTCC, Fujitsu Australia Software Technology, IBM - All Rights Reserved. + * + * 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 com.chinaunicom.mall.ebtp.common.crypto.security; + +import com.chinaunicom.mall.ebtp.common.crypto.exception.CryptoException; +import com.chinaunicom.mall.ebtp.common.crypto.exception.InvalidArgumentException; +import com.chinaunicom.mall.ebtp.common.crypto.helper.Config; +import com.chinaunicom.mall.ebtp.common.crypto.helper.DiagnosticFileDumper; +import com.chinaunicom.mall.ebtp.common.crypto.helper.Utils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x9.ECNamedCurveTable; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.digests.SHA3Digest; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; + +import javax.security.auth.x500.X500Principal; +import javax.xml.bind.DatatypeConverter; +import java.io.*; +import java.math.BigInteger; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.*; +import java.security.interfaces.ECPrivateKey; +import java.security.spec.ECGenParameterSpec; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.chinaunicom.mall.ebtp.common.crypto.helper.Utils.isNullOrEmpty; +import static java.lang.String.format; + +public class CryptoPrimitives implements CryptoSuite { + private static final Log logger = LogFactory.getLog(CryptoPrimitives.class); + private static final Config config = Config.getConfig(); + private static final boolean IS_TRACE_LEVEL = logger.isTraceEnabled(); + + private static final DiagnosticFileDumper diagnosticFileDumper = IS_TRACE_LEVEL + ? config.getDiagnosticFileDumper() : null; + + private String curveName; + private CertificateFactory cf; + private Provider SECURITY_PROVIDER; + private String hashAlgorithm = config.getHashAlgorithm(); + private int securityLevel = config.getSecurityLevel(); + private String CERTIFICATE_FORMAT = config.getCertificateFormat(); + private String DEFAULT_SIGNATURE_ALGORITHM = config.getSignatureAlgorithm(); + + private Map securityCurveMapping = config.getSecurityCurveMapping(); + + // Following configuration settings are hardcoded as they don't deal with any interactions with Fabric MSP and BCCSP components + // If you wish to make these customizable, follow the logic from setProperties(); + //TODO May need this for TCERTS ? +// private String ASYMMETRIC_KEY_TYPE = "EC"; +// private String KEY_AGREEMENT_ALGORITHM = "ECDH"; +// private String SYMMETRIC_KEY_TYPE = "AES"; +// private int SYMMETRIC_KEY_BYTE_COUNT = 32; +// private String SYMMETRIC_ALGORITHM = "AES/CFB/NoPadding"; +// private int MAC_KEY_BYTE_COUNT = 32; + + public CryptoPrimitives() throws ClassNotFoundException, IllegalAccessException, InstantiationException { + String securityProviderClassName = config.getSecurityProviderClassName(); + + SECURITY_PROVIDER = setUpExplicitProvider(securityProviderClassName); + + //Decided TO NOT do this as it can have affects over the whole JVM and could have + // unexpected results. The embedding application can easily do this! + // Leaving this here as a warning. + // Security.insertProviderAt(SECURITY_PROVIDER, 1); // 1 is top not 0 :) + } + + Provider setUpExplicitProvider(String securityProviderClassName) throws InstantiationException, ClassNotFoundException, IllegalAccessException { + if (null == securityProviderClassName) { + throw new InstantiationException(format("Security provider class name property (%s) set to null ", Config.SECURITY_PROVIDER_CLASS_NAME)); + } + + if (CryptoSuiteFactory.DEFAULT_JDK_PROVIDER.equals(securityProviderClassName)) { + return null; + } + + Class aClass = Class.forName(securityProviderClassName); + if (null == aClass) { + throw new InstantiationException(format("Getting class for security provider %s returned null ", securityProviderClassName)); + } + if (!Provider.class.isAssignableFrom(aClass)) { + throw new InstantiationException(format("Class for security provider %s is not a Java security provider", aClass.getName())); + } + Provider securityProvider = (Provider) aClass.newInstance(); + if (securityProvider == null) { + throw new InstantiationException(format("Creating instance of security %s returned null ", aClass.getName())); + } + return securityProvider; + } + +// /** +// * sets the signature algorithm used for signing/verifying. +// * +// * @param sigAlg the name of the signature algorithm. See the list of valid names in the JCA Standard Algorithm Name documentation +// */ +// public void setSignatureAlgorithm(String sigAlg) { +// this.DEFAULT_SIGNATURE_ALGORITHM = sigAlg; +// } + +// /** +// * returns the signature algorithm used by this instance of CryptoPrimitives. +// * Note that fabric and fabric-ca have not yet standardized on which algorithms are supported. +// * While that plays out, CryptoPrimitives will try the algorithm specified in the certificate and +// * the default SHA256withECDSA that's currently hardcoded for fabric and fabric-ca +// * +// * @return the name of the signature algorithm +// */ +// public String getSignatureAlgorithm() { +// return this.DEFAULT_SIGNATURE_ALGORITHM; +// } + + @Override + public Certificate bytesToCertificate(byte[] certBytes) throws CryptoException { + if (certBytes == null || certBytes.length == 0) { + throw new CryptoException("bytesToCertificate: input null or zero length"); + } + + return getX509Certificate(certBytes); +// X509Certificate certificate; +// try { +// BufferedInputStream pem = new BufferedInputStream(new ByteArrayInputStream(certBytes)); +// CertificateFactory certFactory = CertificateFactory.getInstance(CERTIFICATE_FORMAT); +// certificate = (X509Certificate) certFactory.generateCertificate(pem); +// } catch (CertificateException e) { +// String emsg = "Unable to converts byte array to certificate. error : " + e.getMessage(); +// logger.error(emsg); +// logger.debug("input bytes array :" + new String(certBytes)); +// throw new CryptoException(emsg, e); +// } +// +// return certificate; + } + + /** + * Return X509Certificate from pem bytes. + * So you may ask why this ? Well some providers (BC) seems to have problems with creating the + * X509 cert from bytes so here we go through all available providers till one can convert. :) + * + * @param pemCertificate + * @return + */ + + private X509Certificate getX509Certificate(byte[] pemCertificate) throws CryptoException { + X509Certificate ret = null; + CryptoException rete = null; + + List providerList = new LinkedList<>(Arrays.asList(Security.getProviders())); + if (SECURITY_PROVIDER != null) { //Add if overridden + providerList.add(SECURITY_PROVIDER); + } + try { + providerList.add(BouncyCastleProvider.class.newInstance()); // bouncy castle is there always. + } catch (Exception e) { + logger.warn(e); + + } + for (Provider provider : providerList) { + try { + if (null == provider) { + continue; + } + CertificateFactory certFactory = CertificateFactory.getInstance(CERTIFICATE_FORMAT, provider); + if (null != certFactory) { + try (ByteArrayInputStream bis = new ByteArrayInputStream(pemCertificate)) { + Certificate certificate = certFactory.generateCertificate(bis); + + if (certificate instanceof X509Certificate) { + ret = (X509Certificate) certificate; + rete = null; + break; + } + } + + } + } catch (Exception e) { + + rete = new CryptoException(e.getMessage(), e); + + } + + } + + if (null != rete) { + + throw rete; + + } + + if (ret == null) { + + logger.error("Could not convert pem bytes"); + + } + + return ret; + + } + + /** + * Return PrivateKey from pem bytes. + * + * @param pemKey pem-encoded private key + * @return + */ + public PrivateKey bytesToPrivateKey(byte[] pemKey) throws CryptoException { + PrivateKey pk; + + try { + PemReader pr = new PemReader(new StringReader(new String(pemKey))); + PemObject po = pr.readPemObject(); + PEMParser pem = new PEMParser(new StringReader(new String(pemKey))); + + if (po.getType().equals("PRIVATE KEY")) { + pk = new JcaPEMKeyConverter().getPrivateKey((PrivateKeyInfo) pem.readObject()); + } else { + logger.trace("Found private key with type " + po.getType()); + PEMKeyPair kp = (PEMKeyPair) pem.readObject(); + pk = new JcaPEMKeyConverter().getPrivateKey(kp.getPrivateKeyInfo()); + } + } catch (Exception e) { + throw new CryptoException("Failed to convert private key bytes", e); + } + return pk; + } + + @Override + public boolean verify(byte[] pemCertificate, String signatureAlgorithm, byte[] signature, byte[] plainText) throws CryptoException { + boolean isVerified = false; + + if (plainText == null || signature == null || pemCertificate == null) { + return false; + } + + if (config.extraLogLevel(10)) { + if (null != diagnosticFileDumper) { + String message = "plaintext in hex: " + DatatypeConverter.printHexBinary(plainText) + '\n' + + "signature in hex: " + DatatypeConverter.printHexBinary(signature) + '\n' + + "PEM cert in hex: " + DatatypeConverter.printHexBinary(pemCertificate); + logger.trace("verify : " + + diagnosticFileDumper.createDiagnosticFile(message)); + } + } + + try { + + X509Certificate certificate = getX509Certificate(pemCertificate); + + if (certificate != null) { + + //isVerified = validateCertificate(certificate); + isVerified = true; + if (isVerified) { // only proceed if cert is trusted + + Signature sig = Signature.getInstance(signatureAlgorithm); + sig.initVerify(certificate); + sig.update(plainText); + isVerified = sig.verify(signature); + } + } + } catch (InvalidKeyException e) { + CryptoException ex = new CryptoException("Cannot verify signature. Error is: " + + e.getMessage() + "\r\nCertificate: " + + DatatypeConverter.printHexBinary(pemCertificate), e); + logger.error(ex.getMessage(), ex); + throw ex; + } catch (NoSuchAlgorithmException | SignatureException e) { + CryptoException ex = new CryptoException("Cannot verify. Signature algorithm is invalid. Error is: " + e.getMessage(), e); + logger.error(ex.getMessage(), ex); + throw ex; + } + + return isVerified; + } // verify + + private KeyStore trustStore = null; + + private void createTrustStore() throws CryptoException { + try { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, null); + setTrustStore(keyStore); + } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | InvalidArgumentException e) { + throw new CryptoException("Cannot create trust store. Error: " + e.getMessage(), e); + } + } + + /** + * setTrustStore uses the given KeyStore object as the container for trusted + * certificates + * + * @param keyStore the KeyStore which will be used to hold trusted certificates + * @throws InvalidArgumentException + */ + private void setTrustStore(KeyStore keyStore) throws InvalidArgumentException { + + if (keyStore == null) { + throw new InvalidArgumentException("Need to specify a java.security.KeyStore input parameter"); + } + + trustStore = keyStore; + } + + /** + * getTrustStore returns the KeyStore object where we keep trusted certificates. + * If no trust store has been set, this method will create one. + * + * @return the trust store as a java.security.KeyStore object + * @throws CryptoException + * @see KeyStore + */ + public KeyStore getTrustStore() throws CryptoException { + if (trustStore == null) { + createTrustStore(); + } + return trustStore; + } + + /** + * addCACertificateToTrustStore adds a CA cert to the set of certificates used for signature validation + * + * @param caCertPem an X.509 certificate in PEM format + * @param alias an alias associated with the certificate. Used as shorthand for the certificate during crypto operations + * @throws CryptoException + * @throws InvalidArgumentException + */ + public void addCACertificateToTrustStore(File caCertPem, String alias) throws CryptoException, InvalidArgumentException { + + if (caCertPem == null) { + throw new InvalidArgumentException("The certificate cannot be null"); + } + + if (alias == null || alias.isEmpty()) { + throw new InvalidArgumentException("You must assign an alias to a certificate when adding to the trust store"); + } + + try { + try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(FileUtils.readFileToByteArray(caCertPem)))) { + + Certificate caCert = cf.generateCertificate(bis); + addCACertificateToTrustStore(caCert, alias); + } + } catch (CertificateException | IOException e) { + throw new CryptoException("Unable to add CA certificate to trust store. Error: " + e.getMessage(), e); + } + + } + + /** + * addCACertificatesToTrustStore adds a CA certs in a stream to the trust store used for signature validation + * + * @param bis an X.509 certificate stream in PEM format in bytes + * @throws CryptoException + * @throws InvalidArgumentException + */ + public void addCACertificatesToTrustStore(BufferedInputStream bis) throws CryptoException, InvalidArgumentException { + + if (bis == null) { + throw new InvalidArgumentException("The certificate stream bis cannot be null"); + } + + try { + final Collection certificates = cf.generateCertificates(bis); + for (Certificate certificate : certificates) { + addCACertificateToTrustStore(certificate); + } + + } catch (CertificateException e) { + throw new CryptoException("Unable to add CA certificate to trust store. Error: " + e.getMessage(), e); + } + } + + final Set certificateSet = ConcurrentHashMap.newKeySet(); + + private void addCACertificateToTrustStore(Certificate certificate) throws InvalidArgumentException, CryptoException { + + String alias; + if (certificate instanceof X509Certificate) { + alias = ((X509Certificate) certificate).getSerialNumber().toString(); + } else { // not likely ... + alias = Integer.toString(certificate.hashCode()); + } + addCACertificateToTrustStore(certificate, alias); + } + + /** + * addCACertificateToTrustStore adds a CA cert to the set of certificates used for signature validation + * + * @param caCert an X.509 certificate + * @param alias an alias associated with the certificate. Used as shorthand for the certificate during crypto operations + * @throws CryptoException + * @throws InvalidArgumentException + */ + void addCACertificateToTrustStore(Certificate caCert, String alias) throws InvalidArgumentException, CryptoException { + + if (alias == null || alias.isEmpty()) { + throw new InvalidArgumentException("You must assign an alias to a certificate when adding to the trust store."); + } + + if (caCert == null) { + throw new InvalidArgumentException("Certificate cannot be null."); + } + + try { + if (config.extraLogLevel(10)) { + if (null != diagnosticFileDumper) { + logger.trace(format("Adding cert to trust store. alias: %s. certificate:", alias) + diagnosticFileDumper.createDiagnosticFile(alias + "cert: " + caCert.toString())); + } + } + synchronized (certificateSet) { + if (certificateSet.contains(alias)) { + return; + } + + getTrustStore().setCertificateEntry(alias, caCert); + certificateSet.add(alias); + + } + } catch (KeyStoreException e) { + String emsg = "Unable to add CA certificate to trust store. Error: " + e.getMessage(); + logger.error(emsg, e); + throw new CryptoException(emsg, e); + } + } + + @Override + public void loadCACertificates(Collection certificates) throws CryptoException { + if (certificates == null || certificates.size() == 0) { + throw new CryptoException("Unable to load CA certificates. List is empty"); + } + + try { + for (Certificate cert : certificates) { + + addCACertificateToTrustStore(cert); + } + } catch (InvalidArgumentException e) { + // Note: This can currently never happen (as cert<>null and alias<>null) + throw new CryptoException("Unable to add certificate to trust store. Error: " + e.getMessage(), e); + } + } + + /* (non-Javadoc) + * @see org.hyperledger.fabric.sdk.security.CryptoSuite#loadCACertificatesAsBytes(java.util.Collection) + */ + @Override + public void loadCACertificatesAsBytes(Collection certificatesBytes) throws CryptoException { + if (certificatesBytes == null || certificatesBytes.size() == 0) { + throw new CryptoException("List of CA certificates is empty. Nothing to load."); + } + + ArrayList certList = new ArrayList<>(); + for (byte[] certBytes : certificatesBytes) { + certList.add(bytesToCertificate(certBytes)); + } + loadCACertificates(certList); + + } + + /** + * Adds a CA certificate with a private key and password. + * + * @param clientKey the private key bytes input stream. Cannot be null. + * @param clientCert the client certificate bytes input stream. Cannot be null. + * @param clientKeyPassword the password as a String. Can be null. + */ + public void addClientCACertificateToTrustStore(byte[] clientKey, byte[] clientCert, String clientKeyPassword) throws CryptoException, IllegalArgumentException { + if (clientKey == null) { + throw new IllegalArgumentException("Client key byte input stream is required."); + } + if (clientCert == null) { + throw new IllegalArgumentException("Client certificate byte input stream is required."); + } + try { + Certificate tlsClientCertificate = bytesToCertificate(clientCert); + + String alias; + if (tlsClientCertificate instanceof X509Certificate) { + alias = ((X509Certificate) tlsClientCertificate).getSerialNumber().toString(); + } else { // not likely ... + alias = Integer.toString(tlsClientCertificate.hashCode()); + } + char[] password = clientKeyPassword == null ? new char[0] : clientKeyPassword.toCharArray(); + + getTrustStore().setKeyEntry(alias, bytesToPrivateKey(clientKey), password, new Certificate[] {tlsClientCertificate}); + } catch (KeyStoreException e) { + throw new CryptoException("Unable to add client CA certificate to trust store.", e); + } + } + + + /** + * validateCertificate checks whether the given certificate is trusted. It + * checks if the certificate is signed by one of the trusted certs in the + * trust store. + * + * @param certPEM the certificate in PEM format + * @return true if the certificate is trusted + */ + boolean validateCertificate(byte[] certPEM) { + + if (certPEM == null) { + return false; + } + + try { + + X509Certificate certificate = getX509Certificate(certPEM); + if (null == certificate) { + throw new Exception("Certificate transformation returned null"); + } + + return validateCertificate(certificate); + } catch (Exception e) { + logger.error("Cannot validate certificate. Error is: " + e.getMessage() + "\r\nCertificate (PEM, hex): " + + DatatypeConverter.printHexBinary(certPEM)); + return false; + } + } + + boolean validateCertificate(Certificate cert) { + boolean isValidated; + + if (cert == null) { + return false; + } + + try { + KeyStore keyStore = getTrustStore(); + + PKIXParameters parms = new PKIXParameters(keyStore); + parms.setRevocationEnabled(false); + + CertPathValidator certValidator = CertPathValidator.getInstance(CertPathValidator.getDefaultType()); // PKIX + + ArrayList start = new ArrayList<>(); + start.add(cert); + CertificateFactory certFactory = CertificateFactory.getInstance(CERTIFICATE_FORMAT); + CertPath certPath = certFactory.generateCertPath(start); + + certValidator.validate(certPath, parms); + isValidated = true; + } catch (KeyStoreException | InvalidAlgorithmParameterException | NoSuchAlgorithmException + | CertificateException | CertPathValidatorException | CryptoException e) { + logger.error("Cannot validate certificate. Error is: " + e.getMessage() + "\r\nCertificate" + + cert.toString()); + isValidated = false; + } + + return isValidated; + } // validateCertificate + + /** + * Security Level determines the elliptic curve used in key generation + * + * @param securityLevel currently 256 or 384 + * @throws InvalidArgumentException + */ + void setSecurityLevel(final int securityLevel) throws InvalidArgumentException { + logger.trace(format("setSecurityLevel to %d", securityLevel)); + + if (securityCurveMapping.isEmpty()) { + throw new InvalidArgumentException("Security curve mapping has no entries."); + } + + if (!securityCurveMapping.containsKey(securityLevel)) { + StringBuilder sb = new StringBuilder(); + String sp = ""; + for (int x : securityCurveMapping.keySet()) { + sb.append(sp).append(x); + + sp = ", "; + + } + throw new InvalidArgumentException(format("Illegal security level: %d. Valid values are: %s", securityLevel, sb.toString())); + } + + String lcurveName = securityCurveMapping.get(securityLevel); + + logger.debug(format("Mapped curve strength %d to %s", securityLevel, lcurveName)); + + X9ECParameters params = ECNamedCurveTable.getByName(lcurveName); + //Check if can match curve name to requested strength. + if (params == null) { + + InvalidArgumentException invalidArgumentException = new InvalidArgumentException( + format("Curve %s defined for security strength %d was not found.", curveName, securityLevel)); + + logger.error(invalidArgumentException); + throw invalidArgumentException; + + } + + curveName = lcurveName; + this.securityLevel = securityLevel; + } + + void setHashAlgorithm(String algorithm) throws InvalidArgumentException { + if (isNullOrEmpty(algorithm) + || !("SHA2".equals(algorithm) || "SHA3".equals(algorithm))) { + throw new InvalidArgumentException("Illegal Hash function family: " + + algorithm + " - must be either SHA2 or SHA3"); + } + + hashAlgorithm = algorithm; + } + + @Override + public KeyPair keyGen() throws CryptoException { + return ecdsaKeyGen(); + } + + private KeyPair ecdsaKeyGen() throws CryptoException { + return generateKey("EC", curveName); + } + + private KeyPair generateKey(String encryptionName, String curveName) throws CryptoException { + try { + ECGenParameterSpec ecGenSpec = new ECGenParameterSpec(curveName); + KeyPairGenerator g = SECURITY_PROVIDER == null ? KeyPairGenerator.getInstance(encryptionName) : + KeyPairGenerator.getInstance(encryptionName, SECURITY_PROVIDER); + g.initialize(ecGenSpec, new SecureRandom()); + return g.generateKeyPair(); + } catch (Exception exp) { + throw new CryptoException("Unable to generate key pair", exp); + } + } + + /** + * Decodes an ECDSA signature and returns a two element BigInteger array. + * + * @param signature ECDSA signature bytes. + * @return BigInteger array for the signature's r and s values + * @throws Exception + */ + private static BigInteger[] decodeECDSASignature(byte[] signature) throws Exception { + + try (ByteArrayInputStream inStream = new ByteArrayInputStream(signature)) { + ASN1InputStream asnInputStream = new ASN1InputStream(inStream); + ASN1Primitive asn1 = asnInputStream.readObject(); + + BigInteger[] sigs = new BigInteger[2]; + int count = 0; + if (asn1 instanceof ASN1Sequence) { + ASN1Sequence asn1Sequence = (ASN1Sequence) asn1; + ASN1Encodable[] asn1Encodables = asn1Sequence.toArray(); + for (ASN1Encodable asn1Encodable : asn1Encodables) { + ASN1Primitive asn1Primitive = asn1Encodable.toASN1Primitive(); + if (asn1Primitive instanceof ASN1Integer) { + ASN1Integer asn1Integer = (ASN1Integer) asn1Primitive; + BigInteger integer = asn1Integer.getValue(); + if (count < 2) { + sigs[count] = integer; + } + count++; + } + } + } + if (count != 2) { + throw new CryptoException(format("Invalid ECDSA signature. Expected count of 2 but got: %d. Signature is: %s", count, + DatatypeConverter.printHexBinary(signature))); + } + return sigs; + } + + } + + /** + * Sign data with the specified elliptic curve private key. + * + * @param privateKey elliptic curve private key. + * @param data data to sign + * @return the signed data. + * @throws CryptoException + */ + private byte[] ecdsaSignToBytes(ECPrivateKey privateKey, byte[] data) throws CryptoException { + if (data == null) { + throw new CryptoException("Data that to be signed is null."); + } + if (data.length == 0) { + throw new CryptoException("Data to be signed was empty."); + } + + try { + X9ECParameters params = ECNamedCurveTable.getByName(curveName); + BigInteger curveN = params.getN(); + + Signature sig = SECURITY_PROVIDER == null ? Signature.getInstance(DEFAULT_SIGNATURE_ALGORITHM) : + Signature.getInstance(DEFAULT_SIGNATURE_ALGORITHM, SECURITY_PROVIDER); + sig.initSign(privateKey); + sig.update(data); + byte[] signature = sig.sign(); + + BigInteger[] sigs = preventMalleability(decodeECDSASignature(signature), curveN); + + try (ByteArrayOutputStream s = new ByteArrayOutputStream()) { + + DERSequenceGenerator seq = new DERSequenceGenerator(s); + seq.addObject(new ASN1Integer(sigs[0])); + seq.addObject(new ASN1Integer(sigs[1])); + seq.close(); + return s.toByteArray(); + } + + } catch (Exception e) { + throw new CryptoException("Could not sign the message using private key", e); + } + + } + + /** + * @throws ClassCastException if the supplied private key is not of type {@link ECPrivateKey}. + */ + @Override + public byte[] sign(PrivateKey key, byte[] data) throws CryptoException { + return ecdsaSignToBytes((ECPrivateKey) key, data); + } + + private BigInteger[] preventMalleability(BigInteger[] sigs, BigInteger curveN) { + BigInteger cmpVal = curveN.divide(BigInteger.valueOf(2L)); + + BigInteger sval = sigs[1]; + + if (sval.compareTo(cmpVal) > 0) { + + sigs[1] = curveN.subtract(sval); + } + + return sigs; + } + + /** + * generateCertificationRequest + * + * @param subject The subject to be added to the certificate + * @param pair Public private key pair + * @return PKCS10CertificationRequest Certificate Signing Request. + * @throws OperatorCreationException + */ + + @Override + public String generateCertificationRequest(String subject, KeyPair pair) + throws InvalidArgumentException { + + try { + PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder( + new X500Principal("CN=" + subject), pair.getPublic()); + + JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder("SHA256withECDSA"); + + if (null != SECURITY_PROVIDER) { + csBuilder.setProvider(SECURITY_PROVIDER); + } + ContentSigner signer = csBuilder.build(pair.getPrivate()); + + return certificationRequestToPEM(p10Builder.build(signer)); + } catch (Exception e) { + + logger.error(e); + throw new InvalidArgumentException(e); + + } + + } + + /** + * certificationRequestToPEM - Convert a PKCS10CertificationRequest to PEM + * format. + * + * @param csr The Certificate to convert + * @return An equivalent PEM format certificate. + * @throws IOException + */ + + private String certificationRequestToPEM(PKCS10CertificationRequest csr) throws IOException { + PemObject pemCSR = new PemObject("CERTIFICATE REQUEST", csr.getEncoded()); + + StringWriter str = new StringWriter(); + JcaPEMWriter pemWriter = new JcaPEMWriter(str); + pemWriter.writeObject(pemCSR); + pemWriter.close(); + str.close(); + return str.toString(); + } + +// public PrivateKey ecdsaKeyFromPrivate(byte[] key) throws CryptoException { +// try { +// EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(key); +// KeyFactory generator = KeyFactory.getInstance("ECDSA", SECURITY_PROVIDER_NAME); +// PrivateKey privateKey = generator.generatePrivate(privateKeySpec); +// +// return privateKey; +// } catch (Exception exp) { +// throw new CryptoException("Unable to convert byte[] into PrivateKey", exp); +// } +// } + + @Override + public byte[] hash(byte[] input) { + Digest digest = getHashDigest(); + byte[] retValue = new byte[digest.getDigestSize()]; + digest.update(input, 0, input.length); + digest.doFinal(retValue, 0); + return retValue; + } + + @Override + public CryptoSuiteFactory getCryptoSuiteFactory() { + return HLSDKJCryptoSuiteFactory.instance(); //Factory for this crypto suite. + } + + private final AtomicBoolean inited = new AtomicBoolean(false); + + // @Override + public void init() throws CryptoException, InvalidArgumentException { + if (inited.getAndSet(true)) { + throw new InvalidArgumentException("Crypto suite already initialized"); + } else { + resetConfiguration(); + } + + } + + private Digest getHashDigest() { + if ("SHA3".equals(hashAlgorithm)) { + return new SHA3Digest(); + } else { + // Default to SHA2 + return new SHA256Digest(); + } + } + +// /** +// * Shake256 hash the supplied byte data. +// * +// * @param in byte array to be hashed. +// * @param bitLength of the result. +// * @return the hashed byte data. +// */ +// public byte[] shake256(byte[] in, int bitLength) { +// +// if (bitLength % 8 != 0) { +// throw new IllegalArgumentException("bit length not modulo 8"); +// +// } +// +// final int byteLen = bitLength / 8; +// +// SHAKEDigest sd = new SHAKEDigest(256); +// +// sd.update(in, 0, in.length); +// +// byte[] out = new byte[byteLen]; +// +// sd.doFinal(out, 0, byteLen); +// +// return out; +// +// } + + /** + * Resets curve name, hash algorithm and cert factory. Call this method when a config value changes + * + * @throws CryptoException + * @throws InvalidArgumentException + */ + private void resetConfiguration() throws CryptoException, InvalidArgumentException { + + setSecurityLevel(securityLevel); + + setHashAlgorithm(hashAlgorithm); + + try { + cf = CertificateFactory.getInstance(CERTIFICATE_FORMAT); + } catch (CertificateException e) { + CryptoException ex = new CryptoException("Cannot initialize " + CERTIFICATE_FORMAT + " certificate factory. Error = " + e.getMessage(), e); + logger.error(ex.getMessage(), ex); + throw ex; + } + } + + // /* (non-Javadoc) +// * @see org.hyperledger.fabric.sdk.security.CryptoSuite#setProperties(java.util.Properties) +// */ +// @Override + void setProperties(Properties properties) throws CryptoException, InvalidArgumentException { + if (properties == null) { + throw new InvalidArgumentException("properties must not be null"); + } + // if (properties != null) { + hashAlgorithm = Optional.ofNullable(properties.getProperty(Config.HASH_ALGORITHM)).orElse(hashAlgorithm); + String secLevel = Optional.ofNullable(properties.getProperty(Config.SECURITY_LEVEL)).orElse(Integer.toString(securityLevel)); + securityLevel = Integer.parseInt(secLevel); + if (properties.containsKey(Config.SECURITY_CURVE_MAPPING)) { + securityCurveMapping = Config.parseSecurityCurveMappings(properties.getProperty(Config.SECURITY_CURVE_MAPPING)); + } else { + securityCurveMapping = config.getSecurityCurveMapping(); + } + + final String providerName = properties.containsKey(Config.SECURITY_PROVIDER_CLASS_NAME) ? + properties.getProperty(Config.SECURITY_PROVIDER_CLASS_NAME) : + config.getSecurityProviderClassName(); + + try { + SECURITY_PROVIDER = setUpExplicitProvider(providerName); + } catch (Exception e) { + throw new InvalidArgumentException(format("Getting provider for class name: %s", providerName), e); + + } + CERTIFICATE_FORMAT = Optional.ofNullable(properties.getProperty(Config.CERTIFICATE_FORMAT)).orElse(CERTIFICATE_FORMAT); + DEFAULT_SIGNATURE_ALGORITHM = Optional.ofNullable(properties.getProperty(Config.SIGNATURE_ALGORITHM)).orElse(DEFAULT_SIGNATURE_ALGORITHM); + + resetConfiguration(); + + } + + /* (non-Javadoc) + * @see org.hyperledger.fabric.sdk.security.CryptoSuite#getProperties() + */ + @Override + public Properties getProperties() { + Properties properties = new Properties(); + properties.setProperty(Config.HASH_ALGORITHM, hashAlgorithm); + properties.setProperty(Config.SECURITY_LEVEL, Integer.toString(securityLevel)); + properties.setProperty(Config.CERTIFICATE_FORMAT, CERTIFICATE_FORMAT); + properties.setProperty(Config.SIGNATURE_ALGORITHM, DEFAULT_SIGNATURE_ALGORITHM); + return properties; + } + + public byte[] certificateToDER(String certificatePEM) { + + byte[] content = null; + + try (PemReader pemReader = new PemReader(new StringReader(certificatePEM))) { + final PemObject pemObject = pemReader.readPemObject(); + content = pemObject.getContent(); + + } catch (IOException e) { + // best attempt + } + + return content; + } + +} diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/security/CryptoSuite.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/security/CryptoSuite.java new file mode 100644 index 0000000..3b11461 --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/security/CryptoSuite.java @@ -0,0 +1,169 @@ +/* + * Copyright 2016,2017 DTCC, Fujitsu Australia Software Technology, IBM - All Rights Reserved. + * + * 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 com.chinaunicom.mall.ebtp.common.crypto.security; + + +import com.chinaunicom.mall.ebtp.common.crypto.exception.CryptoException; +import com.chinaunicom.mall.ebtp.common.crypto.exception.InvalidArgumentException; + +import java.lang.reflect.InvocationTargetException; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.util.Collection; +import java.util.Properties; + +/** + * All packages for PKI key creation/signing/verification implement this interface + */ +public interface CryptoSuite { + + /** + * Get Crypto Suite Factory for this implementation. + * + * @return MUST return the one and only one instance of a factory that produced this crypto suite. + */ + + CryptoSuiteFactory getCryptoSuiteFactory(); + + /** + * @return the {@link Properties} object containing implementation specific key generation properties + */ + Properties getProperties(); + + /** + * Set the Certificate Authority certificates to be used when validating a certificate chain of trust + * + * @param certificates A collection of {@link Certificate}s + * @throws CryptoException + */ + void loadCACertificates(Collection certificates) throws CryptoException; + + /** + * Set the Certificate Authority certificates to be used when validating a certificate chain of trust. + * + * @param certificates a collection of certificates in PEM format + * @throws CryptoException + */ + void loadCACertificatesAsBytes(Collection certificates) throws CryptoException; + + /** + * Generate a key. + * + * @return the generated key. + * @throws CryptoException + */ + KeyPair keyGen() throws CryptoException; + + /** + * Sign the specified byte string. + * + * @param key the {@link PrivateKey} to be used for signing + * @param plainText the byte string to sign + * @return the signed data. + * @throws CryptoException + */ + byte[] sign(PrivateKey key, byte[] plainText) throws CryptoException; + + /** + * Verify the specified signature + * + * @param certificate the certificate of the signer as the contents of the PEM file + * @param signatureAlgorithm the algorithm used to create the signature. + * @param signature the signature to verify + * @param plainText the original text that is to be verified + * @return {@code true} if the signature is successfully verified; otherwise {@code false}. + * @throws CryptoException + */ + boolean verify(byte[] certificate, String signatureAlgorithm, byte[] signature, byte[] plainText) throws CryptoException; + + /** + * Hash the specified text byte data. + * + * @param plainText the text to hash + * @return the hashed data. + */ + byte[] hash(byte[] plainText); + + /** + * Generates a CertificationRequest + * + * @param user + * @param keypair + * @return String in PEM format for certificate request. + * @throws InvalidArgumentException + */ + String generateCertificationRequest(String user, KeyPair keypair) throws InvalidArgumentException; + + /** + * Convert bytes in PEM format to Certificate. + * + * @param certBytes + * @return Certificate + * @throws CryptoException + */ + Certificate bytesToCertificate(byte[] certBytes) throws CryptoException, CryptoException; + + /** + * The CryptoSuite factory. Currently {@link #getCryptoSuite} will always + * give you a {@link CryptoPrimitives} object + */ + + class Factory { + private Factory() { + + } + + /** + * Get a crypto suite with the default factory with default settings. + * Settings which can define such parameters such as curve strength, are specific to the crypto factory. + * + * @return Default crypto suite. + * @throws IllegalAccessException + * @throws InstantiationException + * @throws ClassNotFoundException + * @throws CryptoException + * @throws InvalidArgumentException + * @throws NoSuchMethodException + * @throws InvocationTargetException + */ + + public static CryptoSuite getCryptoSuite() throws IllegalAccessException, InstantiationException, + ClassNotFoundException, CryptoException, InvalidArgumentException, NoSuchMethodException, + InvocationTargetException { + return CryptoSuiteFactory.getDefault().getCryptoSuite(); + } + + /** + * Get a crypto suite with the default factory with settings defined by properties + * Properties are uniquely defined by the specific crypto factory. + * + * @param properties properties that define suite characteristics such as strength, curve, hashing . + * @return + * @throws IllegalAccessException + * @throws InstantiationException + * @throws ClassNotFoundException + * @throws CryptoException + * @throws InvalidArgumentException + * @throws NoSuchMethodException + * @throws InvocationTargetException + */ + public static CryptoSuite getCryptoSuite(Properties properties) throws IllegalAccessException, InstantiationException, + ClassNotFoundException, CryptoException, InvalidArgumentException, NoSuchMethodException, + InvocationTargetException { + return CryptoSuiteFactory.getDefault().getCryptoSuite(properties); + } + + } +} diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/security/CryptoSuiteFactory.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/security/CryptoSuiteFactory.java new file mode 100644 index 0000000..754a3d2 --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/security/CryptoSuiteFactory.java @@ -0,0 +1,81 @@ +/* + * + * Copyright 2016,2017 DTCC, Fujitsu Australia Software Technology, IBM - All Rights Reserved. + * + * 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 com.chinaunicom.mall.ebtp.common.crypto.security; + + +import com.chinaunicom.mall.ebtp.common.crypto.exception.CryptoException; +import com.chinaunicom.mall.ebtp.common.crypto.exception.InvalidArgumentException; + +import java.lang.reflect.InvocationTargetException; +import java.util.Properties; + +/** + * Factory to produce a set of crypto suite implementations offering differing cryptographic algorithms and strengths. + */ + +public interface CryptoSuiteFactory { + + /** + * If set as the default security provider then default crypto suite will not use explicit + * provider + */ + + String DEFAULT_JDK_PROVIDER = "org.hyperledger.fabric.sdk.security.default_jdk_provider"; + + /** + * Produce a crypto suite by specified by these properties. + * Properties are unique to each Crypto Suite implementation. + * + * @param properties + * @return + * @throws CryptoException + * @throws InvalidArgumentException + */ + + CryptoSuite getCryptoSuite(Properties properties) throws CryptoException, InvalidArgumentException; + + /** + * Return a default crypto suite + * @return + * @throws CryptoException + * @throws InvalidArgumentException + */ + + CryptoSuite getCryptoSuite() throws CryptoException, InvalidArgumentException; + + /** + * This will return the default Crypto Suite Factory implementation. + * Can be overwritten by org.hyperledger.fabric.sdk.crypto.default_crypto_suite_factory property. + * see + * Classes specified by this property must implement a public static method instance that + * returns back a single instance of this factory. + * + * @return A single instance of a CryptoSuiteFactory. + * @throws ClassNotFoundException + * @throws IllegalAccessException + * @throws InstantiationException + * @throws NoSuchMethodException + * @throws InvocationTargetException + */ + + static CryptoSuiteFactory getDefault() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { + + return HLSDKJCryptoSuiteFactory.getDefault(); + + } + +} \ No newline at end of file diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/security/HLSDKJCryptoSuiteFactory.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/security/HLSDKJCryptoSuiteFactory.java new file mode 100644 index 0000000..b47a3eb --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/security/HLSDKJCryptoSuiteFactory.java @@ -0,0 +1,120 @@ +/* + * Copyright 2016,2017 DTCC, Fujitsu Australia Software Technology, IBM - All Rights Reserved. + * + * 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 com.chinaunicom.mall.ebtp.common.crypto.security; + + +import com.chinaunicom.mall.ebtp.common.crypto.exception.CryptoException; +import com.chinaunicom.mall.ebtp.common.crypto.exception.InvalidArgumentException; +import com.chinaunicom.mall.ebtp.common.crypto.helper.Config; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +/** + * SDK's Default implementation of CryptoSuiteFactory. + */ +public class HLSDKJCryptoSuiteFactory implements CryptoSuiteFactory { + private static final Config config = Config.getConfig(); + private static final int SECURITY_LEVEL = config.getSecurityLevel(); + private static final String HASH_ALGORITHM = config.getHashAlgorithm(); + + private HLSDKJCryptoSuiteFactory() { + + } + + private static final Map cache = new ConcurrentHashMap<>(); + + @Override + public CryptoSuite getCryptoSuite(Properties properties) throws CryptoException, InvalidArgumentException { + + CryptoSuite ret = cache.get(properties); + if (ret == null) { + try { + CryptoPrimitives cp = new CryptoPrimitives(); + cp.setProperties(properties); + cp.init(); + ret = cp; + } catch (Exception e) { + throw new CryptoException(e.getMessage(), e); + } + + cache.put(properties, ret); + + } + + return ret; + + } + + @Override + public CryptoSuite getCryptoSuite() throws CryptoException, InvalidArgumentException { + + Properties properties = new Properties(); + properties.put(Config.SECURITY_LEVEL, SECURITY_LEVEL); + properties.put(Config.HASH_ALGORITHM, HASH_ALGORITHM); + + return getCryptoSuite(properties); + } + + private static final HLSDKJCryptoSuiteFactory INSTANCE = new HLSDKJCryptoSuiteFactory(); + + static synchronized HLSDKJCryptoSuiteFactory instance() { + + return INSTANCE; + } + + private static CryptoSuiteFactory theFACTORY = null; // one and only factory. + + static final synchronized CryptoSuiteFactory getDefault() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { + + if (null == theFACTORY) { + + String cf = config.getDefaultCryptoSuiteFactory(); + if (null == cf || cf.isEmpty() || cf.equals(HLSDKJCryptoSuiteFactory.class.getName())) { // Use this class as the factory. + + theFACTORY = HLSDKJCryptoSuiteFactory.instance(); + + } else { + + // Invoke static method instance on factory class specified by config properties. + // In this case this class will no longer be used as the factory. + + Class aClass = Class.forName(cf); + + Method method = aClass.getMethod("instance"); + Object theFACTORYObject = method.invoke(null); + if (null == theFACTORYObject) { + throw new InstantiationException(String.format("Class specified by %s has instance method returning null. Expected object implementing CryptoSuiteFactory interface.", cf)); + } + + if (!(theFACTORYObject instanceof CryptoSuiteFactory)) { + + throw new InstantiationException(String.format("Class specified by %s has instance method returning a class %s which does not implement interface CryptoSuiteFactory ", + cf, theFACTORYObject.getClass().getName())); + + } + + theFACTORY = (CryptoSuiteFactory) theFACTORYObject; + + } + } + + return theFACTORY; + } + +} \ No newline at end of file diff --git a/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/tenderfee/test.java b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/tenderfee/test.java new file mode 100644 index 0000000..c2d0a7e --- /dev/null +++ b/uboot-common/src/main/java/com/chinaunicom/mall/ebtp/common/crypto/tenderfee/test.java @@ -0,0 +1,63 @@ +package com.chinaunicom.mall.ebtp.common.crypto.tenderfee; + + +import com.chinaunicom.mall.ebtp.common.crypto.exception.InvalidArgumentException; +import com.chinaunicom.mall.ebtp.common.crypto.service.CrypServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.crypto.CryptoException; + +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +@Slf4j +public class test { + + // 私钥文件路径 - 加密 + private static String PEM_PATH = "admin_certPrivate.pem"; + + // 证书文件路径 - 解密 + private static String CRT_PATH = "admin.crt"; + public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, CryptoException, InvalidArgumentException, UnsupportedEncodingException { + + //String token = AccessToken.tokenCreate(); + + URL pem = test.class.getClassLoader().getResource(PEM_PATH); + URL crt = test.class.getClassLoader().getResource(CRT_PATH); + + PEM_PATH = pem.getPath(); + CRT_PATH = crt.getPath(); + + + // example of bean entity + String pemVal = "-----BEGIN PRIVATE KEY-----\n" + + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgjF+tq8oc1tNjot69\n" + + "9OQgzr7Dqg1OkVo4PY4tKBL8+82hRANCAARKLIuOloTZe1B0J0k+CITZdsx8Gham\n" + + "JTuxCRGLdCLpq6wHHGEqWn9VDkwk5eX6OYQxYuBRiPZp7gP/njpx5CkF\n" + + "-----END PRIVATE KEY-----"; + + // 需正确设置bean的@JSONField,以确保解析到的字段名称与文档一致 +// List paramList = new ArrayList<>(); +// BidTenderFeeBaseParam bean = new BidTenderFeeBaseParam(); +// bean.setTenderId("8533"); +// bean.setShoppingCartId("L3307"); +// bean.setAmount("1000"); +// bean.setTpId("L3307A"); +// bean.setSectionId("1111"); +// +// paramList.add(bean); +// // 用于签名的Bean将被signObject转换为json(String,然后转换为byte[]),请确保该json只包含文档规定的业务字段,且“SGIN"不应包含其中 +// String signatureOfBean = CrypServiceImpl.signObject2(paramList,pemVal);//CrypService.signObject(paramList); +// System.out.println("signature of Bean: "+signatureOfBean); +// boolean isOkBean = CrypServiceImpl.verifyObject(signatureOfBean,paramList); +// System.out.println("verify result of Bean: "+ isOkBean); +// String signatureOfBean = signObject(paramList,PEM_PATH); +// // 生成的签名现在可以追加到签名字段 +// System.out.println("signature of Bean: "+signatureOfBean); +// boolean isOkBean = verifyObject(signatureOfBean,paramList,CRT_PATH); +// System.out.println("verify result of Bean: "+ isOkBean); + + + } + +}