基於 TrueLicense 的項目證書驗證

1、簡述

開發的軟件產品在交付使用的時候,每每有一段時間的試用期,這期間咱們不但願本身的代碼被客戶二次拷貝,這個時候 license 就派上用場了,license 的功能包括設定有效期、綁定 ip、綁定 mac 等。受權方直接生成一個 license 給使用方使用,若是須要延長試用期,也只須要從新生成一份 license 便可,無需手動修改源代碼。java

TrueLicense 是一個開源的證書管理引擎,詳細介紹見 https://truelicense.java.net/linux

首先介紹下 license 受權機制的原理:app

  1. 生成密鑰對,包含私鑰和公鑰。
  2. 受權者保留私鑰,使用私鑰對受權信息諸如使用截止日期,mac 地址等內容生成 license 簽名證書。
  3. 公鑰給使用者,放在代碼中使用,用於驗證 license 簽名證書是否符合使用條件。

2、生成密鑰對

如下命令在 window cmd 命令窗口執行,注意當前執行目錄,最後生成的密鑰對即在該目錄下:
一、首先要用 KeyTool 工具來生成私匙庫:(-alias別名 -validity 3650 表示10年有效)工具

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

二、而後把私匙庫內的證書導出到一個文件當中ui

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

三、而後再把這個證書文件導入到公匙庫this

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

最後生成的文件 privateKeys.store(私鑰)、publicCerts.store(公鑰)拷貝出來備用。加密

3、準備工做

首先,咱們須要引入 truelicense 的 jar 包,用於實現咱們的證書管理。.net

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

而後,咱們創建一個單例模式下的證書管理器。code

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;
    }
}

4、利用私鑰生成證書

利用私鑰生成證書,咱們須要兩部份內容,一部分是私鑰的配置信息(私鑰的配置信息在生成私鑰庫的過程當中得到),一部分是自定義的項目證書信息。以下展現:orm

########## 私鑰的配置信息 ###########
# 私鑰的別名
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();
    }

4、利用公鑰驗證證書

利用公鑰生成證書,咱們須要有公鑰庫、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);
        }
    }
}
相關文章
相關標籤/搜索