在软件或产品交付时,我们往往会授权给第三方或者防止程序乱部署而对部署的服务器进行认证,此时License就排上用途了。授权的方便在于如果证书过期,我们可以重新生成一个认证文件而不用修改程序。下面我将讲述使用truelicense来实现license的生成和使用。Truelicense是一个开源的证书管理引擎,只不过这个已经不在java官网中维护了,而在http://grepcode.com/project/repo1.maven.org/maven2/de.schlichtherle.truelicense/truelicense-core/这里。
下面简单描述license授权机制的原理:
1、生成密钥对,公钥和私钥(私钥对信息加密,公钥对信息解密)
2、授权者保留私钥,使用私钥对包含授权信息(如使用开始日期,截止日期,IP地址以及MAC地址等)的license进行数字签名。
3、公钥以及认证文件给使用者(放在验证的代码中使用),用于验证license是否符合使用条件。
java中已经自带了生成密钥的工具,就是keytool,此工具的详细使用可参考一下地址:https://docs.oracle.com/javase/7/docs/technotes/tools/solaris/keytool.html ,其中注意jdk6和jdk7之间的差异,我这用的jdk7的方式,当然jdk6 的指令也可以使用。
windows下进入cmd,Linux进去terminal并执行以下命令(需要安装jdk7并且keytool要加入到path中,详细可自行google)
keytool -alias privateKey -genkeypair -keystore privateKeys.keystore -validity 3650 -storepass abc123456 -keypass xyz123456 -dname="test"
简单说明:
-alias 别名
-genkeypair 生成命令(-genkey jdk6的方式)
-keystore 密钥库
-validity 有效期 (单位:天)
-storepass 存储密码
-keypass 密钥密码
-dname 描述信息
按照提示完成此命令则会在当前目录下生成一个privateKeys.keystore文件
keytool -export -alias privateKey -file certfile.cer -keystore privateKeys.keystore
keytool -import -alias publicCert -file certfile.cer -keystore publicCerts.keystore
这样我们就生成了两个文件privateKey.keystore和publicCert.keystore,一个是私钥一个公钥。
我使用的idea,创建一个maven工程在pom文件中添加一下jar包依赖
<dependencies>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
<version>1.10version>
dependency>
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>2.4version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.4version>
dependency>
<dependency>
<groupId>de.schlichtherle.truelicensegroupId>
<artifactId>truelicense-coreartifactId>
<version>1.33version>
dependency>
<dependency>
<groupId>de.schlichtherle.truelicensegroupId>
<artifactId>truelicense-xmlartifactId>
<version>1.33version>
dependency>
<dependency>
<groupId>de.schlichtherle.truelicensegroupId>
<artifactId>truelicense-swingartifactId>
<version>1.33version>
dependency>
dependencies>
另外把生成的私钥密钥文件放入到classpath下面。
生成证书主要使用了LicenseManager类,我们先创建一个单例的实例来用,代码如下:
public class LicenseManagerHolder {
private static LicenseManager licenseManager;
public static synchronized LicenseManager getLicenseManager(LicenseParam licenseParams) {
if (licenseManager == null) {
licenseManager = new LicenseManager(licenseParams);
}
return licenseManager;
}
}
接下来就可以创建生成证书的类,CreateLicense.java代码如下:
package com.test.license;
import de.schlichtherle.license.*;
import javax.security.auth.x500.X500Principal;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Properties;
import java.util.prefs.Preferences;
/**
* 证书生成类
*/
public class CreateLicense {
/**
* common param
*/
private static String PRIVATE_ALIAS = "";
private static String KEY_PWD = "";
private static String STORE_PWD = "";
private static String SUBJECT = "";
private static String licPath = "";
private static String priPath = "";
/**
* license content
*/
private static String issuedTime = "";
private static String notBefore = "";
private static String notAfter = "";
private static String consumerType = "";
private static int consumerAmount = 0;
private static String info = "";
// 为了方便直接用的API里的例子
// X500Princal是一个证书文件的固有格式,详见API
private final static X500Principal DEFAULT_HOLDER_AND_ISSUER = new X500Principal("CN=Duke、OU=JavaSoft、O=Sun Microsystems、C=US");
/**
* 读取配置文件数据
*/
public void setParam(String propertiesPath) {
// 获取参数
Properties prop = new Properties();
InputStream in = CreateLicense.class.getClassLoader().getResourceAsStream(propertiesPath);
try {
prop.load(in);
} catch (IOException e) {
e.printStackTrace();
}
PRIVATE_ALIAS = prop.getProperty("PRIVATEALIAS");
KEY_PWD = prop.getProperty("KEYPWD");
STORE_PWD = prop.getProperty("STOREPWD");
SUBJECT = prop.getProperty("SUBJECT");
licPath = prop.getProperty("licPath");
priPath = prop.getProperty("priPath");
//license content
issuedTime = prop.getProperty("issuedTime");
notBefore = prop.getProperty("notBefore");
notAfter = prop.getProperty("notAfter");
consumerType = prop.getProperty("consumerType");
consumerAmount = Integer.valueOf(prop.getProperty("consumerAmount"));
info = prop.getProperty("info");
}
/**
* 证书发布者端执行
*/
public boolean create() {
try {
LicenseManager licenseManager = LicenseManagerHolder
.getLicenseManager(initLicenseParams0());
LicenseContent content = createLicenseContent();
licenseManager.store(content, new File(licPath));
} catch (Exception e) {
e.printStackTrace();
System.out.println("客户端证书生成失败!");
return false;
}
System.out.println("服务器端生成证书成功!");
return true;
}
// 返回生成证书时需要的参数
private static LicenseParam initLicenseParams0() {
Preferences preference = Preferences
.userNodeForPackage(CreateLicense.class);
// 设置对证书内容加密的对称密码
CipherParam cipherParam = new DefaultCipherParam(STORE_PWD);
// 参数1,2从Class.getResource()获得密钥库;
// 参数3密钥库的别名;
// 参数4密钥库存储密码;
// 参数5密钥库密码
KeyStoreParam privateStoreParam = new DefaultKeyStoreParam(
CreateLicense.class, priPath, PRIVATE_ALIAS, STORE_PWD, KEY_PWD);
LicenseParam licenseParams = new DefaultLicenseParam(SUBJECT,
preference, privateStoreParam, cipherParam);
return licenseParams;
}
// 从外部表单拿到证书的内容
public final static LicenseContent createLicenseContent() {
DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
LicenseContent content = null;
content = new LicenseContent();
content.setSubject(SUBJECT);
content.setHolder(DEFAULT_HOLDER_AND_ISSUER);
content.setIssuer(DEFAULT_HOLDER_AND_ISSUER);
try {
content.setIssued(format.parse(issuedTime));
content.setNotBefore(format.parse(notBefore));
content.setNotAfter(format.parse(notAfter));
} catch (ParseException e) {
e.printStackTrace();
}
content.setConsumerType(consumerType);
content.setConsumerAmount(consumerAmount);
content.setInfo(info);
// 扩展,后期可添加自定义数据,比如校验ip和mac地址等。
content.setExtra(new Object());
return content;
}
}
送上配置文件配置数据:
##########common parameters###########
#alias
PRIVATEALIAS=privateKey
#key(该密码生成密钥对的密码,需要妥善保管,不能让使用者知道)
KEYPWD=xyz123456
#STOREPWD(该密码是在使用keytool生成密钥对时设置的密钥库的访问密码)
STOREPWD=abc123456
#SUBJECT
SUBJECT=test
#licPath
licPath=test.lic
#priPath放入到classpath下,按照以下方式读取,文件名前加"/"
priPath=/privateKeys.keystore
##########license content###########
#issuedTime 发布时间
issuedTime=2017-05-01
#notBeforeTime 在xx之前不生效
notBefore=2017-05-01
#notAfterTime 在xx之后生效
notAfter=2019-11-29
#consumerType
consumerType=user
#ConsumerAmount
consumerAmount=1
#info 说明信息
info=this is a license
下面我们就可以测试了,代码如下:
package com.test.license;
public class LicenseTest {
public static void main(String[] args) {
CreateLicense cLicense = new CreateLicense();
//获取参数
cLicense.setParam("createparam.properties");
//生成证书
cLicense.create();
}
}
这样就会在classpath下生成一个test.lic的证书,这个证书就是要给三方提供的文件。
创建证书认证类,如下:
package com.test.license;
import de.schlichtherle.license.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Properties;
import java.util.prefs.Preferences;
/**
* 证书验证类.
*/
public class VerifyLicense {
//common param
private static String PUBLICALIAS = "";
private static String STOREPWD = "";
private static String SUBJECT = "";
private static String licPath = "";
private static String pubPath = "";
public void setParam(String propertiesPath) {
// 获取参数
Properties prop = new Properties();
InputStream in = VerifyLicense.class.getClassLoader().getResourceAsStream(propertiesPath);
try {
prop.load(in);
} catch (IOException e) {
e.printStackTrace();
}
PUBLICALIAS = prop.getProperty("PUBLICALIAS");
STOREPWD = prop.getProperty("STOREPWD");
SUBJECT = prop.getProperty("SUBJECT");
licPath = prop.getProperty("licPath");
pubPath = prop.getProperty("pubPath");
}
public boolean verify() {
/************** 证书使用者端执行 ******************/
LicenseManager licenseManager = LicenseManagerHolder
.getLicenseManager(initLicenseParams());
// 安装证书
try {
licenseManager.uninstall();
URL url = VerifyLicense.class.getClassLoader().getResource(licPath);
System.out.println(url.getFile());
licenseManager.install(new File(url.getFile()));
System.out.println("客户端安装证书成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("客户端证书安装失败!");
return false;
}
// 验证证书
try {
licenseManager.verify();
System.out.println("客户端验证证书成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("客户端证书验证失效!");
return false;
}
return true;
}
// 返回验证证书需要的参数
private static LicenseParam initLicenseParams() {
Preferences preference = Preferences
.userNodeForPackage(VerifyLicense.class);
CipherParam cipherParam = new DefaultCipherParam(STOREPWD);
KeyStoreParam privateStoreParam = new DefaultKeyStoreParam(
VerifyLicense.class, pubPath, PUBLICALIAS, STOREPWD, null);
LicenseParam licenseParams = new DefaultLicenseParam(SUBJECT,
preference, privateStoreParam, cipherParam);
return licenseParams;
}
}
测试类如下:
package com.test.license;
public class LicenseVerifyTest {
public static void main(String[] args) {
VerifyLicense vLicense = new VerifyLicense();
//获取参数
vLicense.setParam("verifyparam.properties");
//验证证书
vLicense.verify();
}
}
以下是配置文件:
##########common parameters###########
#alias
PUBLICALIAS=publicCert
#STOREPWD(该密码是在使用keytool生成密钥对时设置的密钥库的访问密码)
STOREPWD=abc123456
#SUBJECT
SUBJECT=test
#licPath
licPath=test.lic
#pubPath
pubPath=/publicCerts.keystore
到此,关于java的证书生成以及代码实现全部搞定,但是我发现这个三方库暂时只支持SHA1withDSA这个算法,如果我们在使用keytool -keyalg RSA 算法时,这个工具是不支持的,要支持就得手动修改其源码,具体的后续再详细记录一下。