Java tutorial
/** * The MIT License * Copyright (c) 2015 Estonian Information System Authority (RIA), Population Register Centre (VRK) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package ee.ria.xroad.signer.tokenmanager.module; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import iaik.pkcs.pkcs11.wrapper.PKCS11Constants; import lombok.extern.slf4j.Slf4j; import org.apache.commons.configuration.ConfigurationRuntimeException; import org.apache.commons.configuration.ConversionException; import org.apache.commons.configuration.HierarchicalINIConfiguration; import org.apache.commons.configuration.SubnodeConfiguration; import org.apache.commons.lang.StringUtils; import ee.ria.xroad.common.util.FileContentChangeChecker; import static ee.ria.xroad.common.SystemProperties.getDeviceConfFile; /** * Encapsulates module data read form the external configuration file. * * Each module specifies an UID, the pkcs#11 library path and other options specific to that module. */ @Slf4j public final class ModuleConf { /** The type used to identify software keys in key configuration. */ private static final String SOFTKEY_TYPE = "softToken"; // Maps Type (UID) to ModuleType private static final Map<String, ModuleType> MODULES = new HashMap<>(); private static final String DEFAULT_TOKEN_ID_FORMAT = "{moduleType}{slotIndex}{serialNumber}{label}"; // Maps mechanism name to mechanism code of supported sign mechanisms private static final Map<String, Long> SUPPORTED_SIGN_MECHANISMS = createSupportedSignMechanismsMap(); // Maps mechanism name to mechanism code of supported key allowed mechanisms private static final Map<String, Long> SUPPORTED_KEY_ALLOWED_MECHANISMS = createSupportedKeyAllowedMechanismMap(); private static final String DEFAULT_SIGN_MECHANISM_NAME = PKCS11Constants.NAME_CKM_RSA_PKCS; // Module configuration fields private static final String ENABLED_PARAM = "enabled"; private static final String SIGN_VERIFY_PIN_PARAM = "sign_verify_pin"; private static final String BATCH_SIGNING_ENABLED_PARAM = "batch_signing_enabled"; private static final String READ_ONLY_PARAM = "read_only"; private static final String TOKEN_ID_FORMAT_PARAM = "token_id_format"; private static final String SIGN_MECHANISM_PARAM = "sign_mechanism"; private static final String PUB_KEY_ATTRIBUTE_ENCRYPT_PARAM = "pub_key_attribute_encrypt"; private static final String PUB_KEY_ATTRIBUTE_VERIFY_PARAM = "pub_key_attribute_verify"; private static final String PUB_KEY_ATTRIBUTE_VERIFY_RECOVER_PARAM = "pub_key_attribute_verify_recover"; private static final String PUB_KEY_ATTRIBUTE_WRAP_PARAM = "pub_key_attribute_wrap"; private static final String PUB_KEY_ATTRIBUTE_TRUSTED_PARAM = "pub_key_attribute_trusted"; private static final String PUB_KEY_ATTRIBUTE_ALLOWED_MECHANISMS_PARAM = "pub_key_attribute_allowed_mechanisms"; private static final String PRIV_KEY_ATTRIBUTE_SENSITIVE_PARAM = "priv_key_attribute_sensitive"; private static final String PRIV_KEY_ATTRIBUTE_DECRYPT_PARAM = "priv_key_attribute_decrypt"; private static final String PRIV_KEY_ATTRIBUTE_SIGN_PARAM = "priv_key_attribute_sign"; private static final String PRIV_KEY_ATTRIBUTE_SIGN_RECOVER_PARAM = "priv_key_attribute_sign_recover"; private static final String PRIV_KEY_ATTRIBUTE_UNWRAP_PARAM = "priv_key_attribute_unwrap"; private static final String PRIV_KEY_ATTRIBUTE_EXTRACTABLE_PARAM = "priv_key_attribute_extractable"; private static final String PRIV_KEY_ATTRIBUTE_ALWAYS_SENSITIVE_PARAM = "priv_key_attribute_always_sensitive"; private static final String PRIV_KEY_ATTRIBUTE_NEVER_EXTRACTABLE_PARAM = "priv_key_attribute_never_extractable"; private static final String PRIV_KEY_ATTRIBUTE_WRAP_WITH_TRUSTED_PARAM = "priv_key_attribute_wrap_with_trusted"; private static final String PRIV_KEY_ATTRIBUTE_ALLOWED_MECHANISMS_PARAM = "priv_key_attribute_allowed_mechanisms"; private static FileContentChangeChecker changeChecker = null; private static Map<String, Long> createSupportedSignMechanismsMap() { Map<String, Long> mechanisms = new HashMap<>(); mechanisms.put(PKCS11Constants.NAME_CKM_RSA_PKCS, PKCS11Constants.CKM_RSA_PKCS); mechanisms.put(PKCS11Constants.NAME_CKM_RSA_PKCS_PSS, PKCS11Constants.CKM_RSA_PKCS_PSS); return mechanisms; } private static Map<String, Long> createSupportedKeyAllowedMechanismMap() { Map<String, Long> mechanisms = new HashMap<>(); mechanisms.put(PKCS11Constants.NAME_CKM_RSA_PKCS, PKCS11Constants.CKM_RSA_PKCS); mechanisms.put(PKCS11Constants.NAME_CKM_SHA256_RSA_PKCS, PKCS11Constants.CKM_SHA256_RSA_PKCS); mechanisms.put(PKCS11Constants.NAME_CKM_SHA384_RSA_PKCS, PKCS11Constants.CKM_SHA384_RSA_PKCS); mechanisms.put(PKCS11Constants.NAME_CKM_SHA512_RSA_PKCS, PKCS11Constants.CKM_SHA512_RSA_PKCS); mechanisms.put(PKCS11Constants.NAME_CKM_RSA_PKCS_PSS, PKCS11Constants.CKM_RSA_PKCS_PSS); mechanisms.put(PKCS11Constants.NAME_CKM_SHA256_RSA_PKCS_PSS, PKCS11Constants.CKM_SHA256_RSA_PKCS_PSS); mechanisms.put(PKCS11Constants.NAME_CKM_SHA384_RSA_PKCS_PSS, PKCS11Constants.CKM_SHA384_RSA_PKCS_PSS); mechanisms.put(PKCS11Constants.NAME_CKM_SHA512_RSA_PKCS_PSS, PKCS11Constants.CKM_SHA512_RSA_PKCS_PSS); return mechanisms; } private ModuleConf() { } /** * @return sign mechanism code, null in case not supported sign mechanism */ public static Long getSupportedSignMechanismCode(String signMechanismName) { return SUPPORTED_SIGN_MECHANISMS.get(signMechanismName); } /** * @return all modules */ static Collection<ModuleType> getModules() { return MODULES.values(); } /** * @return true, if the configuration file has changed (modified on disk) */ static boolean hasChanged() { try { if (changeChecker == null) { changeChecker = new FileContentChangeChecker(getDeviceConfFile()); return true; } return changeChecker.hasChanged(); } catch (Exception e) { log.error("Failed to create content change checker or calculate check sum", e); return true; } } /** * Reloads the modules from the configuration. */ static void reload() { try { reload(getDeviceConfFile()); } catch (Exception e) { log.error("Failed to load module conf", e); } } private static void reload(String fileName) throws Exception { log.trace("Loading module configuration from '{}'", fileName); MODULES.clear(); MODULES.put(SoftwareModuleType.TYPE, new SoftwareModuleType()); HierarchicalINIConfiguration conf = new HierarchicalINIConfiguration(fileName); for (String uid : conf.getSections()) { if (StringUtils.isBlank(uid)) { log.error("No UID specified for module, skipping..."); continue; } try { parseSection(uid, conf.getSection(uid)); } catch (ConfigurationRuntimeException e) { log.error("Parse section failed with", e); } } } private static void parseSection(String uid, SubnodeConfiguration section) { boolean enabled = section.getBoolean(ENABLED_PARAM, true); if (SOFTKEY_TYPE.equalsIgnoreCase(uid)) { if (!enabled) { MODULES.remove(SoftwareModuleType.TYPE); } return; } if (!enabled) { return; } String library = section.getString("library"); if (StringUtils.isBlank(library)) { log.error("No pkcs#11 library specified for module ({}), skipping...", uid); return; } boolean verifyPin = getBoolean(section, SIGN_VERIFY_PIN_PARAM, false); boolean batchSigning = getBoolean(section, BATCH_SIGNING_ENABLED_PARAM, true); boolean readOnly = getBoolean(section, READ_ONLY_PARAM, false); String tokenIdFormat = section.getString(TOKEN_ID_FORMAT_PARAM); if (StringUtils.isBlank(tokenIdFormat)) { tokenIdFormat = DEFAULT_TOKEN_ID_FORMAT; } String signMechanismName = section.getString(SIGN_MECHANISM_PARAM); if (StringUtils.isBlank(signMechanismName)) { signMechanismName = DEFAULT_SIGN_MECHANISM_NAME; } Long signMechanism = getSupportedSignMechanismCode(signMechanismName); if (signMechanism == null) { log.error("Not supported sign mechanism ({}) specified for module ({}), skipping...", signMechanismName, uid); return; } PubKeyAttributes pubKeyAttributes = loadPubKeyAttributes(section); PrivKeyAttributes privKeyAttributes = loadPrivKeyAttributes(section); log.trace( "Read module configuration (UID = {}, library = {}, tokenIdFormat = {}" + ", pinVerificationPerSigning = {}, batchSigning = {}, signMechanism = {}" + ", pubKeyAttributes = {}, privKeyAttributes = {})", uid, library, tokenIdFormat, verifyPin, batchSigning, signMechanismName, pubKeyAttributes, privKeyAttributes); if (MODULES.containsKey(uid)) { log.warn("Module information already defined for {}, skipping...", uid); return; } MODULES.put(uid, new HardwareModuleType(uid, library, tokenIdFormat, verifyPin, batchSigning, readOnly, signMechanismName, privKeyAttributes, pubKeyAttributes)); } private static PubKeyAttributes loadPubKeyAttributes(SubnodeConfiguration section) { PubKeyAttributes attributes = new PubKeyAttributes(); // Default values for backward compatibility are used. attributes.setEncrypt(getBoolean(section, PUB_KEY_ATTRIBUTE_ENCRYPT_PARAM, true)); attributes.setVerify(getBoolean(section, PUB_KEY_ATTRIBUTE_VERIFY_PARAM, true)); attributes.setVerifyRecover(getBoolean(section, PUB_KEY_ATTRIBUTE_VERIFY_RECOVER_PARAM, null)); attributes.setWrap(getBoolean(section, PUB_KEY_ATTRIBUTE_WRAP_PARAM, null)); attributes.setTrusted(getBoolean(section, PUB_KEY_ATTRIBUTE_TRUSTED_PARAM, null)); attributes.setAllowedMechanisms( loadAllowedKeyUsageMechanisms(section, PUB_KEY_ATTRIBUTE_ALLOWED_MECHANISMS_PARAM)); return attributes; } private static PrivKeyAttributes loadPrivKeyAttributes(SubnodeConfiguration section) { PrivKeyAttributes attributes = new PrivKeyAttributes(); // Default values for backward compatibility are used. attributes.setSensitive(getBoolean(section, PRIV_KEY_ATTRIBUTE_SENSITIVE_PARAM, true)); attributes.setDecrypt(getBoolean(section, PRIV_KEY_ATTRIBUTE_DECRYPT_PARAM, true)); attributes.setSign(getBoolean(section, PRIV_KEY_ATTRIBUTE_SIGN_PARAM, true)); attributes.setSignRecover(getBoolean(section, PRIV_KEY_ATTRIBUTE_SIGN_RECOVER_PARAM, null)); attributes.setUnwrap(getBoolean(section, PRIV_KEY_ATTRIBUTE_UNWRAP_PARAM, null)); attributes.setExtractable(getBoolean(section, PRIV_KEY_ATTRIBUTE_EXTRACTABLE_PARAM, null)); attributes.setAlwaysSensitive(getBoolean(section, PRIV_KEY_ATTRIBUTE_ALWAYS_SENSITIVE_PARAM, null)); attributes.setNeverExtractable(getBoolean(section, PRIV_KEY_ATTRIBUTE_NEVER_EXTRACTABLE_PARAM, null)); attributes.setWrapWithTrusted(getBoolean(section, PRIV_KEY_ATTRIBUTE_WRAP_WITH_TRUSTED_PARAM, null)); attributes.setAllowedMechanisms( loadAllowedKeyUsageMechanisms(section, PRIV_KEY_ATTRIBUTE_ALLOWED_MECHANISMS_PARAM)); return attributes; } private static Set<Long> loadAllowedKeyUsageMechanisms(SubnodeConfiguration section, String key) { Set<Long> allowedMechanism = new HashSet<>(); String[] mechanisms = getStringArray(section, key); for (String m : mechanisms) { Long mechamism = SUPPORTED_KEY_ALLOWED_MECHANISMS.get(StringUtils.strip(m)); if (mechamism != null) { allowedMechanism.add(mechamism); } else { throw new ConfigurationRuntimeException( String.format("Unsupported value '%s' of '%s' for module (%s), skipping...", m, key, section.getSubnodeKey())); } } return allowedMechanism; } private static Boolean getBoolean(SubnodeConfiguration section, String key, Boolean defaultValue) { try { return section.getBoolean(key, defaultValue); } catch (ConversionException e) { throw new ConversionException(String.format("Invalid value of '%s' for module (%s), skipping...", key, section.getSubnodeKey()), e); } } private static String[] getStringArray(SubnodeConfiguration section, String key) { try { return section.getStringArray(key); } catch (ConversionException e) { throw new ConversionException(String.format("Invalid value of '%s' for module (%s), skipping...", key, section.getSubnodeKey()), e); } } }