![JAR search and dependency download from the Maven repository](/logo.png)
net.weweave.commerce.LicenseKeyEncoder Maven / Gradle / Ivy
package net.weweave.commerce;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import javax.json.*;
import java.io.*;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
/**
* A {@link LicenseKeyEncoder} object generates and parses signed license
* keys.
*
* License keys generated by this class are signed using an RSA private-
* public-key-pair in PEM format.
*
* See the documentation of weweave Commerce for more details at:
* weweave.net
*
* @author weweave GbR
* @see LicenseKeyEncoder
*/
public class LicenseKeyEncoder {
public static final String TYPE_TRIAL = "trial";
public static final String TYPE_LIMITED = "limited";
public static final String TYPE_LIFETIME = "lifetime";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
private static final String RSA = "RSA";
private String product = "";
private String description = "";
private Date issueDate = null;
private Date expiryDate = null;
private String owner = "";
private String subject;
private String type = "";
private String uuid = "";
private boolean onlineVerification = false;
public String getProduct() {
return product;
}
public void setProduct(String product) {
this.product = product;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getIssueDate() {
return issueDate;
}
public void setIssueDate(Date issueDate) {
this.issueDate = issueDate;
}
public Date getExpiryDate() {
return expiryDate;
}
public void setExpiryDate(Date expiryDate) {
this.expiryDate = expiryDate;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public Integer getVersion() {
return 2;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public boolean getOnlineVerification() {
return onlineVerification;
}
public void setOnlineVerification(boolean onlineVerification) {
this.onlineVerification = onlineVerification;
}
/**
* Creates a Base64 encoded license key signed with an RSA private key.
*
* @param privateKey the private key for signing the license key (PEM)
* @return a Base64 encoded, signed license key string
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws InvalidKeyException
* @throws SignatureException
*/
public String getLicenseString(File privateKey) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
InputStream is = new FileInputStream(privateKey);
return getLicenseString(is);
}
/**
* Creates a Base64 encoded license key signed with an RSA private key.
*
* @param privateKey the private key for signing the license key (PEM)
* @return a Base64 encoded, signed license key string
* @throws InvalidKeySpecException
* @throws SignatureException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws IOException
*/
public String getLicenseString(InputStream privateKey) throws InvalidKeySpecException, SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException {
JsonObject details = createDetailsObject();
JsonObjectBuilder result = Json.createObjectBuilder();
String detailsBase64 = bytesToBase64(getJsonString(details).getBytes());
String signature = LicenseKeyEncoder.createSignature(detailsBase64, privateKey);
result.add("signature", signature);
result.add("details", detailsBase64);
return bytesToBase64(getJsonString(result.build()).getBytes());
}
private JsonObject createDetailsObject() {
SimpleDateFormat sdf = getDateFormatter();
JsonObjectBuilder details = Json.createObjectBuilder();
details.add("version", getVersion());
details.add("description", getDescription());
details.add("type", getType());
details.add("uuid", getUuid());
details.add("onlineVerification", getOnlineVerification());
details.add("product", getProduct());
details.add("issueDate", sdf.format(getIssueDate()));
details.add("expiryDate", sdf.format(getExpiryDate()));
details.add("subject", getSubject());
details.add("owner", getOwner());
return details.build();
}
/**
* Checks whether a signature for a given string is valid according to a
* specified RSA public key.
*
* @param s the string to check the signature for
* @param signature the signature to check
* @param publicKey the public key for calculating the signature (PEM)
* @return true if the specified signature is valid for this string
* @throws FileNotFoundException
*/
public static boolean isSignatureValid(String s, String signature, File publicKey) throws FileNotFoundException {
InputStream is = new FileInputStream(publicKey);
return isSignatureValid(s, signature, is);
}
/**
* * Checks whether a signature for a given string is valid according to a
* specified RSA public key.
*
* @param s the string to check the signature for
* @param signature the signature to check
* @param publicKey the public key for calculating the signature (PEM)
* @return true if the specified signature is valid for this string
*/
public static boolean isSignatureValid(String s, String signature, InputStream publicKey) {
try {
Signature signatureToVerify = Signature.getInstance(SIGNATURE_ALGORITHM);
signatureToVerify.initVerify(readPublicKey(publicKey));
signatureToVerify.update(s.getBytes());
return signatureToVerify.verify(base64ToBytes(signature));
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* Constructs a new {@link LicenseKeyEncoder} instance for a given license
* key.
*
* @param licenseKey a Base64 encoded, signed license key
* @param publicKey the public key for checking the signature (PEM)
* @return an instance of {@link LicenseKeyEncoder} containing the data
* parsed from the license key.
* @throws SignatureException
* @throws ParseException
* @throws FileNotFoundException
*/
public static LicenseKeyEncoder factory(String licenseKey, File publicKey) throws SignatureException, ParseException, FileNotFoundException {
InputStream is = new FileInputStream(publicKey);
return factory(licenseKey, is);
}
/**
* Constructs a new {@link LicenseKeyEncoder} instance for a given license
* key.
*
* @param licenseKey a Base64 encoded, signed license key
* @param publicKey the public key for checking the signature (PEM)
* @return an instance of {@link LicenseKeyEncoder} containing the data
* parsed from the license key.
* @throws SignatureException
* @throws ParseException
* @throws FileNotFoundException
*/
public static LicenseKeyEncoder factory(String licenseKey, InputStream publicKey) throws SignatureException, ParseException {
JsonObject o;
String decodedLicenseKey = LicenseKeyEncoder.base64ToString(licenseKey);
try (JsonReader jr = Json.createReader(new StringReader(decodedLicenseKey))) {
o = jr.readObject();
} catch (Exception e) {
throw new ParseException("licenseKey is invalid JSON", 0);
}
String signature = o.getString("signature");
String detailsBase64 = o.getString("details");
if (!isSignatureValid(detailsBase64, signature, publicKey)) {
throw new SignatureException("Invalid signature");
}
JsonObject details;
try (JsonReader jr = Json.createReader(new StringReader(new String(base64ToBytes(detailsBase64))))) {
details = jr.readObject();
} catch (Exception e) {
throw new ParseException("details is invalid JSON", 0);
}
if (!details.containsKey("version") || details.getInt("version") != 2) {
throw new IllegalArgumentException("Only version 2 is supported");
}
return createLicenseKeyEncoderFromJson(details);
}
private static LicenseKeyEncoder createLicenseKeyEncoderFromJson(JsonObject details) throws ParseException {
LicenseKeyEncoder result = new LicenseKeyEncoder();
result.setDescription(details.getString("description", ""));
result.setType(details.getString("type", ""));
result.setUuid(details.getString("uuid", ""));
result.setOnlineVerification(details.getBoolean("onlineVerification", false));
result.setProduct(details.getString("product", ""));
result.setSubject(details.getString("subject", ""));
result.setOwner(details.getString("owner", ""));
SimpleDateFormat sdf = getDateFormatter();
if (details.containsKey("issueDate")) {
result.setIssueDate(sdf.parse(details.getString("issueDate")));
}
if (details.containsKey("expiryDate")) {
result.setExpiryDate(sdf.parse(details.getString("expiryDate")));
}
return result;
}
protected static SimpleDateFormat getDateFormatter() {
return new SimpleDateFormat("yyyy-MM-dd");
}
/**
* Calculates the signature for a given string.
*
* @param s the string to calculate the signature for
* @param privateKey the private key (PEM)
* @return the Base64 encoded signature for the string
* @throws IOException
* @throws InvalidKeySpecException
* @throws SignatureException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public static String createSignature(String s, File privateKey) throws IOException, InvalidKeySpecException, SignatureException, NoSuchAlgorithmException, InvalidKeyException {
InputStream is = new FileInputStream(privateKey);
return createSignature(s, is);
}
/**
* * Calculates the signature for a given string.
*
* @param s the string to calculate the signature for
* @param privateKey the private key (PEM)
* @return the Base64 encoded signature for the string
* @throws NoSuchAlgorithmException
* @throws IOException
* @throws InvalidKeySpecException
* @throws InvalidKeyException
* @throws SignatureException
*/
public static String createSignature(String s, InputStream privateKey) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException, InvalidKeyException, SignatureException {
PrivateKey pk = LicenseKeyEncoder.readPrivateKey(privateKey);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(pk, new SecureRandom());
signature.update(s.getBytes());
byte[] signatureBytes = signature.sign();
return bytesToBase64(signatureBytes);
}
private static PrivateKey readPrivateKey(InputStream keyFile) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
PemReader pemReader = new PemReader(new InputStreamReader(keyFile));
PemObject pemObject = pemReader.readPemObject();
KeyFactory factory = KeyFactory.getInstance(RSA, new BouncyCastleProvider());
KeySpec keySpec = new PKCS8EncodedKeySpec(pemObject.getContent());
return factory.generatePrivate(keySpec);
}
private static PublicKey readPublicKey(InputStream keyFile) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
PemReader pemReader = new PemReader(new InputStreamReader(keyFile));
PemObject pemObject = pemReader.readPemObject();
KeyFactory factory = KeyFactory.getInstance(RSA, new BouncyCastleProvider());
KeySpec keySpec = new X509EncodedKeySpec(pemObject.getContent());
return factory.generatePublic(keySpec);
}
private static String getJsonString(JsonObject o) {
StringWriter sw = new StringWriter();
try (JsonWriter jw = Json.createWriter(sw)) {
jw.writeObject(o);
}
return sw.toString();
}
private static String bytesToBase64(byte[] b) {
return Base64.getEncoder().encodeToString(b);
}
private static byte[] base64ToBytes(String s) {
byte[] result = Base64.getDecoder().decode(s.getBytes());
return result;
}
private static String base64ToString(String s) {
byte[] result = Base64.getDecoder().decode(s.getBytes());
try {
return new String(result, "UTF-8");
} catch (UnsupportedEncodingException e) {
return "";
}
}
}