TrueLicense源码解析-寻找docker中使用license安装后存放的位置

TrueLicense源码解析-寻找license安装后存放的位置

1、需求来源:

项目结构:
springboot调用trueLicense生成license证书

遇到问题:
但是在docker容器环境中部署,只要容器重新构建,都需要重新上传license证书

解决方案:
因此需要找到容器内部环境中—license证书安装后存储的位置,并把这个位置下所有文件进行持久化,便不用每次构建都需要重新上传license文件了

2、解析过程:

  1. 从读取license中的详细信息方法入手,发现有两个大步骤需要完成

1.1 )初始化证书读取环境
1.2 )读取证书具体信息


package com.abc.license;

public class LicenseVerify {

    public LicenseContent readAndVerify(LicenseVerifyParam param) {
       //1.初始化证书读取环境,具体看下边的initLicenseParam方法
        LicenseManager licenseManager = LicenseManagerHolder.getInstance(this.initLicenseParam(param));
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        try {
           //2.读取证书具体信息,具体看第【6】步
            LicenseContent licenseContent = licenseManager.verify();
            if (null != licenseContent.getNotAfter()) {
                log.info(MessageFormat.format("证书校验通过,证书有效期:{0} - {1}", format.format(licenseContent.getNotBefore()), format.format(licenseContent.getNotAfter())));
            } else {
                log.info(MessageFormat.format("证书校验通过,证书有效期:{0} - {1}", format.format(licenseContent.getNotBefore()), "永久"));
            }

            return licenseContent;
        } catch (Exception var5) {
            log.error("证书校验失败!", var5);
            return null;
        }
    }


    private LicenseParam initLicenseParam(LicenseVerifyParam param) {
        //初始化证书读取环境,此时进入Preferences.userNodeForPackage具体方法中
        Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class);
        CipherParam cipherParam = new DefaultCipherParam(param.getStorePass());
        KeyStoreParam publicStoreParam = new CustomKeyStoreParam(LicenseVerify.class, param.getPublicKeysStorePath(), param.getPublicAlias(), param.getStorePass(), (String)null);
        return new DefaultLicenseParam(param.getSubject(), preferences, publicStoreParam, cipherParam);
    }
}
  1. 开始初始化证书读取环境,进入Preferences.userNodeForPackage具体方法中
package java.util.prefs;

public abstract class Preferences {


    /**
     * 此处的c 就是 LicenseVerify.clas
     * @param c the class for whose package a user preference node is desired.
     * @return the user preference node associated with the package of which
     *         <tt>c</tt> is a member.
     * @throws NullPointerException if <tt>c</tt> is <tt>null</tt>.
     * @throws SecurityException if a security manager is present and
     *         it denies <tt>RuntimePermission("preferences")</tt>.
     * @see    RuntimePermission
     */
    public static Preferences userNodeForPackage(Class<?> c) {
        return userRoot().node(nodeName(c));
    }

}
  1. 开始调用Preferences的userRoot()方法,最后进入到【factory】的userRoot方法。
    而【factory】是调用factory()方法进行初始化的,factory()方法继续调用factory1()方法
    在factory1()方法中的返回值发现,因为我本地是Windows,所以最终userRoot()返回的Preferences
    是【WindowsPreferencesFactory】
package java.util.prefs;

public abstract class Preferences {

private static final PreferencesFactory factory = factory();

      /**
     * Returns the root preference node for the calling user.
     *
     * @return the root preference node for the calling user.
     * @throws SecurityException If a security manager is present and
     *         it denies <tt>RuntimePermission("preferences")</tt>.
     * @see    RuntimePermission
     */
    public static Preferences userRoot() {
        SecurityManager security = System.getSecurityManager();
        if (security != null)
            security.checkPermission(prefsPerm);

       //继续到factory的userRoot方法中,
        return factory.userRoot();
    }

