All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xipki.pkcs11.wrapper.PKCS11Module Maven / Gradle / Ivy

There is a newer version: 1.0.9
Show newest version
// Copyright (c) 2002 Graz University of Technology. All rights reserved.
// License IAIK PKCS#11 Wrapper License.
//
// Copyright (c) 2022 xipki. All rights reserved.
// License Apache License 2.0

package org.xipki.pkcs11.wrapper;

import sun.security.pkcs11.wrapper.CK_C_INITIALIZE_ARGS;
import sun.security.pkcs11.wrapper.PKCS11;

import java.io.*;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Caution:
 * Unlike the original IAIK PKCS#11 wrapper, we only call initialize() once per
 * native .so/.dll. Once finalize(Object) has been called, the module cannot
 * be initialized anymore.
 * 
 * 

* Objects of this class represent a PKCS#11 module. The application should * create an instance by calling getInstance and passing the name of the * PKCS#11 module of the desired token; e.g. "slbck.dll". The application * must give the full path of the PKCS#11 module unless the module is in the * system's search path or in the path of the java.library.path system * property. *

* According to the specification, the application must call the initialize * method before calling any other method of the module. * This class contains slot and token management functions as defined by the * PKCS#11 standard. * * All applications using this library will contain the following code. *


 *      PKCS11Module pkcs11Module = PKCS11Module.getInstance("cryptoki.dll");
 *      pkcs11Module.initialize();
 *
 *      // ... work with the module
 *
 *      pkcs11Module.finalize(null);
 * 
* Instead of cryptoki.dll, the application will use the name of * the PKCS#11 module of the installed crypto hardware. * After the application initialized the module, it can get a list of all * available slots. A slot is an object that represents a physical or logical * device that can accept a cryptographic token; for instance, the card slot of * a smart card reader. The application can call *

 * Slot[] slots = pkcs11Module.getSlotList(false);
 * 
* to get a list of all available slots or *

 * Slot[] slotsWithToken = pkcs11Module.getSlotList(true);
 * 
* to get a list of all those slots in which there is a currently a token * present. *

