/** * */ package edu.washington.cs.oneswarm.f2f; import java.util.Arrays; import java.util.Date; import org.bouncycastle.util.encoders.Base64; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.SHA1Hasher; import org.gudy.azureus2.core3.util.SHA1Simple; public class FriendInvitation { public final static int INV_KEY_LENGTH = 36; public final static int INV_PUB_KEY_VERIFICATION_LENGTH = 16; public final static int INV_DHT_KEY_LENGTH = 26; public final static int INV_DHT_VALUE_XOR_START = INV_PUB_KEY_VERIFICATION_LENGTH; public final static int INV_DHT_VALUE_XOR_LENGTH = 10; public final static int INV_SECURITY_TYPE_POS = INV_KEY_LENGTH - 1; public final static byte[] NOUNCE_PUBLISH_CREATED_INVITATION = Base64.decode("GNvEsbbVLWOXtNLsmRQak0PXEpGnAwRamKsf5yQXSoYjVfEVT1aV2zDIyPcA7ttTbpdAQDj6Ye79NVLc30LAf0Z0TSPtHO4v17zd5vL0j0F7FVJagx2Ywx5c4O4zFKD9vRQmklMuiKfGRYlJP4FK9kyLjg3WKe8WeOY5HaPIA585Rk50rb4Ta8QtvG5s1pAl74CJkshg"); public final static byte[] NOUNCE_PUBLISH_REDEEMED_INVITATION = Base64.decode("lmLdVREVe2HFdenxRFa9pCXWLBmTbpvAk3FVBdtAc4Hp2BSIaDLjfnSt6ByA04x0nrur0zCJWIVP21pnuMPEBOx3WzFYWHKBAzJ9vb6BXCjBh6xoteWRdXylhFowvZwJ33MwTsvD1K7X9d6ZBooxsyF3CK2gXUvJQWZoAB413rOsGAtIsdGRwnCa6n2GAT1CloDuQub1"); public static final int SECURITY_LEVEL_LOW = 0; public static final int SECURITY_LEVEL_PIN = 1; private boolean canSeeFileList; private long createdDate; private boolean hasChanged = false; private final byte[] key; private long lastConnectDate; private String lastConnectIp; private int lastConnectPort = 0; private long maxAge; private String name; private byte[] remotePublicKey; private int securityLevel; private boolean createdLocally; private Status status = Status.STATUS_NEW; private transient long lastConnectAttempt = 0; private static long MIN_MS_BETWEEN_CONNECT_ATTEMPTS = 60 * 1000; public void connectAttempted() { lastConnectAttempt = System.currentTimeMillis(); } public boolean connectAttemptsAllowed() { long age = System.currentTimeMillis() - lastConnectAttempt; boolean rateOk = age > MIN_MS_BETWEEN_CONNECT_ATTEMPTS; boolean stateOk = hasConnectableStatus(); return rateOk && stateOk; } public boolean hasConnectableStatus() { boolean stateOk = status.getCode() >= FriendInvitation.Status.STATUS_NEW.getCode() && status.getCode() < FriendInvitation.Status.STATUS_SUCCESS.getCode(); return stateOk; } public boolean isStillValid() { long age = System.currentTimeMillis() - getCreatedDate(); if (age > maxAge) { return false; } if (!hasConnectableStatus()) { return false; } return true; } public FriendInvitation(byte[] key) { this.key = key; } public long getCreatedDate() { return createdDate; } public byte[] getKey() { return key; } public byte[] getDHTKeyBase(boolean creatorAddress) { /* * the base we use for keys is the first 30 bytes of the invite appended * with a nounce. Different nounces are used depending on wether we * created the invite or redeemed it */ int nounceLength = FriendInvitation.NOUNCE_PUBLISH_CREATED_INVITATION.length; byte[] keyBase = new byte[FriendInvitation.INV_DHT_KEY_LENGTH + nounceLength]; System.arraycopy(getKey(), 0, keyBase, 0, FriendInvitation.INV_DHT_KEY_LENGTH); if (creatorAddress) { System.arraycopy(FriendInvitation.NOUNCE_PUBLISH_CREATED_INVITATION, 0, keyBase, FriendInvitation.INV_DHT_KEY_LENGTH, nounceLength); } else { System.arraycopy(FriendInvitation.NOUNCE_PUBLISH_REDEEMED_INVITATION, 0, keyBase, FriendInvitation.INV_DHT_KEY_LENGTH, nounceLength); } return keyBase; } public byte[] xorDhtValue(byte[] value) { byte[] sha1 = new SHA1Simple().calculateHash(getKey(), FriendInvitation.INV_DHT_VALUE_XOR_START, FriendInvitation.INV_DHT_VALUE_XOR_LENGTH); byte[] xored = xor(value, sha1, 0); return xored; } private static byte[] xor(byte[] value, byte[] key, int keyOffset) { byte[] xored = new byte[value.length]; for (int i = 0; i < value.length; i++) { xored[i] = (byte) (value[i] ^ key[keyOffset + i]); } return xored; } public long getLastConnectDate() { return lastConnectDate; } public String getLastConnectIp() { return lastConnectIp; } public int getLastConnectPort() { return lastConnectPort; } public long getMaxAge() { return maxAge; } public String getName() { return name; } public byte[] getRemotePublicKey() { return remotePublicKey; } public int getSecurityLevel() { return securityLevel; } public Status getStatus() { if (createdLocally) { if (status != Status.STATUS_SUCCESS && System.currentTimeMillis() > createdDate + maxAge) { status = Status.STATUS_EXPIRED; } } return status; } public boolean isCanSeeFileList() { return canSeeFileList; } public boolean isHasChanged() { return hasChanged; } public boolean isCreatedLocally() { return createdLocally; } public boolean isRedeemed() { return !createdLocally; } public boolean keyEquals(byte[] key2) { if (key2 == null) { return false; } return Arrays.equals(key2, this.key); } public boolean pubKeyMatch(byte[] remotePublicKey) { byte[] remoteKeySha = new SHA1Hasher().calculateHash(remotePublicKey); // the lower INV_PUB_KEY_VERIFICATION_LENGTH bytes of the remote public // key sha1 is the lower INV_PUB_KEY_VERIFICATION_LENGTH bytes of the invite if (remoteKeySha.length != 20) { System.err.println("wrong len: " + remoteKeySha.length + "!=20"); return false; } for (int i = 0; i < INV_PUB_KEY_VERIFICATION_LENGTH; i++) { if (key[i] != remoteKeySha[i]) { // System.err.println("wrong byte on pos " + i + " " + key[i] + "!=" // + remoteKeySha[i]); // System.err.println("key=" + Base32.encode(key)); // System.err.println("pubkeysha=" + Base32.encode(remoteKeySha)); return false; } } return true; } public void setCanSeeFileList(boolean canSeeFileList) { this.canSeeFileList = canSeeFileList; } public void setCreatedDate(long date) { this.createdDate = date; } public void setHasChanged(boolean hasChanged) { this.hasChanged = hasChanged; } public void setLastConnectDate(long lastConnectDate) { this.lastConnectDate = lastConnectDate; } public void setLastConnectIp(String lastConnectIp) { this.lastConnectIp = lastConnectIp; } public void setLastConnectPort(int port) { this.lastConnectPort = port; } public void setMaxAge(long maxAge) { this.maxAge = maxAge; } public void setName(String name) { this.name = name; } public void setRemotePublicKey(byte[] remotePublicKey) { this.remotePublicKey = remotePublicKey; } public void setSecurityLevel(int securityLevel) { this.securityLevel = securityLevel; } public void setCreatedLocally(boolean sent) { this.createdLocally = sent; } public void setStatus(Status newStatus) { status = newStatus; } public String toString() { return name + " max_age=" + maxAge + " date=" + new Date(createdDate) + " status=" + status.getDisplayString() + " sent=" + createdLocally + " security_level=" + securityLevel; } public static enum Status { STATUS_ACCESS_DENIED(-2, "Access Denied"), STATUS_AUTHENTICATED(4, "Authenticated"), STATUS_CONNECTED(3, "Connected"), STATUS_EXPIRED(-1, "Expired"), STATUS_INVALID(-3, "Invalid"), STATUS_IP_PORT_LOCATED(2, "Ip located"), STATUS_IP_PORT_PUBLISHED(1, "Ip published"), STATUS_NEW( 0, "New"), STATUS_SUCCESS(5, "Success"); private final int code; private final String displayString; Status(int code, String displayString) { this.code = code; this.displayString = displayString; } public int getCode() { return code; } public String getDisplayString() { return displayString; } public static Status getFromCode(int code) { if (code == STATUS_NEW.code) { return STATUS_NEW; } else if (code == STATUS_IP_PORT_PUBLISHED.code) { return STATUS_IP_PORT_PUBLISHED; } else if (code == STATUS_IP_PORT_LOCATED.code) { return STATUS_IP_PORT_LOCATED; } else if (code == STATUS_CONNECTED.code) { return STATUS_CONNECTED; } else if (code == STATUS_AUTHENTICATED.code) { return STATUS_AUTHENTICATED; } else if (code == STATUS_SUCCESS.code) { return STATUS_SUCCESS; } else if (code == STATUS_EXPIRED.code) { return STATUS_EXPIRED; } else if (code == STATUS_ACCESS_DENIED.code) { return STATUS_ACCESS_DENIED; } else if (code == STATUS_INVALID.code) { return STATUS_INVALID; } else { Debug.out("unknown status code: " + code); return STATUS_INVALID; } } } }