   private static PreferencesFactory factory() {
        // 1. Try user-specified system property
        String factoryName = AccessController.doPrivileged(
            new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty(
                        "java.util.prefs.PreferencesFactory");}});
        if (factoryName != null) {
            // FIXME: This code should be run in a doPrivileged and
            // not use the context classloader, to avoid being
            // dependent on the invoking thread.
            // Checking AllPermission also seems wrong.
            try {
                return (PreferencesFactory)
                    Class.forName(factoryName, false,
                                  ClassLoader.getSystemClassLoader())
                    .newInstance();
            } catch (Exception ex) {
                try {
                    // workaround for javaws, plugin,
                    // load factory class using non-system classloader
                    SecurityManager sm = System.getSecurityManager();
                    if (sm != null) {
                        sm.checkPermission(new java.security.AllPermission());
                    }
                    return (PreferencesFactory)
                        Class.forName(factoryName, false,
                                      Thread.currentThread()
                                      .getContextClassLoader())
                        .newInstance();
                } catch (Exception e) {
                    throw new InternalError(
                        "Can't instantiate Preferences factory "
                        + factoryName, e);
                }
            }
        }

        return AccessController.doPrivileged(
            new PrivilegedAction<PreferencesFactory>() {
                public PreferencesFactory run() {
                    return factory1();}});
    }

    private static PreferencesFactory factory1() {
        // 2. Try service provider interface
        Iterator<PreferencesFactory> itr = ServiceLoader
            .load(PreferencesFactory.class, ClassLoader.getSystemClassLoader())
            .iterator();

        // choose first provider instance
        while (itr.hasNext()) {
            try {
                return itr.next();
            } catch (ServiceConfigurationError sce) {
                if (sce.getCause() instanceof SecurityException) {
                    // Ignore the security exception, try the next provider
                    continue;
                }
                throw sce;
            }
        }

        // 3. Use platform-specific system-wide default
        String osName = System.getProperty("os.name");
        String platformFactory;
        if (osName.startsWith("Windows")) {
            platformFactory = "java.util.prefs.WindowsPreferencesFactory";
        } else if (osName.contains("OS X")) {
            platformFactory = "java.util.prefs.MacOSXPreferencesFactory";
        } else {
            platformFactory = "java.util.prefs.FileSystemPreferencesFactory";
        }
        try {
            return (PreferencesFactory)
                Class.forName(platformFactory, false,
                              Preferences.class.getClassLoader()).newInstance();
        } catch (Exception e) {
            throw new InternalError(
                "Can't instantiate platform default Preferences factory "
                + platformFactory, e);
        }
    }
}


  1. 在【WindowsPreferencesFactory】类中,发现【2】中调用的是【WindowsPreferencesFactory】 的userRoot() ,返回的是 WindowsPreferences.userRoot;
package java.util.prefs;

/**
 * Implementation of  <tt>PreferencesFactory</tt> to return
 * WindowsPreferences objects.
 *
 * @author  Konstantin Kladko
 * @see Preferences
 * @see WindowsPreferences
 * @since 1.4
 */
class WindowsPreferencesFactory implements PreferencesFactory  {

    /**
     * 最后调用的是这个方法
     * Returns WindowsPreferences.userRoot
     */
    public Preferences userRoot() {
        return WindowsPreferences.userRoot;
    }

    /**
     * Returns WindowsPreferences.systemRoot
     */
    public Preferences systemRoot() {
        return WindowsPreferences.systemRoot;
    }
}

  1. WindowsPreferences.userRoot返回的结果如下,最终拼接起来就是
    HKEY_CURRENT_USER\Software\JavaSoft\Prefs
package java.util.prefs;
class WindowsPreferences extends AbstractPreferences{

    /**
     * Mount point for <tt>Preferences</tt>'  user root.
     */
    private static final int USER_ROOT_NATIVE_HANDLE = HKEY_CURRENT_USER;
    
  /**
     * Windows registry path to <tt>Preferences</tt>'s root nodes.
     */
    private static final byte[] WINDOWS_ROOT_PATH =
        stringToByteArray("Software\\JavaSoft\\Prefs");


    /**
     * User root node.
     */
    static final Preferences userRoot =
         new WindowsPreferences(USER_ROOT_NATIVE_HANDLE, WINDOWS_ROOT_PATH);


    //node方法会调用此方法处理子节点
    protected AbstractPreferences childSpi(String name) {
        return new WindowsPreferences(this, name);
    }


   /**
     * Constructs a <tt>WindowsPreferences</tt> node, creating underlying
     * Windows registry node and all its Windows parents, if they are not yet
     * created.
     * Logs a warning message, if Windows Registry is unavailable.
     */
    private WindowsPreferences(WindowsPreferences parent, String name) {
        super(parent, name);
        int parentNativeHandle = parent.openKey(KEY_CREATE_SUB_KEY, KEY_READ);
        if (parentNativeHandle == NULL_NATIVE_HANDLE) {
            // if here, openKey failed and logged
            isBackingStoreAvailable = false;
            return;
        }
        int[] result =
               WindowsRegCreateKeyEx1(parentNativeHandle, toWindowsName(name));
        if (result[ERROR_CODE] != ERROR_SUCCESS) {
            logger().warning("Could not create windows registry node " +
                    byteArrayToString(windowsAbsolutePath()) +
                    " at root 0x" + Integer.toHexString(rootNativeHandle()) +
                    ". Windows RegCreateKeyEx(...) returned error code " +
                    result[ERROR_CODE] + ".");
            isBackingStoreAvailable = false;
            return;
        }
        newNode = (result[DISPOSITION] == REG_CREATED_NEW_KEY);
        closeKey(parentNativeHandle);
        closeKey(result[NATIVE_HANDLE]);
    }
}

