task-6329: move OptionParser creation to its own function
[tor-metrics-tasks/delber.git] / task-2768 / VerifyDescriptors.java
blobb784cdd520d24499c9488c3c0151f878cc59cb9b
1 /* Copyright 2012 The Tor Project
2 * See LICENSE for licensing information */
3 import java.io.ByteArrayOutputStream;
4 import java.io.File;
5 import java.io.StringReader;
6 import java.security.Security;
7 import java.security.interfaces.RSAPublicKey;
8 import java.util.HashMap;
9 import java.util.Iterator;
10 import java.util.Map;
12 import org.apache.commons.codec.binary.Base64;
13 import org.apache.commons.codec.binary.Hex;
14 import org.bouncycastle.asn1.ASN1OutputStream;
15 import org.bouncycastle.crypto.digests.SHA1Digest;
16 import org.bouncycastle.crypto.encodings.PKCS1Encoding;
17 import org.bouncycastle.crypto.engines.RSAEngine;
18 import org.bouncycastle.crypto.params.RSAKeyParameters;
19 import org.bouncycastle.jce.provider.BouncyCastleProvider;
20 import org.bouncycastle.openssl.PEMReader;
21 import org.torproject.descriptor.Descriptor;
22 import org.torproject.descriptor.DescriptorFile;
23 import org.torproject.descriptor.DescriptorReader;
24 import org.torproject.descriptor.DescriptorSourceFactory;
25 import org.torproject.descriptor.DirectoryKeyCertificate;
26 import org.torproject.descriptor.DirectorySignature;
27 import org.torproject.descriptor.RelayNetworkStatusConsensus;
28 import org.torproject.descriptor.ServerDescriptor;
31 * Verify server descriptors using the contained signing key. Verify that
32 * 1) a contained fingerprint is actually a hash of the signing key and
33 * 2) a router signature was created using the signing key.
35 * Verify consensuses using the separate certs. Verify that
36 * 1) the fingerprint in a cert is actually a hash of the identity key,
37 * 2) a cert was signed using the identity key,
38 * 3) a consensus was signed using the signing key from the cert.
40 * Usage:
41 * - Put certs in in/certs/, consensuses in in/consensuses/, and server
42 * descriptors in in/server-descriptors/.
43 * - Clone metrics-lib, run `ant jar`, and copy descriptor.jar to this
44 * directory.
45 * - Download Apache Commons Codec and Compress jar files
46 * commons-codec-1.4.jar and commons-compress-1.3.jar and put them in
47 * this directory.
48 * - Download BouncyCastle 1.47 jar files bcprov-jdk15on-147.jar and
49 * bcpkix-jdk15on-147.jar and put them in this directory.
50 * - Compile and run this class: ./run.sh.
52 public class VerifyDescriptors {
53 public static void main(String[] args) throws Exception {
54 System.out.println("Verifying consensuses...");
55 if (Security.getProvider("BC") == null) {
56 Security.addProvider(new BouncyCastleProvider());
58 verifyServerDescriptors();
59 verifyConsensuses();
62 private static void verifyServerDescriptors() throws Exception {
63 File serverDescriptorDirectory = new File("in/server-descriptors");
64 if (!serverDescriptorDirectory.exists()) {
65 return;
67 DescriptorReader descriptorReader = DescriptorSourceFactory
68 .createDescriptorReader();
69 descriptorReader.addDirectory(serverDescriptorDirectory);
70 Iterator<DescriptorFile> descriptorFiles =
71 descriptorReader.readDescriptors();
72 int processedDescriptors = 0, verifiedDescriptors = 0;
73 while (descriptorFiles.hasNext()) {
74 DescriptorFile descriptorFile = descriptorFiles.next();
75 if (descriptorFile.getException() != null) {
76 System.err.println("Could not read/parse descriptor file "
77 + descriptorFile.getFileName() + ": "
78 + descriptorFile.getException().getMessage());
79 continue;
81 if (descriptorFile.getDescriptors() == null) {
82 continue;
84 for (Descriptor descriptor : descriptorFile.getDescriptors()) {
85 if (!(descriptor instanceof ServerDescriptor)) {
86 continue;
88 ServerDescriptor serverDescriptor = (ServerDescriptor) descriptor;
89 boolean isVerified = true;
91 /* Verify that the contained fingerprint is a hash of the signing
92 * key. */
93 String signingKeyHashString = determineKeyHash(
94 serverDescriptor.getSigningKey());
95 String fingerprintString =
96 serverDescriptor.getFingerprint().toLowerCase();
97 if (!signingKeyHashString.equals(fingerprintString)) {
98 System.out.println("In " + descriptorFile.getFile()
99 + ", server descriptor, the calculated signing key hash "
100 + " does not match the contained fingerprint!");
101 isVerified = false;
104 /* Verify that the router signature was created using the signing
105 * key. */
106 if (!verifySignature(serverDescriptor.getServerDescriptorDigest(),
107 serverDescriptor.getRouterSignature(),
108 serverDescriptor.getSigningKey())) {
109 System.out.println("In " + descriptorFile.getFile()
110 + ", the decrypted signature does not match the descriptor "
111 + "digest!");
112 isVerified = false;
115 processedDescriptors++;
116 if (isVerified) {
117 verifiedDescriptors++;
121 System.out.println("Verified " + verifiedDescriptors + "/"
122 + processedDescriptors + " server descriptors.");
125 private static void verifyConsensuses() throws Exception {
126 File certsDirectory = new File("in/certs");
127 File consensusDirectory = new File("in/consensuses");
128 if (!certsDirectory.exists() || !consensusDirectory.exists()) {
129 return;
131 Map<String, String> signingKeys = new HashMap<String, String>();
133 DescriptorReader certsReader = DescriptorSourceFactory
134 .createDescriptorReader();
135 certsReader.addDirectory(certsDirectory);
136 Iterator<DescriptorFile> descriptorFiles =
137 certsReader.readDescriptors();
138 int processedCerts = 0, verifiedCerts = 0;
139 while (descriptorFiles.hasNext()) {
140 DescriptorFile descriptorFile = descriptorFiles.next();
141 if (descriptorFile.getException() != null) {
142 System.err.println("Could not read/parse descriptor file "
143 + descriptorFile.getFileName() + ": "
144 + descriptorFile.getException().getMessage());
145 continue;
147 if (descriptorFile.getDescriptors() == null) {
148 continue;
150 for (Descriptor descriptor : descriptorFile.getDescriptors()) {
151 if (!(descriptor instanceof DirectoryKeyCertificate)) {
152 continue;
154 DirectoryKeyCertificate cert =
155 (DirectoryKeyCertificate) descriptor;
156 boolean isVerified = true;
158 /* Verify that the contained fingerprint is a hash of the signing
159 * key. */
160 String dirIdentityKeyHashString = determineKeyHash(
161 cert.getDirIdentityKey());
162 String fingerprintString = cert.getFingerprint().toLowerCase();
163 if (!dirIdentityKeyHashString.equals(fingerprintString)) {
164 System.out.println("In " + descriptorFile.getFile()
165 + ", the calculated directory identity key hash "
166 + dirIdentityKeyHashString
167 + " does not match the contained fingerprint "
168 + fingerprintString + "!");
169 isVerified = false;
172 /* Verify that the router signature was created using the signing
173 * key. */
174 if (!verifySignature(cert.getCertificateDigest(),
175 cert.getDirKeyCertification(), cert.getDirIdentityKey())) {
176 System.out.println("In " + descriptorFile.getFile()
177 + ", the decrypted directory key certification does not "
178 + "match the certificate digest!");
179 isVerified = false;
182 /* Determine the signing key digest and remember the signing key
183 * to verify consensus signatures. */
184 String dirSigningKeyString = cert.getDirSigningKey();
185 PEMReader pemReader2 = new PEMReader(new StringReader(
186 dirSigningKeyString));
187 RSAPublicKey dirSigningKey =
188 (RSAPublicKey) pemReader2.readObject();
189 ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
190 new ASN1OutputStream(baos2).writeObject(
191 new org.bouncycastle.asn1.pkcs.RSAPublicKey(
192 dirSigningKey.getModulus(),
193 dirSigningKey.getPublicExponent()).toASN1Primitive());
194 byte[] pkcs2 = baos2.toByteArray();
195 byte[] dirSigningKeyHashBytes = new byte[20];
196 SHA1Digest sha1_2 = new SHA1Digest();
197 sha1_2.update(pkcs2, 0, pkcs2.length);
198 sha1_2.doFinal(dirSigningKeyHashBytes, 0);
199 String dirSigningKeyHashString = Hex.encodeHexString(
200 dirSigningKeyHashBytes).toUpperCase();
201 signingKeys.put(dirSigningKeyHashString, cert.getDirSigningKey());
203 processedCerts++;
204 if (isVerified) {
205 verifiedCerts++;
209 System.out.println("Verified " + verifiedCerts + "/"
210 + processedCerts + " certs.");
212 DescriptorReader consensusReader = DescriptorSourceFactory
213 .createDescriptorReader();
214 consensusReader.addDirectory(consensusDirectory);
215 Iterator<DescriptorFile> consensusFiles =
216 consensusReader.readDescriptors();
217 int processedConsensuses = 0, verifiedConsensuses = 0;
218 while (consensusFiles.hasNext()) {
219 DescriptorFile consensusFile = consensusFiles.next();
220 if (consensusFile.getException() != null) {
221 System.err.println("Could not read/parse descriptor file "
222 + consensusFile.getFileName() + ": "
223 + consensusFile.getException().getMessage());
224 continue;
226 if (consensusFile.getDescriptors() == null) {
227 continue;
229 for (Descriptor descriptor : consensusFile.getDescriptors()) {
230 if (!(descriptor instanceof RelayNetworkStatusConsensus)) {
231 continue;
233 RelayNetworkStatusConsensus consensus =
234 (RelayNetworkStatusConsensus) descriptor;
235 boolean isVerified = true;
237 /* Verify all signatures using the corresponding certificates. */
238 if (consensus.getDirectorySignatures().isEmpty()) {
239 System.out.println(consensusFile.getFile()
240 + " does not contain any signatures.");
241 continue;
243 for (DirectorySignature signature :
244 consensus.getDirectorySignatures().values()) {
245 String signingKeyDigest = signature.getSigningKeyDigest();
246 if (!signingKeys.containsKey(signingKeyDigest)) {
247 System.out.println("Cannot find signing key with digest "
248 + signingKeyDigest + "!");
250 if (!verifySignature(consensus.getConsensusDigest(),
251 signature.getSignature(),
252 signingKeys.get(signingKeyDigest))) {
253 System.out.println("In " + consensusFile.getFile()
254 + ", the decrypted signature digest does not match the "
255 + "consensus digest!");
256 isVerified = false;
259 processedConsensuses++;
260 if (isVerified) {
261 verifiedConsensuses++;
265 System.out.println("Verified " + verifiedConsensuses + "/"
266 + processedConsensuses + " consensuses.");
269 private static String determineKeyHash(String key) throws Exception {
270 PEMReader pemReader = new PEMReader(new StringReader(key));
271 RSAPublicKey dirIdentityKey = (RSAPublicKey) pemReader.readObject();
272 ByteArrayOutputStream baos = new ByteArrayOutputStream();
273 new ASN1OutputStream(baos).writeObject(
274 new org.bouncycastle.asn1.pkcs.RSAPublicKey(
275 dirIdentityKey.getModulus(),
276 dirIdentityKey.getPublicExponent()).toASN1Primitive());
277 byte[] pkcs = baos.toByteArray();
278 byte[] dirIdentityKeyHashBytes = new byte[20];
279 SHA1Digest sha1 = new SHA1Digest();
280 sha1.update(pkcs, 0, pkcs.length);
281 sha1.doFinal(dirIdentityKeyHashBytes, 0);
282 String keyHash = Hex.encodeHexString(dirIdentityKeyHashBytes);
283 return keyHash;
286 private static boolean verifySignature(String digest, String signature,
287 String signingKey) throws Exception {
288 byte[] signatureBytes = Base64.decodeBase64(signature.substring(
289 0 + "-----BEGIN SIGNATURE-----\n".length(),
290 signature.length() - "-----END SIGNATURE-----\n".length()).
291 replaceAll("\n", ""));
292 RSAPublicKey rsaSigningKey = (RSAPublicKey) new PEMReader(
293 new StringReader(signingKey)).readObject();
294 RSAKeyParameters rsakp = new RSAKeyParameters(false,
295 rsaSigningKey.getModulus(),
296 rsaSigningKey.getPublicExponent());
297 PKCS1Encoding pe = new PKCS1Encoding(new RSAEngine());
298 pe.init(false, rsakp);
299 byte[] decryptedSignatureDigest = pe.processBlock(
300 signatureBytes, 0, signatureBytes.length);
301 String decryptedSignatureDigestString =
302 Hex.encodeHexString(decryptedSignatureDigest);
303 return decryptedSignatureDigestString.equalsIgnoreCase(digest);