/*
 * Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.pkcs11;

import java.io.*;
import static java.io.StreamTokenizer.*;
import java.math.BigInteger;
import java.util.*;

import java.security.*;

import sun.security.action.GetPropertyAction;
import sun.security.util.PropertyExpander;

import sun.security.pkcs11.wrapper.*;
import static sun.security.pkcs11.wrapper.PKCS11Constants.*;
import static sun.security.pkcs11.wrapper.CK_ATTRIBUTE.*;

import static sun.security.pkcs11.TemplateManager.*;

Configuration container and file parsing.
Author: Andreas Sterbenz
Since: 1.5
/** * Configuration container and file parsing. * * @author Andreas Sterbenz * @since 1.5 */
final class Config { static final int ERR_HALT = 1; static final int ERR_IGNORE_ALL = 2; static final int ERR_IGNORE_LIB = 3; // same as allowSingleThreadedModules but controlled via a system property // and applied to all providers. if set to false, no SunPKCS11 instances // will accept single threaded modules regardless of the setting in their // config files. private static final boolean staticAllowSingleThreadedModules; static { String p = "sun.security.pkcs11.allowSingleThreadedModules"; String s = AccessController.doPrivileged(new GetPropertyAction(p)); if ("false".equalsIgnoreCase(s)) { staticAllowSingleThreadedModules = false; } else { staticAllowSingleThreadedModules = true; } } // temporary storage for configurations // needed because the SunPKCS11 needs to call the superclass constructor // in provider before accessing any instance variables private final static Map<String,Config> configMap = new HashMap<String,Config>(); static Config getConfig(final String name, final InputStream stream) { Config config = configMap.get(name); if (config != null) { return config; } try { config = new Config(name, stream); configMap.put(name, config); return config; } catch (Exception e) { throw new ProviderException("Error parsing configuration", e); } } static Config removeConfig(String name) { return configMap.remove(name); } private final static boolean DEBUG = false; private static void debug(Object o) { if (DEBUG) { System.out.println(o); } } // Reader and StringTokenizer used during parsing private Reader reader; private StreamTokenizer st; private Set<String> parsedKeywords; // name suffix of the provider private String name; // name of the PKCS#11 library private String library; // description to pass to the provider class private String description; // slotID of the slot to use private int slotID = -1; // slot to use, specified as index in the slotlist private int slotListIndex = -1; // set of enabled mechanisms (or null to use default) private Set<Long> enabledMechanisms; // set of disabled mechanisms private Set<Long> disabledMechanisms; // whether to print debug info during startup private boolean showInfo = false; // template manager, initialized from parsed attributes private TemplateManager templateManager; // how to handle error during startup, one of ERR_ private int handleStartupErrors = ERR_HALT; // flag indicating whether the P11KeyStore should // be more tolerant of input parameters private boolean keyStoreCompatibilityMode = true; // flag indicating whether we need to explicitly cancel operations // see Token private boolean explicitCancel = true; // how often to test for token insertion, if no token is present private int insertionCheckInterval = 2000; // flag inidicating whether to omit the call to C_Initialize() // should be used only if we are running within a process that // has already called it (e.g. Plugin inside of Mozilla/NSS) private boolean omitInitialize = false; // whether to allow modules that only support single threaded access. // they cannot be used safely from multiple PKCS#11 consumers in the // same process, for example NSS and SunPKCS11 private boolean allowSingleThreadedModules = true; // name of the C function that returns the PKCS#11 functionlist // This option primarily exists for the deprecated // Secmod.Module.getProvider() method. private String functionList = "C_GetFunctionList"; // whether to use NSS secmod mode. Implicitly set if nssLibraryDirectory, // nssSecmodDirectory, or nssModule is specified. private boolean nssUseSecmod; // location of the NSS library files (libnss3.so, etc.) private String nssLibraryDirectory; // location of secmod.db private String nssSecmodDirectory; // which NSS module to use private String nssModule; private Secmod.DbMode nssDbMode = Secmod.DbMode.READ_WRITE; // Whether the P11KeyStore should specify the CKA_NETSCAPE_DB attribute // when creating private keys. Only valid if nssUseSecmod is true. private boolean nssNetscapeDbWorkaround = true; // Special init argument string for the NSS softtoken. // This is used when using the NSS softtoken directly without secmod mode. private String nssArgs; // whether to use NSS trust attributes for the KeyStore of this provider // this option is for internal use by the SunPKCS11 code only and // works only for NSS providers created via the Secmod API private boolean nssUseSecmodTrust = false; private Config(String filename, InputStream in) throws IOException { if (in == null) { if (filename.startsWith("--")) { // inline config String config = filename.substring(2).replace("\\n", "\n"); reader = new StringReader(config); } else { in = new FileInputStream(expand(filename)); } } if (reader == null) { reader = new BufferedReader(new InputStreamReader(in)); } parsedKeywords = new HashSet<String>(); st = new StreamTokenizer(reader); setupTokenizer(); parse(); } String getName() { return name; } String getLibrary() { return library; } String getDescription() { if (description != null) { return description; } return "SunPKCS11-" + name + " using library " + library; } int getSlotID() { return slotID; } int getSlotListIndex() { if ((slotID == -1) && (slotListIndex == -1)) { // if neither is set, default to first slot return 0; } else { return slotListIndex; } } boolean getShowInfo() { return (SunPKCS11.debug != null) || showInfo; } TemplateManager getTemplateManager() { if (templateManager == null) { templateManager = new TemplateManager(); } return templateManager; } boolean isEnabled(long m) { if (enabledMechanisms != null) { return enabledMechanisms.contains(Long.valueOf(m)); } if (disabledMechanisms != null) { return !disabledMechanisms.contains(Long.valueOf(m)); } return true; } int getHandleStartupErrors() { return handleStartupErrors; } boolean getKeyStoreCompatibilityMode() { return keyStoreCompatibilityMode; } boolean getExplicitCancel() { return explicitCancel; } int getInsertionCheckInterval() { return insertionCheckInterval; } boolean getOmitInitialize() { return omitInitialize; } boolean getAllowSingleThreadedModules() { return staticAllowSingleThreadedModules && allowSingleThreadedModules; } String getFunctionList() { return functionList; } boolean getNssUseSecmod() { return nssUseSecmod; } String getNssLibraryDirectory() { return nssLibraryDirectory; } String getNssSecmodDirectory() { return nssSecmodDirectory; } String getNssModule() { return nssModule; } Secmod.DbMode getNssDbMode() { return nssDbMode; } public boolean getNssNetscapeDbWorkaround() { return nssUseSecmod && nssNetscapeDbWorkaround; } String getNssArgs() { return nssArgs; } boolean getNssUseSecmodTrust() { return nssUseSecmodTrust; } private static String expand(final String s) throws IOException { try { return PropertyExpander.expand(s); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } private void setupTokenizer() { st.resetSyntax(); st.wordChars('a', 'z'); st.wordChars('A', 'Z'); st.wordChars('0', '9'); st.wordChars(':', ':'); st.wordChars('.', '.'); st.wordChars('_', '_'); st.wordChars('-', '-'); st.wordChars('/', '/'); st.wordChars('\\', '\\'); st.wordChars('$', '$'); st.wordChars('{', '{'); // need {} for property subst st.wordChars('}', '}'); st.wordChars('*', '*'); // XXX check ASCII table and add all other characters except special // special: #="(), st.whitespaceChars(0, ' '); st.commentChar('#'); st.eolIsSignificant(true); st.quoteChar('\"'); } private ConfigurationException excToken(String msg) { return new ConfigurationException(msg + " " + st); } private ConfigurationException excLine(String msg) { return new ConfigurationException(msg + ", line " + st.lineno()); } private void parse() throws IOException { while (true) { int token = nextToken(); if (token == TT_EOF) { break; } if (token == TT_EOL) { continue; } if (token != TT_WORD) { throw excToken("Unexpected token:"); } String word = st.sval; if (word.equals("name")) { name = parseStringEntry(word); } else if (word.equals("library")) { library = parseLibrary(word); } else if (word.equals("description")) { parseDescription(word); } else if (word.equals("slot")) { parseSlotID(word); } else if (word.equals("slotListIndex")) { parseSlotListIndex(word); } else if (word.equals("enabledMechanisms")) { parseEnabledMechanisms(word); } else if (word.equals("disabledMechanisms")) { parseDisabledMechanisms(word); } else if (word.equals("attributes")) { parseAttributes(word); } else if (word.equals("handleStartupErrors")) { parseHandleStartupErrors(word); } else if (word.endsWith("insertionCheckInterval")) { insertionCheckInterval = parseIntegerEntry(word); if (insertionCheckInterval < 100) { throw excLine(word + " must be at least 100 ms"); } } else if (word.equals("showInfo")) { showInfo = parseBooleanEntry(word); } else if (word.equals("keyStoreCompatibilityMode")) { keyStoreCompatibilityMode = parseBooleanEntry(word); } else if (word.equals("explicitCancel")) { explicitCancel = parseBooleanEntry(word); } else if (word.equals("omitInitialize")) { omitInitialize = parseBooleanEntry(word); } else if (word.equals("allowSingleThreadedModules")) { allowSingleThreadedModules = parseBooleanEntry(word); } else if (word.equals("functionList")) { functionList = parseStringEntry(word); } else if (word.equals("nssUseSecmod")) { nssUseSecmod = parseBooleanEntry(word); } else if (word.equals("nssLibraryDirectory")) { nssLibraryDirectory = parseLibrary(word); nssUseSecmod = true; } else if (word.equals("nssSecmodDirectory")) { nssSecmodDirectory = expand(parseStringEntry(word)); nssUseSecmod = true; } else if (word.equals("nssModule")) { nssModule = parseStringEntry(word); nssUseSecmod = true; } else if (word.equals("nssDbMode")) { String mode = parseStringEntry(word); if (mode.equals("readWrite")) { nssDbMode = Secmod.DbMode.READ_WRITE; } else if (mode.equals("readOnly")) { nssDbMode = Secmod.DbMode.READ_ONLY; } else if (mode.equals("noDb")) { nssDbMode = Secmod.DbMode.NO_DB; } else { throw excToken("nssDbMode must be one of readWrite, readOnly, and noDb:"); } nssUseSecmod = true; } else if (word.equals("nssNetscapeDbWorkaround")) { nssNetscapeDbWorkaround = parseBooleanEntry(word); nssUseSecmod = true; } else if (word.equals("nssArgs")) { parseNSSArgs(word); } else if (word.equals("nssUseSecmodTrust")) { nssUseSecmodTrust = parseBooleanEntry(word); } else { throw new ConfigurationException ("Unknown keyword '" + word + "', line " + st.lineno()); } parsedKeywords.add(word); } reader.close(); reader = null; st = null; parsedKeywords = null; if (name == null) { throw new ConfigurationException("name must be specified"); } if (nssUseSecmod == false) { if (library == null) { throw new ConfigurationException("library must be specified"); } } else { if (library != null) { throw new ConfigurationException ("library must not be specified in NSS mode"); } if ((slotID != -1) || (slotListIndex != -1)) { throw new ConfigurationException ("slot and slotListIndex must not be specified in NSS mode"); } if (nssArgs != null) { throw new ConfigurationException ("nssArgs must not be specified in NSS mode"); } if (nssUseSecmodTrust != false) { throw new ConfigurationException("nssUseSecmodTrust is an " + "internal option and must not be specified in NSS mode"); } } } // // Parsing helper methods // private int nextToken() throws IOException { int token = st.nextToken(); debug(st); return token; } private void parseEquals() throws IOException { int token = nextToken(); if (token != '=') { throw excToken("Expected '=', read"); } } private void parseOpenBraces() throws IOException { while (true) { int token = nextToken(); if (token == TT_EOL) { continue; } if ((token == TT_WORD) && st.sval.equals("{")) { return; } throw excToken("Expected '{', read"); } } private boolean isCloseBraces(int token) { return (token == TT_WORD) && st.sval.equals("}"); } private String parseWord() throws IOException { int token = nextToken(); if (token != TT_WORD) { throw excToken("Unexpected value:"); } return st.sval; } private String parseStringEntry(String keyword) throws IOException { checkDup(keyword); parseEquals(); int token = nextToken(); if (token != TT_WORD && token != '\"') { // not a word token nor a string enclosed by double quotes throw excToken("Unexpected value:"); } String value = st.sval; debug(keyword + ": " + value); return value; } private boolean parseBooleanEntry(String keyword) throws IOException { checkDup(keyword); parseEquals(); boolean value = parseBoolean(); debug(keyword + ": " + value); return value; } private int parseIntegerEntry(String keyword) throws IOException { checkDup(keyword); parseEquals(); int value = decodeNumber(parseWord()); debug(keyword + ": " + value); return value; } private boolean parseBoolean() throws IOException { String val = parseWord(); if ("true".equals(val)) { return true; } else if ("false".equals(val)) { return false; } else { throw excToken("Expected boolean value, read:"); } } private String parseLine() throws IOException { String s = parseWord(); while (true) { int token = nextToken(); if ((token == TT_EOL) || (token == TT_EOF)) { break; } if (token != TT_WORD) { throw excToken("Unexpected value"); } s = s + " " + st.sval; } return s; } private int decodeNumber(String str) throws IOException { try { if (str.startsWith("0x") || str.startsWith("0X")) { return Integer.parseInt(str.substring(2), 16); } else { return Integer.parseInt(str); } } catch (NumberFormatException e) { throw excToken("Expected number, read"); } } private static boolean isNumber(String s) { if (s.length() == 0) { return false; } char ch = s.charAt(0); return ((ch >= '0') && (ch <= '9')); } private void parseComma() throws IOException { int token = nextToken(); if (token != ',') { throw excToken("Expected ',', read"); } } private static boolean isByteArray(String val) { return val.startsWith("0h"); } private byte[] decodeByteArray(String str) throws IOException { if (str.startsWith("0h") == false) { throw excToken("Expected byte array value, read"); } str = str.substring(2); // XXX proper hex parsing try { return new BigInteger(str, 16).toByteArray(); } catch (NumberFormatException e) { throw excToken("Expected byte array value, read"); } } private void checkDup(String keyword) throws IOException { if (parsedKeywords.contains(keyword)) { throw excLine(keyword + " must only be specified once"); } } // // individual entry parsing methods // private String parseLibrary(String keyword) throws IOException { checkDup(keyword); parseEquals(); String lib = parseLine(); lib = expand(lib); int i = lib.indexOf("/$ISA/"); if (i != -1) { // replace "/$ISA/" with "/sparcv9/" on 64-bit Solaris SPARC // and with "/amd64/" on Solaris AMD64. // On all other platforms, just turn it into a "/" String osName = System.getProperty("os.name", ""); String osArch = System.getProperty("os.arch", ""); String prefix = lib.substring(0, i); String suffix = lib.substring(i + 5); if (osName.equals("SunOS") && osArch.equals("sparcv9")) { lib = prefix + "/sparcv9" + suffix; } else if (osName.equals("SunOS") && osArch.equals("amd64")) { lib = prefix + "/amd64" + suffix; } else { lib = prefix + suffix; } } debug(keyword + ": " + lib); return lib; } private void parseDescription(String keyword) throws IOException { checkDup(keyword); parseEquals(); description = parseLine(); debug("description: " + description); } private void parseSlotID(String keyword) throws IOException { if (slotID >= 0) { throw excLine("Duplicate slot definition"); } if (slotListIndex >= 0) { throw excLine ("Only one of slot and slotListIndex must be specified"); } parseEquals(); String slotString = parseWord(); slotID = decodeNumber(slotString); debug("slot: " + slotID); } private void parseSlotListIndex(String keyword) throws IOException { if (slotListIndex >= 0) { throw excLine("Duplicate slotListIndex definition"); } if (slotID >= 0) { throw excLine ("Only one of slot and slotListIndex must be specified"); } parseEquals(); String slotString = parseWord(); slotListIndex = decodeNumber(slotString); debug("slotListIndex: " + slotListIndex); } private void parseEnabledMechanisms(String keyword) throws IOException { enabledMechanisms = parseMechanisms(keyword); } private void parseDisabledMechanisms(String keyword) throws IOException { disabledMechanisms = parseMechanisms(keyword); } private Set<Long> parseMechanisms(String keyword) throws IOException { checkDup(keyword); Set<Long> mechs = new HashSet<Long>(); parseEquals(); parseOpenBraces(); while (true) { int token = nextToken(); if (isCloseBraces(token)) { break; } if (token == TT_EOL) { continue; } if (token != TT_WORD) { throw excToken("Expected mechanism, read"); } long mech = parseMechanism(st.sval); mechs.add(Long.valueOf(mech)); } if (DEBUG) { System.out.print("mechanisms: ["); for (Long mech : mechs) { System.out.print(Functions.getMechanismName(mech)); System.out.print(", "); } System.out.println("]"); } return mechs; } private long parseMechanism(String mech) throws IOException { if (isNumber(mech)) { return decodeNumber(mech); } else { try { return Functions.getMechanismId(mech); } catch (IllegalArgumentException e) { throw excLine("Unknown mechanism: " + mech); } } } private void parseAttributes(String keyword) throws IOException { if (templateManager == null) { templateManager = new TemplateManager(); } int token = nextToken(); if (token == '=') { String s = parseWord(); if (s.equals("compatibility") == false) { throw excLine("Expected 'compatibility', read " + s); } setCompatibilityAttributes(); return; } if (token != '(') { throw excToken("Expected '(' or '=', read"); } String op = parseOperation(); parseComma(); long objectClass = parseObjectClass(); parseComma(); long keyAlg = parseKeyAlgorithm(); token = nextToken(); if (token != ')') { throw excToken("Expected ')', read"); } parseEquals(); parseOpenBraces(); List<CK_ATTRIBUTE> attributes = new ArrayList<CK_ATTRIBUTE>(); while (true) { token = nextToken(); if (isCloseBraces(token)) { break; } if (token == TT_EOL) { continue; } if (token != TT_WORD) { throw excToken("Expected mechanism, read"); } String attributeName = st.sval; long attributeId = decodeAttributeName(attributeName); parseEquals(); String attributeValue = parseWord(); attributes.add(decodeAttributeValue(attributeId, attributeValue)); } templateManager.addTemplate (op, objectClass, keyAlg, attributes.toArray(CK_A0)); } private void setCompatibilityAttributes() { // all secret keys templateManager.addTemplate(O_ANY, CKO_SECRET_KEY, PCKK_ANY, new CK_ATTRIBUTE[] { TOKEN_FALSE, SENSITIVE_FALSE, EXTRACTABLE_TRUE, ENCRYPT_TRUE, DECRYPT_TRUE, WRAP_TRUE, UNWRAP_TRUE, }); // generic secret keys are special // They are used as MAC keys plus for the SSL/TLS (pre)master secrets templateManager.addTemplate(O_ANY, CKO_SECRET_KEY, CKK_GENERIC_SECRET, new CK_ATTRIBUTE[] { SIGN_TRUE, VERIFY_TRUE, ENCRYPT_NULL, DECRYPT_NULL, WRAP_NULL, UNWRAP_NULL, DERIVE_TRUE, }); // all private and public keys templateManager.addTemplate(O_ANY, CKO_PRIVATE_KEY, PCKK_ANY, new CK_ATTRIBUTE[] { TOKEN_FALSE, SENSITIVE_FALSE, EXTRACTABLE_TRUE, }); templateManager.addTemplate(O_ANY, CKO_PUBLIC_KEY, PCKK_ANY, new CK_ATTRIBUTE[] { TOKEN_FALSE, }); // additional attributes for RSA private keys templateManager.addTemplate(O_ANY, CKO_PRIVATE_KEY, CKK_RSA, new CK_ATTRIBUTE[] { DECRYPT_TRUE, SIGN_TRUE, SIGN_RECOVER_TRUE, UNWRAP_TRUE, }); // additional attributes for RSA public keys templateManager.addTemplate(O_ANY, CKO_PUBLIC_KEY, CKK_RSA, new CK_ATTRIBUTE[] { ENCRYPT_TRUE, VERIFY_TRUE, VERIFY_RECOVER_TRUE, WRAP_TRUE, }); // additional attributes for DSA private keys templateManager.addTemplate(O_ANY, CKO_PRIVATE_KEY, CKK_DSA, new CK_ATTRIBUTE[] { SIGN_TRUE, }); // additional attributes for DSA public keys templateManager.addTemplate(O_ANY, CKO_PUBLIC_KEY, CKK_DSA, new CK_ATTRIBUTE[] { VERIFY_TRUE, }); // additional attributes for DH private keys templateManager.addTemplate(O_ANY, CKO_PRIVATE_KEY, CKK_DH, new CK_ATTRIBUTE[] { DERIVE_TRUE, }); // additional attributes for EC private keys templateManager.addTemplate(O_ANY, CKO_PRIVATE_KEY, CKK_EC, new CK_ATTRIBUTE[] { SIGN_TRUE, DERIVE_TRUE, }); // additional attributes for EC public keys templateManager.addTemplate(O_ANY, CKO_PUBLIC_KEY, CKK_EC, new CK_ATTRIBUTE[] { VERIFY_TRUE, }); } private final static CK_ATTRIBUTE[] CK_A0 = new CK_ATTRIBUTE[0]; private String parseOperation() throws IOException { String op = parseWord(); if ("*".equals(op)) { return TemplateManager.O_ANY; } else if ("generate".equals(op)) { return TemplateManager.O_GENERATE; } else if ("import".equals(op)) { return TemplateManager.O_IMPORT; } else { throw excLine("Unknown operation " + op); } } private long parseObjectClass() throws IOException { String name = parseWord(); try { return Functions.getObjectClassId(name); } catch (IllegalArgumentException e) { throw excLine("Unknown object class " + name); } } private long parseKeyAlgorithm() throws IOException { String name = parseWord(); if (isNumber(name)) { return decodeNumber(name); } else { try { return Functions.getKeyId(name); } catch (IllegalArgumentException e) { throw excLine("Unknown key algorithm " + name); } } } private long decodeAttributeName(String name) throws IOException { if (isNumber(name)) { return decodeNumber(name); } else { try { return Functions.getAttributeId(name); } catch (IllegalArgumentException e) { throw excLine("Unknown attribute name " + name); } } } private CK_ATTRIBUTE decodeAttributeValue(long id, String value) throws IOException { if (value.equals("null")) { return new CK_ATTRIBUTE(id); } else if (value.equals("true")) { return new CK_ATTRIBUTE(id, true); } else if (value.equals("false")) { return new CK_ATTRIBUTE(id, false); } else if (isByteArray(value)) { return new CK_ATTRIBUTE(id, decodeByteArray(value)); } else if (isNumber(value)) { return new CK_ATTRIBUTE(id, Integer.valueOf(decodeNumber(value))); } else { throw excLine("Unknown attribute value " + value); } } private void parseNSSArgs(String keyword) throws IOException { checkDup(keyword); parseEquals(); int token = nextToken(); if (token != '"') { throw excToken("Expected quoted string"); } nssArgs = expand(st.sval); debug("nssArgs: " + nssArgs); } private void parseHandleStartupErrors(String keyword) throws IOException { checkDup(keyword); parseEquals(); String val = parseWord(); if (val.equals("ignoreAll")) { handleStartupErrors = ERR_IGNORE_ALL; } else if (val.equals("ignoreMissingLibrary")) { handleStartupErrors = ERR_IGNORE_LIB; } else if (val.equals("halt")) { handleStartupErrors = ERR_HALT; } else { throw excToken("Invalid value for handleStartupErrors:"); } debug("handleStartupErrors: " + handleStartupErrors); } } class ConfigurationException extends IOException { private static final long serialVersionUID = 254492758807673194L; ConfigurationException(String msg) { super(msg); } }