看完userRoot()再去看它的node方法: userRoot().node(nodeName©);
node() 方法是对传进来的class的路径进行递归处理,一层一层的拼接
我们传进来的class是【LicenseVerify.class】,这个类的路径假设是【com.abc.license】
最终,node() 方法执行完,会得到一个完整的路径(此路径便是下面要查询的路径): HKEY_CURRENT_USER\Software\JavaSoft\Prefs\com\abc\license

package java.util.prefs;

public abstract class AbstractPreferences extends Preferences {

    public Preferences node(String path) {
        synchronized(lock) {
            if (removed)
                throw new IllegalStateException("Node has been removed.");
            if (path.equals(""))
                return this;
            if (path.equals("/"))
                return root;
            if (path.charAt(0) != '/')
                return node(new StringTokenizer(path, "/", true));
        }

        // Absolute path.  Note that we've dropped our lock to avoid deadlock
        return root.node(new StringTokenizer(path.substring(1), "/", true));
    }
}

   /**
     * tokenizer contains <name> {'/' <name>}*
     */
    private Preferences node(StringTokenizer path) {
        String token = path.nextToken();
        if (token.equals("/"))  // Check for consecutive slashes
            throw new IllegalArgumentException("Consecutive slashes in path");
        synchronized(lock) {
            AbstractPreferences child = kidCache.get(token);
            if (child == null) {
                if (token.length() > MAX_NAME_LENGTH)
                    throw new IllegalArgumentException(
                        "Node name " + token + " too long");
                child = childSpi(token);
                if (child.newNode)
                    enqueueNodeAddedEvent(child);
                kidCache.put(token, child);
            }
            if (!path.hasMoreTokens())
                return child;
            path.nextToken();  // Consume slash
            if (!path.hasMoreTokens())
                throw new IllegalArgumentException("Path ends with slash");
             //针对class路径的递归处理
            return child.node(path);
        }
    }
  1. 至此第一步:
    初始化证书读取环境已经完成,读取到了license在Windows注册表存放的路径
    然后开始:
    读取证书具体信息
    getLicenseKey() 方法便是读取具体的证书字节信息也就是详细信息(只不过此时读到的只是字节,后期还要进行各种转换,但是我们此时只关注从哪里读取,不关注后期怎么转换)
   /**
     * Decrypts, decompresses, decodes and verifies the current license key,
     * validates its license content and returns it.
     *
     * @param  notary the license notary used to verify the current license key
     *         - may <em>not</em> be {@code null}.
     * @throws NoLicenseInstalledException if no license key is installed.
     * @throws Exception for any other reason.
     *         Note that you should always use
     *         {@link Throwable#getLocalizedMessage()} to get a (possibly
     *         localized) meaningful detail message.
     * @return A clone of the verified and validated content of the license key
     *         - {@code null} is never returned.
     * @see    #validate(LicenseContent)
     */
    protected synchronized LicenseContent verify(final LicenseNotary notary)
    throws Exception {
        GenericCertificate certificate = getCertificate();
        //此时读取内容是空白的,所以不会直接return
        if (null != certificate)
            return (LicenseContent) certificate.getContent();

        // Load license key from preferences, 
        //这一步便是读取具体的证书字节信息,读取到之后进行各种转换格式化
        final byte[] key = getLicenseKey();
        if (null == key)
            throw new NoLicenseInstalledException(getLicenseParam().getSubject());
        certificate = getPrivacyGuard().key2cert(key);
        notary.verify(certificate);
        final LicenseContent content = (LicenseContent) certificate.getContent();
        validate(content);
        setCertificate(certificate);

        return content;
    }
  1. getLicenseKey()往下走,最终会调用到【AbstractPreferences】 的 getByteArray() 方法中的 get() 方法,继续调用 get() 方法中的 getSpi() 方法
package de.schlichtherle.license;

public class LicenseManager implements LicenseCreator, LicenseVerifier {
    //
    // Methods for license keys.
    // Note that in contrast to the methods of the privacy guard,
    // the following methods may have side effects (preferences, file system).
    //

    /**
     * 直接调用到此方法,此时的PREFERENCES_KEY = "license"
     * Returns the current license key.
     */
    protected synchronized byte[] getLicenseKey() {
        return getLicenseParam().getPreferences().getByteArray(PREFERENCES_KEY, null);
    }
}
package java.util.prefs;

