/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.keys; import java.io.Serializable; import java.net.MalformedURLException; import java.util.Arrays; import java.util.Comparator; import java.util.regex.Pattern; import freenet.support.Fields; import freenet.support.Logger; /** * Updatable Subspace Key. * Not really a ClientKey as it cannot be directly requested. * * Contains: * - Enough information to produce a real SSK. * - Site name. * - Site edition number. * * WARNING: Changing non-transient members on classes that are Serializable can result in * restarting downloads or losing uploads. */ public class USK extends BaseClientKey implements Comparable<USK>, Serializable { private static final long serialVersionUID = 1L; /* The character to separate the site name from the edition number in its SSK form. * I chose "-", because it makes it ludicrously easy to go from the USK form to the * SSK form, and we don't need to go vice versa. */ static protected final String SEPARATOR = "-"; /** Encryption type */ public final byte cryptoAlgorithm; /** Public key hash */ protected final byte[] pubKeyHash; /** Encryption key */ protected final byte[] cryptoKey; // Extra must be verified on creation, and is fixed for now. FIXME if it becomes changeable, need to keep values here. public final String siteName; public final long suggestedEdition; private final int hashCode; public USK(byte[] pubKeyHash, byte[] cryptoKey, byte[] extra, String siteName, long suggestedEdition) throws MalformedURLException { this.pubKeyHash = pubKeyHash; this.cryptoKey = cryptoKey; this.siteName = siteName; this.suggestedEdition = suggestedEdition; if(extra == null) throw new MalformedURLException("No extra bytes (third bit) in USK"); if(pubKeyHash == null) throw new MalformedURLException("No pubkey hash (first bit) in USK"); if(cryptoKey == null) throw new MalformedURLException("No crypto key (second bit) in USK"); // Verify extra bytes, get cryptoAlgorithm - FIXME this should be a static method or something? ClientSSK tmp = new ClientSSK(siteName, pubKeyHash, extra, null, cryptoKey); cryptoAlgorithm = tmp.cryptoAlgorithm; if(pubKeyHash.length != NodeSSK.PUBKEY_HASH_SIZE) throw new MalformedURLException("Pubkey hash wrong length: "+pubKeyHash.length+" should be "+NodeSSK.PUBKEY_HASH_SIZE); if(cryptoKey.length != ClientSSK.CRYPTO_KEY_LENGTH) throw new MalformedURLException("Decryption key wrong length: "+cryptoKey.length+" should be "+ClientSSK.CRYPTO_KEY_LENGTH); hashCode = Fields.hashCode(pubKeyHash) ^ Fields.hashCode(cryptoKey) ^ siteName.hashCode() ^ (int)suggestedEdition ^ (int)(suggestedEdition >> 32); } public static USK create(FreenetURI uri) throws MalformedURLException { if(!uri.isUSK()) throw new MalformedURLException("Not a USK"); return new USK(uri.getRoutingKey(), uri.getCryptoKey(), uri.getExtra(), uri.getDocName(), uri.getSuggestedEdition()); } protected USK(byte[] pubKeyHash2, byte[] cryptoKey2, String siteName2, long suggestedEdition2, byte cryptoAlgorithm) { this.pubKeyHash = pubKeyHash2; this.cryptoKey = cryptoKey2; this.siteName = siteName2; this.suggestedEdition = suggestedEdition2; this.cryptoAlgorithm = cryptoAlgorithm; hashCode = Fields.hashCode(pubKeyHash) ^ Fields.hashCode(cryptoKey) ^ siteName.hashCode() ^ (int)suggestedEdition ^ (int)(suggestedEdition >> 32); } protected USK() { // For serialization. pubKeyHash = null; cryptoKey = null; siteName = null; suggestedEdition = 0; cryptoAlgorithm = 0; hashCode = 0; } private static final Pattern badDocNamePattern; static { badDocNamePattern = Pattern.compile(".*\\-[0-9]+(\\/.*)?$"); } // FIXME: Be careful with this constructor! There must not be an edition in the ClientSSK! public USK(ClientSSK ssk, long myARKNumber) { this.pubKeyHash = ssk.pubKeyHash; this.cryptoKey = ssk.cryptoKey; this.siteName = ssk.docName; this.suggestedEdition = myARKNumber; this.cryptoAlgorithm = ssk.cryptoAlgorithm; if (badDocNamePattern.matcher(siteName).matches()) // not error -- just "possible" bug Logger.normal(this, "POSSIBLE BUG: edition in ClientSSK " + ssk, new Exception("debug")); hashCode = Fields.hashCode(pubKeyHash) ^ Fields.hashCode(cryptoKey) ^ siteName.hashCode() ^ (int)suggestedEdition ^ (int)(suggestedEdition >> 32); } public USK(USK usk) { // FIXME can we not copy pubKeyHash? // If we can guarantee that neither USK nor anything getting it without copying will change it? // db4o treats byte[] as individual byte members, so there are no issues with deactivation. this.pubKeyHash = usk.pubKeyHash.clone(); this.cryptoAlgorithm = usk.cryptoAlgorithm; // FIXME should we copy cryptoKey? this.cryptoKey = usk.cryptoKey; this.siteName = usk.siteName; this.suggestedEdition = usk.suggestedEdition; hashCode = Fields.hashCode(pubKeyHash) ^ Fields.hashCode(cryptoKey) ^ siteName.hashCode() ^ (int)suggestedEdition ^ (int)(suggestedEdition >> 32); } @Override public FreenetURI getURI() { return new FreenetURI(pubKeyHash, cryptoKey, ClientSSK.getExtraBytes(cryptoAlgorithm), siteName, suggestedEdition); } public ClientSSK getSSK(long ver) { return getSSK(getName(ver)); } public ClientSSK getSSK(String string) { try { return new ClientSSK(string, pubKeyHash, ClientSSK.getExtraBytes(cryptoAlgorithm), null, cryptoKey); } catch (MalformedURLException e) { Logger.error(this, "Caught "+e+" should not be possible in USK.getSSK", e); throw new Error(e); } } public String getName(long ver) { return siteName + SEPARATOR + ver; } public ClientKey getSSK() { return getSSK(suggestedEdition); } public USK copy(long edition) { if(suggestedEdition == edition) return this; return new USK(pubKeyHash, cryptoKey, siteName, edition, cryptoAlgorithm); } public USK clearCopy() { return copy(0); } public final USK copy() { // We need our own constructor to make sure we copy pubKeyHash. // So clone() doesn't work for this. // FIXME when we are sure we don't need to copy the byte[]'s we might be able to switch back to clone(). return new USK(this); } @Override public boolean equals(Object o) { if(o == null || !(o instanceof USK)) return false; return equals(o, true); } public boolean equals(Object o, boolean includeVersion) { if(o instanceof USK) { USK u = (USK)o; if(!Arrays.equals(pubKeyHash, u.pubKeyHash)) return false; if(!Arrays.equals(cryptoKey, u.cryptoKey)) return false; if(!siteName.equals(u.siteName)) return false; if(includeVersion && (suggestedEdition != u.suggestedEdition)) return false; return true; } return false; } @Override public int hashCode() { return hashCode; } public FreenetURI getBaseSSK() { return new FreenetURI("SSK", siteName, pubKeyHash, cryptoKey, ClientSSK.getExtraBytes(cryptoAlgorithm)); } @Override public String toString() { return super.toString()+ ':' +getURI(); } public FreenetURI turnMySSKIntoUSK(FreenetURI uri) { if(uri.getKeyType().equals("SSK") && Arrays.equals(uri.getRoutingKey(), pubKeyHash) && Arrays.equals(uri.getCryptoKey(), cryptoKey) && Arrays.equals(uri.getExtra(), ClientSSK.getExtraBytes(cryptoAlgorithm)) && uri.getDocName() != null && uri.getDocName().startsWith(siteName)) { String doc = uri.getDocName(); doc = doc.substring(siteName.length()); if(doc.length() < 2 || doc.charAt(0) != '-') return uri; doc = doc.substring(1); long edition; try { edition = Long.parseLong(doc); } catch (NumberFormatException e) { Logger.normal(this, "Trying to turn SSK back into USK: "+uri+" doc="+doc+" caught "+e, e); return uri; } if(!doc.equals(Long.toString(edition))) return uri; return new FreenetURI("USK", siteName, uri.getAllMetaStrings(), pubKeyHash, cryptoKey, ClientSSK.getExtraBytes(cryptoAlgorithm), edition); } return uri; } @Override public int compareTo(USK o) { if(this == o) return 0; if(cryptoAlgorithm < o.cryptoAlgorithm) return -1; if(cryptoAlgorithm > o.cryptoAlgorithm) return 1; int cmp = Fields.compareBytes(pubKeyHash, o.pubKeyHash); if(cmp != 0) return cmp; cmp = Fields.compareBytes(cryptoKey, o.cryptoKey); if(cmp != 0) return cmp; cmp = siteName.compareTo(o.siteName); if(cmp != 0) return cmp; if(suggestedEdition > o.suggestedEdition) return 1; if(suggestedEdition < o.suggestedEdition) return -1; return 0; } public static final Comparator<USK> FAST_COMPARATOR = new Comparator<USK>() { @Override public int compare(USK o1, USK o2) { if(o1.hashCode > o2.hashCode) return 1; else if(o1.hashCode < o2.hashCode) return -1; return o1.compareTo(o2); } }; public byte[] getPubKeyHash() { return Arrays.copyOf(pubKeyHash, pubKeyHash.length); } public boolean samePubKeyHash(NodeSSK k) { return Arrays.equals(k.getPubKeyHash(), pubKeyHash); } }