mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-14 05:52:52 +00:00
Add some more tests
This adds tests for all of the importers, a new scrypt test and some more OTP tests. More to come.
This commit is contained in:
parent
dea13f56f5
commit
4f8a0b9020
40 changed files with 974 additions and 321 deletions
|
@ -1,43 +0,0 @@
|
|||
package com.beemdevelopment.aegis;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
||||
import com.beemdevelopment.aegis.crypto.SCryptParameters;
|
||||
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||
import com.beemdevelopment.aegis.encoding.Hex;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class SCryptTest {
|
||||
@Test
|
||||
public void testTrailingNullCollision() throws EncodingException {
|
||||
byte[] salt = new byte[0];
|
||||
SCryptParameters params = new SCryptParameters(
|
||||
CryptoUtils.CRYPTO_SCRYPT_N,
|
||||
CryptoUtils.CRYPTO_SCRYPT_p,
|
||||
CryptoUtils.CRYPTO_SCRYPT_r,
|
||||
salt
|
||||
);
|
||||
|
||||
byte[] head = new byte[]{'t', 'e', 's', 't'};
|
||||
byte[] expectedKey = Hex.decode("41cd8110d0c66ede16f97ce84fd8e2bd2269c9318532a01437789dfbadd1392e");
|
||||
|
||||
for (int i = 0; i < 128; i += 4) {
|
||||
byte[] input = new byte[head.length + i];
|
||||
System.arraycopy(head, 0, input, 0, head.length);
|
||||
|
||||
// once the length of the input is over 64 bytes, trailing nulls do not cause a collision anymore
|
||||
SecretKey key = CryptoUtils.deriveKey(input, params);
|
||||
if (input.length <= 64) {
|
||||
assertArrayEquals(expectedKey, key.getEncoded());
|
||||
} else {
|
||||
assertFalse(Arrays.equals(expectedKey, key.getEncoded()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package com.beemdevelopment.aegis.crypto;
|
||||
|
||||
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||
import com.beemdevelopment.aegis.encoding.Hex;
|
||||
|
||||
import org.bouncycastle.crypto.generators.SCrypt;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class SCryptTest {
|
||||
private static class Vector {
|
||||
private final byte[] _key;
|
||||
private final char[] _password;
|
||||
private final byte[] _salt;
|
||||
private final int _n;
|
||||
private final int _r;
|
||||
private final int _p;
|
||||
private final int _len;
|
||||
|
||||
public Vector(String key, String password, String salt, int n, int r, int p, int len) throws EncodingException {
|
||||
_key = Hex.decode(key);
|
||||
_password = password.toCharArray();
|
||||
_salt = CryptoUtils.toBytes(salt.toCharArray());
|
||||
_n = n;
|
||||
_r = r;
|
||||
_p = p;
|
||||
_len = len;
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
SCryptParameters params = new SCryptParameters(_n, _r, _p, _salt);
|
||||
byte[] key = SCrypt.generate(CryptoUtils.toBytes(_password), params.getSalt(), params.getN(), params.getR(), params.getP(), _len);
|
||||
assertArrayEquals(_key, key);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void vectorsMatch() throws EncodingException {
|
||||
// https://tools.ietf.org/html/rfc7914.html#section-12
|
||||
final Vector[] vectors = new Vector[]{
|
||||
new Vector("77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906",
|
||||
"", "", 1 << 4, 1, 1, 64),
|
||||
new Vector("fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640",
|
||||
"password", "NaCl", 1 << 10, 8, 16, 64),
|
||||
new Vector("7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887",
|
||||
"pleaseletmein", "SodiumChloride", 1 << 14, 8, 1, 64),
|
||||
new Vector("2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4",
|
||||
"pleaseletmein", "SodiumChloride", 1 << 20, 8, 1, 64)
|
||||
};
|
||||
|
||||
for (Vector vector : vectors) {
|
||||
vector.validate();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrailingNullCollision() throws EncodingException {
|
||||
byte[] salt = new byte[0];
|
||||
SCryptParameters params = new SCryptParameters(
|
||||
CryptoUtils.CRYPTO_SCRYPT_N,
|
||||
CryptoUtils.CRYPTO_SCRYPT_p,
|
||||
CryptoUtils.CRYPTO_SCRYPT_r,
|
||||
salt
|
||||
);
|
||||
|
||||
byte[] head = new byte[]{'t', 'e', 's', 't'};
|
||||
byte[] expectedKey = Hex.decode("41cd8110d0c66ede16f97ce84fd8e2bd2269c9318532a01437789dfbadd1392e");
|
||||
|
||||
for (int i = 0; i < 128; i += 4) {
|
||||
byte[] input = new byte[head.length + i];
|
||||
System.arraycopy(head, 0, input, 0, head.length);
|
||||
|
||||
// once the length of the input is over 64 bytes, trailing nulls do not cause a collision anymore
|
||||
SecretKey key = CryptoUtils.deriveKey(input, params);
|
||||
if (input.length <= 64) {
|
||||
assertArrayEquals(expectedKey, key.getEncoded());
|
||||
} else {
|
||||
assertFalse(Arrays.equals(expectedKey, key.getEncoded()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,15 @@
|
|||
package com.beemdevelopment.aegis;
|
||||
package com.beemdevelopment.aegis.crypto.otp;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.otp.HOTP;
|
||||
import com.beemdevelopment.aegis.crypto.otp.OTP;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class HOTPTest {
|
||||
// https://tools.ietf.org/html/rfc4226#page-32
|
||||
private final String[] _vectors = {
|
||||
public static final String[] VECTORS = {
|
||||
"755224", "287082",
|
||||
"359152", "969429",
|
||||
"338314", "254676",
|
||||
|
@ -20,16 +17,16 @@ public class HOTPTest {
|
|||
"399871", "520489"
|
||||
};
|
||||
|
||||
private final byte[] _secret = new byte[]{
|
||||
public static final byte[] SECRET = new byte[]{
|
||||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
|
||||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30
|
||||
};
|
||||
|
||||
@Test
|
||||
public void vectorsMatch() throws InvalidKeyException, NoSuchAlgorithmException {
|
||||
for (int i = 0; i < _vectors.length; i++) {
|
||||
OTP otp = HOTP.generateOTP(_secret, "HmacSHA1", 6, i);
|
||||
assertEquals(_vectors[i], otp.toString());
|
||||
for (int i = 0; i < VECTORS.length; i++) {
|
||||
OTP otp = HOTP.generateOTP(SECRET, "HmacSHA1", 6, i);
|
||||
assertEquals(VECTORS[i], otp.toString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,14 @@
|
|||
package com.beemdevelopment.aegis;
|
||||
package com.beemdevelopment.aegis.crypto.otp;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.otp.OTP;
|
||||
import com.beemdevelopment.aegis.crypto.otp.TOTP;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TOTPTest {
|
||||
private static class Vector {
|
||||
public static class Vector {
|
||||
public long Time;
|
||||
public String OTP;
|
||||
public String Algo;
|
||||
|
@ -25,7 +21,7 @@ public class TOTPTest {
|
|||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc6238#appendix-B
|
||||
private final Vector[] _vectors = {
|
||||
public static final Vector[] VECTORS = {
|
||||
new Vector(59, "94287082", "HmacSHA1"),
|
||||
new Vector(59, "46119246", "HmacSHA256"),
|
||||
new Vector(59, "90693936", "HmacSHA512"),
|
||||
|
@ -46,19 +42,19 @@ public class TOTPTest {
|
|||
new Vector(20000000000L, "47863826", "HmacSHA512")
|
||||
};
|
||||
|
||||
private final byte[] _seed = new byte[]{
|
||||
private static final byte[] SEED = new byte[]{
|
||||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
|
||||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30
|
||||
};
|
||||
|
||||
private final byte[] _seed32 = new byte[]{
|
||||
private static final byte[] SEED32 = new byte[]{
|
||||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
|
||||
0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34,
|
||||
0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32
|
||||
};
|
||||
|
||||
private final byte[] _seed64 = new byte[]{
|
||||
private static final byte[] SEED64 = new byte[]{
|
||||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
|
||||
0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32,
|
||||
0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
|
@ -67,26 +63,23 @@ public class TOTPTest {
|
|||
|
||||
@Test
|
||||
public void vectorsMatch() throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
for (Vector vector : _vectors) {
|
||||
byte[] seed;
|
||||
|
||||
switch (vector.Algo) {
|
||||
case "HmacSHA1":
|
||||
seed = _seed;
|
||||
break;
|
||||
case "HmacSHA256":
|
||||
seed = _seed32;
|
||||
break;
|
||||
case "HmacSHA512":
|
||||
seed = _seed64;
|
||||
break;
|
||||
default:
|
||||
fail("unsupported mode");
|
||||
return;
|
||||
}
|
||||
|
||||
for (Vector vector : VECTORS) {
|
||||
byte[] seed = getSeed(vector.Algo);
|
||||
OTP otp = TOTP.generateOTP(seed, vector.Algo, 8, 30, vector.Time);
|
||||
assertEquals(vector.OTP, otp.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getSeed(String algorithm) {
|
||||
switch (algorithm) {
|
||||
case "HmacSHA1":
|
||||
return SEED;
|
||||
case "HmacSHA256":
|
||||
return SEED32;
|
||||
case "HmacSHA512":
|
||||
return SEED64;
|
||||
default:
|
||||
throw new RuntimeException(String.format("Unsupported algorithm: %s", algorithm));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,331 @@
|
|||
package com.beemdevelopment.aegis.importers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.beemdevelopment.aegis.encoding.Base32;
|
||||
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||
import com.beemdevelopment.aegis.otp.HotpInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||
import com.beemdevelopment.aegis.otp.SteamInfo;
|
||||
import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||
import com.beemdevelopment.aegis.util.UUIDMap;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
@Config(sdk = { Build.VERSION_CODES.P })
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class DatabaseImporterTest {
|
||||
private List<VaultEntry> _vectors;
|
||||
|
||||
/**
|
||||
* The procedure for adding new importer tests is as follows:
|
||||
* 1. Generate QR codes for each test vector:
|
||||
* -> while read line; do (qrencode "$line" -o - | feh -); done < ./app/src/test/resources/com/beemdevelopment/aegis/importers/plain
|
||||
* 2. Scan the QR codes with the app we want to test our import functionality of
|
||||
* 3. Create an export and add the file to the importers resource directory.
|
||||
* 4. Add a new test for it here.
|
||||
*/
|
||||
|
||||
@Before
|
||||
public void initVectors() throws EncodingException, OtpInfoException {
|
||||
_vectors = Lists.newArrayList(
|
||||
new VaultEntry(new TotpInfo(Base32.decode("4SJHB4GSD43FZBAI7C2HLRJGPQ")), "Mason", "Deno"),
|
||||
new VaultEntry(new TotpInfo(Base32.decode("5OM4WOOGPLQEF6UGN3CPEOOLWU"), "SHA256", 7, 20), "James", "SPDX"),
|
||||
new VaultEntry(new TotpInfo(Base32.decode("7ELGJSGXNCCTV3O6LKJWYFV2RA"), "SHA512", 8, 50), "Elijah", "Airbnb"),
|
||||
new VaultEntry(new HotpInfo(Base32.decode("YOOMIXWS5GN6RTBPUFFWKTW5M4"), "SHA1", 6, 1), "James", "Issuu"),
|
||||
new VaultEntry(new HotpInfo(Base32.decode("KUVJJOM753IHTNDSZVCNKL7GII"), "SHA256", 7, 50), "Benjamin", "Air Canada"),
|
||||
new VaultEntry(new HotpInfo(Base32.decode("5VAML3X35THCEBVRLV24CGBKOY"), "SHA512", 8, 10300), "Mason", "WWE"),
|
||||
new VaultEntry(new SteamInfo(Base32.decode("JRZCL47CMXVOQMNPZR2F7J4RGI"), "SHA1", 5, 30), "Sophia", "Boeing")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportPlainText() throws IOException, DatabaseImporterException {
|
||||
List<VaultEntry> entries = importPlain(GoogleAuthUriImporter.class, "plain.txt");
|
||||
checkImportedEntries(entries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportAegisPlain() throws IOException, DatabaseImporterException {
|
||||
List<VaultEntry> entries = importPlain(AegisImporter.class, "aegis_plain.json");
|
||||
checkImportedEntries(entries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportAegisEncrypted() throws IOException, DatabaseImporterException {
|
||||
List<VaultEntry> entries = importEncrypted(AegisImporter.class, "aegis_encrypted.json", encryptedState -> {
|
||||
final char[] password = "test".toCharArray();
|
||||
return ((AegisImporter.EncryptedState) encryptedState).decrypt(password);
|
||||
});
|
||||
|
||||
checkImportedEntries(entries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportWinAuth() throws IOException, DatabaseImporterException {
|
||||
List<VaultEntry> entries = importPlain(WinAuthImporter.class, "plain.txt");
|
||||
for (VaultEntry entry : entries) {
|
||||
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
|
||||
entryVector.setIssuer(entryVector.getName());
|
||||
entryVector.setName("WinAuth");
|
||||
checkImportedEntry(entryVector, entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportAndOTP() throws IOException, DatabaseImporterException {
|
||||
List<VaultEntry> entries = importPlain(AndOtpImporter.class, "andotp_plain.json");
|
||||
checkImportedEntries(entries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportAndOTPEncrypted() throws IOException, DatabaseImporterException {
|
||||
List<VaultEntry> entries = importEncrypted(AndOtpImporter.class, "andotp_encrypted.bin", encryptedState -> {
|
||||
final char[] password = "test".toCharArray();
|
||||
return ((AndOtpImporter.EncryptedState) encryptedState).decryptNewFormat(password);
|
||||
});
|
||||
|
||||
checkImportedEntries(entries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportAndOTPEncryptedOld() throws IOException, DatabaseImporterException {
|
||||
List<VaultEntry> entries = importEncrypted(AndOtpImporter.class, "andotp_encrypted_old.bin", encryptedState -> {
|
||||
final char[] password = "test".toCharArray();
|
||||
return ((AndOtpImporter.EncryptedState) encryptedState).decryptOldFormat(password);
|
||||
});
|
||||
|
||||
for (VaultEntry entry : entries) {
|
||||
// old versions of andOTP have a bug where the issuer/name is not parsed correctly, so account for that here
|
||||
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
|
||||
entryVector.setName(String.format("%s:%s", entryVector.getIssuer(), entryVector.getName()));
|
||||
checkImportedEntry(entryVector, entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportTotpAuthenticator() throws IOException, DatabaseImporterException, OtpInfoException {
|
||||
List<VaultEntry> entries = importEncrypted(TotpAuthenticatorImporter.class, "totp_authenticator.bin", encryptedState -> {
|
||||
final char[] password = "Testtest1".toCharArray();
|
||||
return ((TotpAuthenticatorImporter.EncryptedState) encryptedState).decrypt(password);
|
||||
});
|
||||
|
||||
checkImportedTotpAuthenticatorEntries(entries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportTotpAuthenticatorInternal() throws IOException, DatabaseImporterException, OtpInfoException {
|
||||
List<VaultEntry> entries = importPlain(TotpAuthenticatorImporter.class, "totp_authenticator_internal.xml", true);
|
||||
checkImportedTotpAuthenticatorEntries(entries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportAuthy() throws IOException, DatabaseImporterException, OtpInfoException {
|
||||
List<VaultEntry> entries = importPlain(AuthyImporter.class, "authy_plain.xml");
|
||||
checkImportedAuthyEntries(entries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportAuthyEncrypted() throws IOException, DatabaseImporterException, OtpInfoException {
|
||||
List<VaultEntry> entries = importEncrypted(AuthyImporter.class, "authy_encrypted.xml", encryptedState -> {
|
||||
final char[] password = "testtest".toCharArray();
|
||||
return ((AuthyImporter.EncryptedState) encryptedState).decrypt(password);
|
||||
});
|
||||
|
||||
checkImportedAuthyEntries(entries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportFreeOtp() throws IOException, DatabaseImporterException, OtpInfoException {
|
||||
List<VaultEntry> entries = importPlain(FreeOtpImporter.class, "freeotp.xml");
|
||||
checkImportedFreeOtpEntries(entries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportFreeOtpPlus() throws IOException, DatabaseImporterException, OtpInfoException {
|
||||
List<VaultEntry> entries = importPlain(FreeOtpPlusImporter.class, "freeotp_plus.json");
|
||||
checkImportedFreeOtpEntries(entries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportFreeOtpPlusInternal() throws IOException, DatabaseImporterException, OtpInfoException {
|
||||
List<VaultEntry> entries = importPlain(FreeOtpPlusImporter.class, "freeotp_plus_internal.xml", true);
|
||||
checkImportedFreeOtpEntries(entries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportGoogleAuthenticator() throws IOException, DatabaseImporterException, OtpInfoException {
|
||||
List<VaultEntry> entries = importPlain(GoogleAuthImporter.class, "google_authenticator.sqlite");
|
||||
for (VaultEntry entry : entries) {
|
||||
// Google Authenticator doesn't support different hash algorithms, periods or digits, so fix those up here
|
||||
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
|
||||
entryVector.getInfo().setDigits(6);
|
||||
if (entryVector.getInfo() instanceof TotpInfo) {
|
||||
((TotpInfo) entryVector.getInfo()).setPeriod(30);
|
||||
}
|
||||
entryVector.getInfo().setAlgorithm("SHA1");
|
||||
checkImportedEntry(entryVector, entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportMicrosoftAuthenticator() throws IOException, DatabaseImporterException, OtpInfoException {
|
||||
List<VaultEntry> entries = importPlain(MicrosoftAuthImporter.class, "microsoft_authenticator.sqlite");
|
||||
for (VaultEntry entry : entries) {
|
||||
// Microsoft Authenticator doesn't support HOTP, different hash algorithms, periods or digits, so fix those up here
|
||||
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
|
||||
entryVector.setInfo(new TotpInfo(entryVector.getInfo().getSecret()));
|
||||
checkImportedEntry(entryVector, entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportSteam() throws IOException, DatabaseImporterException {
|
||||
List<VaultEntry> entries = importPlain(SteamImporter.class, "steam.json");
|
||||
for (VaultEntry entry : entries) {
|
||||
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
|
||||
entryVector.setIssuer("Steam");
|
||||
checkImportedEntry(entryVector, entry);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportAuthenticatorPlus() throws IOException, DatabaseImporterException, OtpInfoException {
|
||||
List<VaultEntry> entries = importEncrypted(AuthenticatorPlusImporter.class, "authenticator_plus.zip", encryptedState -> {
|
||||
final char[] password = "testtesttest".toCharArray();
|
||||
return ((AuthenticatorPlusImporter.EncryptedState) encryptedState).decrypt(password);
|
||||
});
|
||||
|
||||
checkImportedEntries(entries);
|
||||
}
|
||||
|
||||
private List<VaultEntry> importPlain(Class<? extends DatabaseImporter> type, String resName)
|
||||
throws IOException, DatabaseImporterException {
|
||||
return importPlain(type, resName, false);
|
||||
}
|
||||
|
||||
private List<VaultEntry> importPlain(Class<? extends DatabaseImporter> type, String resName, boolean isInternal)
|
||||
throws IOException, DatabaseImporterException {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
DatabaseImporter importer = DatabaseImporter.create(context, type);
|
||||
try (InputStream stream = openResource(resName)) {
|
||||
DatabaseImporter.State state = importer.read(stream, isInternal);
|
||||
assertFalse(state.isEncrypted());
|
||||
DatabaseImporter.Result result = state.convert();
|
||||
return Lists.newArrayList(getEntries(result));
|
||||
}
|
||||
}
|
||||
|
||||
private List<VaultEntry> importEncrypted(Class<? extends DatabaseImporter> type, String resName, Decryptor decryptor)
|
||||
throws IOException, DatabaseImporterException {
|
||||
return importEncrypted(type, resName, false, decryptor);
|
||||
}
|
||||
|
||||
private List<VaultEntry> importEncrypted(Class<? extends DatabaseImporter> type, String resName, boolean isInternal, Decryptor decryptor)
|
||||
throws IOException, DatabaseImporterException {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
DatabaseImporter importer = DatabaseImporter.create(context, type);
|
||||
try (InputStream stream = openResource(resName)) {
|
||||
DatabaseImporter.State state = importer.read(stream, isInternal);
|
||||
assertTrue(state.isEncrypted());
|
||||
DatabaseImporter.Result result = decryptor.decrypt(state).convert();
|
||||
return Lists.newArrayList(getEntries(result));
|
||||
}
|
||||
}
|
||||
|
||||
private static UUIDMap<VaultEntry> getEntries(DatabaseImporter.Result result) {
|
||||
for (DatabaseImporterEntryException e : result.getErrors()) {
|
||||
fail(e.toString());
|
||||
}
|
||||
|
||||
return result.getEntries();
|
||||
}
|
||||
|
||||
private void checkImportedAuthyEntries(List<VaultEntry> entries) throws OtpInfoException {
|
||||
for (VaultEntry entry : entries) {
|
||||
// Authy doesn't support different hash algorithms or periods, so fix those up here
|
||||
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
|
||||
entryVector.getInfo().setAlgorithm("SHA1");
|
||||
((TotpInfo) entry.getInfo()).setPeriod(((TotpInfo) entryVector.getInfo()).getPeriod());
|
||||
checkImportedEntry(entryVector, entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkImportedTotpAuthenticatorEntries(List<VaultEntry> entries) throws OtpInfoException {
|
||||
for (VaultEntry entry : entries) {
|
||||
// TOTP Authenticator doesn't support different hash algorithms, periods or digits, so fix those up here
|
||||
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
|
||||
entryVector.getInfo().setDigits(6);
|
||||
((TotpInfo) entryVector.getInfo()).setPeriod(30);
|
||||
entryVector.getInfo().setAlgorithm("SHA1");
|
||||
entryVector.setName(entryVector.getName().toLowerCase());
|
||||
checkImportedEntry(entryVector, entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkImportedFreeOtpEntries(List<VaultEntry> entries) throws OtpInfoException {
|
||||
for (VaultEntry entry : entries) {
|
||||
// for some reason, FreeOTP adds -1 to the counter
|
||||
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
|
||||
if (entryVector.getInfo() instanceof HotpInfo) {
|
||||
((HotpInfo) entryVector.getInfo()).setCounter(((HotpInfo) entryVector.getInfo()).getCounter() - 1);
|
||||
}
|
||||
checkImportedEntry(entryVector, entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkImportedEntries(List<VaultEntry> entries) {
|
||||
for (VaultEntry entry : entries) {
|
||||
checkImportedEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkImportedEntry(VaultEntry entry) {
|
||||
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
|
||||
checkImportedEntry(entryVector, entry);
|
||||
}
|
||||
|
||||
private void checkImportedEntry(VaultEntry entryVector, VaultEntry entry) {
|
||||
String message = String.format("Entries are not equivalent: (%s) (%s)", entryVector.toJson().toString(), entry.toJson().toString());
|
||||
assertTrue(message, entryVector.equivalates(entry));
|
||||
assertEquals(message, entryVector.getInfo().getOtp(), entry.getInfo().getOtp());
|
||||
}
|
||||
|
||||
private VaultEntry getEntryVectorBySecret(byte[] secret) {
|
||||
for (VaultEntry entry : _vectors) {
|
||||
if (Arrays.equals(entry.getInfo().getSecret(), secret)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
fail(String.format("No entry found for secret: %s", Base32.encode(secret)));
|
||||
return null;
|
||||
}
|
||||
|
||||
private InputStream openResource(String name) {
|
||||
return getClass().getResourceAsStream(name);
|
||||
}
|
||||
|
||||
private interface Decryptor {
|
||||
DatabaseImporter.State decrypt(DatabaseImporter.State encryptedState) throws DatabaseImporterException;
|
||||
}
|
||||
}
|
27
app/src/test/java/com/beemdevelopment/aegis/otp/OtpTest.java
Normal file
27
app/src/test/java/com/beemdevelopment/aegis/otp/OtpTest.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
package com.beemdevelopment.aegis.otp;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.otp.HOTPTest;
|
||||
import com.beemdevelopment.aegis.crypto.otp.TOTPTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class OtpTest {
|
||||
@Test
|
||||
public void testHotpInfoOtp() throws OtpInfoException {
|
||||
for (int i = 0; i < HOTPTest.VECTORS.length; i++) {
|
||||
HotpInfo info = new HotpInfo(HOTPTest.SECRET, "SHA1", 6, i);
|
||||
assertEquals(HOTPTest.VECTORS[i], info.getOtp());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTotpInfoOtp() throws OtpInfoException {
|
||||
for (TOTPTest.Vector vector : TOTPTest.VECTORS) {
|
||||
byte[] seed = TOTPTest.getSeed(vector.Algo);
|
||||
TotpInfo info = new TotpInfo(seed, vector.Algo, 8, 30);
|
||||
assertEquals(vector.OTP, info.getOtp(vector.Time));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +1,25 @@
|
|||
package com.beemdevelopment.aegis;
|
||||
package com.beemdevelopment.aegis.util;
|
||||
|
||||
import com.beemdevelopment.aegis.util.Cloner;
|
||||
import com.beemdevelopment.aegis.util.UUIDMap;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class UUIDMapTest {
|
||||
private UUIDMap<Value> _map;
|
||||
|
||||
@BeforeEach
|
||||
@Before
|
||||
public void init() {
|
||||
_map = new UUIDMap<>();
|
||||
}
|
||||
|
@ -87,7 +90,7 @@ public class UUIDMapTest {
|
|||
// swap the values and see if the lists are equal now
|
||||
_map.swap(value1, value4);
|
||||
_map.swap(value2, value3);
|
||||
assertIterableEquals(values, ref);
|
||||
assertArrayEquals(values.toArray(), ref.toArray());
|
||||
}
|
||||
|
||||
private Value addNewValue() {
|
|
@ -1,15 +1,11 @@
|
|||
package com.beemdevelopment.aegis;
|
||||
package com.beemdevelopment.aegis.vault.slots;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
||||
import com.beemdevelopment.aegis.crypto.MasterKey;
|
||||
import com.beemdevelopment.aegis.crypto.SCryptParameters;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.RawSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotIntegrityException;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
|
@ -20,13 +16,13 @@ import javax.crypto.NoSuchPaddingException;
|
|||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class SlotTest {
|
||||
private MasterKey _masterKey;
|
||||
|
||||
@BeforeEach
|
||||
@Before
|
||||
public void init() {
|
||||
_masterKey = MasterKey.generate();
|
||||
}
|
||||
|
@ -75,18 +71,26 @@ public class SlotTest {
|
|||
public void testSlotIntegrity() throws
|
||||
InvalidAlgorithmParameterException, NoSuchAlgorithmException,
|
||||
InvalidKeyException, NoSuchPaddingException,
|
||||
SlotException {
|
||||
SlotException, SlotIntegrityException {
|
||||
RawSlot slot = new RawSlot();
|
||||
SecretKey rawKey = CryptoUtils.generateKey();
|
||||
Cipher cipher = CryptoUtils.createEncryptCipher(rawKey);
|
||||
slot.setKey(_masterKey, cipher);
|
||||
|
||||
// garble the first byte of the key
|
||||
byte[] rawKeyBytes = rawKey.getEncoded();
|
||||
rawKeyBytes[0] = (byte) ~rawKeyBytes[0];
|
||||
rawKey = new SecretKeySpec(rawKeyBytes, "AES");
|
||||
// try to decrypt with good key/ciphertext first
|
||||
final Cipher decryptCipher = slot.createDecryptCipher(rawKey);
|
||||
slot.getKey(decryptCipher);
|
||||
|
||||
Cipher decryptCipher = slot.createDecryptCipher(rawKey);
|
||||
// garble the first byte of the key and try to decrypt
|
||||
byte[] garbledKeyBytes = rawKey.getEncoded();
|
||||
garbledKeyBytes[0] = (byte) ~garbledKeyBytes[0];
|
||||
SecretKey garbledKey = new SecretKeySpec(garbledKeyBytes, "AES");
|
||||
final Cipher garbledDecryptCipher = slot.createDecryptCipher(garbledKey);
|
||||
assertThrows(SlotIntegrityException.class, () -> slot.getKey(garbledDecryptCipher));
|
||||
|
||||
// garble the first byte of the ciphertext and try to decrypt
|
||||
byte[] garbledCiphertext = slot.getEncryptedMasterKey();
|
||||
garbledCiphertext[0] = (byte) ~garbledCiphertext[0];
|
||||
assertThrows(SlotIntegrityException.class, () -> slot.getKey(decryptCipher));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue