package com.subgraph.orchid.circuits.hs;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.subgraph.orchid.TorParsingException;
import com.subgraph.orchid.circuits.hs.HSDescriptorCookie.CookieType;
import com.subgraph.orchid.crypto.TorMessageDigest;
import com.subgraph.orchid.crypto.TorStreamCipher;
public class HSAuthentication {
private final static int BASIC_ID_LENGTH = 4;
private final HSDescriptorCookie cookie;
public HSAuthentication(HSDescriptorCookie cookie) {
this.cookie = cookie;
}
public byte[] decryptIntroductionPoints(byte[] content) throws HSAuthenticationException {
final ByteBuffer buffer = ByteBuffer.wrap(content);
final int firstByte = buffer.get() & 0xFF;
if(firstByte == 1) {
return decryptIntroductionPointsWithBasicAuth(buffer);
} else if(firstByte == 2) {
return decryptIntroductionPointsWithStealthAuth(buffer);
} else {
throw new HSAuthenticationException("Introduction points section begins with unrecognized byte ("+ firstByte +")");
}
}
private static class BasicAuthEntry {
final byte[] id;
final byte[] skey;
BasicAuthEntry(byte[] id, byte[] skey) {
this.id = id;
this.skey = skey;
}
}
private BasicAuthEntry createEntry(ByteBuffer bb) {
final byte[] id = new byte[BASIC_ID_LENGTH];
final byte[] skey = new byte[TorStreamCipher.KEY_LEN];
bb.get(id);
bb.get(skey);
return new BasicAuthEntry(id, skey);
}
private byte[] decryptIntroductionPointsWithBasicAuth(ByteBuffer buffer) throws HSAuthenticationException {
if(cookie == null || cookie.getType() != CookieType.COOKIE_BASIC) {
throw new TorParsingException("Introduction points encrypted with 'basic' authentication and no cookie available to decrypt");
}
final List<BasicAuthEntry> entries = readBasicEntries(buffer);
final byte[] iv = readAuthIV(buffer);
final byte[] id = generateAuthId(iv);
final byte[] k = findKeyInAuthEntries(entries, id);
return decryptRemaining(buffer, k, iv);
}
private List<BasicAuthEntry> readBasicEntries(ByteBuffer b) {
final int blockCount = b.get() & 0xFF;
final int entryCount = blockCount * 16;
final List<BasicAuthEntry> entries = new ArrayList<BasicAuthEntry>(entryCount);
for(int i = 0; i < entryCount; i++) {
entries.add( createEntry(b) );
}
return entries;
}
private byte[] readAuthIV(ByteBuffer b) {
final byte[] iv = new byte[16];
b.get(iv);
return iv;
}
private byte[] generateAuthId(byte[] iv) {
final TorMessageDigest md = new TorMessageDigest();
md.update(cookie.getValue());
md.update(iv);
final byte[] digest = md.getDigestBytes();
final byte[] id = new byte[BASIC_ID_LENGTH];
System.arraycopy(digest, 0, id, 0, BASIC_ID_LENGTH);
return id;
}
private byte[] findKeyInAuthEntries(List<BasicAuthEntry> entries, byte[] id) throws HSAuthenticationException {
for(BasicAuthEntry e: entries) {
if(Arrays.equals(id, e.id)) {
return decryptAuthEntry(e);
}
}
throw new HSAuthenticationException("Could not find matching cookie id for basic authentication");
}
private byte[] decryptAuthEntry(BasicAuthEntry entry) throws HSAuthenticationException {
TorStreamCipher cipher = TorStreamCipher.createFromKeyBytes(cookie.getValue());
cipher.encrypt(entry.skey);
return entry.skey;
}
private byte[] decryptRemaining(ByteBuffer buffer, byte[] key, byte[] iv) {
TorStreamCipher streamCipher = TorStreamCipher.createFromKeyBytesWithIV(key, iv);
final byte[] remaining = new byte[buffer.remaining()];
buffer.get(remaining);
streamCipher.encrypt(remaining);
return remaining;
}
private byte[] decryptIntroductionPointsWithStealthAuth(ByteBuffer buffer) {
if(cookie == null || cookie.getType() != CookieType.COOKIE_STEALTH) {
throw new TorParsingException("Introduction points encrypted with 'stealth' authentication and no cookie available to descrypt");
}
final byte[] iv = readAuthIV(buffer);
return decryptRemaining(buffer, cookie.getValue(), iv);
}
}