1 /* Copyright 2012 The Tor Project
2 * See LICENSE for licensing information */
3 import java
.io
.ByteArrayOutputStream
;
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
;
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.
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
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
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();
62 private static void verifyServerDescriptors() throws Exception
{
63 File serverDescriptorDirectory
= new File("in/server-descriptors");
64 if (!serverDescriptorDirectory
.exists()) {
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());
81 if (descriptorFile
.getDescriptors() == null) {
84 for (Descriptor descriptor
: descriptorFile
.getDescriptors()) {
85 if (!(descriptor
instanceof ServerDescriptor
)) {
88 ServerDescriptor serverDescriptor
= (ServerDescriptor
) descriptor
;
89 boolean isVerified
= true;
91 /* Verify that the contained fingerprint is a hash of the signing
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!");
104 /* Verify that the router signature was created using the signing
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 "
115 processedDescriptors
++;
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()) {
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());
147 if (descriptorFile
.getDescriptors() == null) {
150 for (Descriptor descriptor
: descriptorFile
.getDescriptors()) {
151 if (!(descriptor
instanceof DirectoryKeyCertificate
)) {
154 DirectoryKeyCertificate cert
=
155 (DirectoryKeyCertificate
) descriptor
;
156 boolean isVerified
= true;
158 /* Verify that the contained fingerprint is a hash of the signing
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
+ "!");
172 /* Verify that the router signature was created using the signing
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!");
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());
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());
226 if (consensusFile
.getDescriptors() == null) {
229 for (Descriptor descriptor
: consensusFile
.getDescriptors()) {
230 if (!(descriptor
instanceof RelayNetworkStatusConsensus
)) {
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.");
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!");
259 processedConsensuses
++;
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
);
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
);