Skip to content

Commit 7c3b3a6

Browse files
committed
Added PGP clearsign verifier
1 parent 1716c4b commit 7c3b3a6

File tree

8 files changed

+227
-4
lines changed

8 files changed

+227
-4
lines changed

app/test/cc/arduino/contributions/SignatureVerifierTest.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@
2929

3030
package cc.arduino.contributions;
3131

32-
import org.junit.Before;
33-
import org.junit.Test;
32+
import static org.junit.Assert.assertFalse;
33+
import static org.junit.Assert.assertTrue;
3434

3535
import java.io.File;
3636

37-
import static org.junit.Assert.assertFalse;
38-
import static org.junit.Assert.assertTrue;
37+
import org.junit.Before;
38+
import org.junit.Test;
3939

4040
public class SignatureVerifierTest {
4141

@@ -62,4 +62,24 @@ public void testSignatureFailingVerification() throws Exception {
6262
File sign = new File(SignatureVerifierTest.class.getResource("./package_index.json.sig").getFile());
6363
assertFalse(verifier.verify(fakeSignedFile, sign));
6464
}
65+
66+
@Test
67+
public void testClearTextSignatureVerification() throws Exception {
68+
File signedFile = new File(SignatureVerifierTest.class.getResource("./text.txt.asc").getFile());
69+
assertTrue(verifier.verifyCleartextSignature(signedFile));
70+
File signedFile2 = new File(SignatureVerifierTest.class.getResource("./escaped-text.txt.asc").getFile());
71+
assertTrue(verifier.verifyCleartextSignature(signedFile2));
72+
File oneBlankLines = new File(SignatureVerifierTest.class.getResource("./one-blank-line.txt.asc").getFile());
73+
assertTrue(verifier.verifyCleartextSignature(oneBlankLines));
74+
File twoBlankLines = new File(SignatureVerifierTest.class.getResource("./two-blank-lines.txt.asc").getFile());
75+
assertTrue(verifier.verifyCleartextSignature(twoBlankLines));
76+
File noBlankLines = new File(SignatureVerifierTest.class.getResource("./no-blank-lines.txt.asc").getFile());
77+
assertTrue(verifier.verifyCleartextSignature(noBlankLines));
78+
}
79+
80+
@Test
81+
public void testClearTextSignatureFailingVerification() throws Exception {
82+
File signedFile = new File(SignatureVerifierTest.class.getResource("./wrong-text.txt.asc").getFile());
83+
assertFalse(verifier.verifyCleartextSignature(signedFile));
84+
}
6585
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
-----BEGIN PGP SIGNED MESSAGE-----
2+
Hash: SHA512
3+
4+
- -
5+
- -
6+
- - abc
7+
- -- abc
8+
helllo!!!!
9+
10+
11+
12+
13+
14+
-----BEGIN PGP SIGNATURE-----
15+
16+
iQIzBAEBCgAdFiEEMmVnwcayiN8yywYalfpvQ+IRiMQFAl6CcaUACgkQlfpvQ+IR
17+
iMQD9w/+KA2ZusmFnpt7dXTy/6ohNCijMp3pFSVsBzGWBDpGJjYfwek2N8HssXIa
18+
Tq0uzCiTm1Z4kn+SWjIHxKzdihUi9mwkMCqdhNTZN0+FKXc+c358KUYXOlyigOmX
19+
256bG7Ep2P/3ZBzhvVC5WKLIwYhq6cj9fSnt26XP02qt/9ztczGJHDpEfIhPA0LI
20+
PYnUUo4KftQzHp41EPENIyLTkT1YzhypnIHCv2mG8qql+W9blx1eO8gIUGmzQQ0y
21+
CnTY0AIvmrAGd5WQWwKpKy5aLpWDmIW/zSSoDc7jC74i2V6n5Y+Fqq++SVDvIApd
22+
yP+BmL6pDfZf9cV8nDQOI9Jd+/JT3tUct5js9lDhj74g1ZGobx06kZPv/ojbSE42
23+
jJi75HQ3NDG8dBDMtYzrDeO0QhcpT94LNTdZ8IdR5jCf3I9SkpHB6sb27elVgcBb
24+
stXLRhrf0se2U2pI3CGDkjumm04cDOhY9tLt2CHMlL9yl9LZejyT3xUoLGAy1Ird
25+
Jw6knZM5O/bWzFq1bxlqgz6EspafNy+ZM8/1s+b9ecxKkds7UEZLpOVd+QYMO6i1
26+
lxm4ZQqRoTiIcVxzhwChd41zxPw9bMNq03prBMnetJ9tvb76qgGcCvRh/ANYWBfa
27+
2zeH3UGaOyMB79oKOSw0qzXkFA+0qXzROObVWTzjGOPZJtkfaA8=
28+
=BqjQ
29+
-----END PGP SIGNATURE-----
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-----BEGIN PGP SIGNED MESSAGE-----
2+
Hash: SHA512
3+
4+
hello
5+
-----BEGIN PGP SIGNATURE-----
6+
7+
iQIzBAEBCgAdFiEEMmVnwcayiN8yywYalfpvQ+IRiMQFAl6C/AsACgkQlfpvQ+IR
8+
iMQYUA//SEHkb60xiXyP8TfTfxiJORr7ltvLtL7N9IwpnrkeKdvvrxC71O5U4kdf
9+
Ek5XUALXUPArWJHByKBZ3saQapRPMUNbsLDG9QqtIffORqgPnfPAgMiGYceIaT1o
10+
QChspIQRL6xt6k8c6o2M5R7CBMjiDqaldpJHsq3kcjcgHBmJFPdDdhjA2D9s4mCo
11+
nymy3PqLcZ4RARrED61VlF4hcVHv5BHPnDZDXug3oGiR2EQTpdQgvBg/rL2xiVHl
12+
MgZ6/5owF6omeNM30JNPQfNdDnV2jDhxILfjY2Rqyj9zClrnZB6qox6h1RoSKIFy
13+
UkBUAs3WapZMqR2UKq3K7dL8cqUKoOFdSpd/R2T4ZBC/6exiwVnbDGa161ur8f4/
14+
55RxAe/VE+75bC7HI3jNAUn7xGMUGBWgTKh+xTnyh50BHifmCIzyLOgRWds/rvMH
15+
PT0fmtbTWhjrJOfuhi15uSJ0Lvq/XuM/z/aIgeZHaIBnIxvRYUOdP6Rz1xKHmc2q
16+
+puI2FxpbENUyt8TNlrnHJeq72UubVIRJ08CE2iiuWjP+1jFxPYA1tWBYrqtbK7y
17+
2ZPq5c+vy8LMij/SfZkeOa388Ss3lXr5CTJ+O2VMLellyCdUd0G47T26umCr6zaF
18+
jX1huU0rfA+vGLVfr1Z2nexX0r7kwebkz/PWIO1+Kc41gBY+cek=
19+
=Rejh
20+
-----END PGP SIGNATURE-----
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-----BEGIN PGP SIGNED MESSAGE-----
2+
Hash: SHA512
3+
4+
hello
5+
6+
-----BEGIN PGP SIGNATURE-----
7+
8+
iQIzBAEBCgAdFiEEMmVnwcayiN8yywYalfpvQ+IRiMQFAl6C/E8ACgkQlfpvQ+IR
9+
iMS/iA/+KxhaOTokmXTKgSkZwaCdPwTIdcfIVWMsK6mxXcSeWtmQpCxMyH3sudaA
10+
MvVg8qMHTTiYbFD5TMBF79Eu3nJuLemnm7WbpQMAQ7/Ay8qDrH6MIIqeR6TLa6CO
11+
VQw2a9H3AoqVxBtH0y6vVk+mOnr8NnZA3XoT0B2TuLApqY4uFt9quHBkrycHdXve
12+
6++EyiYeDgY6oG1bAR0S/peFhCL45CId9xJe5RIHT7aFoDKw78LPcfoTLdKCtzzE
13+
CdX1IpzP/tOVlv0LTNPWQQeRQ07BKpquQnQbzuAgVFbLf27kI2e+AxjIdmozEjO9
14+
rk9Za0tpKYCYT5lF9zfmaCWzkhTfrjB8OPXWYHjozzv4aUhfvgQ+TF9dynWU4ct8
15+
ACRkb+bp+ENZVy0/1VdDDs3wrFnMHuZyrBWXNI8sBu6f1JSG4ddgy7NfPveOJEdG
16+
wg/4mxzSnBQ7jogRaJLioC+mjxulmN1FCoDMwphOLkmaIX1qIL3OxkEBfSzpfkfe
17+
wAYhjSqLHHXwx0W0resDahC2L81gBnTbf0yP1jEWjnSs96AqwvLSAj0gyC4BtVoN
18+
LSgYDFeNitoBMDx8osi0KjLydOzbQW9L5u//Rm/Q/8/p7+4u/X3X8EWHULHIsrRW
19+
T9M6jun8zTr9OLAk9W1T76Yu2Wyu+ps6f6iAeSlvqTTpr3+j2W4=
20+
=0JE+
21+
-----END PGP SIGNATURE-----
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-----BEGIN PGP SIGNED MESSAGE-----
2+
Hash: SHA512
3+
4+
helllo!!!!
5+
6+
-----BEGIN PGP SIGNATURE-----
7+
8+
iQIzBAEBCgAdFiEEMmVnwcayiN8yywYalfpvQ+IRiMQFAl6CPtwACgkQlfpvQ+IR
9+
iMS8AA//dCIbYCPwTdOFf3YRFO2Z7YSgAgEpctcH1UW9IaZpGlfZDqiskgNT5zWx
10+
ooW0EqVagyyY+1Fips4a52/urQmbQ+0RIU6r4zDUvyMoN/f41jFKd1eY365oqORo
11+
G5avMFhZ7PwbYXKhDeVQrDCRSYSvWYyRXH97VpFvo/SSuvB7KWvlTFFK9rt1xysx
12+
rjikCqMWuPeReSclLCMCGQGjcJExNsu7E9l55NcJMOP8a3yVlY9b1LwH5nuVXOfw
13+
UvSzHK2K16O3COb0otWN8VZHB0x2y3Y1boIF2J/Wt9zBaB/d4cmacwL4KLHiw7KR
14+
q5YzDfEpmwMM9S5QLsLPYBWr9B1XSQ+PrylPSha1NnDStr/RkFtwVsags3igJu5E
15+
Ye+aJra6D/VREFc/IQYsEmcDHYdKp3CNJn/BdNwTDI4BIdj4lBDd6lVrcL+Id8pl
16+
ivdk1bV/575eTH9cDf65aF/lmd/z6dGNLcke4N76n8g/xjAuSkN7FDm91KGv9oCJ
17+
e0eO0+GDJbfMHz61vQ1DPfUWqyQLC3z9OeX+bUvqSb1Xy9i2OI0FlY3GMtwDONto
18+
qShjcbeXXYt74bINsAEV78pSoYPHc2rYco1/qrAL1bYY9Hngy3FvgD7mVNd7nC/J
19+
buFQ4Ek07urJpA9lw8o/z49O7iGisc8UXuPeHY2z7LmUuCvKpKI=
20+
=DKVh
21+
-----END PGP SIGNATURE-----
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN PGP SIGNED MESSAGE-----
2+
Hash: SHA512
3+
4+
hello
5+
6+
7+
-----BEGIN PGP SIGNATURE-----
8+
9+
iQIzBAEBCgAdFiEEMmVnwcayiN8yywYalfpvQ+IRiMQFAl6C/H8ACgkQlfpvQ+IR
10+
iMQD9RAAk34n7WegdXdreKLU5nAkx9/lcPwZYRcwTeV6pgroxQ3XuC3oEqUwDQtD
11+
RLT+Gc+1j7XMGxTIJtHhpwpJk2IeT5x/lfqpMwpHoJUEOA/QZ7YtUTUj5aqc3bJV
12+
QEWXrNyceqaYkUguR7gk0ahtniTuaj9WaixRpxiu9+AMht74g0iZm99h2fpgiIb1
13+
7M2SdMbmyW1mK7BFN9Cghz/8NedV6TeKpQnWWpN+hdO1fsjGIVRNRfTMod7gL6QK
14+
KhPa3eQW+yxlEshKZwQJwIe3vcgquK6bAl/p20EU7+2ytnFd3zBhcCFJtC6fV+zF
15+
s89BOAoAJPRaBKbQp01IsBB5mNZUxKFOa+e94tFLmaaI7KhHB7oQ7Qnx+hTgq7bG
16+
9PRza0bHMkBPumACM8FNjOlzJNY5eTnfDcAq2kgaAdoX5LFLrIiYUNtz9gmIIHNj
17+
xekG9LB3WUv013jPSOUnbmGkch0VvVgFvcsOtGWTHp+VlZLOWZjXkO+Cco2qWcVr
18+
F7ckPTXs+nzoOTVsYZLfN8g92y2sT548+Q9bCtT1QStGCSTyNF1o0JW9Ih1hsSkn
19+
qJVgo8wGO45mTT4GJ6VWUkFkAjoaZM14F70CJp0eLw5PSxDIkSsmkL7CYwGeA+6N
20+
UtKIJ1ZoFMElW5bMuSayLEjtC9RkGILjplkiFY+h7gJd/vitTvI=
21+
=g/9c
22+
-----END PGP SIGNATURE-----
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-----BEGIN PGP SIGNED MESSAGE-----
2+
Hash: SHA512
3+
4+
!!helllo!!!!
5+
6+
-----BEGIN PGP SIGNATURE-----
7+
8+
iQIzBAEBCgAdFiEEMmVnwcayiN8yywYalfpvQ+IRiMQFAl6CPtwACgkQlfpvQ+IR
9+
iMS8AA//dCIbYCPwTdOFf3YRFO2Z7YSgAgEpctcH1UW9IaZpGlfZDqiskgNT5zWx
10+
ooW0EqVagyyY+1Fips4a52/urQmbQ+0RIU6r4zDUvyMoN/f41jFKd1eY365oqORo
11+
G5avMFhZ7PwbYXKhDeVQrDCRSYSvWYyRXH97VpFvo/SSuvB7KWvlTFFK9rt1xysx
12+
rjikCqMWuPeReSclLCMCGQGjcJExNsu7E9l55NcJMOP8a3yVlY9b1LwH5nuVXOfw
13+
UvSzHK2K16O3COb0otWN8VZHB0x2y3Y1boIF2J/Wt9zBaB/d4cmacwL4KLHiw7KR
14+
q5YzDfEpmwMM9S5QLsLPYBWr9B1XSQ+PrylPSha1NnDStr/RkFtwVsags3igJu5E
15+
Ye+aJra6D/VREFc/IQYsEmcDHYdKp3CNJn/BdNwTDI4BIdj4lBDd6lVrcL+Id8pl
16+
ivdk1bV/575eTH9cDf65aF/lmd/z6dGNLcke4N76n8g/xjAuSkN7FDm91KGv9oCJ
17+
e0eO0+GDJbfMHz61vQ1DPfUWqyQLC3z9OeX+bUvqSb1Xy9i2OI0FlY3GMtwDONto
18+
qShjcbeXXYt74bINsAEV78pSoYPHc2rYco1/qrAL1bYY9Hngy3FvgD7mVNd7nC/J
19+
buFQ4Ek07urJpA9lw8o/z49O7iGisc8UXuPeHY2z7LmUuCvKpKI=
20+
=DKVh
21+
-----END PGP SIGNATURE-----

arduino-core/src/cc/arduino/contributions/SignatureVerifier.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,20 @@
3131

3232
import java.io.File;
3333
import java.io.FileInputStream;
34+
import java.io.FileNotFoundException;
3435
import java.io.IOException;
3536
import java.io.InputStream;
3637

3738
import org.apache.commons.compress.utils.IOUtils;
39+
import org.bouncycastle.bcpg.ArmoredInputStream;
3840
import org.bouncycastle.openpgp.PGPException;
3941
import org.bouncycastle.openpgp.PGPObjectFactory;
4042
import org.bouncycastle.openpgp.PGPPublicKey;
4143
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
4244
import org.bouncycastle.openpgp.PGPSignature;
4345
import org.bouncycastle.openpgp.PGPSignatureList;
4446
import org.bouncycastle.openpgp.PGPUtil;
47+
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
4548
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
4649
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
4750

@@ -127,4 +130,70 @@ private PGPPublicKey readPublicKey(long id) throws IOException, PGPException {
127130
}
128131
}
129132

