/* 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.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.security.MessageDigest;
import java.util.Arrays;
import freenet.crypt.DSAPublicKey;
import freenet.crypt.SHA256;
import freenet.crypt.UnsupportedCipherException;
import freenet.crypt.ciphers.Rijndael;
import freenet.support.Fields;
import freenet.support.HexUtil;
import freenet.support.Logger;
/** Client-level SSK, i.e. a low level SSK with the decryption key needed to
* decrypt the data once it is fetched. Note that you can only use this to
* *REQUEST* keys, not to *INSERT* them, because it only has the public key
* and not the private key, @see InsertableClientSSK.
*/
public class ClientSSK extends ClientKey {
private static final long serialVersionUID = 1L;
/** Crypto type */
public final byte cryptoAlgorithm;
/** Document name */
public final String docName;
/** Public key */
protected transient DSAPublicKey pubKey;
/** Public key hash */
public final byte[] pubKeyHash;
/** Encryption key */
public final byte[] cryptoKey;
/** Encrypted hashed docname */
public final byte[] ehDocname;
private final int hashCode;
public static final int CRYPTO_KEY_LENGTH = 32;
public static final int EXTRA_LENGTH = 5;
private ClientSSK(ClientSSK key) {
this.cryptoAlgorithm = key.cryptoAlgorithm;
this.docName = key.docName;
if(key.pubKey != null)
this.pubKey = key.pubKey.cloneKey();
else
this.pubKey = null;
pubKeyHash = key.pubKeyHash.clone();
cryptoKey = key.cryptoKey.clone();
ehDocname = key.ehDocname.clone();
hashCode = Fields.hashCode(pubKeyHash) ^ Fields.hashCode(cryptoKey) ^ Fields.hashCode(ehDocname) ^ docName.hashCode();
}
public ClientSSK(String docName, byte[] pubKeyHash, byte[] extras, DSAPublicKey pubKey, byte[] cryptoKey) throws MalformedURLException {
this.docName = docName;
this.pubKey = pubKey;
this.pubKeyHash = pubKeyHash;
if(docName == null)
throw new MalformedURLException("No document name.");
if(extras == null)
throw new MalformedURLException("No extra bytes in SSK - maybe a 0.5 key?");
if(extras.length < 5)
throw new MalformedURLException("Extra bytes too short: "+extras.length+" bytes");
this.cryptoAlgorithm = extras[2];
if(cryptoAlgorithm != Key.ALGO_AES_PCFB_256_SHA256)
throw new MalformedURLException("Unknown encryption algorithm "+cryptoAlgorithm);
if(!Arrays.equals(extras, getExtraBytes()))
throw new MalformedURLException("Wrong extra bytes");
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 != CRYPTO_KEY_LENGTH)
throw new MalformedURLException("Decryption key wrong length: "+cryptoKey.length+" should be "+CRYPTO_KEY_LENGTH);
MessageDigest md = SHA256.getMessageDigest();
try {
if (pubKey != null) {
byte[] pubKeyAsBytes = pubKey.asBytes();
md.update(pubKeyAsBytes);
byte[] otherPubKeyHash = md.digest();
if (!Arrays.equals(otherPubKeyHash, pubKeyHash))
throw new IllegalArgumentException();
}
this.cryptoKey = cryptoKey;
try {
md.update(docName.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new Error("Impossible: JVM doesn't support UTF-8: " + e, e);
}
byte[] buf = md.digest();
try {
Rijndael aes = new Rijndael(256, 256);
aes.initialize(cryptoKey);
aes.encipher(buf, buf);
ehDocname = buf;
} catch (UnsupportedCipherException e) {
throw new Error(e);
}
} finally {
SHA256.returnMessageDigest(md);
}
if(ehDocname == null)
throw new NullPointerException();
hashCode = Fields.hashCode(pubKeyHash) ^ Fields.hashCode(cryptoKey) ^ Fields.hashCode(ehDocname) ^ docName.hashCode();
}
public ClientSSK(FreenetURI origURI) throws MalformedURLException {
this(origURI.getDocName(), origURI.getRoutingKey(), origURI.getExtra(), null, origURI.getCryptoKey());
if(!origURI.getKeyType().equalsIgnoreCase("SSK"))
throw new MalformedURLException();
}
protected ClientSSK() {
// For serialization.
this.cryptoAlgorithm = 0;
this.docName = null;
this.pubKeyHash = null;
this.cryptoKey = null;
this.ehDocname = null;
this.hashCode = 0;
}
public synchronized void setPublicKey(DSAPublicKey pubKey) {
if((this.pubKey != null) && (this.pubKey != pubKey) && !this.pubKey.equals(pubKey))
throw new IllegalArgumentException("Cannot reassign: was "+this.pubKey+" now "+pubKey);
byte[] newKeyHash = pubKey.asBytesHash();
if(!Arrays.equals(newKeyHash, pubKeyHash))
throw new IllegalArgumentException("New pubKey hash does not match pubKeyHash: "+HexUtil.bytesToHex(newKeyHash)+" ( "+HexUtil.bytesToHex(pubKey.asBytesHash())+" != "+HexUtil.bytesToHex(pubKeyHash)+" for "+pubKey);
this.pubKey = pubKey;
this.cachedNodeKey = null;
}
@Override
public FreenetURI getURI() {
return new FreenetURI("SSK", docName, pubKeyHash, cryptoKey, getExtraBytes());
}
protected final byte[] getExtraBytes() {
return getExtraBytes(cryptoAlgorithm);
}
protected static byte[] getExtraBytes(byte cryptoAlgorithm) {
// 5 bytes.
byte[] extra = new byte[5];
extra[0] = NodeSSK.SSK_VERSION;
extra[1] = 0; // 0 = fetch (public) URI; 1 = insert (private) URI
extra[2] = cryptoAlgorithm;
extra[3] = (byte) (KeyBlock.HASH_SHA256 >> 8);
extra[4] = (byte) KeyBlock.HASH_SHA256;
return extra;
}
static final byte[] STANDARD_EXTRA = getExtraBytes(Key.ALGO_AES_PCFB_256_SHA256);
public static byte[] internExtra(byte[] buf) {
if(Arrays.equals(buf, STANDARD_EXTRA)) return STANDARD_EXTRA;
return buf;
}
private transient Key cachedNodeKey;
@Override
public Key getNodeKey(boolean cloneKey) {
try {
Key nodeKey;
synchronized(this) {
if(ehDocname == null)
throw new NullPointerException();
if(pubKeyHash == null)
throw new NullPointerException();
if (cachedNodeKey == null || cachedNodeKey.getKeyBytes() == null || cachedNodeKey.getRoutingKey() == null)
cachedNodeKey = new NodeSSK(pubKeyHash, ehDocname, pubKey, cryptoAlgorithm);
nodeKey = cachedNodeKey;
}
return cloneKey ? nodeKey.cloneKey() : nodeKey;
} catch (SSKVerifyException e) {
Logger.error(this, "Have already verified and yet it fails!: "+e);
throw (AssertionError)new AssertionError("Have already verified and yet it fails!").initCause(e);
}
}
public DSAPublicKey getPubKey() {
return pubKey;
}
@Override
public String toString() {
return "ClientSSK:"+getURI().toString();
}
@Override
public ClientKey cloneKey() {
return new ClientSSK(this);
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object o) {
if(!(o instanceof ClientSSK)) return false;
ClientSSK key = (ClientSSK) o;
if(cryptoAlgorithm != key.cryptoAlgorithm) return false;
if(!docName.equals(key.docName)) return false;
if(!Arrays.equals(pubKeyHash, key.pubKeyHash)) return false;
if(!Arrays.equals(cryptoKey, key.cryptoKey)) return false;
if(!Arrays.equals(ehDocname, key.ehDocname)) return false;
return true;
}
}