Skip to content

Commit

Permalink
Merge pull request #188 from socrata/rjm/obfuscate-saved-credentials
Browse files Browse the repository at this point in the history
Obfuscate saved credentials
  • Loading branch information
rjmac authored Nov 20, 2019
2 parents 99de134 + 3534268 commit 5cd5d6f
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.socrata.datasync.config.userpreferences;

import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.NoSuchPaddingException;
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.nio.charset.StandardCharsets;
import java.io.*;
import java.util.Arrays;
import java.util.prefs.Preferences;

import org.apache.commons.net.util.Base64;

final class CryptoUtil {
private static final byte[] obfuscatedPrefix;
private static final byte[] key;

static {
int[] ints =
// SOCRATAOBFUS in control characters
new int[] { 0x13, 0x0f, 0x03, 0x12, 0x01, 0x14, 0x01, 0x0f, 0x02, 0x06, 0x15, 0x13, 0x0a };
byte[] bytes = new byte[ints.length];
for(int i = 0; i != ints.length; ++i) {
bytes[i] = (byte) ints[i];
}
obfuscatedPrefix = bytes;

Preferences p = Preferences.userRoot().node("SocrataIntegrationPrefs");
bytes = p.getByteArray("junk", null);
if(bytes == null || bytes.length != 16) {
bytes = new byte[16];
new SecureRandom().nextBytes(bytes);
p.putByteArray("junk", bytes);
}
key = bytes;
}

private static byte[] writeIv(OutputStream os) throws IOException {
byte[] result = new byte[16];
new SecureRandom().nextBytes(result);
os.write(result);
return result;
}

private static byte[] readIv(InputStream is) throws IOException {
byte[] result = new byte[16];
int off = 0;
while(off < result.length) {
int count = is.read(result, off, result.length - off);
if(count == -1) throw new EOFException();
else off += count;
}
return result;
}

private static Cipher cipher(int mode, byte[] iv) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, InvalidAlgorithmParameterException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

cipher.init(mode, new SecretKeySpec(Arrays.copyOf(key, key.length), "AES"), new IvParameterSpec(iv));
return cipher;
}

public static String obfuscate(String s) {
if(s == null) return null;

try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(obfuscatedPrefix);

try(CipherOutputStream cos = new CipherOutputStream(baos, cipher(Cipher.ENCRYPT_MODE, writeIv(baos)));
OutputStreamWriter osw = new OutputStreamWriter(cos, StandardCharsets.UTF_8)) {
osw.write(s);
}

return Base64.encodeBase64URLSafeString(baos.toByteArray());
} catch(Exception e) {
throw new RuntimeException("Unexpected error", e);
}
}

public static String deobfuscate(String s, String ifInvalid) {
if(s == null) return null;

try {
byte[] bytes = Base64.decodeBase64(s);
if(bytes.length >= obfuscatedPrefix.length && Arrays.equals(obfuscatedPrefix, Arrays.copyOfRange(bytes, 0, obfuscatedPrefix.length))) {
bytes = Arrays.copyOfRange(bytes, obfuscatedPrefix.length, bytes.length);
try(ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
CipherInputStream cis = new CipherInputStream(bais, cipher(Cipher.DECRYPT_MODE, readIv(bais)));
InputStreamReader isr = new InputStreamReader(cis, StandardCharsets.UTF_8)) {
StringBuilder sb = new StringBuilder();
int c;
while((c = isr.read()) != -1) {
sb.append((char) c);
}
return sb.toString();
}
} else {
return s;
}
} catch(IOException e) {
return ifInvalid;
} catch(IndexOutOfBoundsException e) {
return ifInvalid;
} catch(Exception e) {
throw new RuntimeException("Unexpected error", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.socrata.datasync.SocrataConnectionInfo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import java.util.List;
Expand Down Expand Up @@ -53,6 +54,16 @@ public String getUsername() {
}

@JsonProperty("password")
public String getPasswordObfuscated() {
return CryptoUtil.obfuscate(password);
}

@JsonProperty("password")
public void setPasswordObfuscated(String pwd) {
password = CryptoUtil.deobfuscate(pwd, null);
}

@JsonIgnore
public String getPassword() {
return password;
}
Expand All @@ -73,6 +84,11 @@ public String getProxyUsername() {
}

@JsonProperty("proxyPassword")
public String getProxyPasswordObfuscated() {
return CryptoUtil.obfuscate(proxyPassword);
}

@JsonIgnore
public String getProxyPassword() {
return proxyPassword;
}
Expand Down Expand Up @@ -113,6 +129,16 @@ public String getSmtpUsername() {
}

@JsonProperty("smtpPassword")
public String getSmtpPasswordObfuscated() {
return CryptoUtil.obfuscate(smtpPassword);
}

@JsonProperty("smtpPassword")
public void setSmtpPasswordObfuscated(String pwd) {
smtpPassword = CryptoUtil.deobfuscate(pwd, null);
}

@JsonIgnore
public String getSmtpPassword() {
return smtpPassword;
}
Expand All @@ -131,6 +157,9 @@ public String getNumRowsPerChunk() {
public void setProxyUsername(String username) { proxyUsername = username; }

@JsonProperty("proxyPassword")
public void setProxyPasswordObfuscated(String password) { proxyPassword = CryptoUtil.deobfuscate(password, null); }

@JsonIgnore
public void setProxyPassword(String password) { proxyPassword = password; }

@JsonProperty("defaultTimeFormats")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void saveUsername(String username) {
}

public void savePassword(String password) {
saveKeyValuePair(PASSWORD, password);
saveKeyValuePair(PASSWORD, CryptoUtil.obfuscate(password));
}

public void saveProxyHost(String host) { saveKeyValuePair(PROXY_HOST, host); }
Expand All @@ -84,7 +84,7 @@ public void saveProxyPort(String port) {
public void saveProxyUsername(String username) { saveKeyValuePair(PROXY_USERNAME, username); }

public void saveProxyPassword(String password) {
saveKeyValuePair(PROXY_PASSWORD, password);
saveKeyValuePair(PROXY_PASSWORD, CryptoUtil.obfuscate(password));
}

public void saveAdminEmail(String adminEmail) { saveKeyValuePair(ADMIN_EMAIL, adminEmail); }
Expand Down Expand Up @@ -123,7 +123,7 @@ public void saveSMTPUsername(String username) {
}

public void saveSMTPPassword(String password) {
saveKeyValuePair(SMTP_PASSWORD, password);
saveKeyValuePair(SMTP_PASSWORD, CryptoUtil.obfuscate(password));
}

public void saveDefaultTimeFormats(List<String> defaultTimeFormats) {
Expand All @@ -148,7 +148,10 @@ public String getUsername() {
}

public String getPassword() {
return userPrefs.get(PASSWORD, "");
String base = userPrefs.get(PASSWORD, "");
String result = CryptoUtil.deobfuscate(base, "");
if(base == result) savePassword(result);
return result;
}

public String getProxyHost() { return userPrefs.get(PROXY_HOST, null); }
Expand All @@ -162,7 +165,10 @@ public String getProxyUsername() {
}

public String getProxyPassword() {
return userPrefs.get(PROXY_PASSWORD, null);
String base = userPrefs.get(PROXY_PASSWORD, null);
String result = CryptoUtil.deobfuscate(base, null);
if(base == result) saveProxyPassword(result);
return result;
}

public String getAdminEmail() {
Expand Down Expand Up @@ -195,7 +201,10 @@ public String getSmtpUsername() {
}

public String getSmtpPassword() {
return userPrefs.get(SMTP_PASSWORD, "");
String base = userPrefs.get(SMTP_PASSWORD, "");
String result = CryptoUtil.deobfuscate(base, "");
if(base == result) saveSMTPPassword(result);
return result;
}

public String getFilesizeChunkingCutoffMB() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.socrata.datasync.config.userpreferences;

import junit.framework.TestCase;
import org.junit.Test;

public class CryptoUtilTest {
@Test
public void testObfuscationRoundtrips() {
for(String x : new String[] { "hello", "smiling gnus are happy", "¥€$", "", "bleh" }) {
TestCase.assertEquals(x, CryptoUtil.deobfuscate(CryptoUtil.obfuscate(x), null));
}
}
}

0 comments on commit 5cd5d6f

Please sign in to comment.