public abstract class AbstractPreferences extends Preferences {
    /**
     * Implements the <tt>getByteArray</tt> method as per the specification in
     * {@link Preferences#getByteArray(String,byte[])}.
     *
     * @param key key whose associated value is to be returned as a byte array.
     * @param def the value to be returned in the event that this
     *        preference node has no value associated with <tt>key</tt>
     *        or the associated value cannot be interpreted as a byte array.
     * @return the byte array value represented by the string associated with
     *         <tt>key</tt> in this preference node, or <tt>def</tt> if the
     *         associated value does not exist or cannot be interpreted as
     *         a byte array.
     * @throws IllegalStateException if this node (or an ancestor) has been
     *         removed with the {@link #removeNode()} method.
     * @throws NullPointerException if <tt>key</tt> is <tt>null</tt>.  (A
     *         <tt>null</tt> value for <tt>def</tt> <i>is</i> permitted.)
     */
    public byte[] getByteArray(String key, byte[] def) {
        byte[] result = def;
        //最终调用到此get方法
        String value = get(key, null);
        try {
            if (value != null)
                result = Base64.base64ToByteArray(value);
        }
        catch (RuntimeException e) {
            // Ignoring exception causes specified default to be returned
        }

        return result;
    }

  /**
     * Implements the <tt>get</tt> method as per the specification in
     * {@link Preferences#get(String,String)}.
     *
     * <p>This implementation first checks to see if <tt>key</tt> is
     * <tt>null</tt> throwing a <tt>NullPointerException</tt> if this is
     * the case.  Then it obtains this preference node's lock,
     * checks that the node has not been removed, invokes {@link
     * #getSpi(String)}, and returns the result, unless the <tt>getSpi</tt>
     * invocation returns <tt>null</tt> or throws an exception, in which case
     * this invocation returns <tt>def</tt>.
     *
     * @param key key whose associated value is to be returned.
     * @param def the value to be returned in the event that this
     *        preference node has no value associated with <tt>key</tt>.
     * @return the value associated with <tt>key</tt>, or <tt>def</tt>
     *         if no value is associated with <tt>key</tt>.
     * @throws IllegalStateException if this node (or an ancestor) has been
     *         removed with the {@link #removeNode()} method.
     * @throws NullPointerException if key is <tt>null</tt>.  (A
     *         <tt>null</tt> default <i>is</i> permitted.)
     */
    public String get(String key, String def) {
        if (key==null)
            throw new NullPointerException("Null key");
        synchronized(lock) {
            if (removed)
                throw new IllegalStateException("Node has been removed.");

            String result = null;
            try {
                result = getSpi(key);
            } catch (Exception e) {
                // Ignoring exception causes default to be returned
            }
            return (result==null ? def : result);
        }
    }
}
  1. 【AbstractPreferences】 的 getSpi() 调用会走到【WindowsPreferences】的 getSpi() 方法,此方法中最重要的一句就是 WindowsRegQueryValueEx,这个方法是:为windows提供的查询注册表的api接口,因为是native,就不继续往下了
    WindowsRegQueryValueEx方法有两个参数:

    1)javaName 往上追溯便是: “license”
    2)nativeHandle :要查询的注册表的key,我们在第【5】步的时候已经得出了一个明文的路径,不过在这个方法中是转换为int了

因此得出我们最终要去注册表查询 HKEY_CURRENT_USER\Software\JavaSoft\Prefs\com\abc\license 路径下的文件,便是license存储证书的位置。

package java.util.prefs;
class WindowsPreferences extends AbstractPreferences{
    /**
     * Implements <tt>AbstractPreferences</tt> <tt>getSpi()</tt> method.
     * Gets a string value from the underlying Windows registry node.
     * Logs a warning, if Windows registry is unavailable.
     * @see #putSpi(String, String)
     */
    protected String getSpi(String javaName) {
    int nativeHandle = openKey(KEY_QUERY_VALUE);
    if (nativeHandle == NULL_NATIVE_HANDLE) {
        return null;
    }
    
    Object resultObject =  WindowsRegQueryValueEx(nativeHandle,
                                                  toWindowsName(javaName));
    if (resultObject == null) {
        closeKey(nativeHandle);
        return null;
    }
    closeKey(nativeHandle);
    return toJavaValueString((byte[]) resultObject);
    }

  /**
     * java为windows提供的查询注册表的api接口
     * Java wrapper for Windows registry API RegQueryValueEx()
     */
    private static native byte[] WindowsRegQueryValueEx(int hKey,
                                                        byte[] valueName);
}

关于native的一些解释

  • 使用 native 关键字说明这个方法是原生函数,也就是这个方法是用 C/C++ 语言实现的,并且被编译成了 DLL,由 Java去调用
  • native 的意思就是通知操作系统,这个函数你必须给我实现,因为我要使用。
  • 所以 native 关键字的函数都是操作系统实现的,Java 只能调用

关于怎么在linux中找出存储位置,此处不再具体说明
可以根据windows中找到的存储路径的文件夹名字,比如直接搜索此案例中windows路径是:HKEY_CURRENT_USER\Software\JavaSoft\Prefs\com\abc\license
在linux中搜索【abc】 文件夹的位置 find / -name abc

注册表格式如图所示
注册表存储的信息如图

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值