|
|
|
@ -3,8 +3,10 @@ package me.impy.aegis.crypto;
|
|
|
|
|
import android.net.Uri;
|
|
|
|
|
|
|
|
|
|
import java.io.Serializable;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
|
|
|
|
|
import me.impy.aegis.encoding.Base32;
|
|
|
|
|
import me.impy.aegis.encoding.Base32Exception;
|
|
|
|
|
|
|
|
|
|
public class KeyInfo implements Serializable {
|
|
|
|
|
private String _type = "totp";
|
|
|
|
@ -24,7 +26,7 @@ public class KeyInfo implements Serializable {
|
|
|
|
|
builder.appendQueryParameter("digits", Integer.toString(_digits));
|
|
|
|
|
builder.appendQueryParameter("period", Integer.toString(_period));
|
|
|
|
|
builder.appendQueryParameter("algorithm", _algorithm);
|
|
|
|
|
builder.appendQueryParameter("secret", Base32.encodeOriginal(_secret));
|
|
|
|
|
builder.appendQueryParameter("secret", new String(Base32.encode(_secret)));
|
|
|
|
|
if (_type.equals("hotp")) {
|
|
|
|
|
builder.appendQueryParameter("counter", Long.toString(_counter));
|
|
|
|
|
}
|
|
|
|
@ -44,30 +46,25 @@ public class KeyInfo implements Serializable {
|
|
|
|
|
return p - (System.currentTimeMillis() % p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static KeyInfo fromURL(String s) throws Exception {
|
|
|
|
|
public static KeyInfo fromURL(String s) throws KeyInfoException {
|
|
|
|
|
final Uri url = Uri.parse(s);
|
|
|
|
|
if (!url.getScheme().equals("otpauth")) {
|
|
|
|
|
throw new Exception("unsupported protocol");
|
|
|
|
|
throw new KeyInfoException("unsupported protocol");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
KeyInfo info = new KeyInfo();
|
|
|
|
|
|
|
|
|
|
// only 'totp' and 'hotp' are supported
|
|
|
|
|
info._type = url.getHost();
|
|
|
|
|
if (info._type.equals("totp") && info._type.equals("hotp")) {
|
|
|
|
|
throw new Exception("unsupported type");
|
|
|
|
|
}
|
|
|
|
|
info.setType(url.getHost());
|
|
|
|
|
|
|
|
|
|
// 'secret' is a required parameter
|
|
|
|
|
String secret = url.getQueryParameter("secret");
|
|
|
|
|
if (secret == null) {
|
|
|
|
|
throw new Exception("'secret' is not set");
|
|
|
|
|
throw new KeyInfoException("'secret' is not set");
|
|
|
|
|
}
|
|
|
|
|
info._secret = Base32.decode(secret);
|
|
|
|
|
info.setSecret(secret.toCharArray());
|
|
|
|
|
|
|
|
|
|
// provider info used to disambiguate accounts
|
|
|
|
|
String path = url.getPath();
|
|
|
|
|
String label = path != null ? path.substring(1) : "";
|
|
|
|
|
String label = path != null && path.length() > 0 ? path.substring(1) : "";
|
|
|
|
|
|
|
|
|
|
if (label.contains(":")) {
|
|
|
|
|
// a label can only contain one colon
|
|
|
|
@ -75,40 +72,40 @@ public class KeyInfo implements Serializable {
|
|
|
|
|
String[] strings = label.split(":");
|
|
|
|
|
|
|
|
|
|
if (strings.length == 2) {
|
|
|
|
|
info._issuer = strings[0];
|
|
|
|
|
info._accountName = strings[1];
|
|
|
|
|
info.setIssuer(strings[0]);
|
|
|
|
|
info.setAccountName(strings[1]);
|
|
|
|
|
} else {
|
|
|
|
|
// at this point, just dump the whole thing into the accountName
|
|
|
|
|
info._accountName = label;
|
|
|
|
|
info.setAccountName(label);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// label only contains the account name
|
|
|
|
|
// grab the issuer's info from the 'issuer' parameter if it's present
|
|
|
|
|
String issuer = url.getQueryParameter("issuer");
|
|
|
|
|
info._issuer = issuer != null ? issuer : "";
|
|
|
|
|
info._accountName = label;
|
|
|
|
|
info.setIssuer(issuer != null ? issuer : "");
|
|
|
|
|
info.setAccountName(label);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// just use the defaults if these parameters aren't set
|
|
|
|
|
String algorithm = url.getQueryParameter("algorithm");
|
|
|
|
|
if (algorithm != null) {
|
|
|
|
|
info._algorithm = algorithm;
|
|
|
|
|
info.setAlgorithm(algorithm);
|
|
|
|
|
}
|
|
|
|
|
String period = url.getQueryParameter("period");
|
|
|
|
|
if (period != null) {
|
|
|
|
|
info._period = Integer.parseInt(period);
|
|
|
|
|
info.setPeriod(Integer.parseInt(period));
|
|
|
|
|
}
|
|
|
|
|
String digits = url.getQueryParameter("digits");
|
|
|
|
|
if (digits != null) {
|
|
|
|
|
info._digits = Integer.parseInt(digits);
|
|
|
|
|
info.setDigits(Integer.parseInt(digits));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 'counter' is required if the type is 'hotp'
|
|
|
|
|
String counter = url.getQueryParameter("counter");
|
|
|
|
|
if (counter != null) {
|
|
|
|
|
info._counter = Long.parseLong(counter);
|
|
|
|
|
} else if (info._type.equals("hotp")) {
|
|
|
|
|
throw new Exception("'counter' was not set which is required for 'hotp'");
|
|
|
|
|
info.setCounter(Long.parseLong(counter));
|
|
|
|
|
} else if (info.getType().equals("hotp")) {
|
|
|
|
|
throw new KeyInfoException("'counter' was not set which is required for 'hotp'");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return info;
|
|
|
|
@ -117,56 +114,119 @@ public class KeyInfo implements Serializable {
|
|
|
|
|
public String getType() {
|
|
|
|
|
return _type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] getSecret() {
|
|
|
|
|
return _secret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String getAccountName() {
|
|
|
|
|
return _accountName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String getIssuer() {
|
|
|
|
|
return _issuer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String getAlgorithm(boolean java) {
|
|
|
|
|
if (java) {
|
|
|
|
|
return "Hmac" + _algorithm;
|
|
|
|
|
}
|
|
|
|
|
return _algorithm;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getDigits() {
|
|
|
|
|
return _digits;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public long getCounter() {
|
|
|
|
|
return _counter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getPeriod() {
|
|
|
|
|
return _period;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setType(String type) {
|
|
|
|
|
public boolean isTypeValid(String type) {
|
|
|
|
|
return type.equals("totp") || type.equals("hotp");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setType(String type) throws KeyInfoException {
|
|
|
|
|
type = type.toLowerCase();
|
|
|
|
|
if (!isTypeValid(type)) {
|
|
|
|
|
throw new KeyInfoException(String.format("unsupported otp type: %s", type));
|
|
|
|
|
}
|
|
|
|
|
_type = type.toLowerCase();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setSecret(char[] base32) throws KeyInfoException {
|
|
|
|
|
byte[] secret;
|
|
|
|
|
try {
|
|
|
|
|
secret = Base32.decode(base32);
|
|
|
|
|
} catch (Base32Exception e) {
|
|
|
|
|
throw new KeyInfoException("bad secret", e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setSecret(secret);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setSecret(byte[] secret) {
|
|
|
|
|
_secret = secret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setAccountName(String accountName) {
|
|
|
|
|
_accountName = accountName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setIssuer(String issuer) {
|
|
|
|
|
_issuer = issuer;
|
|
|
|
|
}
|
|
|
|
|
public void setAlgorithm(String algorithm) {
|
|
|
|
|
|
|
|
|
|
public boolean isAlgorithmValid(String algorithm) {
|
|
|
|
|
return algorithm.equals("SHA1") || algorithm.equals("SHA256") || algorithm.equals("SHA512");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setAlgorithm(String algorithm) throws KeyInfoException {
|
|
|
|
|
if (algorithm.startsWith("Hmac")) {
|
|
|
|
|
algorithm = algorithm.substring(4);
|
|
|
|
|
}
|
|
|
|
|
_algorithm = algorithm.toUpperCase();
|
|
|
|
|
algorithm = algorithm.toUpperCase();
|
|
|
|
|
|
|
|
|
|
if (!isAlgorithmValid(algorithm)) {
|
|
|
|
|
throw new KeyInfoException(String.format("unsupported algorithm: %s", algorithm));
|
|
|
|
|
}
|
|
|
|
|
_algorithm = algorithm;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean isDigitsValid(int digits) {
|
|
|
|
|
return digits == 6 || digits == 8;
|
|
|
|
|
}
|
|
|
|
|
public void setDigits(int digits) {
|
|
|
|
|
|
|
|
|
|
public void setDigits(int digits) throws KeyInfoException {
|
|
|
|
|
if (!isDigitsValid(digits)) {
|
|
|
|
|
throw new KeyInfoException(String.format("unsupported amount of digits: %d", digits));
|
|
|
|
|
}
|
|
|
|
|
_digits = digits;
|
|
|
|
|
}
|
|
|
|
|
public void setCounter(long count) {
|
|
|
|
|
|
|
|
|
|
public boolean isCounterValid(long count) {
|
|
|
|
|
return count >= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setCounter(long count) throws KeyInfoException {
|
|
|
|
|
if (!isCounterValid(count)) {
|
|
|
|
|
throw new KeyInfoException(String.format("bad count: %d", count));
|
|
|
|
|
}
|
|
|
|
|
_counter = count;
|
|
|
|
|
}
|
|
|
|
|
public void setPeriod(int period) {
|
|
|
|
|
|
|
|
|
|
public boolean isPeriodValid(int period) {
|
|
|
|
|
return period > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setPeriod(int period) throws KeyInfoException {
|
|
|
|
|
if (!isPeriodValid(period)) {
|
|
|
|
|
throw new KeyInfoException(String.format("bad period: %d", period));
|
|
|
|
|
}
|
|
|
|
|
_period = period;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|