package com.subgraph.orchid.data; import java.util.Arrays; import java.util.List; import com.subgraph.orchid.Tor; import com.subgraph.orchid.TorException; import com.subgraph.orchid.crypto.TorMessageDigest; import com.subgraph.orchid.encoders.Base64; import com.subgraph.orchid.encoders.Hex; /** * This class represents both digests and fingerprints that appear in directory * documents. The names fingerprint and digest are used interchangeably in * the specification but generally a fingerprint is a message digest (ie: SHA1) * over the DER ASN.1 encoding of a public key. A digest is usually * a message digest over a set of fields in a directory document. * * Digests always appear as a 40 character hex string: * * 0EA20CAA3CE696E561BC08B15E00106700E8F682 * * Fingerprints may either appear as a single hex string as above or sometimes in * a more easily human-parsed spaced format: * * 1E0F 5874 2268 E82F C600 D81D 9064 07C5 7CC2 C3A7 * */ public class HexDigest { public static HexDigest createFromStringList(List<String> strings) { StringBuilder builder = new StringBuilder(); for(String chunk: strings) builder.append(chunk); return createFromString(builder.toString()); } public static HexDigest createFromBase32String(String b32) { return new HexDigest(Base32.base32Decode(b32)); } public static HexDigest createFromString(String fingerprint) { final String[] parts = fingerprint.split(" "); if(parts.length > 1) return createFromStringList(Arrays.asList(parts)); final byte[] digestData = Hex.decode(fingerprint); return new HexDigest(digestData); } public static HexDigest createFromDigestBytes(byte[] data) { return new HexDigest(data); } public static HexDigest createDigestForData(byte[] data) { final TorMessageDigest digest = new TorMessageDigest(); digest.update(data); return new HexDigest(digest.getDigestBytes()); } private final byte[] digestBytes; private final boolean isDigest256; private HexDigest(byte[] data) { if(data.length != TorMessageDigest.TOR_DIGEST_SIZE && data.length != TorMessageDigest.TOR_DIGEST256_SIZE) { throw new TorException("Digest data is not the correct length "+ data.length +" != (" + TorMessageDigest.TOR_DIGEST_SIZE + " or "+ TorMessageDigest.TOR_DIGEST256_SIZE +")"); } digestBytes = new byte[data.length]; isDigest256 = digestBytes.length == TorMessageDigest.TOR_DIGEST256_SIZE; System.arraycopy(data, 0, digestBytes, 0, data.length); } public boolean isDigest256() { return isDigest256; } public byte[] getRawBytes() { return Arrays.copyOf(digestBytes, digestBytes.length); } public String toString() { return new String(Hex.encode(digestBytes)); } /** * Return a spaced fingerprint representation of this HexDigest. * * ex: * * 1E0F 5874 2268 E82F C600 D81D 9064 07C5 7CC2 C3A7 * * @return A string representation of this HexDigest in the spaced fingerprint format. */ public String toSpacedString() { final String original = toString(); final StringBuilder builder = new StringBuilder(); for(int i = 0; i < original.length(); i++) { if(i > 0 && (i % 4) == 0) builder.append(' '); builder.append(original.charAt(i)); } return builder.toString(); } public String toBase32() { return Base32.base32Encode(digestBytes); } public String toBase64(boolean stripTrailingEquals) { final String b64 = new String(Base64.encode(digestBytes), Tor.getDefaultCharset()); if(stripTrailingEquals) { return stripTrailingEquals(b64); } else { return b64; } } private String stripTrailingEquals(String s) { int idx = s.length(); while(idx > 0 && s.charAt(idx - 1) == '=') { idx -= 1; } return s.substring(0, idx); } public boolean equals(Object o) { if(!(o instanceof HexDigest)) return false; final HexDigest other = (HexDigest)o; return Arrays.equals(other.digestBytes, this.digestBytes); } public int hashCode() { int hash = 0; for(int i = 0; i < 4; i++) { hash <<= 8; hash |= (digestBytes[i] & 0xFF); } return hash; } }