zoukankan      html  css  js  c++  java
  • 基于 TrueLicense 的项目证书验证

    一、简述

    开发的软件产品在交付使用的时候,往往有一段时间的试用期,这期间我们不希望自己的代码被客户二次拷贝,这个时候 license 就派上用场了,license 的功能包括设定有效期、绑定 ip、绑定 mac 等。授权方直接生成一个 license 给使用方使用,如果需要延长试用期,也只需要重新生成一份 license 即可,无需手动修改源代码。

    TrueLicense 是一个开源的证书管理引擎,详细介绍见 https://truelicense.java.net/

    首先介绍下 license 授权机制的原理:

    1. 生成密钥对,包含私钥和公钥。
    2. 授权者保留私钥,使用私钥对授权信息诸如使用截止日期,mac 地址等内容生成 license 签名证书。
    3. 公钥给使用者,放在代码中使用,用于验证 license 签名证书是否符合使用条件。

    二、生成密钥对

    以下命令在 window cmd 命令窗口执行,注意当前执行目录,最后生成的密钥对即在该目录下:
    1、首先要用 KeyTool 工具来生成私匙库:(-alias别名 -validity 3650 表示10年有效)

    keytool -genkey -alias privatekey -keysize 1024 -keystore privateKeys.store -validity 3650
    

    2、然后把私匙库内的证书导出到一个文件当中

    keytool -export -alias privatekey -file certfile.cer -keystore privateKeys.store
    

    3、然后再把这个证书文件导入到公匙库

    keytool -import -alias publiccert -file certfile.cer -keystore publicCerts.store
    

    最后生成的文件 privateKeys.store(私钥)、publicCerts.store(公钥)拷贝出来备用。

    三、准备工作

    首先,我们需要引入 truelicense 的 jar 包,用于实现我们的证书管理。

    <dependency>
          <groupId>de.schlichtherle.truelicense</groupId>
          <artifactId>truelicense-core</artifactId>
          <version>1.33</version>
    </dependency>
    

    然后,我们建立一个单例模式下的证书管理器。

    public class LicenseManagerHolder {
    
        private static volatile LicenseManager licenseManager = null;
    
        private LicenseManagerHolder() {
        }
    
        public static LicenseManager getLicenseManager(LicenseParam param) {
            if (licenseManager == null) {
                synchronized (LicenseManagerHolder.class) {
                    if (licenseManager == null) {
                        licenseManager = new LicenseManager(param);
                    }
                }
            }
            return licenseManager;
        }
    }
    

    四、利用私钥生成证书

    利用私钥生成证书,我们需要两部分内容,一部分是私钥的配置信息(私钥的配置信息在生成私钥库的过程中获得),一部分是自定义的项目证书信息。如下展示:

    ########## 私钥的配置信息 ###########
    # 私钥的别名
    private.key.alias=privatekey
    # privateKeyPwd(该密码是生成密钥对的密码 — 需要妥善保管,不能让使用者知道)
    private.key.pwd=123456
    # keyStorePwd(该密码是访问密钥库的密码 — 使用 keytool 生成密钥对时设置,使用者知道该密码)
    key.store.pwd=123456
    # 项目的唯一识别码
    subject=demo
    # 密钥库的地址(放在 resource 目录下)
    priPath=/privateKeys.store
    
    ########## license content ###########
    # 发布日期
    issuedTime=2019-09-12
    # 有效开始日期
    notBefore=2019-09-12
    # 有效截止日期
    notAfter=2019-12-30
    # ip 地址
    ipAddress=192.168.31.25
    # mac 地址
    macAddress=5C-C5-D4-3E-CA-A6
    # 使用者类型,用户(user)、电脑(computer)、其他(else)
    consumerType=user
    # 证书允许使用的消费者数量
    consumerAmount=1
    # 证书说明
    info=power by xiamen yungu
    
    
    #生成证书的地址
    licPath=D:\license.lic
    

    接下来,就是如何生成证书的实操部分了

    @Slf4j
    public class CreateLicense {
    
        /**
         * X500Princal 是一个证书文件的固有格式,详见API
         */
        private final static X500Principal DEFAULT_HOLDERAND_ISSUER = new X500Principal("CN=Duke, OU=JavaSoft, O=Sun Microsystems, C=US");
    
        private String priAlias;
        private String privateKeyPwd;
        private String keyStorePwd;
        private String subject;
        private String priPath;
    
        private String issued;
        private String notBefore;
        private String notAfter;
        private String ipAddress;
        private String macAddress;
        private String consumerType;
        private int consumerAmount;
        private String info;
    
        private String licPath;
    
    
        /**
         * 构造器,参数初始化
         *
         * @param confPath 参数配置文件路径
         */
        public CreateLicense(String confPath) {
            // 获取参数
            Properties prop = new Properties();
            try (InputStream in = getClass().getResourceAsStream(confPath)) {
                prop.load(in);
            } catch (IOException e) {
                log.error("CreateLicense Properties load inputStream error.", e);
            }
            //common param
            priAlias = prop.getProperty("private.key.alias");
            privateKeyPwd = prop.getProperty("private.key.pwd");
            keyStorePwd = prop.getProperty("key.store.pwd");
            subject = prop.getProperty("subject");
            priPath = prop.getProperty("priPath");
            // license content
            issued = prop.getProperty("issuedTime");
            notBefore = prop.getProperty("notBefore");
            notAfter = prop.getProperty("notAfter");
            ipAddress = prop.getProperty("ipAddress");
            macAddress = prop.getProperty("macAddress");
            consumerType = prop.getProperty("consumerType");
            consumerAmount = Integer.valueOf(prop.getProperty("consumerAmount"));
            info = prop.getProperty("info");
    
            licPath = prop.getProperty("licPath");
        }
    
    
        /**
         * 生成证书,在证书发布者端执行
         *
         * @throws Exception
         */
        public void create() throws Exception {
            LicenseManager licenseManager = LicenseManagerHolder.getLicenseManager(initLicenseParams());
            licenseManager.store(buildLicenseContent(), new File(licPath));
            log.info("------ 证书发布成功 ------");
        }
    
        /**
         * 初始化证书的相关参数
         *
         * @return
         */
        private LicenseParam initLicenseParams() {
            Class<CreateLicense> clazz = CreateLicense.class;
            Preferences preferences = Preferences.userNodeForPackage(clazz);
            // 设置对证书内容加密的对称密码
            CipherParam cipherParam = new DefaultCipherParam(keyStorePwd);
            // 参数 1,2 从哪个Class.getResource()获得密钥库;
            // 参数 3 密钥库的别名;
            // 参数 4 密钥库存储密码;
            // 参数 5 密钥库密码
            KeyStoreParam privateStoreParam = new DefaultKeyStoreParam(clazz, priPath, priAlias, keyStorePwd, privateKeyPwd);
            // 返回生成证书时需要的参数
            return new DefaultLicenseParam(subject, preferences, privateStoreParam, cipherParam);
        }
    
        /**
         * 通过外部配置文件构建证书的的相关信息
         *
         * @return
         * @throws ParseException
         */
        public LicenseContent buildLicenseContent() throws ParseException {
            LicenseContent content = new LicenseContent();
            SimpleDateFormat formate = new SimpleDateFormat("yyyy-MM-dd");
            content.setConsumerAmount(consumerAmount);
            content.setConsumerType(consumerType);
            content.setHolder(DEFAULT_HOLDERAND_ISSUER);
            content.setIssuer(DEFAULT_HOLDERAND_ISSUER);
            content.setIssued(formate.parse(issued));
            content.setNotBefore(formate.parse(notBefore));
            content.setNotAfter(formate.parse(notAfter));
            content.setInfo(info);
            // 扩展字段
            Map<String, String> map = new HashMap<>(4);
            map.put("ip", ipAddress);
            map.put("mac", macAddress);
            content.setExtra(map);
            return content;
        }
    }
    

    最后,来尝试生成一份证书吧!

        public static void main(String[] args) throws Exception {
            CreateLicense clicense = new CreateLicense("/licenseCreateParam.properties");
            clicense.create();
        }
    

    四、利用公钥验证证书

    利用公钥生成证书,我们需要有公钥库、license 证书等信息。

    ########## 公钥的配置信息 ###########
    # 公钥别名
    public.alias=publiccert
    # 该密码是访问密钥库的密码 — 使用 keytool 生成密钥对时设置,使用者知道该密码
    key.store.pwd=123456
    # 项目的唯一识别码 — 和私钥的 subject 保持一致
    subject = yungu
    # 证书路径(我这边配置在了 linux 根路径下,即 /license.lic )
    license.dir=/license.lic
    # 公共库路径(放在 resource 目录下)
    public.store.path=/publicCerts.store
    

    接下来就是怎么用公钥验证 license 证书,怎样验证 ip、mac 地址等信息的过程了~

    @Slf4j
    public class VerifyLicense {
    
        private String pubAlias;
        private String keyStorePwd;
        private String subject;
        private String licDir;
        private String pubPath;
    
        public VerifyLicense() {
            // 取默认配置
            setConf("/licenseVerifyParam.properties");
        }
    
        public VerifyLicense(String confPath) {
            setConf(confPath);
        }
    
        /**
         * 通过外部配置文件获取配置信息
         *
         * @param confPath 配置文件路径
         */
        private void setConf(String confPath) {
            // 获取参数
            Properties prop = new Properties();
            InputStream in = getClass().getResourceAsStream(confPath);
            try {
                prop.load(in);
            } catch (IOException e) {
                log.error("VerifyLicense Properties load inputStream error.", e);
            }
            this.subject = prop.getProperty("subject");
            this.pubAlias = prop.getProperty("public.alias");
            this.keyStorePwd = prop.getProperty("key.store.pwd");
            this.licDir = prop.getProperty("license.dir");
            this.pubPath = prop.getProperty("public.store.path");
        }
    
        /**
         * 安装证书证书
         */
        public void install() {
            try {
                LicenseManager licenseManager = getLicenseManager();
                licenseManager.install(new File(licDir));
                log.info("安装证书成功!");
            } catch (Exception e) {
                log.error("安装证书失败!", e);
                Runtime.getRuntime().halt(1);
            }
    
        }
    
        private LicenseManager getLicenseManager() {
            return LicenseManagerHolder.getLicenseManager(initLicenseParams());
        }
    
        /**
         * 初始化证书的相关参数
         */
        private LicenseParam initLicenseParams() {
            Class<VerifyLicense> clazz = VerifyLicense.class;
            Preferences pre = Preferences.userNodeForPackage(clazz);
            CipherParam cipherParam = new DefaultCipherParam(keyStorePwd);
            KeyStoreParam pubStoreParam = new DefaultKeyStoreParam(clazz, pubPath, pubAlias, keyStorePwd, null);
            return new DefaultLicenseParam(subject, pre, pubStoreParam, cipherParam);
        }
    
        /**
         * 验证证书的合法性
         */
        public boolean vertify() {
            try {
                LicenseManager licenseManager = getLicenseManager();
                LicenseContent verify = licenseManager.verify();
                log.info("验证证书成功!");
                Map<String, String> extra = (Map) verify.getExtra();
                String ip = extra.get("ip");
                InetAddress inetAddress = InetAddress.getLocalHost();
                String localIp = inetAddress.toString().split("/")[1];
                if (!Objects.equals(ip, localIp)) {
                    log.error("IP 地址验证不通过");
                    return false;
                }
                String mac = extra.get("mac");
                String localMac = getLocalMac(inetAddress);
                if (!Objects.equals(mac, localMac)) {
                    log.error("MAC 地址验证不通过");
                    return false;
                }
                log.info("IP、MAC地址验证通过");
                return true;
            } catch (LicenseContentException ex) {
                log.error("证书已经过期!", ex);
                return false;
            } catch (Exception e) {
                log.error("验证证书失败!", e);
                return false;
            }
        }
    
        /**
         * 得到本机 mac 地址
         *
         * @param inetAddress
         * @throws SocketException
         */
        private String getLocalMac(InetAddress inetAddress) throws SocketException {
            //获取网卡,获取地址
            byte[] mac = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress();
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < mac.length; i++) {
                if (i != 0) {
                    sb.append("-");
                }
                //字节转换为整数
                int temp = mac[i] & 0xff;
                String str = Integer.toHexString(temp);
                if (str.length() == 1) {
                    sb.append("0" + str);
                } else {
                    sb.append(str);
                }
            }
            return sb.toString().toUpperCase();
        }
    }
    

    有了公钥的验证过程了,等下!事情还没结束呢!我们需要在项目启动的时候,安装 licnese 证书,然后验证ip、mac 等信息。如果校验不通过,就阻止项目启动!

    @Component
    public class LicenseCheck {
    
        @PostConstruct
        public void init() {
            VerifyLicense vlicense = new VerifyLicense();
            vlicense.install();
            if (!vlicense.vertify()) {
                Runtime.getRuntime().halt(1);
            }
        }
    }
    
  • 相关阅读:
    辨析六种单例模式
    理解模板模式
    常用DOM API总结
    自我剖析——一天的效率
    对两个数组合并,并去重
    Qt 创建一个QtDesinger第三方控件
    Qt中QComboBox中自定义界面使用stylesheet实现下拉按钮独立效果
    内核对象 windows操作系统
    python之基本内容
    python之总体理解
  • 原文地址:https://www.cnblogs.com/jmcui/p/11909579.html
Copyright © 2011-2022 走看看