Adrian
12 years ago
6 changed files with 336 additions and 4 deletions
@ -0,0 +1,115 @@
|
||||
package com.englishtown.stash.hook; |
||||
|
||||
import com.atlassian.sal.api.pluginsettings.PluginSettings; |
||||
import org.apache.commons.codec.binary.Base64; |
||||
|
||||
import javax.crypto.*; |
||||
import javax.crypto.spec.SecretKeySpec; |
||||
import java.io.UnsupportedEncodingException; |
||||
import java.security.InvalidKeyException; |
||||
import java.security.NoSuchAlgorithmException; |
||||
|
||||
/** |
||||
* Service to encrypt/decrypt git user passwords |
||||
*/ |
||||
public class DefaultPasswordEncryptor implements PasswordEncryptor { |
||||
|
||||
private SecretKey secretKey; |
||||
|
||||
public static final String ENCRYPTED_PREFIX = "encrypted:"; |
||||
public static final String SETTINGS_CRYPTO_KEY = "crypto.key"; |
||||
|
||||
@Override |
||||
public void init(PluginSettings pluginSettings) { |
||||
|
||||
try { |
||||
String keyBase64; |
||||
Object value = pluginSettings.get(SETTINGS_CRYPTO_KEY); |
||||
|
||||
if (value == null || value.toString().isEmpty()) { |
||||
KeyGenerator gen = KeyGenerator.getInstance("AES"); |
||||
secretKey = gen.generateKey(); |
||||
keyBase64 = Base64.encodeBase64String(secretKey.getEncoded()); |
||||
pluginSettings.put(SETTINGS_CRYPTO_KEY, keyBase64); |
||||
} else { |
||||
keyBase64 = value.toString(); |
||||
byte[] data = Base64.decodeBase64(keyBase64); |
||||
secretKey = new SecretKeySpec(data, 0, data.length, "AES"); |
||||
} |
||||
|
||||
} catch (NoSuchAlgorithmException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
|
||||
} |
||||
|
||||
protected byte[] runCipher(byte[] data, boolean encrypt) { |
||||
|
||||
try { |
||||
int mode = encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE; |
||||
Cipher cipher = getCipher(mode); |
||||
return cipher.doFinal(data); |
||||
|
||||
} catch (IllegalBlockSizeException e) { |
||||
throw new RuntimeException(e); |
||||
} catch (BadPaddingException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
private Cipher getCipher(int mode) { |
||||
|
||||
try { |
||||
// Create Cipher
|
||||
Cipher cipher = Cipher.getInstance("AES"); |
||||
|
||||
// Initialize Cipher with key
|
||||
cipher.init(mode, secretKey); |
||||
return cipher; |
||||
|
||||
} catch (NoSuchAlgorithmException e) { |
||||
throw new RuntimeException(e); |
||||
} catch (NoSuchPaddingException e) { |
||||
throw new RuntimeException(e); |
||||
} catch (InvalidKeyException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public boolean isEncrypted(String password) { |
||||
if (password == null || password.isEmpty()) { |
||||
return false; |
||||
} |
||||
return password.startsWith(ENCRYPTED_PREFIX); |
||||
} |
||||
|
||||
@Override |
||||
public String encrypt(String password) { |
||||
if (isEncrypted(password)) { |
||||
return password; |
||||
} |
||||
try { |
||||
byte[] encryptedData = runCipher(password.getBytes("UTF-8"), true); |
||||
return ENCRYPTED_PREFIX + Base64.encodeBase64String(encryptedData); |
||||
} catch (UnsupportedEncodingException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String decrypt(String password) { |
||||
if (!isEncrypted(password)) { |
||||
return password; |
||||
} |
||||
try { |
||||
byte[] encryptedData = Base64.decodeBase64(password.substring(ENCRYPTED_PREFIX.length())); |
||||
byte[] clearData = runCipher(encryptedData, false); |
||||
return new String(clearData, "UTF-8"); |
||||
} catch (UnsupportedEncodingException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,18 @@
|
||||
package com.englishtown.stash.hook; |
||||
|
||||
import com.atlassian.sal.api.pluginsettings.PluginSettings; |
||||
|
||||
/** |
||||
* Service to encrypt/decrypt git user passwords |
||||
*/ |
||||
public interface PasswordEncryptor { |
||||
|
||||
void init(PluginSettings pluginSettings); |
||||
|
||||
boolean isEncrypted(String password); |
||||
|
||||
String encrypt(String password); |
||||
|
||||
String decrypt(String password); |
||||
|
||||
} |
@ -0,0 +1,129 @@
|
||||
package com.englishtown.stash.hook; |
||||
|
||||
import com.atlassian.sal.api.pluginsettings.PluginSettings; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.Mock; |
||||
import org.mockito.runners.MockitoJUnitRunner; |
||||
|
||||
import static org.junit.Assert.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
/** |
||||
* DefaultPasswordEncryptor unit tests |
||||
*/ |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class DefaultPasswordEncryptorTest { |
||||
|
||||
private final static String CRYPTO_KEY = "m3ys5YexQc7irRlmJeCwAw=="; |
||||
|
||||
@Mock |
||||
private PluginSettings pluginSettings; |
||||
|
||||
private DefaultPasswordEncryptor encryptor; |
||||
|
||||
@Before |
||||
public void setUp() throws Exception { |
||||
|
||||
when(pluginSettings.get(DefaultPasswordEncryptor.SETTINGS_CRYPTO_KEY)).thenReturn(CRYPTO_KEY); |
||||
|
||||
encryptor = new DefaultPasswordEncryptor(); |
||||
encryptor.init(pluginSettings); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void testInit() throws Exception { |
||||
|
||||
DefaultPasswordEncryptor encryptor = new DefaultPasswordEncryptor(); |
||||
PluginSettings pluginSettings = mock(PluginSettings.class); |
||||
encryptor.init(pluginSettings); |
||||
|
||||
verify(pluginSettings).put(eq(DefaultPasswordEncryptor.SETTINGS_CRYPTO_KEY), anyString()); |
||||
|
||||
when(pluginSettings.get(DefaultPasswordEncryptor.SETTINGS_CRYPTO_KEY)).thenReturn(CRYPTO_KEY); |
||||
|
||||
encryptor.init(pluginSettings); |
||||
|
||||
// Verify put hasn't been called again
|
||||
verify(pluginSettings).put(eq(DefaultPasswordEncryptor.SETTINGS_CRYPTO_KEY), anyString()); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void testRunCipher() throws Exception { |
||||
|
||||
DefaultPasswordEncryptor encryptor = new DefaultPasswordEncryptor(); |
||||
encryptor.init(pluginSettings); |
||||
|
||||
String clearText = "clear text"; |
||||
byte[] clearData = clearText.getBytes(); |
||||
byte[] encryptedData; |
||||
byte[] resultData; |
||||
String resultText; |
||||
|
||||
encryptedData = encryptor.runCipher(clearData, true); |
||||
|
||||
resultData = encryptor.runCipher(encryptedData, false); |
||||
resultText = new String(resultData); |
||||
|
||||
assertArrayEquals(clearData, resultData); |
||||
assertEquals(clearText, resultText); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void testIsEncrypted() throws Exception { |
||||
|
||||
DefaultPasswordEncryptor encryptor = new DefaultPasswordEncryptor(); |
||||
String password = "clear-text-key"; |
||||
boolean result; |
||||
|
||||
result = encryptor.isEncrypted(password); |
||||
assertFalse(result); |
||||
|
||||
password = null; |
||||
|
||||
result = encryptor.isEncrypted(password); |
||||
assertFalse(result); |
||||
|
||||
password = ""; |
||||
|
||||
result = encryptor.isEncrypted(password); |
||||
assertFalse(result); |
||||
|
||||
password = DefaultPasswordEncryptor.ENCRYPTED_PREFIX + "encrypted-key"; |
||||
|
||||
result = encryptor.isEncrypted(password); |
||||
assertTrue(result); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void testEncrypt() throws Exception { |
||||
|
||||
String password = "test"; |
||||
String encrypted; |
||||
String clear; |
||||
String result; |
||||
|
||||
assertFalse(encryptor.isEncrypted(password)); |
||||
|
||||
encrypted = encryptor.encrypt(password); |
||||
assertTrue(encryptor.isEncrypted(encrypted)); |
||||
|
||||
result = encryptor.encrypt(encrypted); |
||||
assertEquals(encrypted, result); |
||||
|
||||
clear = encryptor.decrypt(encrypted); |
||||
assertEquals(password, clear); |
||||
|
||||
assertFalse(encryptor.isEncrypted(clear)); |
||||
|
||||
result = encryptor.decrypt(clear); |
||||
assertEquals(clear, result); |
||||
|
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue