[tor-commits] [metrics-tasks/master] Add code to verify consensuses (#2768).

karsten at torproject.org karsten at torproject.org
Thu Apr 26 14:15:46 UTC 2012


commit 90ff9b555c9d043d8f4392e08ee6a5e5dcdc9f68
Author: Karsten Loesing <karsten.loesing at gmx.net>
Date:   Thu Apr 26 16:14:58 2012 +0200

    Add code to verify consensuses (#2768).
---
 task-2768/VerifyDescriptors.java       |  306 ++++++++++++++++++++++++++++++++
 task-2768/VerifyServerDescriptors.java |  145 ---------------
 task-2768/run.sh                       |    2 +-
 3 files changed, 307 insertions(+), 146 deletions(-)

diff --git a/task-2768/VerifyDescriptors.java b/task-2768/VerifyDescriptors.java
new file mode 100644
index 0000000..b784cdd
--- /dev/null
+++ b/task-2768/VerifyDescriptors.java
@@ -0,0 +1,306 @@
+/* Copyright 2012 The Tor Project
+ * See LICENSE for licensing information */
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.StringReader;
+import java.security.Security;
+import java.security.interfaces.RSAPublicKey;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.encodings.PKCS1Encoding;
+import org.bouncycastle.crypto.engines.RSAEngine;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMReader;
+import org.torproject.descriptor.Descriptor;
+import org.torproject.descriptor.DescriptorFile;
+import org.torproject.descriptor.DescriptorReader;
+import org.torproject.descriptor.DescriptorSourceFactory;
+import org.torproject.descriptor.DirectoryKeyCertificate;
+import org.torproject.descriptor.DirectorySignature;
+import org.torproject.descriptor.RelayNetworkStatusConsensus;
+import org.torproject.descriptor.ServerDescriptor;
+
+/*
+ * Verify server descriptors using the contained signing key.  Verify that
+ * 1) a contained fingerprint is actually a hash of the signing key and
+ * 2) a router signature was created using the signing key.
+ *
+ * Verify consensuses using the separate certs.  Verify that
+ * 1) the fingerprint in a cert is actually a hash of the identity key,
+ * 2) a cert was signed using the identity key,
+ * 3) a consensus was signed using the signing key from the cert.
+ *
+ * Usage:
+ * - Put certs in in/certs/, consensuses in in/consensuses/, and server
+ *   descriptors in in/server-descriptors/.
+ * - Clone metrics-lib, run `ant jar`, and copy descriptor.jar to this
+ *   directory.
+ * - Download Apache Commons Codec and Compress jar files
+ *   commons-codec-1.4.jar and commons-compress-1.3.jar and put them in
+ *   this directory.
+ * - Download BouncyCastle 1.47 jar files bcprov-jdk15on-147.jar and
+ *   bcpkix-jdk15on-147.jar and put them in this directory.
+ * - Compile and run this class: ./run.sh.
+ */
+public class VerifyDescriptors {
+  public static void main(String[] args) throws Exception {
+    System.out.println("Verifying consensuses...");
+    if (Security.getProvider("BC") == null) {
+      Security.addProvider(new BouncyCastleProvider());
+    }
+    verifyServerDescriptors();
+    verifyConsensuses();
+  }
+
+  private static void verifyServerDescriptors() throws Exception {
+    File serverDescriptorDirectory = new File("in/server-descriptors");
+    if (!serverDescriptorDirectory.exists()) {
+      return;
+    }
+    DescriptorReader descriptorReader = DescriptorSourceFactory
+        .createDescriptorReader();
+    descriptorReader.addDirectory(serverDescriptorDirectory);
+    Iterator<DescriptorFile> descriptorFiles =
+        descriptorReader.readDescriptors();
+    int processedDescriptors = 0, verifiedDescriptors = 0;
+    while (descriptorFiles.hasNext()) {
+      DescriptorFile descriptorFile = descriptorFiles.next();
+      if (descriptorFile.getException() != null) {
+        System.err.println("Could not read/parse descriptor file "
+            + descriptorFile.getFileName() + ": "
+            + descriptorFile.getException().getMessage());
+        continue;
+      }
+      if (descriptorFile.getDescriptors() == null) {
+        continue;
+      }
+      for (Descriptor descriptor : descriptorFile.getDescriptors()) {
+        if (!(descriptor instanceof ServerDescriptor)) {
+          continue;
+        }
+        ServerDescriptor serverDescriptor = (ServerDescriptor) descriptor;
+        boolean isVerified = true;
+
+        /* Verify that the contained fingerprint is a hash of the signing
+         * key. */
+        String signingKeyHashString = determineKeyHash(
+            serverDescriptor.getSigningKey());
+        String fingerprintString =
+            serverDescriptor.getFingerprint().toLowerCase();
+        if (!signingKeyHashString.equals(fingerprintString)) {
+          System.out.println("In " + descriptorFile.getFile()
+              + ", server descriptor, the calculated signing key hash "
+              + " does not match the contained fingerprint!");
+          isVerified = false;
+        }
+
+        /* Verify that the router signature was created using the signing
+         * key. */
+        if (!verifySignature(serverDescriptor.getServerDescriptorDigest(),
+            serverDescriptor.getRouterSignature(),
+            serverDescriptor.getSigningKey())) {
+          System.out.println("In " + descriptorFile.getFile()
+              + ", the decrypted signature does not match the descriptor "
+              + "digest!");
+          isVerified = false;
+        }
+
+        processedDescriptors++;
+        if (isVerified) {
+          verifiedDescriptors++;
+        }
+      }
+    }
+    System.out.println("Verified " + verifiedDescriptors + "/"
+        + processedDescriptors + " server descriptors.");
+  }
+
+  private static void verifyConsensuses() throws Exception {
+    File certsDirectory = new File("in/certs");
+    File consensusDirectory = new File("in/consensuses");
+    if (!certsDirectory.exists() || !consensusDirectory.exists()) {
+      return;
+    }
+    Map<String, String> signingKeys = new HashMap<String, String>();
+
+    DescriptorReader certsReader = DescriptorSourceFactory
+        .createDescriptorReader();
+    certsReader.addDirectory(certsDirectory);
+    Iterator<DescriptorFile> descriptorFiles =
+        certsReader.readDescriptors();
+    int processedCerts = 0, verifiedCerts = 0;
+    while (descriptorFiles.hasNext()) {
+      DescriptorFile descriptorFile = descriptorFiles.next();
+      if (descriptorFile.getException() != null) {
+        System.err.println("Could not read/parse descriptor file "
+            + descriptorFile.getFileName() + ": "
+            + descriptorFile.getException().getMessage());
+        continue;
+      }
+      if (descriptorFile.getDescriptors() == null) {
+        continue;
+      }
+      for (Descriptor descriptor : descriptorFile.getDescriptors()) {
+        if (!(descriptor instanceof DirectoryKeyCertificate)) {
+          continue;
+        }
+        DirectoryKeyCertificate cert =
+            (DirectoryKeyCertificate) descriptor;
+        boolean isVerified = true;
+
+        /* Verify that the contained fingerprint is a hash of the signing
+         * key. */
+        String dirIdentityKeyHashString = determineKeyHash(
+            cert.getDirIdentityKey());
+        String fingerprintString = cert.getFingerprint().toLowerCase();
+        if (!dirIdentityKeyHashString.equals(fingerprintString)) {
+          System.out.println("In " + descriptorFile.getFile()
+              + ", the calculated directory identity key hash "
+              + dirIdentityKeyHashString
+              + " does not match the contained fingerprint "
+              + fingerprintString + "!");
+          isVerified = false;
+        }
+
+        /* Verify that the router signature was created using the signing
+         * key. */
+        if (!verifySignature(cert.getCertificateDigest(),
+            cert.getDirKeyCertification(), cert.getDirIdentityKey())) {
+          System.out.println("In " + descriptorFile.getFile()
+              + ", the decrypted directory key certification does not "
+              + "match the certificate digest!");
+          isVerified = false;
+        }
+
+        /* Determine the signing key digest and remember the signing key
+         * to verify consensus signatures. */
+        String dirSigningKeyString = cert.getDirSigningKey();
+        PEMReader pemReader2 = new PEMReader(new StringReader(
+            dirSigningKeyString));
+        RSAPublicKey dirSigningKey =
+            (RSAPublicKey) pemReader2.readObject();
+        ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
+        new ASN1OutputStream(baos2).writeObject(
+            new org.bouncycastle.asn1.pkcs.RSAPublicKey(
+            dirSigningKey.getModulus(),
+            dirSigningKey.getPublicExponent()).toASN1Primitive());
+        byte[] pkcs2 = baos2.toByteArray();
+        byte[] dirSigningKeyHashBytes = new byte[20];
+        SHA1Digest sha1_2 = new SHA1Digest();
+        sha1_2.update(pkcs2, 0, pkcs2.length);
+        sha1_2.doFinal(dirSigningKeyHashBytes, 0);
+        String dirSigningKeyHashString = Hex.encodeHexString(
+            dirSigningKeyHashBytes).toUpperCase();
+        signingKeys.put(dirSigningKeyHashString, cert.getDirSigningKey());
+
+        processedCerts++;
+        if (isVerified) {
+          verifiedCerts++;
+        }
+      }
+    }
+    System.out.println("Verified " + verifiedCerts + "/"
+        + processedCerts + " certs.");
+
+    DescriptorReader consensusReader = DescriptorSourceFactory
+        .createDescriptorReader();
+    consensusReader.addDirectory(consensusDirectory);
+    Iterator<DescriptorFile> consensusFiles =
+        consensusReader.readDescriptors();
+    int processedConsensuses = 0, verifiedConsensuses = 0;
+    while (consensusFiles.hasNext()) {
+      DescriptorFile consensusFile = consensusFiles.next();
+      if (consensusFile.getException() != null) {
+        System.err.println("Could not read/parse descriptor file "
+            + consensusFile.getFileName() + ": "
+            + consensusFile.getException().getMessage());
+        continue;
+      }
+      if (consensusFile.getDescriptors() == null) {
+        continue;
+      }
+      for (Descriptor descriptor : consensusFile.getDescriptors()) {
+        if (!(descriptor instanceof RelayNetworkStatusConsensus)) {
+          continue;
+        }
+        RelayNetworkStatusConsensus consensus =
+            (RelayNetworkStatusConsensus) descriptor;
+        boolean isVerified = true;
+
+        /* Verify all signatures using the corresponding certificates. */
+        if (consensus.getDirectorySignatures().isEmpty()) {
+          System.out.println(consensusFile.getFile()
+              + " does not contain any signatures.");
+          continue;
+        }
+        for (DirectorySignature signature :
+            consensus.getDirectorySignatures().values()) {
+          String signingKeyDigest = signature.getSigningKeyDigest();
+          if (!signingKeys.containsKey(signingKeyDigest)) {
+            System.out.println("Cannot find signing key with digest "
+                + signingKeyDigest + "!");
+          }
+          if (!verifySignature(consensus.getConsensusDigest(),
+              signature.getSignature(),
+              signingKeys.get(signingKeyDigest))) {
+            System.out.println("In " + consensusFile.getFile()
+                + ", the decrypted signature digest does not match the "
+                + "consensus digest!");
+            isVerified = false;
+          }
+        }
+        processedConsensuses++;
+        if (isVerified) {
+          verifiedConsensuses++;
+        }
+      }
+    }
+    System.out.println("Verified " + verifiedConsensuses + "/"
+        + processedConsensuses + " consensuses.");
+  }
+
+  private static String determineKeyHash(String key) throws Exception {
+    PEMReader pemReader = new PEMReader(new StringReader(key));
+    RSAPublicKey dirIdentityKey = (RSAPublicKey) pemReader.readObject();
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    new ASN1OutputStream(baos).writeObject(
+        new org.bouncycastle.asn1.pkcs.RSAPublicKey(
+        dirIdentityKey.getModulus(),
+        dirIdentityKey.getPublicExponent()).toASN1Primitive());
+    byte[] pkcs = baos.toByteArray();
+    byte[] dirIdentityKeyHashBytes = new byte[20];
+    SHA1Digest sha1 = new SHA1Digest();
+    sha1.update(pkcs, 0, pkcs.length);
+    sha1.doFinal(dirIdentityKeyHashBytes, 0);
+    String keyHash = Hex.encodeHexString(dirIdentityKeyHashBytes);
+    return keyHash;
+  }
+
+  private static boolean verifySignature(String digest, String signature,
+      String signingKey) throws Exception {
+    byte[] signatureBytes = Base64.decodeBase64(signature.substring(
+        0 + "-----BEGIN SIGNATURE-----\n".length(),
+        signature.length() - "-----END SIGNATURE-----\n".length()).
+        replaceAll("\n", ""));
+    RSAPublicKey rsaSigningKey = (RSAPublicKey) new PEMReader(
+        new StringReader(signingKey)).readObject();
+    RSAKeyParameters rsakp = new RSAKeyParameters(false,
+        rsaSigningKey.getModulus(),
+        rsaSigningKey.getPublicExponent());
+    PKCS1Encoding pe = new PKCS1Encoding(new RSAEngine());
+    pe.init(false, rsakp);
+    byte[] decryptedSignatureDigest = pe.processBlock(
+        signatureBytes, 0, signatureBytes.length);
+    String decryptedSignatureDigestString =
+        Hex.encodeHexString(decryptedSignatureDigest);
+    return decryptedSignatureDigestString.equalsIgnoreCase(digest);
+  }
+}
+
diff --git a/task-2768/VerifyServerDescriptors.java b/task-2768/VerifyServerDescriptors.java
deleted file mode 100644
index e648d92..0000000
--- a/task-2768/VerifyServerDescriptors.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/* Copyright 2012 The Tor Project
- * See LICENSE for licensing information */
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.StringReader;
-import java.security.Security;
-import java.security.interfaces.RSAPublicKey;
-import java.util.Iterator;
-
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.binary.Hex;
-import org.bouncycastle.asn1.ASN1OutputStream;
-import org.bouncycastle.crypto.digests.SHA1Digest;
-import org.bouncycastle.crypto.encodings.PKCS1Encoding;
-import org.bouncycastle.crypto.engines.RSAEngine;
-import org.bouncycastle.crypto.params.RSAKeyParameters;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openssl.PEMReader;
-import org.torproject.descriptor.Descriptor;
-import org.torproject.descriptor.DescriptorFile;
-import org.torproject.descriptor.DescriptorReader;
-import org.torproject.descriptor.DescriptorSourceFactory;
-import org.torproject.descriptor.ServerDescriptor;
-
-/*
- * Verify server descriptors using the contained signing key.  Verify that
- * 1) the contained fingerprint is actually a hash of the signing key and
- * 2) the router signature was created using the signing key.
- *
- * Usage:
- * - Extract server descriptors to in/.
- * - Clone metrics-lib, run `ant jar`, and copy descriptor.jar to this
- *   directory.
- * - Download Apache Commons Codec and Compress jar files
- *   commons-codec-1.4.jar and commons-compress-1.3.jar and put them in
- *   this directory.
- * - Download BouncyCastle 1.47 jar files bcprov-jdk15on-147.jar and
- *   bcpkix-jdk15on-147.jar and put them in this directory.
- * - Compile and run this class: ./run.sh.
- */
-public class VerifyServerDescriptors {
-  public static void main(String[] args) throws Exception {
-    System.out.println("Verifying descriptors...");
-    if (Security.getProvider("BC") == null) {
-      Security.addProvider(new BouncyCastleProvider());
-    }
-    File inputDirectory = new File("in/");
-    DescriptorReader reader = DescriptorSourceFactory
-        .createDescriptorReader();
-    reader.addDirectory(inputDirectory);
-    Iterator<DescriptorFile> descriptorFiles = reader.readDescriptors();
-    int processedDescriptors = 0, verifiedDescriptors = 0;
-    while (descriptorFiles.hasNext()) {
-      DescriptorFile descriptorFile = descriptorFiles.next();
-      if (descriptorFile.getException() != null) {
-        System.err.println("Could not read/parse descriptor file "
-            + descriptorFile.getFileName() + ": "
-            + descriptorFile.getException().getMessage());
-        continue;
-      }
-      if (descriptorFile.getDescriptors() == null) {
-        continue;
-      }
-      for (Descriptor descriptor : descriptorFile.getDescriptors()) {
-        if (!(descriptor instanceof ServerDescriptor)) {
-          continue;
-        }
-        ServerDescriptor serverDescriptor = (ServerDescriptor) descriptor;
-        boolean isVerified = true;
-
-        /* Verify that the contained fingerprint is a hash of the signing
-         * key. */
-        String signingKeyString = serverDescriptor.getSigningKey();
-        String fingerprintString =
-            serverDescriptor.getFingerprint().toLowerCase();
-        PEMReader pemReader = new PEMReader(new StringReader(
-            signingKeyString));
-        RSAPublicKey signingKey = (RSAPublicKey) pemReader.readObject();
-
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new ASN1OutputStream(baos).writeObject(
-            new org.bouncycastle.asn1.pkcs.RSAPublicKey(
-            signingKey.getModulus(),
-            signingKey.getPublicExponent()).toASN1Primitive());
-        byte[] pkcs = baos.toByteArray();
-        byte[] signingKeyHashBytes = new byte[20];
-        SHA1Digest sha1 = new SHA1Digest();
-        sha1.update(pkcs, 0, pkcs.length);
-        sha1.doFinal(signingKeyHashBytes, 0);
-        String signingKeyHashString = Hex.encodeHexString(
-            signingKeyHashBytes);
-        if (!signingKeyHashString.equals(fingerprintString)) {
-          System.out.println("In " + descriptorFile.getFile()
-              + ", server descriptor "
-              + serverDescriptor.getServerDescriptorDigest()
-              + ", the calculated signing key hash "
-              + signingKeyHashString
-              + " does not match the contained fingerprint "
-              + fingerprintString + "!");
-          isVerified = false;
-        }
-
-        /* Verify that the router signature was created using the signing
-         * key. */
-        String serverDescriptorDigestString = serverDescriptor
-            .getServerDescriptorDigest().toLowerCase();
-        String routerSignatureString = serverDescriptor
-            .getRouterSignature();
-        byte[] routerSignature = Base64
-            .decodeBase64(routerSignatureString.substring(
-                0 + "-----BEGIN SIGNATURE-----\n".length(),
-                routerSignatureString.length()
-                    - "-----END SIGNATURE-----\n".length()).replaceAll(
-                "\n", ""));
-        RSAKeyParameters rsakp = new RSAKeyParameters(false,
-            signingKey.getModulus(), signingKey.getPublicExponent());
-        PKCS1Encoding pe = new PKCS1Encoding(new RSAEngine());
-        pe.init(false, rsakp);
-        byte[] decryptedSignature = pe.processBlock(routerSignature, 0,
-            routerSignature.length);
-        String decryptedSignatureString =
-            Hex.encodeHexString(decryptedSignature);
-        if (!decryptedSignatureString.equals(
-            serverDescriptorDigestString)) {
-          System.out.println("In " + descriptorFile.getFile()
-              + ", server descriptor "
-              + serverDescriptor.getServerDescriptorDigest()
-              + ", the decrypted signature "
-              + decryptedSignatureString
-              + " does not match the descriptor digest "
-              + serverDescriptorDigestString + "!");
-          isVerified = false;
-        }
-
-        processedDescriptors++;
-        if (isVerified) {
-          verifiedDescriptors++;
-        }
-      }
-    }
-    System.out.println("Verified " + verifiedDescriptors + "/"
-        + processedDescriptors + " server descriptors.");
-  }
-}
-
diff --git a/task-2768/run.sh b/task-2768/run.sh
index ed5fcc4..658ed6a 100755
--- a/task-2768/run.sh
+++ b/task-2768/run.sh
@@ -1,3 +1,3 @@
 #!/bin/bash
-javac -cp descriptor.jar:commons-codec-1.4.jar:commons-compress-1.3.jar:bcprov-jdk15on-147.jar:bcpkix-jdk15on-147.jar VerifyServerDescriptors.java && java -cp descriptor.jar:commons-codec-1.4.jar:commons-compress-1.3.jar:bcprov-jdk15on-147.jar:bcpkix-jdk15on-147.jar:. VerifyServerDescriptors
+javac -cp descriptor.jar:commons-codec-1.4.jar:commons-compress-1.3.jar:bcprov-jdk15on-147.jar:bcpkix-jdk15on-147.jar VerifyDescriptors.java && java -cp descriptor.jar:commons-codec-1.4.jar:commons-compress-1.3.jar:bcprov-jdk15on-147.jar:bcpkix-jdk15on-147.jar:. VerifyDescriptors
 



More information about the tor-commits mailing list