133+
public String[] extractTextFromCleartextSignature(File inFile) throws FileNotFoundException, IOException {
134+
try (ArmoredInputStream in = new ArmoredInputStream(new FileInputStream(inFile))) {
135+
return extractTextFromCleartextSignature(in);
136+
}
137+
}
138+
139+
public boolean verifyCleartextSignature(File inFile) {
140+
try (ArmoredInputStream in = new ArmoredInputStream(new FileInputStream(inFile))) {
141+
String[] clearTextLines = extractTextFromCleartextSignature(in);
142+
int clearTextSize = clearTextLines.length;
143+
144+
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(in);
145+
PGPSignatureList p3 = (PGPSignatureList) pgpFact.nextObject();
146+
PGPSignature sig = p3.get(0);
147+
PGPPublicKey publicKey = readPublicKey(sig.getKeyID());
148+
149+
sig.init(new BcPGPContentVerifierBuilderProvider(), publicKey);
150+
for (int i = 0; i < clearTextSize; i++) {
151+
sig.update(clearTextLines[i].getBytes());
152+
if (i + 1 < clearTextSize) {
153+
// https://tools.ietf.org/html/rfc4880#section-7
154+
// Convert all line endings to '\r\n'
155+
sig.update((byte) '\r');
156+
sig.update((byte) '\n');
157+
}
158+
}
159+
return sig.verify();
160+
} catch (Exception e) {
161+
e.printStackTrace();
162+
return false;
163+
}
164+
}
165+
166+
private String[] extractTextFromCleartextSignature(ArmoredInputStream in) throws FileNotFoundException, IOException {
167+
// https://tools.ietf.org/html/rfc4880#section-7
168+
// ArmoredInputStream does unescape dash-escaped string in armored text and skips
169+
// all headers. To calculate the signature we still need to:
170+
// 1. handle different line endings \n or \n\r or \r\n
171+
// 2. remove trailing whitespaces from each line (' ' and '\t')
172+
// 3. remove the latest line ending
173+
174+
String clearText = "";
175+
for (;;) {
176+
int c = in.read();
177+
// in.isClearText() refers to the PREVIOUS byte read
178+
if (c == -1 || !in.isClearText()) {
179+
break;
180+
}
181+
// 1. convert all line endings to '\r\n'
182+
if (c == '\r') {
183+
continue;
184+
}
185+
clearText += (char) c;
186+
}
187+
188+
// 3. remove the latest line ending
189+
if (clearText.endsWith("\n")) {
190+
clearText = clearText.substring(0, clearText.length() - 1);
191+
}
192+
String[] lines = clearText.split("\n", -1);
193+
for (int i = 0; i < lines.length; i++) {
194+
// 2. remove trailing whitespaces from each line (' ' and '\t')
195+
lines[i] = lines[i].replaceAll("[ \\t]+$", "");
196+
}
197+
return lines;
198+
}
130199
}

0 commit comments

Comments
 (0)