• TrueLicense源码解析-寻找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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    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
         *         c is a member.
         * @throws NullPointerException if c is null.
         * @throws SecurityException if a security manager is present and
         *         it denies RuntimePermission("preferences").
         * @see    RuntimePermission
         */
        public static Preferences userNodeForPackage(Class<?> c) {
            return userRoot().node(nodeName(c));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    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 RuntimePermission("preferences").
         * @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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    1. 在【WindowsPreferencesFactory】类中,发现【2】中调用的是【WindowsPreferencesFactory】 的userRoot() ,返回的是 WindowsPreferences.userRoot;
    package java.util.prefs;
    
    /**
     * Implementation of  PreferencesFactory 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    1. WindowsPreferences.userRoot返回的结果如下,最终拼接起来就是
      HKEY_CURRENT_USER\Software\JavaSoft\Prefs
    package java.util.prefs;
    class WindowsPreferences extends AbstractPreferences{
    
        /**
         * Mount point for Preferences'  user root.
         */
        private static final int USER_ROOT_NATIVE_HANDLE = HKEY_CURRENT_USER;
        
      /**
         * Windows registry path to Preferences'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 WindowsPreferences 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]);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    看完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  {'/' }*
         */
        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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    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 not 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    package java.util.prefs;
    
    public abstract class AbstractPreferences extends Preferences {
        /**
         * Implements the getByteArray 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 key
         *        or the associated value cannot be interpreted as a byte array.
         * @return the byte array value represented by the string associated with
         *         key in this preference node, or def 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 key is null.  (A
         *         null value for def is 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 get method as per the specification in
         * {@link Preferences#get(String,String)}.
         *
         * 

    This implementation first checks to see if key is * null throwing a NullPointerException 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 getSpi * invocation returns null or throws an exception, in which case * this invocation returns def. * * @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 key. * @return the value associated with key, or def * if no value is associated with key. * @throws IllegalStateException if this node (or an ancestor) has been * removed with the {@link #removeNode()} method. * @throws NullPointerException if key is null. (A * null default is 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    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 AbstractPreferences getSpi() 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    关于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

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

  • 相关阅读:
    DayDayUp:计算机技术与软件专业技术资格证书之《系统集成项目管理工程师》课程讲解之十大知识领域之4辅助—项目人力资源管理
    vue3+vite+ts使用Element+Plus
    多边形内部水平方向近似最大矩形python实现
    ODrive移植keil(七)—— 插值算法和偏置校准
    JAVASE 第二十五天
    RPA流程自动化的优势和好处
    附下载,《数据传输安全白皮书》上新,90页详解安全策略
    机器学习——第五章 神经网络
    Oracle 11g DataGuard 搭建笔记(Windows Server 2016)
    为什么寒冷容易诱发痛风?
  • 原文地址:https://blog.csdn.net/MyfishCake/article/details/126426954