commit 47e771f2b772f21563d38a98bb7c7fe284b474d8 Author: Karsten Loesing karsten.loesing@gmx.net Date: Thu Apr 26 16:07:55 2012 +0200
Parse certs and everything we need to verify consensuses. --- .../descriptor/DirectoryKeyCertificate.java | 45 ++++ .../torproject/descriptor/DirectorySignature.java | 16 ++ .../descriptor/RelayNetworkStatusConsensus.java | 6 +- .../descriptor/RelayNetworkStatusVote.java | 2 +- .../torproject/descriptor/impl/DescriptorImpl.java | 4 +- .../impl/DirectoryKeyCertificateImpl.java | 270 ++++++++++++++++++++ .../descriptor/impl/DirectorySignatureImpl.java | 91 +++++++ .../descriptor/impl/NetworkStatusImpl.java | 50 +--- .../impl/RelayNetworkStatusConsensusImpl.java | 33 +++ 9 files changed, 479 insertions(+), 38 deletions(-)
diff --git a/src/org/torproject/descriptor/DirectoryKeyCertificate.java b/src/org/torproject/descriptor/DirectoryKeyCertificate.java new file mode 100644 index 0000000..9a4aeae --- /dev/null +++ b/src/org/torproject/descriptor/DirectoryKeyCertificate.java @@ -0,0 +1,45 @@ +/* Copyright 2012 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor; + +public interface DirectoryKeyCertificate extends Descriptor { + + /* Return the directory key certificate version. */ + public int getDirKeyCertificateVersion(); + + /* Return the IP address, or null if the certificate does not contain an + * address. */ + public String getAddress(); + + /* Return the directory port, or -1 if the certificate does not contain + * one. */ + public int getPort(); + + /* Return the directory identity fingerprint. */ + public String getFingerprint(); + + /* Return the directory identity key. */ + public String getDirIdentityKey(); + + /* Return the directory key certificate publication timestamp. */ + public long getDirKeyPublishedMillis(); + + /* Return the directory key certificate expiration timestamp. */ + public long getDirKeyExpiresMillis(); + + /* Return the directory signing key digest. */ + public String getDirSigningKey(); + + /* Return the signature of the directory identity key made using the + * directory signing key, or null if the certificate does not contain + * this signature. */ + public String getDirKeyCrosscert(); + + /* Return the certificate signature made using the directory identity + * key. */ + public String getDirKeyCertification(); + + /* Return the calculated certificate digest. */ + public String getCertificateDigest(); +} + diff --git a/src/org/torproject/descriptor/DirectorySignature.java b/src/org/torproject/descriptor/DirectorySignature.java new file mode 100644 index 0000000..29eb055 --- /dev/null +++ b/src/org/torproject/descriptor/DirectorySignature.java @@ -0,0 +1,16 @@ +/* Copyright 2012 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor; + +public interface DirectorySignature { + + /* Return the directory identity fingerprint. */ + public String getIdentity(); + + /* Return the directory signing key digest. */ + public String getSigningKeyDigest(); + + /* Return the directory signature made using the signing key. */ + public String getSignature(); +} + diff --git a/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java b/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java index 2c0ff4f..4a1634f 100644 --- a/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java +++ b/src/org/torproject/descriptor/RelayNetworkStatusConsensus.java @@ -60,10 +60,14 @@ public interface RelayNetworkStatusConsensus extends Descriptor { public NetworkStatusEntry getStatusEntry(String fingerprint);
/* Return directory signatures. */ - public SortedMap<String, String> getDirectorySignatures(); + public SortedMap<String, DirectorySignature> getDirectorySignatures();
/* Return bandwidth weights or null if the consensus doesn't contain * bandwidth weights. */ public SortedMap<String, Integer> getBandwidthWeights(); + + /* Return the consensus digest that directory authorities use to sign + * the consensus. */ + public String getConsensusDigest(); }
diff --git a/src/org/torproject/descriptor/RelayNetworkStatusVote.java b/src/org/torproject/descriptor/RelayNetworkStatusVote.java index f9635e2..eda0cef 100644 --- a/src/org/torproject/descriptor/RelayNetworkStatusVote.java +++ b/src/org/torproject/descriptor/RelayNetworkStatusVote.java @@ -92,6 +92,6 @@ public interface RelayNetworkStatusVote extends Descriptor { public NetworkStatusEntry getStatusEntry(String fingerprint);
/* Return directory signatures. */ - public SortedMap<String, String> getDirectorySignatures(); + public SortedMap<String, DirectorySignature> getDirectorySignatures(); }
diff --git a/src/org/torproject/descriptor/impl/DescriptorImpl.java b/src/org/torproject/descriptor/impl/DescriptorImpl.java index 33b94e9..6b1b167 100644 --- a/src/org/torproject/descriptor/impl/DescriptorImpl.java +++ b/src/org/torproject/descriptor/impl/DescriptorImpl.java @@ -60,7 +60,9 @@ public abstract class DescriptorImpl implements Descriptor { parseDescriptors(rawDescriptorBytes, failUnrecognizedDescriptorLines)); } else if (firstLines.startsWith("dir-key-certificate-version ")) { - /* TODO Implement parsing of directory certificates. */ + parsedDescriptors.addAll(DirectoryKeyCertificateImpl. + parseDescriptors(rawDescriptorBytes, + failUnrecognizedDescriptorLines)); } else if (firstLines.startsWith("ExitNode ")) { parsedDescriptors.add(new ExitListImpl(rawDescriptorBytes, fileName, failUnrecognizedDescriptorLines)); diff --git a/src/org/torproject/descriptor/impl/DirectoryKeyCertificateImpl.java b/src/org/torproject/descriptor/impl/DirectoryKeyCertificateImpl.java new file mode 100644 index 0000000..2483aa1 --- /dev/null +++ b/src/org/torproject/descriptor/impl/DirectoryKeyCertificateImpl.java @@ -0,0 +1,270 @@ +/* Copyright 2012 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Scanner; +import java.util.Set; + +import org.apache.commons.codec.digest.DigestUtils; +import org.torproject.descriptor.DirectoryKeyCertificate; + +/* TODO Add test class. */ + +public class DirectoryKeyCertificateImpl extends DescriptorImpl + implements DirectoryKeyCertificate { + + protected static List<DirectoryKeyCertificate> parseDescriptors( + byte[] descriptorsBytes, boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + List<DirectoryKeyCertificate> parsedDescriptors = + new ArrayList<DirectoryKeyCertificate>(); + List<byte[]> splitDescriptorsBytes = + DirectoryKeyCertificateImpl.splitRawDescriptorBytes( + descriptorsBytes, "dir-key-certificate-version "); + for (byte[] descriptorBytes : splitDescriptorsBytes) { + DirectoryKeyCertificate parsedDescriptor = + new DirectoryKeyCertificateImpl(descriptorBytes, + failUnrecognizedDescriptorLines); + parsedDescriptors.add(parsedDescriptor); + } + return parsedDescriptors; + } + + protected DirectoryKeyCertificateImpl(byte[] rawDescriptorBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + super(rawDescriptorBytes, failUnrecognizedDescriptorLines); + this.parseDescriptorBytes(); + this.calculateDigest(); + Set<String> exactlyOnceKeywords = new HashSet<String>(Arrays.asList(( + "dir-key-certificate-version,fingerprint,dir-identity-key," + + "dir-key-published,dir-key-expires,dir-signing-key," + + "dir-key-certification").split(","))); + this.checkExactlyOnceKeywords(exactlyOnceKeywords); + Set<String> atMostOnceKeywords = new HashSet<String>(Arrays.asList(( + "dir-address,dir-key-crosscert").split(","))); + this.checkAtMostOnceKeywords(atMostOnceKeywords); + this.checkFirstKeyword("dir-key-certificate-version"); + this.checkLastKeyword("dir-key-certification"); + } + + private void parseDescriptorBytes() throws DescriptorParseException { + Scanner s = new Scanner(new String(this.rawDescriptorBytes)). + useDelimiter("\n"); + String nextCrypto = null; + StringBuilder crypto = null; + while (s.hasNext()) { + String line = s.next(); + String[] parts = line.split(" "); + String keyword = parts[0]; + if (keyword.equals("dir-key-certificate-version")) { + this.parseDirKeyCertificateVersionLine(line, parts); + } else if (keyword.equals("dir-address")) { + this.parseDirAddressLine(line, parts); + } else if (keyword.equals("fingerprint")) { + this.parseFingerprintLine(line, parts); + } else if (keyword.equals("dir-identity-key")) { + this.parseDirIdentityKeyLine(line, parts); + nextCrypto = "dir-identity-key"; + } else if (keyword.equals("dir-key-published")) { + this.parseDirKeyPublishedLine(line, parts); + } else if (keyword.equals("dir-key-expires")) { + this.parseDirKeyExpiresLine(line, parts); + } else if (keyword.equals("dir-signing-key")) { + this.parseDirSigningKeyLine(line, parts); + nextCrypto = "dir-signing-key"; + } else if (keyword.equals("dir-key-crosscert")) { + this.parseDirKeyCrosscertLine(line, parts); + nextCrypto = "dir-key-crosscert"; + } else if (keyword.equals("dir-key-certification")) { + this.parseDirKeyCertificationLine(line, parts); + nextCrypto = "dir-key-certification"; + } else if (line.startsWith("-----BEGIN")) { + crypto = new StringBuilder(); + crypto.append(line + "\n"); + } else if (line.startsWith("-----END")) { + crypto.append(line + "\n"); + String cryptoString = crypto.toString(); + crypto = null; + if (nextCrypto.equals("dir-identity-key")) { + this.dirIdentityKey = cryptoString; + } else if (nextCrypto.equals("dir-signing-key")) { + this.dirSigningKey = cryptoString; + } else if (nextCrypto.equals("dir-key-crosscert")) { + this.dirKeyCrosscert = cryptoString; + } else if (nextCrypto.equals("dir-key-certification")) { + this.dirKeyCertification = cryptoString; + } else { + throw new DescriptorParseException("Unrecognized crypto " + + "block in directory key certificate."); + } + nextCrypto = null; + } else if (crypto != null) { + crypto.append(line + "\n"); + } else { + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in directory key certificate."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<String>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + + private void parseDirKeyCertificateVersionLine(String line, + String[] parts) throws DescriptorParseException { + if (!line.equals("dir-key-certificate-version 3")) { + throw new DescriptorParseException("Illegal directory key " + + "certificate version number in line '" + line + "'."); + } + this.dirKeyCertificateVersion = 3; + } + + private void parseDirAddressLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 2 || parts[1].split(":").length != 2) { + throw new DescriptorParseException("Illegal line '" + line + + "' in directory key certificate."); + } + this.address = ParseHelper.parseIpv4Address(line, + parts[1].split(":")[0]); + this.port = ParseHelper.parsePort(line, parts[1].split(":")[1]); + } + + private void parseFingerprintLine(String line, String[] parts) + throws DescriptorParseException { + if (parts.length != 2) { + throw new DescriptorParseException("Illegal line '" + line + + "' in directory key certificate."); + } + this.fingerprint = ParseHelper.parseTwentyByteHexString(line, + parts[1]); + } + + private void parseDirIdentityKeyLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-identity-key")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseDirKeyPublishedLine(String line, String[] parts) + throws DescriptorParseException { + this.dirKeyPublishedMillis = ParseHelper.parseTimestampAtIndex(line, + parts, 1, 2); + } + + private void parseDirKeyExpiresLine(String line, String[] parts) + throws DescriptorParseException { + this.dirKeyExpiresMillis = ParseHelper.parseTimestampAtIndex(line, + parts, 1, 2); + } + + private void parseDirSigningKeyLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-signing-key")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseDirKeyCrosscertLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-key-crosscert")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void parseDirKeyCertificationLine(String line, String[] parts) + throws DescriptorParseException { + if (!line.equals("dir-key-certification")) { + throw new DescriptorParseException("Illegal line '" + line + "'."); + } + } + + private void calculateDigest() throws DescriptorParseException { + try { + String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); + String startToken = "dir-key-certificate-version "; + String sigToken = "\ndir-key-certification\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.certificateDigest = DigestUtils.shaHex(forDigest); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } + if (this.certificateDigest == null) { + throw new DescriptorParseException("Could not calculate " + + "certificate digest."); + } + } + + private int dirKeyCertificateVersion; + public int getDirKeyCertificateVersion() { + return this.dirKeyCertificateVersion; + } + + private String address; + public String getAddress() { + return this.address; + } + + private int port = -1; + public int getPort() { + return this.port; + } + + private String fingerprint; + public String getFingerprint() { + return this.fingerprint; + } + + private String dirIdentityKey; + public String getDirIdentityKey() { + return this.dirIdentityKey; + } + + private long dirKeyPublishedMillis; + public long getDirKeyPublishedMillis() { + return this.dirKeyPublishedMillis; + } + + private long dirKeyExpiresMillis; + public long getDirKeyExpiresMillis() { + return this.dirKeyExpiresMillis; + } + + private String dirSigningKey; + public String getDirSigningKey() { + return this.dirSigningKey; + } + + private String dirKeyCrosscert; + public String getDirKeyCrosscert() { + return this.dirKeyCrosscert; + } + + private String dirKeyCertification; + public String getDirKeyCertification() { + return this.dirKeyCertification; + } + + private String certificateDigest; + public String getCertificateDigest() { + return this.certificateDigest; + } +} + diff --git a/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java b/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java new file mode 100644 index 0000000..d205345 --- /dev/null +++ b/src/org/torproject/descriptor/impl/DirectorySignatureImpl.java @@ -0,0 +1,91 @@ +/* Copyright 2012 The Tor Project + * See LICENSE for licensing information */ +package org.torproject.descriptor.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +import org.torproject.descriptor.DirectorySignature; + +public class DirectorySignatureImpl implements DirectorySignature { + + private byte[] directorySignatureBytes; + public byte[] getDirectorySignatureBytes() { + return this.directorySignatureBytes; + } + + private boolean failUnrecognizedDescriptorLines; + private List<String> unrecognizedLines; + protected List<String> getAndClearUnrecognizedLines() { + List<String> lines = this.unrecognizedLines; + this.unrecognizedLines = null; + return lines; + } + + protected DirectorySignatureImpl(byte[] directorySignatureBytes, + boolean failUnrecognizedDescriptorLines) + throws DescriptorParseException { + this.directorySignatureBytes = directorySignatureBytes; + this.failUnrecognizedDescriptorLines = + failUnrecognizedDescriptorLines; + this.parseDirectorySignatureBytes(); + } + + private void parseDirectorySignatureBytes() + throws DescriptorParseException { + Scanner s = new Scanner(new String(this.directorySignatureBytes)). + useDelimiter("\n"); + StringBuilder crypto = null; + while (s.hasNext()) { + String line = s.next(); + if (line.startsWith("directory-signature ")) { + String[] parts = line.split(" ", -1); + if (parts.length != 3) { + throw new DescriptorParseException("Illegal line '" + line + + "'."); + } + this.identity = ParseHelper.parseTwentyByteHexString(line, + parts[1]); + this.signingKeyDigest = ParseHelper.parseTwentyByteHexString( + line, parts[2]); + } else if (line.startsWith("-----BEGIN")) { + crypto = new StringBuilder(); + crypto.append(line + "\n"); + } else if (line.startsWith("-----END")) { + crypto.append(line + "\n"); + String cryptoString = crypto.toString(); + crypto = null; + this.signature = cryptoString; + } else if (crypto != null) { + crypto.append(line + "\n"); + } else { + if (this.failUnrecognizedDescriptorLines) { + throw new DescriptorParseException("Unrecognized line '" + + line + "' in dir-source entry."); + } else { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<String>(); + } + this.unrecognizedLines.add(line); + } + } + } + } + + private String identity; + public String getIdentity() { + return this.identity; + } + + private String signingKeyDigest; + public String getSigningKeyDigest() { + return this.signingKeyDigest; + } + + private String signature; + public String getSignature() { + return this.signature; + } +} + diff --git a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java index f080171..d27e651 100644 --- a/src/org/torproject/descriptor/impl/NetworkStatusImpl.java +++ b/src/org/torproject/descriptor/impl/NetworkStatusImpl.java @@ -4,11 +4,11 @@ package org.torproject.descriptor.impl;
import java.util.ArrayList; import java.util.List; -import java.util.Scanner; import java.util.SortedMap; import java.util.TreeMap;
import org.torproject.descriptor.DirSourceEntry; +import org.torproject.descriptor.DirectorySignature; import org.torproject.descriptor.NetworkStatusEntry;
/* Parse the common parts of v3 consensuses, v3 votes, v3 microdesc @@ -195,39 +195,19 @@ public abstract class NetworkStatusImpl extends DescriptorImpl { protected void parseDirectorySignature(byte[] directorySignatureBytes) throws DescriptorParseException { if (this.directorySignatures == null) { - this.directorySignatures = new TreeMap<String, String>(); + this.directorySignatures = new TreeMap<String, + DirectorySignature>(); } - Scanner s = new Scanner(new String(directorySignatureBytes)). - useDelimiter("\n"); - boolean skipCrypto = false; - while (s.hasNext()) { - String line = s.next(); - if (line.startsWith("directory-signature ")) { - String[] parts = line.split(" ", -1); - if (parts.length != 3) { - throw new DescriptorParseException("Illegal line '" + line - + "'."); - } - String identity = ParseHelper.parseTwentyByteHexString(line, - parts[1]); - String signingKeyDigest = ParseHelper.parseTwentyByteHexString( - line, parts[2]); - this.directorySignatures.put(identity, signingKeyDigest); - } else if (line.startsWith("-----BEGIN")) { - skipCrypto = true; - } else if (line.startsWith("-----END")) { - skipCrypto = false; - } else if (!skipCrypto) { - if (this.failUnrecognizedDescriptorLines) { - throw new DescriptorParseException("Unrecognized line '" - + line + "' in dir-source entry."); - } else { - if (this.unrecognizedLines == null) { - this.unrecognizedLines = new ArrayList<String>(); - } - this.unrecognizedLines.add(line); - } + DirectorySignatureImpl signature = new DirectorySignatureImpl( + directorySignatureBytes, failUnrecognizedDescriptorLines); + this.directorySignatures.put(signature.getIdentity(), signature); + List<String> unrecognizedStatusEntryLines = signature. + getAndClearUnrecognizedLines(); + if (unrecognizedStatusEntryLines != null) { + if (this.unrecognizedLines == null) { + this.unrecognizedLines = new ArrayList<String>(); } + this.unrecognizedLines.addAll(unrecognizedStatusEntryLines); } }
@@ -249,10 +229,10 @@ public abstract class NetworkStatusImpl extends DescriptorImpl { return this.statusEntries.get(fingerprint); }
- private SortedMap<String, String> directorySignatures; - public SortedMap<String, String> getDirectorySignatures() { + private SortedMap<String, DirectorySignature> directorySignatures; + public SortedMap<String, DirectorySignature> getDirectorySignatures() { return this.directorySignatures == null ? null : - new TreeMap<String, String>(this.directorySignatures); + new TreeMap<String, DirectorySignature>(this.directorySignatures); } }
diff --git a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java index 564beb2..3d2af37 100644 --- a/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java +++ b/src/org/torproject/descriptor/impl/RelayNetworkStatusConsensusImpl.java @@ -2,6 +2,7 @@ * See LICENSE for licensing information */ package org.torproject.descriptor.impl;
+import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -13,6 +14,7 @@ import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet;
+import org.apache.commons.codec.digest.DigestUtils; import org.torproject.descriptor.RelayNetworkStatusConsensus;
/* Contains a network status consensus. */ @@ -49,6 +51,32 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl + "bandwidth-weights").split(","))); this.checkAtMostOnceKeywords(atMostOnceKeywords); this.checkFirstKeyword("network-status-version"); + this.calculateDigest(); + } + + private void calculateDigest() throws DescriptorParseException { + try { + String ascii = new String(this.getRawDescriptorBytes(), "US-ASCII"); + String startToken = "network-status-version "; + String sigToken = "\ndirectory-signature "; + 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 = DigestUtils.shaHex(forDigest); + } + } catch (UnsupportedEncodingException e) { + /* Handle below. */ + } + if (this.consensusDigest == null) { + throw new DescriptorParseException("Could not calculate consensus " + + "digest."); + } }
protected void parseHeader(byte[] headerBytes) @@ -238,6 +266,11 @@ public class RelayNetworkStatusConsensusImpl extends NetworkStatusImpl 1); }
+ private String consensusDigest; + public String getConsensusDigest() { + return this.consensusDigest; + } + private int networkStatusVersion; public int getNetworkStatusVersion() { return this.networkStatusVersion;
tor-commits@lists.torproject.org