/* 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.DataInput; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.util.Arrays; import freenet.crypt.DSAPublicKey; import freenet.crypt.SHA256; import freenet.store.BlockMetadata; import freenet.store.GetPubkey; import freenet.support.Fields; import freenet.support.HexUtil; import freenet.support.Logger; /** * An SSK is a Signed Subspace Key. * { pubkey, cryptokey, filename } -> document, basically. * To insert you need the private key corresponding to pubkey. * KSKs are implemented via SSKs. * * This is just the key, so we have the hash of the pubkey, the entire cryptokey, * and the entire filename. */ public class NodeSSK extends Key { /** Crypto algorithm */ final byte cryptoAlgorithm; /** Public key hash */ final byte[] pubKeyHash; /** E(H(docname)) (E = encrypt using decrypt key, which only clients know) */ final byte[] encryptedHashedDocname; /** The signature key, if we know it */ transient DSAPublicKey pubKey; final int hashCode; public static final int SSK_VERSION = 1; public static final int PUBKEY_HASH_SIZE = 32; public static final int E_H_DOCNAME_SIZE = 32; public static final byte BASE_TYPE = 2; public static final int FULL_KEY_LENGTH = 66; public static final int ROUTING_KEY_LENGTH = 32; @Override public String toString() { return super.toString()+":pkh="+HexUtil.bytesToHex(pubKeyHash)+":ehd="+HexUtil.bytesToHex(encryptedHashedDocname); } @Override public Key archivalCopy() { return new ArchiveNodeSSK(pubKeyHash, encryptedHashedDocname, cryptoAlgorithm); } public NodeSSK(byte[] pkHash, byte[] ehDocname, byte cryptoAlgorithm) { super(makeRoutingKey(pkHash, ehDocname)); this.encryptedHashedDocname = ehDocname; this.pubKeyHash = pkHash; this.cryptoAlgorithm = cryptoAlgorithm; this.pubKey = null; if(ehDocname.length != E_H_DOCNAME_SIZE) throw new IllegalArgumentException("ehDocname must be "+E_H_DOCNAME_SIZE+" bytes"); if(pkHash.length != PUBKEY_HASH_SIZE) throw new IllegalArgumentException("pubKeyHash must be "+PUBKEY_HASH_SIZE+" bytes"); hashCode = Fields.hashCode(pkHash) ^ Fields.hashCode(ehDocname); } public NodeSSK(byte[] pkHash, byte[] ehDocname, DSAPublicKey pubKey, byte cryptoAlgorithm) throws SSKVerifyException { super(makeRoutingKey(pkHash, ehDocname)); this.encryptedHashedDocname = ehDocname; this.pubKeyHash = pkHash; this.cryptoAlgorithm = cryptoAlgorithm; this.pubKey = pubKey; if(pubKey != null) { byte[] hash = SHA256.digest(pubKey.asBytes()); if(!Arrays.equals(hash, pkHash)) throw new SSKVerifyException("Invalid pubKey: wrong hash"); } if(ehDocname.length != E_H_DOCNAME_SIZE) throw new IllegalArgumentException("ehDocname must be "+E_H_DOCNAME_SIZE+" bytes"); if(pkHash.length != PUBKEY_HASH_SIZE) throw new IllegalArgumentException("pubKeyHash must be "+PUBKEY_HASH_SIZE+" bytes"); hashCode = Fields.hashCode(pkHash) ^ Fields.hashCode(ehDocname); } private NodeSSK(NodeSSK key) { super(key); this.cryptoAlgorithm = key.cryptoAlgorithm; this.pubKey = key.pubKey; this.pubKeyHash = key.pubKeyHash.clone(); this.encryptedHashedDocname = key.encryptedHashedDocname.clone(); this.hashCode = key.hashCode; } @Override public Key cloneKey() { return new NodeSSK(this); } // routingKey = H( E(H(docname)) + H(pubkey) ) private static byte[] makeRoutingKey(byte[] pkHash, byte[] ehDocname) { MessageDigest md256 = SHA256.getMessageDigest(); md256.update(ehDocname); md256.update(pkHash); byte[] key = md256.digest(); SHA256.returnMessageDigest(md256); return key; } @Override public void write(DataOutput _index) throws IOException { _index.writeShort(getType()); _index.write(encryptedHashedDocname); _index.write(pubKeyHash); } public static Key readSSK(DataInput raf, byte cryptoAlgorithm) throws IOException { byte[] buf = new byte[E_H_DOCNAME_SIZE]; raf.readFully(buf); byte[] buf2 = new byte[PUBKEY_HASH_SIZE]; raf.readFully(buf2); try { return new NodeSSK(buf2, buf, null, cryptoAlgorithm); } catch (SSKVerifyException e) { throw (AssertionError)new AssertionError("Impossible").initCause(e); } } @Override public short getType() { return (short) ((BASE_TYPE << 8) + (cryptoAlgorithm & 0xff)); } @Override public void writeToDataOutputStream(DataOutputStream stream) throws IOException { write(stream); } /** * @return True if we know the pubkey. */ public boolean hasPubKey() { return pubKey != null; } /** * @return The public key, *if* we know it. Otherwise null. */ public DSAPublicKey getPubKey() { return pubKey; } public byte[] getPubKeyHash() { return pubKeyHash; } public void setPubKey(DSAPublicKey pubKey2) throws SSKVerifyException { if(pubKey == pubKey2) return; if(pubKey2 == null) return; if((pubKey == null) || !pubKey2.equals(pubKey)) { if(pubKey2 != null) { byte[] newPubKeyHash = SHA256.digest(pubKey2.asBytes()); if(Arrays.equals(pubKeyHash, newPubKeyHash)) { if(pubKey != null) { // same hash, yet different keys! Logger.error(this, "Found SHA-256 collision or something... WTF?"); throw new SSKVerifyException("Invalid new pubkey: "+pubKey2+" old pubkey: "+pubKey); } // Valid key, assign. } else { throw new SSKVerifyException("New pubkey has invalid hash"); } } pubKey = pubKey2; } } @Override public boolean equals(Object o) { if(o == this) return true; if(!(o instanceof NodeSSK)) return false; NodeSSK key = (NodeSSK)o; if(!Arrays.equals(key.encryptedHashedDocname, encryptedHashedDocname)) return false; if(!Arrays.equals(key.pubKeyHash, pubKeyHash)) return false; if(!Arrays.equals(key.routingKey, routingKey)) return false; // cachedNormalizedDouble and pubKey could be negative/null. return true; } @Override public int hashCode() { return hashCode; } // Not just the routing key, enough data to reconstruct the key (excluding any pubkey needed) @Override public byte[] getKeyBytes() { return encryptedHashedDocname; } @Override public byte[] getFullKey() { byte[] buf = new byte[FULL_KEY_LENGTH]; short type = getType(); buf[0] = (byte) (type >> 8); buf[1] = (byte) (type & 0xFF); System.arraycopy(encryptedHashedDocname, 0, buf, 2, E_H_DOCNAME_SIZE); System.arraycopy(pubKeyHash, 0, buf, 2+E_H_DOCNAME_SIZE, PUBKEY_HASH_SIZE); return buf; } public static NodeSSK construct(byte[] buf) throws SSKVerifyException { if(buf[0] != 2) throw new SSKVerifyException("Unknown type byte "+buf[0]); byte cryptoAlgorithm = buf[1]; if(cryptoAlgorithm != Key.ALGO_AES_PCFB_256_SHA256) throw new SSKVerifyException("Unknown crypto algorithm "+buf[1]); byte[] encryptedHashedDocname = Arrays.copyOfRange(buf, 2, 2+E_H_DOCNAME_SIZE); byte[] pubkeyHash = Arrays.copyOfRange(buf, 2+E_H_DOCNAME_SIZE, 2+E_H_DOCNAME_SIZE+PUBKEY_HASH_SIZE); return new NodeSSK(pubkeyHash, encryptedHashedDocname, null, cryptoAlgorithm); } public boolean grabPubkey(GetPubkey pubkeyCache, boolean canReadClientCache, boolean forULPR, BlockMetadata meta) { if(pubKey != null) return false; pubKey = pubkeyCache.getKey(pubKeyHash, canReadClientCache, forULPR, meta); return pubKey != null; } public static byte[] routingKeyFromFullKey(byte[] keyBuf) { if(keyBuf.length != FULL_KEY_LENGTH) { Logger.error(NodeSSK.class, "routingKeyFromFullKey() on buffer length "+keyBuf.length); } byte[] encryptedHashedDocname = Arrays.copyOfRange(keyBuf, 2, 2+E_H_DOCNAME_SIZE); byte[] pubKeyHash = Arrays.copyOfRange(keyBuf, 2+E_H_DOCNAME_SIZE, 2+E_H_DOCNAME_SIZE+PUBKEY_HASH_SIZE); return makeRoutingKey(pubKeyHash, encryptedHashedDocname); } @Override public int compareTo(Key arg0) { if(arg0 instanceof NodeCHK) return -1; NodeSSK key = (NodeSSK) arg0; int result = Fields.compareBytes(encryptedHashedDocname, key.encryptedHashedDocname); if(result != 0) return result; return Fields.compareBytes(pubKeyHash, key.pubKeyHash); } } final class ArchiveNodeSSK extends NodeSSK { public ArchiveNodeSSK(byte[] pubKeyHash, byte[] encryptedHashedDocname, byte cryptoAlgorithm) { super(pubKeyHash, encryptedHashedDocname, cryptoAlgorithm); } @Override public void setPubKey(DSAPublicKey pubKey2) throws SSKVerifyException { throw new UnsupportedOperationException(); } @Override public boolean grabPubkey(GetPubkey pubkeyCache, boolean canReadClientCache, boolean forULPR, BlockMetadata meta) { throw new UnsupportedOperationException(); } }