Skip to content

Commit 1232b9f

Browse files
author
gefeili
committed
#1768 Implement message decryption using SEIPDv2 and PKESKv6 packets
2 parents 394e709 + 631ff98 commit 1232b9f

11 files changed

+899
-203
lines changed

pg/src/main/java/org/bouncycastle/bcpg/PublicKeyEncSessionPacket.java

+11
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,23 @@ else if (version == VERSION_6)
6060
// anon recipient
6161
keyVersion = 0;
6262
keyFingerprint = new byte[0];
63+
keyID = 0L;
6364
}
6465
else
6566
{
6667
keyVersion = in.read();
6768
keyFingerprint = new byte[keyInfoLen - 1];
6869
in.readFully(keyFingerprint);
70+
// Derived key-ID from fingerprint
71+
// TODO: Replace with getKeyIdentifier
72+
if (keyVersion == PublicKeyPacket.VERSION_4)
73+
{
74+
keyID = FingerprintUtil.keyIdFromV4Fingerprint(keyFingerprint);
75+
}
76+
else
77+
{
78+
keyID = FingerprintUtil.keyIdFromV6Fingerprint(keyFingerprint);
79+
}
6980
}
7081
}
7182
else

pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKeyEncryptedData.java

+78-12
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@ public int getSymmetricAlgorithm(
7272
{
7373
if (keyData.getVersion() == PublicKeyEncSessionPacket.VERSION_3)
7474
{
75-
byte[] plain = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey());
75+
byte[] plain = dataDecryptorFactory.recoverSessionData(keyData, encData);
7676
// symmetric cipher algorithm is stored in first octet of session data
7777
return plain[0];
7878
}
7979
else if (keyData.getVersion() == PublicKeyEncSessionPacket.VERSION_6)
8080
{
81-
// PKESK v5 stores the cipher algorithm in the SEIPD v2 packet fields.
81+
// PKESK v6 stores the cipher algorithm in the SEIPD v2 packet fields.
8282
return ((SymmetricEncIntegrityPacket)encData).getCipherAlgorithm();
8383
}
8484
else
@@ -98,16 +98,57 @@ public PGPSessionKey getSessionKey(
9898
PublicKeyDataDecryptorFactory dataDecryptorFactory)
9999
throws PGPException
100100
{
101-
byte[] sessionData = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey());
102-
if (keyData.getAlgorithm() == PublicKeyAlgorithmTags.X25519 || keyData.getAlgorithm() == PublicKeyAlgorithmTags.X448)
101+
byte[] sessionInfo = dataDecryptorFactory.recoverSessionData(keyData, encData);
102+
103+
// Confirm and discard checksum
104+
if (containsChecksum(keyData.getAlgorithm()))
105+
{
106+
if (!confirmCheckSum(sessionInfo))
107+
{
108+
throw new PGPException("Key checksum failed.");
109+
}
110+
sessionInfo = Arrays.copyOf(sessionInfo, sessionInfo.length - 2);
111+
}
112+
113+
byte[] sessionKey = Arrays.copyOfRange(sessionInfo, 1, sessionInfo.length);
114+
int algorithm;
115+
116+
// OCB (LibrePGP v5 style AEAD)
117+
if (encData instanceof AEADEncDataPacket)
103118
{
104-
return new PGPSessionKey(sessionData[0] & 0xff, Arrays.copyOfRange(sessionData, 1, sessionData.length));
119+
algorithm = ((AEADEncDataPacket) encData).getAlgorithm();
120+
}
121+
122+
// SEIPD (OpenPGP v4 / OpenPGP v6)
123+
else if (encData instanceof SymmetricEncIntegrityPacket)
124+
{
125+
SymmetricEncIntegrityPacket seipd = (SymmetricEncIntegrityPacket) encData;
126+
if (seipd.getVersion() == SymmetricEncIntegrityPacket.VERSION_1)
127+
{
128+
algorithm = sessionInfo[0];
129+
}
130+
else if (seipd.getVersion() == SymmetricEncIntegrityPacket.VERSION_2)
131+
{
132+
algorithm = seipd.getCipherAlgorithm();
133+
}
134+
else
135+
{
136+
throw new UnsupportedPacketVersionException("Unsupported SEIPD packet version: " + seipd.getVersion());
137+
}
105138
}
106-
if (!confirmCheckSum(sessionData))
139+
// SED (Legacy, no integrity protection!)
140+
else
107141
{
108-
throw new PGPKeyValidationException("key checksum failed");
142+
algorithm = sessionInfo[0];
109143
}
110-
return new PGPSessionKey(sessionData[0] & 0xff, Arrays.copyOfRange(sessionData, 1, sessionData.length - 2));
144+
145+
return new PGPSessionKey(algorithm & 0xff, sessionKey);
146+
}
147+
148+
private boolean containsChecksum(int algorithm)
149+
{
150+
return algorithm != PublicKeyAlgorithmTags.X25519 &&
151+
algorithm != PublicKeyAlgorithmTags.X448;
111152
}
112153

