Commit 74de0a5d authored by Karsten Loesing's avatar Karsten Loesing
Browse files

Move descriptor digest computation to DescriptorImpl.

The main intention behind this change is to reduce the number of
places in the code where byte[] is converted to String.  But another
reason is to reduce code duplication, which would have been sufficient
to make this change.
parent 82f555ee
......@@ -7,6 +7,7 @@
- Simplify and avoid repetition in parse helper methods.
- Fix a bug where Microdescriptor's getDigestSha256Base64() returns
a hex string rather than a base64 string.
- Move descriptor digest computation to DescriptorImpl.
# Changes in version 1.7.0 - 2017-05-17
......
......@@ -7,6 +7,9 @@ import org.torproject.descriptor.Descriptor;
import org.torproject.descriptor.DescriptorParseException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
......@@ -14,6 +17,8 @@ import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import javax.xml.bind.DatatypeConverter;
public abstract class DescriptorImpl implements Descriptor {
public static final String NL = "\n";
......@@ -353,5 +358,84 @@ public abstract class DescriptorImpl implements Descriptor {
protected void clearParsedKeys() {
this.parsedKeys = null;
}
private String digestSha1Hex;
protected void setDigestSha1Hex(String digestSha1Hex) {
this.digestSha1Hex = digestSha1Hex;
}
protected void calculateDigestSha1Hex(String startToken, String endToken)
throws DescriptorParseException {
if (null == this.digestSha1Hex) {
String ascii = new String(this.rawDescriptorBytes,
StandardCharsets.US_ASCII);
int start = ascii.indexOf(startToken);
int end = (null == endToken) ? ascii.length()
: (ascii.indexOf(endToken) + endToken.length());
if (start >= 0 && end >= 0 && end > start) {
byte[] forDigest = new byte[end - start];
System.arraycopy(this.rawDescriptorBytes, start, forDigest, 0,
end - start);
try {
this.digestSha1Hex = DatatypeConverter.printHexBinary(
MessageDigest.getInstance("SHA-1").digest(forDigest))
.toLowerCase();
} catch (NoSuchAlgorithmException e) {
/* Handle below. */
}
}
}
if (null == this.digestSha1Hex) {
throw new DescriptorParseException("Could not calculate descriptor "
+ "digest.");
}
}
public String getDigestSha1Hex() {
return this.digestSha1Hex;
}
private String digestSha256Base64;
protected void setDigestSha256Base64(String digestSha256Base64) {
this.digestSha256Base64 = digestSha256Base64;
}
protected void calculateDigestSha256Base64(String startToken,
String endToken) throws DescriptorParseException {
if (null == this.digestSha256Base64) {
String ascii = new String(this.rawDescriptorBytes,
StandardCharsets.US_ASCII);
int start = ascii.indexOf(startToken);
int end = (null == endToken) ? ascii.length()
: (ascii.indexOf(endToken) + endToken.length());
if (start >= 0 && end >= 0 && end > start) {
byte[] forDigest = new byte[end - start];
System.arraycopy(this.rawDescriptorBytes, start, forDigest, 0,
end - start);
try {
this.digestSha256Base64 = DatatypeConverter.printBase64Binary(
MessageDigest.getInstance("SHA-256").digest(forDigest))
.replaceAll("=", "");
} catch (NoSuchAlgorithmException e) {
/* Handle below. */
}
}
}
if (null == this.digestSha256Base64) {
throw new DescriptorParseException("Could not calculate descriptor "
+ "digest.");
}
}
protected void calculateDigestSha256Base64(String startToken)
throws DescriptorParseException {
this.calculateDigestSha256Base64(startToken, null);
}
public String getDigestSha256Base64() {
return this.digestSha256Base64;
}
}
......@@ -6,17 +6,12 @@ package org.torproject.descriptor.impl;
import org.torproject.descriptor.DescriptorParseException;
import org.torproject.descriptor.DirectoryKeyCertificate;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import javax.xml.bind.DatatypeConverter;
public class DirectoryKeyCertificateImpl extends DescriptorImpl
implements DirectoryKeyCertificate {
......@@ -41,7 +36,8 @@ public class DirectoryKeyCertificateImpl extends DescriptorImpl
throws DescriptorParseException {
super(rawDescriptorBytes, failUnrecognizedDescriptorLines, false);
this.parseDescriptorBytes();
this.calculateDigest();
this.calculateDigestSha1Hex(Key.DIR_KEY_CERTIFICATE_VERSION.keyword + SP,
NL + Key.DIR_KEY_CERTIFICATION.keyword + NL);
Set<Key> exactlyOnceKeys = EnumSet.of(
Key.DIR_KEY_CERTIFICATE_VERSION, Key.FINGERPRINT, Key.DIR_IDENTITY_KEY,
Key.DIR_KEY_PUBLISHED, Key.DIR_KEY_EXPIRES, Key.DIR_SIGNING_KEY,
......@@ -211,32 +207,6 @@ public class DirectoryKeyCertificateImpl extends DescriptorImpl
}
}
private void calculateDigest() throws DescriptorParseException {
try {
String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");
String startToken = Key.DIR_KEY_CERTIFICATE_VERSION.keyword + SP;
String sigToken = NL + Key.DIR_KEY_CERTIFICATION.keyword + NL;
int start = ascii.indexOf(startToken);
int sig = ascii.indexOf(sigToken) + sigToken.length();
if (start >= 0 && sig >= 0 && sig > start) {
byte[] forDigest = new byte[sig - start];
System.arraycopy(this.getRawDescriptorBytes(), start,
forDigest, 0, sig - start);
this.certificateDigest = DatatypeConverter.printHexBinary(
MessageDigest.getInstance("SHA-1").digest(forDigest))
.toLowerCase();
}
} catch (UnsupportedEncodingException e) {
/* Handle below. */
} catch (NoSuchAlgorithmException e) {
/* Handle below. */
}
if (this.certificateDigest == null) {
throw new DescriptorParseException("Could not calculate "
+ "certificate digest.");
}
}
private int dirKeyCertificateVersion;
@Override
......@@ -307,16 +277,9 @@ public class DirectoryKeyCertificateImpl extends DescriptorImpl
return this.dirKeyCertification;
}
private String certificateDigest;
@Override
public String getCertificateDigest() {
return this.getDigestSha1Hex();
}
@Override
public String getDigestSha1Hex() {
return this.certificateDigest;
}
}
......@@ -7,9 +7,6 @@ import org.torproject.descriptor.BandwidthHistory;
import org.torproject.descriptor.DescriptorParseException;
import org.torproject.descriptor.ExtraInfoDescriptor;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
......@@ -23,8 +20,6 @@ import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.xml.bind.DatatypeConverter;
public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
implements ExtraInfoDescriptor {
......@@ -43,8 +38,10 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
throws DescriptorParseException {
super(descriptorBytes, failUnrecognizedDescriptorLines, false);
this.parseDescriptorBytes();
this.calculateDigest();
this.calculateDigestSha256();
this.calculateDigestSha1Hex(Key.EXTRA_INFO.keyword + SP,
NL + Key.ROUTER_SIGNATURE.keyword + NL);
this.calculateDigestSha256Base64(Key.EXTRA_INFO.keyword + SP,
NL + "-----END SIGNATURE-----" + NL);
this.checkExactlyOnceKeys(exactlyOnceKeys);
Set<Key> dirreqStatsKeys = EnumSet.of(
Key.DIRREQ_STATS_END, Key.DIRREQ_V2_IPS, Key.DIRREQ_V3_IPS,
......@@ -784,8 +781,8 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
if (partsNoOpt.length != 2) {
throw new DescriptorParseException("Illegal line '" + line + "'.");
}
this.extraInfoDigest = ParseHelper.parseTwentyByteHexString(line,
partsNoOpt[1]);
this.setDigestSha1Hex(ParseHelper.parseTwentyByteHexString(line,
partsNoOpt[1]));
}
private void parseIdentityEd25519Line(String line, String lineNoOpt,
......@@ -836,95 +833,19 @@ public abstract class ExtraInfoDescriptorImpl extends DescriptorImpl
throw new DescriptorParseException("Illegal line '" + line + "'.");
}
ParseHelper.parseThirtyTwoByteBase64String(line, partsNoOpt[1]);
this.extraInfoDigestSha256 = partsNoOpt[1];
}
private void calculateDigest() throws DescriptorParseException {
if (this.extraInfoDigest != null) {
/* We already learned the descriptor digest of this bridge
* descriptor from a "router-digest" line. */
return;
}
try {
String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");
String startToken = Key.EXTRA_INFO.keyword + SP;
String sigToken = NL + Key.ROUTER_SIGNATURE.keyword + NL;
int start = ascii.indexOf(startToken);
int sig = ascii.indexOf(sigToken) + sigToken.length();
if (start >= 0 && sig >= 0 && sig > start) {
byte[] forDigest = new byte[sig - start];
System.arraycopy(this.getRawDescriptorBytes(), start,
forDigest, 0, sig - start);
this.extraInfoDigest = DatatypeConverter.printHexBinary(
MessageDigest.getInstance("SHA-1").digest(forDigest))
.toLowerCase();
}
} catch (UnsupportedEncodingException e) {
/* Handle below. */
} catch (NoSuchAlgorithmException e) {
/* Handle below. */
}
if (this.extraInfoDigest == null) {
throw new DescriptorParseException("Could not calculate extra-info "
+ "descriptor digest.");
}
}
private void calculateDigestSha256() throws DescriptorParseException {
if (this.extraInfoDigestSha256 != null) {
/* We already learned the descriptor digest of this bridge
* descriptor from a "router-digest-sha256" line. */
return;
}
try {
String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");
String startToken = Key.EXTRA_INFO.keyword + SP;
String sigToken = "\n-----END SIGNATURE-----\n";
int start = ascii.indexOf(startToken);
int sig = ascii.indexOf(sigToken) + sigToken.length();
if (start >= 0 && sig >= 0 && sig > start) {
byte[] forDigest = new byte[sig - start];
System.arraycopy(this.getRawDescriptorBytes(), start, forDigest,
0, sig - start);
this.extraInfoDigestSha256 = DatatypeConverter.printBase64Binary(
MessageDigest.getInstance("SHA-256").digest(forDigest))
.replaceAll("=", "");
}
} catch (UnsupportedEncodingException e) {
/* Handle below. */
} catch (NoSuchAlgorithmException e) {
/* Handle below. */
}
if (this.extraInfoDigestSha256 == null) {
throw new DescriptorParseException("Could not calculate extra-info "
+ "descriptor SHA-256 digest.");
}
this.setDigestSha256Base64(partsNoOpt[1]);
}
private String extraInfoDigest;
@Override
public String getExtraInfoDigest() {
return this.getDigestSha1Hex();
}
@Override
public String getDigestSha1Hex() {
return this.extraInfoDigest;
}
private String extraInfoDigestSha256;
@Override
public String getExtraInfoDigestSha256() {
return this.getDigestSha256Base64();
}
@Override
public String getDigestSha256Base64() {
return this.extraInfoDigestSha256;
}
private String nickname;
@Override
......
......@@ -6,9 +6,6 @@ package org.torproject.descriptor.impl;
import org.torproject.descriptor.DescriptorParseException;
import org.torproject.descriptor.Microdescriptor;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
......@@ -16,8 +13,6 @@ import java.util.List;
import java.util.Scanner;
import java.util.Set;
import javax.xml.bind.DatatypeConverter;
/* Contains a microdescriptor. */
public class MicrodescriptorImpl extends DescriptorImpl
implements Microdescriptor {
......@@ -43,7 +38,7 @@ public class MicrodescriptorImpl extends DescriptorImpl
throws DescriptorParseException {
super(descriptorBytes, failUnrecognizedDescriptorLines, false);
this.parseDescriptorBytes();
this.calculateDigest();
this.calculateDigestSha256Base64(Key.ONION_KEY.keyword + NL);
this.checkExactlyOnceKeys(EnumSet.of(Key.ONION_KEY));
Set<Key> atMostOnceKeys = EnumSet.of(
Key.NTOR_ONION_KEY, Key.FAMILY, Key.P, Key.P6, Key.ID);
......@@ -233,43 +228,11 @@ public class MicrodescriptorImpl extends DescriptorImpl
}
}
private void calculateDigest() throws DescriptorParseException {
try {
String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");
String startToken = "onion-key\n";
int start = ascii.indexOf(startToken);
int end = ascii.length();
if (start >= 0 && end > start) {
byte[] forDigest = new byte[end - start];
System.arraycopy(this.getRawDescriptorBytes(), start,
forDigest, 0, end - start);
this.microdescriptorDigest = DatatypeConverter.printBase64Binary(
MessageDigest.getInstance("SHA-256").digest(forDigest))
.replaceAll("=", "");
}
} catch (UnsupportedEncodingException e) {
/* Handle below. */
} catch (NoSuchAlgorithmException e) {
/* Handle below. */
}
if (this.microdescriptorDigest == null) {
throw new DescriptorParseException("Could not calculate "
+ "microdescriptor digest.");
}
}
private String microdescriptorDigest;
@Override
public String getMicrodescriptorDigest() {
return this.getDigestSha256Base64();
}
@Override
public String getDigestSha256Base64() {
return this.microdescriptorDigest;
}
private String onionKey;
@Override
......
......@@ -8,17 +8,12 @@ import org.torproject.descriptor.RelayDirectory;
import org.torproject.descriptor.RouterStatusEntry;
import org.torproject.descriptor.ServerDescriptor;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import javax.xml.bind.DatatypeConverter;
public class RelayDirectoryImpl extends DescriptorImpl
implements RelayDirectory {
......@@ -43,7 +38,8 @@ public class RelayDirectoryImpl extends DescriptorImpl
throws DescriptorParseException {
super(directoryBytes, failUnrecognizedDescriptorLines, true);
this.splitAndParseParts(rawDescriptorBytes);
this.calculateDigest();
this.calculateDigestSha1Hex(Key.SIGNED_DIRECTORY.keyword + NL,
NL + Key.DIRECTORY_SIGNATURE.keyword + SP);
Set<Key> exactlyOnceKeys = EnumSet.of(
Key.SIGNED_DIRECTORY, Key.RECOMMENDED_SOFTWARE,
Key.DIRECTORY_SIGNATURE);
......@@ -55,36 +51,6 @@ public class RelayDirectoryImpl extends DescriptorImpl
this.clearParsedKeys();
}
private void calculateDigest() throws DescriptorParseException {
try {
String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");
String startToken = Key.SIGNED_DIRECTORY.keyword + NL;
String sigToken = NL + Key.DIRECTORY_SIGNATURE.keyword + SP;
if (!ascii.contains(sigToken)) {
return;
}
int start = ascii.indexOf(startToken);
int sig = ascii.indexOf(sigToken) + sigToken.length();
sig = ascii.indexOf(NL, sig) + 1;
if (start >= 0 && sig >= 0 && sig > start) {
byte[] forDigest = new byte[sig - start];
System.arraycopy(this.getRawDescriptorBytes(), start,
forDigest, 0, sig - start);
this.directoryDigest = DatatypeConverter.printHexBinary(
MessageDigest.getInstance("SHA-1").digest(forDigest))
.toLowerCase();
}
} catch (UnsupportedEncodingException e) {
/* Handle below. */
} catch (NoSuchAlgorithmException e) {
/* Handle below. */
}
if (this.directoryDigest == null) {
throw new DescriptorParseException("Could not calculate v1 "
+ "directory digest.");
}
}
private void splitAndParseParts(byte[] rawDescriptorBytes)
throws DescriptorParseException {
if (this.rawDescriptorBytes.length == 0) {
......@@ -553,16 +519,9 @@ public class RelayDirectoryImpl extends DescriptorImpl
return this.nickname;
}
private String directoryDigest;
@Override
public String getDirectoryDigest() {
return this.getDigestSha1Hex();
}
@Override
public String getDigestSha1Hex() {
return this.directoryDigest;
}
}
......@@ -6,9 +6,6 @@ package org.torproject.descriptor.impl;
import org.torproject.descriptor.DescriptorParseException;
import org.torproject.descriptor.RelayNetworkStatusConsensus;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
......@@ -20,8 +17,6 @@ import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.xml.bind.DatatypeConverter;
/* Contains a network status consensus or microdesc consensus. */
public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
implements RelayNetworkStatusConsensus {
......@@ -60,36 +55,8 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
this.checkAtMostOnceKeys(atMostOnceKeys);
this.checkFirstKey(Key.NETWORK_STATUS_VERSION);
this.clearParsedKeys();
this.calculateDigest();
}
private void calculateDigest() throws DescriptorParseException {
try {
String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII");
String startToken = Key.NETWORK_STATUS_VERSION.keyword + SP;
String sigToken = NL + Key.DIRECTORY_SIGNATURE.keyword + SP;
if (!ascii.contains(sigToken)) {
return;
}
int start = ascii.indexOf(startToken);
int sig = ascii.indexOf(sigToken) + sigToken.length();
if (start >= 0 && sig >= 0 && sig > start) {
byte[] forDigest = new byte[sig - start];
System.arraycopy(this.getRawDescriptorBytes(), start,
forDigest, 0, sig - start);
this.consensusDigest = DatatypeConverter.printHexBinary(
MessageDigest.getInstance("SHA-1").digest(forDigest))
.toLowerCase();
}
} catch (UnsupportedEncodingException e) {
/* Handle below. */
} catch (NoSuchAlgorithmException e) {
/* Handle below. */
}
if (this.consensusDigest == null) {
throw new DescriptorParseException("Could not calculate consensus "
+ "digest.");
}
this.calculateDigestSha1Hex(Key.NETWORK_STATUS_VERSION.keyword + SP,
NL + Key.DIRECTORY_SIGNATURE.keyword + SP);
}
protected void parseHeader(byte[] headerBytes)
......@@ -393,18 +360,11 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl
parts, 1);
}
private String consensusDigest;
@Override
public String getConsensusDigest() {
return this.getDigestSha1Hex();
}
@Override
public String getDigestSha1Hex() {
return this.consensusDigest;
}
private int networkStatusVersion;
@Override
......
......@@ -6,9 +6,6 @@ package org.torproject.descriptor.impl;
import org.torproject.descriptor.DescriptorParseException;
import org.torproject.descriptor.RelayNetworkStatus;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
......@@ -18,8 +15,6 @@ import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.xml.bind.DatatypeConverter;
public class RelayNetworkStatusImpl extends NetworkStatusImpl
implements RelayNetworkStatus {
......@@ -51,37 +46,8 @@ public class RelayNetworkStatusImpl extends NetworkStatusImpl
this.checkAtMostOnceKeys(atMostOnceKeys);
this.checkFirstKey(Key.NETWORK_STATUS_VERSION);
this.clearParsedKeys();
this.calculateDigest();
}
private void calculateDigest() throws DescriptorParseException {