Commit b17f595f authored by Patrik Dufresne's avatar Patrik Dufresne

TASK-984 Fix encoding problem

Enforce usage of UTF-8 to read and write the license file.
parent 9268c9b0
Pipeline #412 passed with stages
in 2 minutes and 15 seconds
/**
* Copyright(C) 2018 Patrik Dufresne Service Logiciel inc <info@patrikdufresne.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.patrikdufresne.license;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* This class is used to manage the encryption of the license. It's used to encrypt, sign and validate using a public or
* private key.
*
* @author Patrik Dufresne
*
*/
public class EncryptionManager {
private static final int BUF_SIZE = 4096;
private PublicKey publicKey;
/**
* Our private key.
*/
private PrivateKey privateKey;
/**
* Create a new encryption manager.
*
* @param publicKey
* the public key (can't be null).
* @param privateKey
* the private key (null if not available).
* @throws NoSuchAlgorithmException
* if no Provider supports RSA
* @throws InvalidKeySpecException
* if the given key specification is inappropriate for this key factory to produce a public key.
*/
public EncryptionManager(byte[] publicKey, byte[] privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
if (publicKey == null) {
throw new NullPointerException("publicKey");
}
X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKey);
KeyFactory kf = KeyFactory.getInstance("RSA");
this.publicKey = kf.generatePublic(spec);
if (privateKey != null) {
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory privateKeyFactory = KeyFactory.getInstance("RSA");
this.privateKey = privateKeyFactory.generatePrivate(privateSpec);
}
}
/**
* This function is used to read a stream.
*
* @param input
* the input stream
* @return the data read from the stream
* @throws IOException
*/
public static byte[] readAll(InputStream input) throws IOException {
// Read the content of the file and store it in a byte array.
ByteArrayOutputStream out = new ByteArrayOutputStream(BUF_SIZE);
byte[] buf = new byte[BUF_SIZE];
int size;
while ((size = input.read(buf)) != -1) {
out.write(buf, 0, size);
}
return out.toByteArray();
}
/**
* This function maybe used to read the public and/or private key from a file.
*
* @param file
* the file to read
* @return the file data
*
* @throws IOException
* if the file does not exist, or if the first byte cannot be read for any reason
*/
public static byte[] readAll(File file) throws IOException {
InputStream input = new FileInputStream(file);
try {
return readAll(input);
} finally {
input.close();
}
}
/**
* Use to check if the given data matches the given signature.
*
* @param data
* the data
* @param sig
* the signature associated with the data.
*
* @throws NoSuchAlgorithmException
* if the algorithm SHA1withRSA is not supported.
* @throws NoSuchProviderException
* @throws InvalidKeyException
* if the key is invalid.
* @throws SignatureException
* if this signature algorithm is unable to process the input data
*/
public boolean verify(byte[] data, byte[] sig) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
// Initialize the signing algorithm with our public key
Signature rsaSignature = Signature.getInstance("SHA1withRSA");
rsaSignature.initVerify(publicKey);
// Update the signature algorithm with the data.
rsaSignature.update(data);
// Validate the signature
return rsaSignature.verify(sig);
}
/**
* Sign the given input stream data. The signature is append to the output stream.
*
* @param data
* the the data to be signed.
* @return the signature for the given data.
* @throws NoSuchAlgorithmException
* if no Provider supports a Signature implementation for SHA1withRSA.
* @throws InvalidKeyException
* if the private key is invalid.
* @throws SignatureException
* if this signature algorithm is unable to process the input data provided.
* @throws UnsupportedOperationException
* if the private key was not providedin the constructor.
*/
public byte[] sign(byte[] data) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
if (privateKey == null) {
throw new UnsupportedOperationException("Can't sign when the private key is not available.");
}
// Initialize the signing algorithm with our private key
Signature rsaSignature = Signature.getInstance("SHA1withRSA");
rsaSignature.initSign(privateKey);
rsaSignature.update(data);
// Generate the signature.
return rsaSignature.sign();
}
}
/**
* Copyright(C) 2018 Patrik Dufresne Service Logiciel inc <info@patrikdufresne.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.patrikdufresne.license;
/**
* This exception is throw by the key manager when the key is determined to be
* black listed.
*
* @author Patrik Dufresne
*
*/
public class KeyBlackListedException extends LicenseException {
private static final long serialVersionUID = 4833729281645719038L;
public KeyBlackListedException() {
super("black listed key");
}
}
/**
* Copyright(C) 2018 Patrik Dufresne Service Logiciel inc <info@patrikdufresne.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.patrikdufresne.license;
/**
* This exception is throw when the key manager determine the key as invalid
* because of the checksum or because it's been wrongly generated.
*
* @author Patrik Dufresne
*
*/
public class KeyInvalidException extends LicenseException {
private static final long serialVersionUID = 3455646784833396158L;
public KeyInvalidException() {
super("invalid key");
}
}
...@@ -17,15 +17,24 @@ package com.patrikdufresne.license; ...@@ -17,15 +17,24 @@ package com.patrikdufresne.license;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileInputStream;
import java.io.FileWriter; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException; import java.security.SignatureException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
...@@ -48,10 +57,126 @@ public class LicenseManager { ...@@ -48,10 +57,126 @@ public class LicenseManager {
* Property to store the signature. * Property to store the signature.
*/ */
private static final String SIGNATURE = "signature"; private static final String SIGNATURE = "signature";
/**
* Define the encoding to be used to read and write the license file. Since the license may be generate on different
* platform with different default encoding, we need to hardcode this into a fixed encoding. UTF-8 should be a good
* choice.
*/
private static final String ENCODING = "UTF-8";
private static final int BUF_SIZE = 4096;
private PublicKey publicKey;
/**
* Our private key.
*/
private PrivateKey privateKey;
/**
* This function is used to read a stream.
*
* @param input
* the input stream
* @return the data read from the stream
* @throws IOException
*/
private static byte[] readAll(InputStream input) throws IOException {
if (input == null) {
return null;
}
// Read the content of the file and store it in a byte array.
ByteArrayOutputStream out = new ByteArrayOutputStream(BUF_SIZE);
byte[] buf = new byte[BUF_SIZE];
int size;
while ((size = input.read(buf)) != -1) {
out.write(buf, 0, size);
}
return out.toByteArray();
}
/** /**
* The encryption manager used by this class.F * This function maybe used to read the public and/or private key from a file.
*
* @param file
* the file to read
* @return the file data
*
* @throws IOException
* if the file does not exist, or if the first byte cannot be read for any reason
*/ */
private EncryptionManager encryptionManager; private static byte[] readAll(File file) throws IOException {
if (file == null) {
return null;
}
InputStream input = new FileInputStream(file);
try {
return readAll(input);
} finally {
input.close();
}
}
/**
* Use to check if the given data matches the given signature.
*
* @param data
* the data
* @param sig
* the signature associated with the data.
*
* @throws NoSuchAlgorithmException
* if the algorithm SHA1withRSA is not supported.
* @throws NoSuchProviderException
* @throws InvalidKeyException
* if the key is invalid.
* @throws SignatureException
* if this signature algorithm is unable to process the input data
*/
protected boolean verify(byte[] data, byte[] sig) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
// Initialize the signing algorithm with our public key
Signature rsaSignature = Signature.getInstance("SHA1withRSA");
rsaSignature.initVerify(publicKey);
// Update the signature algorithm with the data.
rsaSignature.update(data);
// Validate the signature
return rsaSignature.verify(sig);
}
/**
* Sign the given input stream data. The signature is append to the output stream.
*
* @param data
* the the data to be signed.
* @return the signature for the given data.
* @throws NoSuchAlgorithmException
* if no Provider supports a Signature implementation for SHA1withRSA.
* @throws InvalidKeyException
* if the private key is invalid.
* @throws SignatureException
* if this signature algorithm is unable to process the input data provided.
* @throws UnsupportedOperationException
* if the private key was not providedin the constructor.
*/
protected byte[] sign(byte[] data) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
if (privateKey == null) {
throw new UnsupportedOperationException("Can't sign when the private key is not available.");
}
// Initialize the signing algorithm with our private key
Signature rsaSignature = Signature.getInstance("SHA1withRSA");
rsaSignature.initSign(privateKey);
rsaSignature.update(data);
// Generate the signature.
return rsaSignature.sign();
}
/** /**
* Utility function to easily validate a license file. * Utility function to easily validate a license file.
...@@ -99,7 +224,21 @@ public class LicenseManager { ...@@ -99,7 +224,21 @@ public class LicenseManager {
* if the provided key are invalid. * if the provided key are invalid.
*/ */
public LicenseManager(byte[] publicKey, byte[] privateKey) throws GeneralSecurityException { public LicenseManager(byte[] publicKey, byte[] privateKey) throws GeneralSecurityException {
this.encryptionManager = new EncryptionManager(publicKey, privateKey);
if (publicKey == null) {
throw new NullPointerException("publicKey");
}
X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKey);
KeyFactory kf = KeyFactory.getInstance("RSA");
this.publicKey = kf.generatePublic(spec);
if (privateKey != null) {
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory privateKeyFactory = KeyFactory.getInstance("RSA");
this.privateKey = privateKeyFactory.generatePrivate(privateSpec);
}
} }
/** /**
...@@ -115,12 +254,7 @@ public class LicenseManager { ...@@ -115,12 +254,7 @@ public class LicenseManager {
* if the file doesn't exists * if the file doesn't exists
*/ */
public LicenseManager(File publicKey, File privateKey) throws GeneralSecurityException, IOException { public LicenseManager(File publicKey, File privateKey) throws GeneralSecurityException, IOException {
byte[] pubdata = EncryptionManager.readAll(publicKey); this(readAll(publicKey), readAll(privateKey));
byte[] privdata = null;
if (privateKey != null) {
privdata = EncryptionManager.readAll(privateKey);
}
this.encryptionManager = new EncryptionManager(pubdata, privdata);
} }
/** /**
...@@ -132,12 +266,7 @@ public class LicenseManager { ...@@ -132,12 +266,7 @@ public class LicenseManager {
* an input stream containing the private key * an input stream containing the private key
*/ */
public LicenseManager(InputStream publicKey, InputStream privateKey) throws GeneralSecurityException, IOException { public LicenseManager(InputStream publicKey, InputStream privateKey) throws GeneralSecurityException, IOException {
byte[] pubdata = EncryptionManager.readAll(publicKey); this(readAll(publicKey), readAll(privateKey));
byte[] privdata = null;
if (privateKey != null) {
privdata = EncryptionManager.readAll(privateKey);
}
this.encryptionManager = new EncryptionManager(pubdata, privdata);
} }
/** /**
...@@ -187,7 +316,7 @@ public class LicenseManager { ...@@ -187,7 +316,7 @@ public class LicenseManager {
String base64Signature = null; String base64Signature = null;
// Read the license file as a property file. // Read the license file as a property file.
Properties prop = new Properties(); Properties prop = new Properties();
prop.load(new FileReader(file)); prop.load(new InputStreamReader(new FileInputStream(file), ENCODING));
License lic = new License(); License lic = new License();
for (Object key : prop.keySet()) { for (Object key : prop.keySet()) {
String value = (String) prop.get(key); String value = (String) prop.get(key);
...@@ -207,7 +336,7 @@ public class LicenseManager { ...@@ -207,7 +336,7 @@ public class LicenseManager {
byte[] data = writeLicenseToByteArray(lic); byte[] data = writeLicenseToByteArray(lic);
// Validate the signature // Validate the signature
if (!encryptionManager.verify(data, sig)) { if (!verify(data, sig)) {
throw new LicenseException("invalid license signature"); throw new LicenseException("invalid license signature");
} }
...@@ -238,7 +367,7 @@ public class LicenseManager { ...@@ -238,7 +367,7 @@ public class LicenseManager {
byte[] data = writeLicenseToByteArray(lic); byte[] data = writeLicenseToByteArray(lic);
// Then sign the byte array // Then sign the byte array
byte[] signature = this.encryptionManager.sign(data); byte[] signature = sign(data);
String base64signature = Base64.encode(signature); String base64signature = Base64.encode(signature);
// Create property file // Create property file
...@@ -249,7 +378,7 @@ public class LicenseManager { ...@@ -249,7 +378,7 @@ public class LicenseManager {
prop.put(SIGNATURE, base64signature); prop.put(SIGNATURE, base64signature);
// Write the property file // Write the property file
prop.store(new FileWriter(file), "License file"); prop.store(new OutputStreamWriter(new FileOutputStream(file), ENCODING), "License file");
} }
/** /**
......
/**
* Copyright(C) 2018 Patrik Dufresne Service Logiciel inc <info@patrikdufresne.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.patrikdufresne.license;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses( { EncryptionManagerTest.class, KeyManagerTest.class, LicenseManagerTest.class })
public class AllTests {
}
/**
* Copyright(C) 2018 Patrik Dufresne Service Logiciel inc <info@patrikdufresne.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.patrikdufresne.license;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import org.junit.Before;
import org.junit.Test;
import org.junit.Assert;
/**
* Test the functionality provided by the class {@link EncryptionManager}
*
* @author Patrik Dufresne
*
*/
public class EncryptionManagerTest {
private EncryptionManager manager;
private byte[] data;
@Before
public void initEncryptionManager() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
byte[] pubKey = EncryptionManager.readAll(getClass().getResourceAsStream("/pubkey.der"));
byte[] privateKey = EncryptionManager.readAll(getClass().getResourceAsStream("/privkey.der"));
this.manager = new EncryptionManager(pubKey, privateKey);
// Prepare data
String string = "This is some data to by sign";
this.data = string.getBytes();
}
@Test
public void sign_WithData_ReturnSignature() throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
byte[] sig = this.manager.sign(data);
}
@Test
public void verify_WithDataAndGoodSignature_ReturnTrue()
throws InvalidKeyException,
NoSuchAlgorithmException,
SignatureException,
FileNotFoundException,
IOException {
byte[] sig = this.manager.sign(data);
Assert.assertTrue(this.manager.verify(data, sig));
}
@Test
public void verify_WithDataAndWrongSignature_ReturnTrue()
throws InvalidKeyException,
NoSuchAlgorithmException,
SignatureException,
FileNotFoundException,
IOException {
byte[] sig = this.manager.sign(data);
sig[0] = (byte) (((int) sig[0]) + 3);
Assert.assertFalse(this.manager.verify(data, sig));
}
}
/**
* Copyright(C) 2018 Patrik Dufresne Service Logiciel inc <info@patrikdufresne.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.patrikdufresne.license;
import org.junit.Before;
import org.junit.Test;
public class KeyManagerTest {
private KeyManager manager;
@Before
public void initKeyManager() {
this.manager = new KeyManager();
}
@Test
public void generateKey_WithAuthCode_ReturnKey() {
String key = this.manager.generateKey(3, "coucou");
}
@Test
public void validateKey_WithValidKey_ReturnTrue() throws KeyInvalidException, KeyBlackListedException {
String key = this.manager.generateKey(3, "coucou");
this.manager.validateKey(key);
}
@Test(expected = KeyInvalidException.class)
public void validateKey_WithInvalidKey_ReturnTrue() throws KeyInvalidException, KeyBlackListedException {
String key = this.manager.generateKey(3, "coucou");
key = key.replace("0", "1");
this.manager.validateKey(key);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment