YubiKey.java

/*
 * Copyright 2021 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.AuthProvider;
import java.security.Provider;
import java.security.ProviderException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

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

import net.jsign.jca.AutoLoginProvider;

/**
 * Helper class for working with YubiKeys.
 *
 * @since 4.0
 */
class YubiKey {

    /**
     * Returns the security provider for the YubiKey.
     *
     * @return the YubiKey security provider
     * @throws ProviderException thrown if the provider can't be initialized
     * @since 4.0
     */
    static Provider getProvider() {
        return new AutoLoginProvider((AuthProvider) ProviderUtils.createSunPKCS11Provider(getSunPKCS11Configuration()));
    }

    /**
     * Returns the SunPKCS11 configuration of the YubiKey.
     *
     * @throws ProviderException thrown if the PKCS11 modules cannot be found
     * @since 4.0
     */
    static String getSunPKCS11Configuration() {
        File libykcs11 = getYkcs11Library();
        if (!libykcs11.exists()) {
            throw new ProviderException("YubiKey PKCS11 module (ykcs11) is not installed (" + libykcs11 + " is missing)");
        }

        long slot;
        try {
            slot = getTokenSlot(libykcs11);
        } catch (Exception e) {
            throw new ProviderException(e);
        }

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

    /**
     * Returns the slot index associated to the token.
     *
     * @since 4.1
     */
    static long getTokenSlot(File libraryPath) throws PKCS11Exception, IOException {
        PKCS11 pkcs11 = PKCS11.getInstance(libraryPath.getAbsolutePath(), "C_GetFunctionList", null, false);
        long[] slots = pkcs11.C_GetSlotList(true);
        return slots.length > 0 ? slots[0] : -1;
    }

    /**
     * Tells if a YubiKey is present on the system.
     */
    static boolean isPresent() {
        try {
            return getTokenSlot(getYkcs11Library()) >= 0;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Attempts to locate the ykcs11 library on the system.
     *
     * @since 4.0
     */
    static File getYkcs11Library() {
        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");
            }
            File libykcs11 = new File(programfiles + "/Yubico/Yubico PIV Tool/bin/libykcs11.dll");

            if (!System.getenv("PATH").contains("Yubico PIV Tool\\bin")) {
                Logger log = Logger.getLogger(YubiKey.class.getName());
                log.warning("The YubiKey library path (" + libykcs11.getParentFile().getAbsolutePath().replace('/', '\\') + ") is missing from the PATH environment variable");
            }

            return libykcs11;

        } else if (osname.contains("Mac")) {
            return new File("/usr/local/lib/libykcs11.dylib");

        } else {
            // Linux
            List<String> paths = new ArrayList<>();
            if ("32".equals(arch)) {
                paths.add("/usr/lib/libykcs11.so");
                paths.add("/usr/lib/libykcs11.so.1");
                paths.add("/usr/lib/i386-linux-gnu/libykcs11.so");
                paths.add("/usr/lib/arm-linux-gnueabi/libykcs11.so");
                paths.add("/usr/lib/arm-linux-gnueabihf/libykcs11.so");
            } else {
                paths.add("/usr/lib64/libykcs11.so");
                paths.add("/usr/lib64/libykcs11.so.1");
                paths.add("/usr/lib/x86_64-linux-gnu/libykcs11.so");
                paths.add("/usr/lib/aarch64-linux-gnu/libykcs11.so");
                paths.add("/usr/lib/mips64el-linux-gnuabi64/libykcs11.so");
                paths.add("/usr/lib/riscv64-linux-gnu/libykcs11.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 libykcs11 = new File(path);
                if (libykcs11.exists()) {
                    return libykcs11;
                }
            }

            return new File("/usr/local/lib/libykcs11.so");
        }
    }
}