113154
/**
@@ -169,13 +210,38 @@ private InputStream getDataStream(
169210
}
170211
else
171212
{
172-
boolean withIntegrityPacket = encData instanceof SymmetricEncIntegrityPacket;
173213

174-
PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(withIntegrityPacket, sessionKey.getAlgorithm(), sessionKey.getKey());
214+
if (encData instanceof SymmetricEncIntegrityPacket)
215+
{
216+
SymmetricEncIntegrityPacket seipd = (SymmetricEncIntegrityPacket) encData;
217+
// SEIPD v1 (OpenPGP v4)
218+
if (seipd.getVersion() == SymmetricEncIntegrityPacket.VERSION_1)
219+
{
220+
PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(true, sessionKey.getAlgorithm(), sessionKey.getKey());
175221

176-
BCPGInputStream encIn = encData.getInputStream();
222+
BCPGInputStream encIn = encData.getInputStream();
177223

178-
processSymmetricEncIntegrityPacketDataStream(withIntegrityPacket, dataDecryptor, encIn);
224+
processSymmetricEncIntegrityPacketDataStream(true, dataDecryptor, encIn);
225+
}
226+
// SEIPD v2 (OpenPGP v6 AEAD)
227+
else
228+
{
229+
PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(seipd, sessionKey);
230+
231+
BCPGInputStream encIn = encData.getInputStream();
232+
233+
encStream = new BCPGInputStream(dataDecryptor.getInputStream(encIn));
234+
}
235+
}
236+
// SED (Symmetrically Encrypted Data without Integrity Protection; Deprecated)
237+
else
238+
{
239+
PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(false, sessionKey.getAlgorithm(), sessionKey.getKey());
240+
241+
BCPGInputStream encIn = encData.getInputStream();
242+
243+
processSymmetricEncIntegrityPacketDataStream(false, dataDecryptor, encIn);
244+
}
179245

180246
//
181247
// some versions of PGP appear to produce 0 for the extra
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package org.bouncycastle.openpgp.operator;
2+
3+
import org.bouncycastle.bcpg.InputStreamPacket;
4+
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
5+
import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
6+
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
7+
import org.bouncycastle.bcpg.X25519PublicBCPGKey;
8+
import org.bouncycastle.bcpg.X448PublicBCPGKey;
9+
import org.bouncycastle.openpgp.PGPException;
10+
import org.bouncycastle.util.Arrays;
11+
12+
public abstract class AbstractPublicKeyDataDecryptorFactory
13+
implements PublicKeyDataDecryptorFactory
14+
{
15+
16+
@Override
17+
public final byte[] recoverSessionData(PublicKeyEncSessionPacket pkesk, InputStreamPacket encData)
18+
throws PGPException
19+
{
20+
byte[] sessionData = recoverSessionData(pkesk.getAlgorithm(), pkesk.getEncSessionKey(), pkesk.getVersion());
21+
return prependSKAlgorithmToSessionData(pkesk, encData, sessionData);
22+
}
23+
24+
25+
protected byte[] prependSKAlgorithmToSessionData(PublicKeyEncSessionPacket pkesk,
26+
InputStreamPacket encData,
27+
byte[] decryptedSessionData)
28+
throws PGPException
29+
{
30+
// V6 PKESK packets do not include the session key algorithm, so source it from the SEIPD2 instead
31+
if (!containsSKAlg(pkesk.getVersion()))
32+
{
33+
if (!(encData instanceof SymmetricEncIntegrityPacket) ||
34+
((SymmetricEncIntegrityPacket) encData).getVersion() != SymmetricEncIntegrityPacket.VERSION_2)
35+
{
36+
throw new PGPException("v6 PKESK packet MUST precede v2 SEIPD packet");
37+
}
38+
39+
SymmetricEncIntegrityPacket seipd2 = (SymmetricEncIntegrityPacket) encData;
40+
return Arrays.prepend(decryptedSessionData,
41+
(byte) (seipd2.getCipherAlgorithm() & 0xff));
42+
}
43+
// V3 PKESK does store the session key algorithm either encrypted or unencrypted, depending on the PK algorithm
44+
else
45+
{
46+
switch (pkesk.getAlgorithm())
47+
{
48+
case PublicKeyAlgorithmTags.X25519:
49+
// X25519 does not encrypt SK algorithm
50+
return Arrays.prepend(decryptedSessionData,
51+
pkesk.getEncSessionKey()[0][X25519PublicBCPGKey.LENGTH + 1]);
52+
case PublicKeyAlgorithmTags.X448:
53+
// X448 does not encrypt SK algorithm
54+
return Arrays.prepend(decryptedSessionData,
55+
pkesk.getEncSessionKey()[0][X448PublicBCPGKey.LENGTH + 1]);
56+
default:
57+
// others already prepended session key algorithm to session key
58+
return decryptedSessionData;
59+
}
60+
}
61+
}
62+
63+
protected boolean containsSKAlg(int pkeskVersion)
64+
{
65+
return pkeskVersion != PublicKeyEncSessionPacket.VERSION_6;
66+
}
67+
68+
protected static void checkRange(int pLen, byte[] enc)
69+
throws PGPException
70+
{
71+
if (pLen > enc.length)
72+
{
73+
throw new PGPException("encoded length out of range");
74+
}
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,39 @@
11
package org.bouncycastle.openpgp.operator;
22

3+
import org.bouncycastle.bcpg.InputStreamPacket;
4+
import org.bouncycastle.bcpg.PublicKeyEncSessionPacket;
35
import org.bouncycastle.openpgp.PGPException;
46

57
public interface PublicKeyDataDecryptorFactory
68
extends PGPDataDecryptorFactory
79
{
8-
byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
10+
/**
11+
* Recover the plain session info by decrypting the encrypted session key.
12+
* The session info ALWAYS has the symmetric algorithm ID prefixed, so the return value is:
13+
* <pre>[sym-alg][session-key][checksum]?</pre>
14+
*
15+
* @param pkesk public-key encrypted session-key packet
16+
* @param encData encrypted data (sed/seipd/oed) packet
17+
* @return decrypted session info
18+
* @throws PGPException
19+
*/
20+
byte[] recoverSessionData(PublicKeyEncSessionPacket pkesk, InputStreamPacket encData)
921
throws PGPException;
22+
23+
/**
24+
* Recover the plain session info by decrypting the encrypted session key.
25+
* This method returns the decrypted session info as-is (without prefixing missing cipher algorithm),
26+
* so the return value is:
27+
* <pre>[sym-alg]?[session-key][checksum]?</pre>
28+
*
29+
* @deprecated use {@link #recoverSessionData(PublicKeyEncSessionPacket, InputStreamPacket)} instead.
30+
* @param keyAlgorithm public key algorithm
31+
* @param secKeyData encrypted session key data
32+
* @param pkeskVersion version of the PKESK packet
33+
* @return decrypted session info
34+
* @throws PGPException
35+
*/
36+
byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData, int pkeskVersion)
37+
throws PGPException;
38+
1039
}

0 commit comments

Comments
 (0)