2018-12-07 16:44:02 +03:00
|
|
|
package im.status.keycard.applet;
|
2018-11-15 20:16:18 +03:00
|
|
|
|
|
|
|
|
import java.util.StringTokenizer;
|
|
|
|
|
|
|
|
|
|
/**
|
2018-12-07 16:44:02 +03:00
|
|
|
* Keypath object to be used with the KeycardCommandSet
|
2018-11-15 20:16:18 +03:00
|
|
|
*/
|
|
|
|
|
public class KeyPath {
|
|
|
|
|
private int source;
|
|
|
|
|
private byte[] data;
|
|
|
|
|
|
|
|
|
|
/**
|
2018-12-07 16:44:02 +03:00
|
|
|
* Parses a keypath into a byte array and source parameter to be used with the KeycardCommandSet object.
|
2018-11-15 20:16:18 +03:00
|
|
|
*
|
|
|
|
|
* A valid string is composed of a minimum of one and a maximum of 11 components separated by "/".
|
|
|
|
|
*
|
|
|
|
|
* The first component can be either "m", indicating the master key, "..", indicating the parent of the current key,
|
|
|
|
|
* or "." indicating the current key. It can also be omitted, in which case it is considered the same as being ".".
|
|
|
|
|
*
|
|
|
|
|
* All other components are positive integers fitting in 31 bit, eventually suffixed by an apostrophe (') sign,
|
|
|
|
|
* which indicates an hardened key.
|
|
|
|
|
*
|
|
|
|
|
* An example of a valid path is "m/44'/0'/0'/0/0"
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* @param keypath the keypath as a string
|
|
|
|
|
*/
|
|
|
|
|
public KeyPath(String keypath) {
|
|
|
|
|
StringTokenizer tokenizer = new StringTokenizer(keypath, "/");
|
|
|
|
|
|
|
|
|
|
String sourceOrFirstElement = tokenizer.nextToken();
|
|
|
|
|
|
|
|
|
|
switch(sourceOrFirstElement) {
|
|
|
|
|
case "m":
|
2018-12-07 16:44:02 +03:00
|
|
|
source = KeycardCommandSet.DERIVE_P1_SOURCE_MASTER;
|
2018-11-15 20:16:18 +03:00
|
|
|
break;
|
|
|
|
|
case "..":
|
2018-12-07 16:44:02 +03:00
|
|
|
source = KeycardCommandSet.DERIVE_P1_SOURCE_PARENT;
|
2018-11-15 20:16:18 +03:00
|
|
|
break;
|
|
|
|
|
case ".":
|
2018-12-07 16:44:02 +03:00
|
|
|
source = KeycardCommandSet.DERIVE_P1_SOURCE_CURRENT;
|
2018-11-15 20:16:18 +03:00
|
|
|
break;
|
|
|
|
|
default:
|
2018-12-07 16:44:02 +03:00
|
|
|
source = KeycardCommandSet.DERIVE_P1_SOURCE_CURRENT;
|
2018-11-15 20:16:18 +03:00
|
|
|
tokenizer = new StringTokenizer(keypath, "/"); // rewind
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int componentCount = tokenizer.countTokens();
|
|
|
|
|
if (componentCount > 10) {
|
|
|
|
|
throw new IllegalArgumentException("Too many components");
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-16 11:12:33 +03:00
|
|
|
data = new byte[4 * componentCount];
|
|
|
|
|
|
2018-11-15 20:16:18 +03:00
|
|
|
for (int i = 0; i < componentCount; i++) {
|
|
|
|
|
long component = parseComponent(tokenizer.nextToken());
|
|
|
|
|
writeComponent(component, i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-16 11:12:33 +03:00
|
|
|
public KeyPath(byte[] data, int source) {
|
|
|
|
|
this.data = data;
|
|
|
|
|
this.source = source;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public KeyPath(byte[] data) {
|
2018-12-07 16:44:02 +03:00
|
|
|
this(data, KeycardCommandSet.DERIVE_P1_SOURCE_MASTER);
|
2018-11-16 11:12:33 +03:00
|
|
|
}
|
|
|
|
|
|
2018-11-15 20:16:18 +03:00
|
|
|
private long parseComponent(String num) {
|
|
|
|
|
long sign;
|
|
|
|
|
|
|
|
|
|
if (num.endsWith("'")) {
|
|
|
|
|
sign = 0x80000000L;
|
|
|
|
|
num = num.substring(0, (num.length() - 1));
|
|
|
|
|
} else {
|
|
|
|
|
sign = 0L;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (num.startsWith("+") || num.startsWith("-")) {
|
|
|
|
|
throw new NumberFormatException("No sign allowed");
|
|
|
|
|
}
|
|
|
|
|
return (sign | Long.parseLong(num));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void writeComponent(long component, int i) {
|
|
|
|
|
int off = (i*4);
|
|
|
|
|
data[off] = (byte)((component >> 24) & 0xff);
|
|
|
|
|
data[off + 1] = (byte)((component >> 16) & 0xff);
|
|
|
|
|
data[off + 2] = (byte)((component >> 8) & 0xff);
|
|
|
|
|
data[off + 3] = (byte)(component & 0xff);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The source of the derive command.
|
|
|
|
|
*
|
|
|
|
|
* @return the source of the derive command
|
|
|
|
|
*/
|
|
|
|
|
public int getSource() {
|
|
|
|
|
return source;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The byte encoded key path.
|
|
|
|
|
*
|
|
|
|
|
* @return byte encoded key path
|
|
|
|
|
*/
|
|
|
|
|
public byte[] getData() {
|
|
|
|
|
return data;
|
|
|
|
|
}
|
2018-11-16 11:12:33 +03:00
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String toString() {
|
|
|
|
|
StringBuffer sb = new StringBuffer();
|
|
|
|
|
|
|
|
|
|
switch(source) {
|
2018-12-07 16:44:02 +03:00
|
|
|
case KeycardCommandSet.DERIVE_P1_SOURCE_MASTER:
|
2018-11-16 11:12:33 +03:00
|
|
|
sb.append('m');
|
|
|
|
|
break;
|
2018-12-07 16:44:02 +03:00
|
|
|
case KeycardCommandSet.DERIVE_P1_SOURCE_PARENT:
|
2018-11-16 11:12:33 +03:00
|
|
|
sb.append("..");
|
|
|
|
|
break;
|
2018-12-07 16:44:02 +03:00
|
|
|
case KeycardCommandSet.DERIVE_P1_SOURCE_CURRENT:
|
2018-11-16 11:12:33 +03:00
|
|
|
sb.append('.');
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < this.data.length; i += 4) {
|
|
|
|
|
sb.append('/');
|
|
|
|
|
appendComponent(sb, i);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sb.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendComponent(StringBuffer sb, int i) {
|
|
|
|
|
int num = ((this.data[i] & 0x7f) << 24) | ((this.data[i+1] & 0xff) << 16) | ((this.data[i+2] & 0xff) << 8) | (this.data[i+3] & 0xff);
|
|
|
|
|
sb.append(num);
|
|
|
|
|
|
|
|
|
|
if ((this.data[i] & 0x80) == 0x80) {
|
|
|
|
|
sb.append('\'');
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-11-15 20:16:18 +03:00
|
|
|
}
|