-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathProver.java
More file actions
182 lines (159 loc) · 7.09 KB
/
Prover.java
File metadata and controls
182 lines (159 loc) · 7.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*
* Copyright (C) 2014 Tim Bray <tbray@textuality.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.textuality.keybase.lib.prover;
import android.util.Base64;
import com.textuality.keybase.lib.JWalk;
import com.textuality.keybase.lib.KeybaseException;
import com.textuality.keybase.lib.Proof;
import com.textuality.keybase.lib.Search;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
/**
* Supports Keybase proof verification. This is self-contained with no dependencies, except on
* the caller. Keybase proof checking requires checking OpenPGP signatures and fetching DNS
* TXT records, but libraries to do these things can be complex and heavyweight. Therefore
* this function requires that the caller provide signature-checking and DNS-fetching functions.
*
* A note on signature checking. Keybase proofs are ASCII-armored OpenPGP compressed messages
* with a signature. That means, in terms of OpenPGP packet types, the message contains a
* Compressed Data packet (tag=8). That in turn contains a one-pass signature packet (tag=4),
* a Literal Data packet (tag=11), and a signature packet (tag=2). You need to do the
* equivalent of pgp --decrypt, validating that the signature is correct and the signing key
* is the one the proof concerns.
*
* How to use:
* 1. call fetchProofData(), which will exhibit network latency. If it returns false the proof
* verification failed; an explanation can be found in the log.
* 2. call checkFingerprint(), passing it the fingerprint of the key you’re checking up on; if
* if it returns false the verification failed.
* 3. fetch the PGP message with getPgpMessage(), check that it’s signed with the right fingerprint
* (see above).
* 4. Call dnsTxtCheckRequired() and if it returns non-null, the return value is a domain name;
* retrieve TXT records from that domain and pass them to checkDnsTxt(); if it returns false
* the proof verification failed; an explanation can be found in the log.
* 5. call rawMessageCheckRequired() and if it returns true, feed the raw (de-armored) bytes
* of the message to checkRawMessageBytes(). if it returns false the proof verification failed;
* an explanation can be found in the log. This may exhibit crypto latency.
* 6. Pass the message to validate(), which should have no real latency. If it returns false the
* proof verification failed; an explanation can be found in the log.
*/
public abstract class Prover {
String mPgpMessage;
String mPayload;
String mShortenedMessageHash;
String mFingerprintUsedInProof = null;
final Proof mProof;
final List<String> mLog = new ArrayList<String>();
public static Prover findProverFor(Proof proof) {
switch (proof.getType()) {
case Proof.PROOF_TYPE_TWITTER: return new Twitter(proof);
case Proof.PROOF_TYPE_GITHUB: return new GitHub(proof);
case Proof.PROOF_TYPE_DNS: return new DNS(proof);
case Proof.PROOF_TYPE_WEB_SITE: return new Website(proof);
case Proof.PROOF_TYPE_HACKERNEWS: return new HackerNews(proof);
case Proof.PROOF_TYPE_COINBASE: return new Coinbase(proof);
case Proof.PROOF_TYPE_REDDIT: return new Reddit(proof);
default: return null;
}
}
public Prover(Proof proof) {
mProof = proof;
}
abstract public boolean fetchProofData(Proxy proxy);
public String getPgpMessage() {
return mPgpMessage;
}
public boolean checkFingerprint(String fingerprint) {
return fingerprint.equalsIgnoreCase(mFingerprintUsedInProof);
}
public boolean validate(String decryptedMessage) {
return mPayload.equals(decryptedMessage);
}
public List<String> getLog() {
return mLog;
}
JSONObject readSig(String sigId, Proxy proxy) throws JSONException, KeybaseException {
// fetch the sig
JSONObject sigJSON = Search.getFromKeybase("_/api/1.0/sig/get.json?sig_id=", sigId, proxy);
mLog.add("Successfully retrieved sig from Keybase");
sigJSON = JWalk.getArray(sigJSON, "sigs").getJSONObject(0);
mPayload = JWalk.getString(sigJSON, "payload_json");
mPgpMessage = JWalk.getString(sigJSON, "sig");
mFingerprintUsedInProof = JWalk.getString(sigJSON, "fingerprint");
mLog.add("Extracted payload & message from sig");
return sigJSON;
}
public boolean rawMessageCheckRequired() {
return false;
}
public boolean checkRawMessageBytes(InputStream in) {
try {
MessageDigest digester = MessageDigest.getInstance("SHA-256");
byte[] buffer = new byte[8192];
int byteCount;
while ((byteCount = in.read(buffer)) > 0) {
digester.update(buffer, 0, byteCount);
}
String digest = Base64.encodeToString(digester.digest(), Base64.URL_SAFE);
if (!digest.startsWith(mShortenedMessageHash)) {
mLog.add("Proof post doesn’t contain correct encoded message.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
mLog.add("SHA-256 not available");
} catch (IOException e) {
mLog.add("Error checking raw message: " + e.getLocalizedMessage());
}
return false;
}
public String dnsTxtCheckRequired() {
return null;
}
public boolean checkDnsTxt(List<List<byte[]>> records) {
return false;
}
/* A proof narrative needs the following strings:
* a Url for the actual proof document (may be null, e.g. for DNS)
* a Url for the person’s presence at the service e.g. https://twitter.com/timbray
* a name for the person's presence, e.g. twitter.com/timbray
*/
public String getProofUrl() throws KeybaseException {
return mProof.getHumanUrl();
}
public String getPresenceUrl() throws KeybaseException {
return mProof.getServiceUrl();
}
public String getPresenceLabel() throws KeybaseException {
String answer = mProof.getServiceUrl();
try {
URL u = new URL(answer);
answer = u.getHost() + u.getPath();
} catch (MalformedURLException e) {
}
return answer;
}
}