* * @author Karl Scheibelhofer (SIC) * @author Lijun Liao (xipki) */ public class PKCS11Module { /** * The ECDSA signature is in X9.62 format. */ static final int BEHAVIOUR_ECDSA_SIGNATURE_X962 = 1; /** * The SM2 signature is in X9.62 format. */ static final int BEHAVIOUR_SM2_SIGNATURE_X962 = 2; /** * The private key of type CKK_EC has the attribute CKA_EC_POINT. */ static final int BEHAVIOUR_EC_PRIVATEKEY_ECPOINT = 3; /** * The private key of type CKK_VENDOR_SM2 has the attribute CKA_EC_POINT. */ static final int BEHAVIOUR_SM2_PRIVATEKEY_ECPOINT = 4; /** * Interface to the underlying PKCS#11 module. */ private PKCS11 pkcs11; private final String pkcs11ModulePath; private Boolean ecPointFixNeeded; private Boolean ecdsaSignatureFixNeeded; private Boolean sm2SignatureFixNeeded; private boolean withVendorCodeMap; private final Map ckkGenericToVendorMap = new HashMap<>(); private final Map ckkVendorToGenericMap = new HashMap<>(); private final Map ckmGenericToVendorMap = new HashMap<>(); private final Map ckmVendorToGenericMap = new HashMap<>(); private final Set vendorBehaviours = new HashSet<>(); private static final AtomicBoolean licensePrinted = new AtomicBoolean(false); /** * Create a new module that uses the given PKCS11 interface to interact with * the token. * * @param pkcs11ModulePath * The interface to interact with the token. */ protected PKCS11Module(String pkcs11ModulePath) { this.pkcs11ModulePath = Functions.requireNonNull("pkcs11ModulePath", pkcs11ModulePath); } /** * Get an instance of this class by giving the name of the PKCS#11 module; e.g. "slbck.dll". Tries * to load the PKCS#11 wrapper native library from the class path (jar file) or library path. * * @param pkcs11ModulePath * The path of the module; e.g. "/path/to/slbck.dll". * @return An instance of Module that is connected to the given PKCS#11 module. * @exception IOException * If connecting to the named module fails. */ public static PKCS11Module getInstance(String pkcs11ModulePath) throws IOException { synchronized (licensePrinted) { if (!licensePrinted.get()) { StaticLogger.info( "This product (ipkcs11wrapper) includes software (IAIK PKCS#11 wrapper version 1.6.8)\n" + "developed by Stiftung SIC which is licensed under \"IAIK PKCS#11 Wrapper License\"- \n" + "A copy of this license is downloadable under \n" + "https://jce.iaik.tugraz.at/products/core-crypto-toolkits/pkcs11-wrapper/#License.\n" + "All other parts are licensed under Apache License, version 2."); licensePrinted.set(true); } } Functions.requireNonNull("pkcs11ModulePath", pkcs11ModulePath); File file = new File(pkcs11ModulePath); if (!file.exists()) { throw new IOException("File " + pkcs11ModulePath + " does not exist"); } if (!file.isFile()) { throw new IOException(pkcs11ModulePath + " is not a file"); } if (!file.canRead()) { throw new IOException("Can not read file " + pkcs11ModulePath + ""); } return new PKCS11Module(pkcs11ModulePath); } Boolean getEcPointFixNeeded() { return ecPointFixNeeded; } void setEcPointFixNeeded(Boolean ecPointFixNeeded) { this.ecPointFixNeeded = ecPointFixNeeded; } Boolean getEcdsaSignatureFixNeeded() { return ecdsaSignatureFixNeeded; } void setEcdsaSignatureFixNeeded(Boolean ecdsaSignatureFixNeeded) { this.ecdsaSignatureFixNeeded = ecdsaSignatureFixNeeded; } Boolean getSm2SignatureFixNeeded() { return sm2SignatureFixNeeded; } void setSm2SignatureFixNeeded(Boolean sm2SignatureFixNeeded) { this.sm2SignatureFixNeeded = sm2SignatureFixNeeded; } /** * Gets information about the module; i.e. the PKCS#11 module behind. * * @return An object holding information about the module. * @exception PKCS11Exception * If getting the information fails. */ public ModuleInfo getInfo() throws PKCS11Exception { assertInitialized(); try { return new ModuleInfo(pkcs11.C_GetInfo()); } catch (sun.security.pkcs11.wrapper.PKCS11Exception ex) { throw new PKCS11Exception(ex.getErrorCode()); } } /** * Initializes the module. The application must call this method before * calling any other method of the module. * * @exception PKCS11Exception * If initialization fails. */ public void initialize() throws TokenException { CK_C_INITIALIZE_ARGS wrapperInitArgs = new CK_C_INITIALIZE_ARGS(); wrapperInitArgs.flags |= PKCS11Constants.CKF_OS_LOCKING_OK; final String functionList = "C_GetFunctionList"; final boolean omitInitialize = false; StaticLogger.info("C_Initialize: pkcs11ModulePath={}, flags=0x{}", pkcs11ModulePath, Functions.toFullHex(wrapperInitArgs.flags)); try { pkcs11 = PKCS11.getInstance(pkcs11ModulePath, functionList, wrapperInitArgs, omitInitialize); } catch (IOException ex) { throw new TokenException(ex.getMessage(), ex); } catch (sun.security.pkcs11.wrapper.PKCS11Exception ex) { throw new PKCS11Exception(ex.getErrorCode()); } catch (NoSuchMethodError ex) { // In some JDKs like red hat, the getInstance is extended by fipsKeyImporter as follows: // getInstance(String pkcs11ModulePath, String functionList, CK_C_INITIALIZE_ARGS pInitArgs, // boolean omitInitialize, MethodHandle fipsKeyImporter) try { Method getInstanceMethod = PKCS11.class.getMethod("getInstance", String.class, String.class, CK_C_INITIALIZE_ARGS.class, boolean.class, MethodHandle.class); pkcs11 = (PKCS11) getInstanceMethod.invoke(null, pkcs11ModulePath, functionList, wrapperInitArgs, omitInitialize, null); } catch (Exception ex1) { throw new TokenException(ex1.getMessage(), ex1); } } initVendor(); } /** * Gets a list of slots that can accept tokens that are compatible with this * module; e.g. a list of PC/SC smart card readers. The parameter determines * if the method returns all compatible slots or only those in which there * is a compatible token present. * * @param tokenPresent * Whether only slots with present token are returned. * @return An array of Slot objects, may be an empty array but not null. * @exception PKCS11Exception * If error occurred. */ public Slot[] getSlotList(boolean tokenPresent) throws PKCS11Exception { assertInitialized(); long[] slotIDs; try { slotIDs = pkcs11.C_GetSlotList(tokenPresent); } catch (sun.security.pkcs11.wrapper.PKCS11Exception ex) { throw new PKCS11Exception(ex.getErrorCode()); } Slot[] slots = new Slot[slotIDs.length]; for (int i = 0; i < slots.length; i++) { slots[i] = new Slot(this, slotIDs[i]); } return slots; } /* * Waits for a slot event. That can be that a token was inserted or * removed. It returns the Slot for which an event occurred. The dontBlock * parameter can have the value false (BLOCK) or true (DONT_BLOCK). * If there is no event present and the method is called with true this * method throws an exception with the error code CKR_NO_EVENT (0x00000008). * * @param dontBlock * Can false (BLOCK) or true (DONT_BLOCK). * @return The slot for which an event occurred. * @exception PKCS11Exception * If the method was called with WaitingBehavior.DONT_BLOCK but * there was no event available, or if an error occurred. * public Slot waitForSlotEvent(boolean dontBlock) throws PKCS11Exception { return new Slot(this, pkcs11.C_WaitForSlotEvent(dontBlock ? CKF_DONT_BLOCK : 0L, null)); } */ /** * Gets the PKCS#11 module of the wrapper package behind this object. * * @return The PKCS#11 module behind this object. */ public PKCS11 getPKCS11() { assertInitialized(); return pkcs11; } boolean hasVendorBehaviour(int vendorBehavior) { return vendorBehaviours.contains(vendorBehavior); } long ckkGenericToVendor(long genericCode) { return withVendorCodeMap ? ckkGenericToVendorMap.getOrDefault(genericCode, genericCode) : genericCode; } long ckkVendorToGeneric(long vendorCode) { return withVendorCodeMap ? ckkVendorToGenericMap.getOrDefault(vendorCode, vendorCode) : vendorCode; } long ckmGenericToVendor(long genericCode) { return withVendorCodeMap ? ckmGenericToVendorMap.getOrDefault(genericCode, genericCode) : genericCode; } long ckmVendorToGeneric(long vendorCode) { return withVendorCodeMap ? ckmVendorToGenericMap.getOrDefault(vendorCode, vendorCode) : vendorCode; } /** * Returns the string representation of this object. * * @return The string representation of object */ @Override public String toString() { return (pkcs11 != null) ? pkcs11.toString() : "null"; } /** * Caution: * Unlike the original PKCS#11 wrapper, we only call initialize() once per * native .so/.dll. Once finalize(Object) has been called, the module cannot * be initialized anymore. * *

* Finalizes this module. The application should call this method when it * finished using the module. * Note that this method is different from the finalize method, * which is the reserved Java method called by the garbage collector. * This method calls the C_Finalize(PKCS11Object) method of the * underlying PKCS11 module. * * @param args * Must be null in version 2.x of PKCS#11. * @exception PKCS11Exception * If finalization fails. */ public void finalize(Object args) throws PKCS11Exception { try { pkcs11.C_Finalize(args); } catch (sun.security.pkcs11.wrapper.PKCS11Exception ex) { throw new PKCS11Exception(ex.getErrorCode()); } } private void assertInitialized() { if (pkcs11 == null) { throw new IllegalStateException("Module not initialized yet, please call initialize() first"); } } private void initVendor() { try { ModuleInfo moduleInfo = getInfo(); String manufacturerID = moduleInfo.getManufacturerID(); String libraryDescription = moduleInfo.getLibraryDescription(); Version libraryVersion = moduleInfo.getLibraryVersion(); String confPath = System.getProperty("org.xipki.pkcs11.vendor.conf"); InputStream in = (confPath != null) ? Files.newInputStream(Paths.get(pkcs11ModulePath)) : PKCS11Module.class.getClassLoader().getResourceAsStream("org/xipki/pkcs11/wrapper/vendor.conf"); try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) { while (true) { VendorBlock block = readVendorBlock(br); if (block == null) { break; } // For better performance, this line should be in the if-block. But we put // it here explicitly to make sure that all vendor blocks ar configured correctly. if (!block.matches(pkcs11ModulePath, manufacturerID, libraryDescription, libraryVersion)) { continue; } StaticLogger.info("found configuration: {}", block); // vendor behaviours if (block.vendorBehaviours != null) { StringTokenizer tokenizer = new StringTokenizer(block.vendorBehaviours, ":, \t"); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if ("SM2_SIGNATURE_X962".equalsIgnoreCase(token)) { vendorBehaviours.add(BEHAVIOUR_SM2_SIGNATURE_X962); } else if ("ECDSA_SIGNATURE_X962".equalsIgnoreCase(token)) { vendorBehaviours.add(BEHAVIOUR_ECDSA_SIGNATURE_X962); } else if ("SM2_PRIVATEKEY_ECPOINT".equalsIgnoreCase(token)) { vendorBehaviours.add(BEHAVIOUR_SM2_PRIVATEKEY_ECPOINT); } else if ("EC_PRIVATEKEY_ECPOINT".equalsIgnoreCase(token)) { vendorBehaviours.add(BEHAVIOUR_EC_PRIVATEKEY_ECPOINT); } else { StaticLogger.warn("Ignored unknown vendor behaviour '" + token + "'."); } } } for (Map.Entry entry : block.nameToCodeMap.entrySet()) { String name = entry.getKey().toUpperCase(Locale.ROOT); String valueStr = entry.getValue().toUpperCase(Locale.ROOT); boolean hex = valueStr.startsWith("0X"); long vendorCode = hex ? Long.parseLong(valueStr.substring(2), 16) : Long.parseLong(valueStr); if (name.startsWith("CKK_VENDOR_")) { Long genericCode = PKCS11Constants.ckkNameToCode(name); if (genericCode == null) { throw new IllegalStateException("unknown name in vendor block: " + name); } ckkGenericToVendorMap.put(genericCode, vendorCode); } else if (name.startsWith("CKM_VENDOR_")) { Long genericCode = PKCS11Constants.ckmNameToCode(name); if (genericCode == null) { throw new IllegalStateException("unknown name in vendor block: " + name); } ckmGenericToVendorMap.put(genericCode, vendorCode); } else { throw new IllegalStateException("Unknown name in vendor block: " + name); } for (Map.Entry m : ckkGenericToVendorMap.entrySet()) { ckkVendorToGenericMap.put(m.getValue(), m.getKey()); } for (Map.Entry m : ckmGenericToVendorMap.entrySet()) { ckmVendorToGenericMap.put(m.getValue(), m.getKey()); } } // end for } // end while } } catch (Exception e) { StaticLogger.warn("error reading VENDOR code mapping, ignore it."); } withVendorCodeMap = !ckmGenericToVendorMap.isEmpty() || !ckkGenericToVendorMap.isEmpty(); } private static VendorBlock readVendorBlock(BufferedReader reader) throws IOException { boolean inBlock = false; String line; VendorBlock block = null; while ((line = reader.readLine()) != null) { line = line.trim(); if (line.isEmpty() || line.charAt(0) == '#') { continue; } if (line.startsWith("")) { block = new VendorBlock(); inBlock = true; } else if (line.startsWith("")) { block.validate(); return block; } else if (inBlock) { if (line.startsWith("module.")) { int idx = line.indexOf(' '); if (idx == -1) { continue; } String value = line.substring(idx + 1).trim(); if (value.isEmpty()) { continue; } String name = line.substring(0, idx).trim(); List textList = Arrays.asList(value.toLowerCase(Locale.ROOT).split(":")); if (name.equalsIgnoreCase("module.path")) { block.modulePaths = textList; } else if (name.equalsIgnoreCase("module.mid")) { block.manufacturerIDs = textList; } else if (name.equalsIgnoreCase("module.description")) { block.descriptions = textList; } else if (name.equalsIgnoreCase("module.version")) { block.versions = textList; } } else if (line.startsWith("CKK_") || line.startsWith("CKM_")) { int idx = line.indexOf(' '); if (idx != -1) { block.nameToCodeMap.put(line.substring(0, idx).trim(), line.substring(idx + 1).trim()); } } else if (line.startsWith("VENDOR_BEHAVIORS ")) { int idx = line.indexOf(' '); String value = line.substring(idx + 1).trim(); if (!value.isEmpty()) { block.vendorBehaviours = value; } } } } return block; } private static final class VendorBlock { private List modulePaths; private List manufacturerIDs; private List descriptions; private List versions; private String vendorBehaviours; private final Map nameToCodeMap = new HashMap<>(); void validate() throws IOException { if (isEmpty(modulePaths) && isEmpty(manufacturerIDs) && isEmpty(descriptions)) { throw new IOException("invalid -block"); } } boolean matches(String modulePath, String manufacturerID, String libraryDescription, Version libraryVersion) { if ((!isEmpty(modulePaths) && !contains(modulePaths, Paths.get(modulePath).getFileName().toString())) || (!isEmpty(manufacturerIDs) && !contains(manufacturerIDs, manufacturerID)) || (!isEmpty(descriptions) && !contains(descriptions, libraryDescription))) { return false; } if (isEmpty(versions)) { return true; } int iVersion = ((0xFF & libraryVersion.getMajor()) << 8) + (0xFF & libraryVersion.getMinor()); boolean match = false; for (String t : versions) { int idx = t.indexOf("-"); int from = (idx == -1) ? toIntVersion(t) : toIntVersion(t.substring(0, idx)); int to = (idx == -1) ? from : toIntVersion(t.substring(idx + 1)); if (iVersion >= from && iVersion <= to) { match = true; break; } } return match; } private static int toIntVersion(String version) { StringTokenizer st = new StringTokenizer(version, "."); return (Integer.parseInt(st.nextToken()) << 8) + Integer.parseInt(st.nextToken()); } private static boolean isEmpty(Collection c) { return c == null || c.isEmpty(); } private static boolean contains(List list, String str) { str = str.toLowerCase(Locale.ROOT); for (String s : list) { if (str.contains(s)) { return true; } } return false; } @Override public String toString() { return "VendorConfBlock" + "\n modulePaths: " + modulePaths + "\n manufacturerIDs: " + manufacturerIDs + "\n descriptions: " + descriptions + "\n versions: " + versions + "\n vendorBehaviours: " + vendorBehaviours + "\n nameToCodeMap: " + nameToCodeMap; } } // class VendorConfBlock }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy