OpenSC.java

/*
 * Copyright 2023 Emmanuel Bourg
 *
 * 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 net.jsign;

import java.io.File;
import java.io.IOException;
import java.security.Provider;
import java.security.ProviderException;
import java.util.ArrayList;
import java.util.List;

import sun.security.pkcs11.wrapper.CK_SLOT_INFO;
import sun.security.pkcs11.wrapper.CK_TOKEN_INFO;
import sun.security.pkcs11.wrapper.PKCS11;
import sun.security.pkcs11.wrapper.PKCS11Exception;

/**
 * Helper class for working with OpenSC.
 *
 * @since 5.0
 */
class OpenSC {

    /**
     * Returns the security provider for OpenSC.
     *
     * @param name the name of the token
     * @return the OpenSC security provider
     * @throws ProviderException thrown if the provider can't be initialized
     */
    static Provider getProvider(String name) {
        return ProviderUtils.createSunPKCS11Provider(getSunPKCS11Configuration(name));
    }

    /**
     * Returns the SunPKCS11 configuration for OpenSC.
     *
     * @param name the name or the slot id of the token
     * @throws ProviderException thrown if the PKCS11 modules cannot be found
     */
    static String getSunPKCS11Configuration(String name) {
        File library = getOpenSCLibrary();
        if (!library.exists()) {
            throw new ProviderException("OpenSC PKCS11 module is not installed (" + library + " is missing)");
        }

        long slot;
        try {
            try {
                slot = Integer.parseInt(name);
            } catch (Exception e) {
                slot = getTokenSlot(library, name);
            }
        } catch (Exception e) {
            throw new ProviderException(e);
        }

        return new PKCS11Configuration().name("opensc").library(library).slot(slot).toString();
    }

    /**
     * Returns the slot index associated to the token.
     *
     * @param libraryPath the path to the PKCS11 library
     * @param name        the partial name of the token
     */
    static long getTokenSlot(File libraryPath, String name) throws PKCS11Exception, IOException {
        PKCS11 pkcs11 = PKCS11.getInstance(libraryPath.getAbsolutePath(), "C_GetFunctionList", null, false);
        long[] slots = pkcs11.C_GetSlotList(true);

        List<String> descriptions = new ArrayList<>();
        List<Long> matches = new ArrayList<>();
        for (long slot : slots) {
            CK_SLOT_INFO info = pkcs11.C_GetSlotInfo(slot);
            String description = new String(info.slotDescription).trim();
            if (name == null || description.toLowerCase().contains(name.toLowerCase())) {
                CK_TOKEN_INFO tokenInfo = pkcs11.C_GetTokenInfo(slot);
                String label = new String(tokenInfo.label).trim();
                if (label.equals("OpenPGP card (User PIN (sig))")) {
                    // OpenPGP cards such as the Nitrokey 3 are exposed as two slots with the same name by OpenSC.
                    // Only the first one contains the signing key and the certificate, so the second one is ignored.
                    continue;
                }

                matches.add(slot);
            }
            descriptions.add(description);
        }

        if (matches.size() == 1) {
            return matches.get(0);
        }

        if (matches.isEmpty()) {
            throw new RuntimeException(descriptions.isEmpty() ? "No PKCS11 token found" : "No PKCS11 token found matching '" + name + "' (available tokens: " + String.join(", ", descriptions) + ")");
        } else {
            throw new RuntimeException("Multiple PKCS11 tokens found" + (name != null ? " matching '" + name + "'" : "") + ", please specify the name of the token to use (available tokens: " + String.join(", ", descriptions) + ")");
        }
    }

    /**
     * Attempts to locate the opensc-pkcs11 library on the system.
     */
    static File getOpenSCLibrary() {
        String osname = System.getProperty("os.name");
        String arch = System.getProperty("sun.arch.data.model");

        if (osname.contains("Windows")) {
            String programfiles;
            if ("32".equals(arch) && System.getenv("ProgramFiles(x86)") != null) {
                programfiles = System.getenv("ProgramFiles(x86)");
            } else {
                programfiles = System.getenv("ProgramFiles");
            }
            return new File(programfiles + "/OpenSC Project/OpenSC/pkcs11/opensc-pkcs11.dll");

        } else if (osname.contains("Mac")) {
            return new File("/Library/OpenSC/lib/opensc-pkcs11.so");

        } else {
            // Linux
            List<String> paths = new ArrayList<>();
            if ("32".equals(arch)) {
                paths.add("/usr/lib/opensc-pkcs11.so");
                paths.add("/usr/lib/i386-linux-gnu/opensc-pkcs11.so");
                paths.add("/usr/lib/arm-linux-gnueabi/opensc-pkcs11.so");
                paths.add("/usr/lib/arm-linux-gnueabihf/opensc-pkcs11.so");
            } else {
                paths.add("/usr/lib64/opensc-pkcs11.so");
                paths.add("/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so");
                paths.add("/usr/lib/aarch64-linux-gnu/opensc-pkcs11.so");
                paths.add("/usr/lib/mips64el-linux-gnuabi64/opensc-pkcs11.so");
                paths.add("/usr/lib/riscv64-linux-gnu/opensc-pkcs11.so");
            }
            String libraryPath = System.getenv("LD_LIBRARY_PATH");
            if (libraryPath != null) {
                for (String s : libraryPath.split(":")) {
                    paths.add(s + "/libykcs11.so");
                }
            }

            for (String path : paths) {
                File library = new File(path);
                if (library.exists()) {
                    return library;
                }
            }

            return new File("/usr/local/lib/opensc-pkcs11.so");
        }
    }
}