Skip to content

Commit 58713da

Browse files
committed
Merge branch '1768-seipdv2-pkeskv6' into 'main'
#1768 Implement message decryption using SEIPDv2 and PKESKv6 packets See merge request root/bc-java!25
2 parents 478f51c + fd19596 commit 58713da

11 files changed

+880
-171
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
@@ -84,13 +84,13 @@ public int getSymmetricAlgorithm(
8484
{
8585
if (keyData.getVersion() == PublicKeyEncSessionPacket.VERSION_3)
8686
{
87-
byte[] plain = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey());
87+
byte[] plain = dataDecryptorFactory.recoverSessionData(keyData, encData);
8888
// symmetric cipher algorithm is stored in first octet of session data
8989
return plain[0];
9090
}
9191
else if (keyData.getVersion() == PublicKeyEncSessionPacket.VERSION_6)
9292
{
93-
// PKESK v5 stores the cipher algorithm in the SEIPD v2 packet fields.
93+
// PKESK v6 stores the cipher algorithm in the SEIPD v2 packet fields.
9494
return ((SymmetricEncIntegrityPacket)encData).getCipherAlgorithm();
9595
}
9696
else
@@ -110,16 +110,57 @@ public PGPSessionKey getSessionKey(
110110
PublicKeyDataDecryptorFactory dataDecryptorFactory)
111111
throws PGPException
112112
{
113-
byte[] sessionData = dataDecryptorFactory.recoverSessionData(keyData.getAlgorithm(), keyData.getEncSessionKey());
114-
if (keyData.getAlgorithm() == PublicKeyAlgorithmTags.X25519 || keyData.getAlgorithm() == PublicKeyAlgorithmTags.X448)
113+
byte[] sessionInfo = dataDecryptorFactory.recoverSessionData(keyData, encData);
114+
115+
// Confirm and discard checksum
116+
if (containsChecksum(keyData.getAlgorithm()))
117+
{
118+
if (!confirmCheckSum(sessionInfo))
119+
{
120+
throw new PGPException("Key checksum failed.");
121+
}
122+
sessionInfo = Arrays.copyOf(sessionInfo, sessionInfo.length - 2);
123+
}
124+
125+
byte[] sessionKey = Arrays.copyOfRange(sessionInfo, 1, sessionInfo.length);
126+
int algorithm;
127+
128+
// OCB (LibrePGP v5 style AEAD)
129+
if (encData instanceof AEADEncDataPacket)
115130
{
116-
return new PGPSessionKey(sessionData[0] & 0xff, Arrays.copyOfRange(sessionData, 1, sessionData.length));
131+
algorithm = ((AEADEncDataPacket) encData).getAlgorithm();
132+
}
133+
134+
// SEIPD (OpenPGP v4 / OpenPGP v6)
135+
else if (encData instanceof SymmetricEncIntegrityPacket)
136+
{
137+
SymmetricEncIntegrityPacket seipd = (SymmetricEncIntegrityPacket) encData;
138+
if (seipd.getVersion() == SymmetricEncIntegrityPacket.VERSION_1)
139+
{
140+
algorithm = sessionInfo[0];
141+
}
142+
else if (seipd.getVersion() == SymmetricEncIntegrityPacket.VERSION_2)
143+
{
144+
algorithm = seipd.getCipherAlgorithm();
145+
}
146+
else
147+
{
148+
throw new UnsupportedPacketVersionException("Unsupported SEIPD packet version: " + seipd.getVersion());
149+
}
117150
}
118-
if (!confirmCheckSum(sessionData))
151+
// SED (Legacy, no integrity protection!)
152+
else
119153
{
120-
throw new PGPKeyValidationException("key checksum failed");
154+
algorithm = sessionInfo[0];
121155
}
122-
return new PGPSessionKey(sessionData[0] & 0xff, Arrays.copyOfRange(sessionData, 1, sessionData.length - 2));
156+
157+
return new PGPSessionKey(algorithm & 0xff, sessionKey);
158+
}
159+
160+
private boolean containsChecksum(int algorithm)
161+
{
162+
return algorithm != PublicKeyAlgorithmTags.X25519 &&
163+
algorithm != PublicKeyAlgorithmTags.X448;
123164
}
124165

125166
/**
@@ -181,13 +222,38 @@ private InputStream getDataStream(
181222
}
182223
else
183224
{
184-
boolean withIntegrityPacket = encData instanceof SymmetricEncIntegrityPacket;
185225

186-
PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(withIntegrityPacket, sessionKey.getAlgorithm(), sessionKey.getKey());
226+
if (encData instanceof SymmetricEncIntegrityPacket)
227+
{
228+
SymmetricEncIntegrityPacket seipd = (SymmetricEncIntegrityPacket) encData;
229+
// SEIPD v1 (OpenPGP v4)
230+
if (seipd.getVersion() == SymmetricEncIntegrityPacket.VERSION_1)
231+
{
232+
PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(true, sessionKey.getAlgorithm(), sessionKey.getKey());
187233

188-
BCPGInputStream encIn = encData.getInputStream();
234+
BCPGInputStream encIn = encData.getInputStream();
189235

190-
processSymmetricEncIntegrityPacketDataStream(withIntegrityPacket, dataDecryptor, encIn);
236+
processSymmetricEncIntegrityPacketDataStream(true, dataDecryptor, encIn);
237+
}
238+
// SEIPD v2 (OpenPGP v6 AEAD)
239+
else
240+
{
241+
PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(seipd, sessionKey);
242+
243+
BCPGInputStream encIn = encData.getInputStream();
244+
245+
encStream = new BCPGInputStream(dataDecryptor.getInputStream(encIn));
246+
}
247+
}
248+
// SED (Symmetrically Encrypted Data without Integrity Protection; Deprecated)
249+
else
250+
{
251+
PGPDataDecryptor dataDecryptor = dataDecryptorFactory.createDataDecryptor(false, sessionKey.getAlgorithm(), sessionKey.getKey());
252+
253+
BCPGInputStream encIn = encData.getInputStream();
254+
255+
processSymmetricEncIntegrityPacketDataStream(false, dataDecryptor, encIn);
256+
}
191257

192258
//
193259
// some versions of PGP appear to produce 0 for the extra
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
@Override
25+
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
26+
throws PGPException
27+
{
28+
return recoverSessionData(keyAlgorithm, secKeyData, PublicKeyEncSessionPacket.VERSION_3);
29+
}
30+
31+
protected byte[] prependSKAlgorithmToSessionData(PublicKeyEncSessionPacket pkesk,
32+
InputStreamPacket encData,
33+
byte[] decryptedSessionData)
34+
throws PGPException
35+
{
36+
// V6 PKESK packets do not include the session key algorithm, so source it from the SEIPD2 instead
37+
if (!containsSKAlg(pkesk.getVersion()))
38+
{
39+
if (!(encData instanceof SymmetricEncIntegrityPacket) ||
40+
((SymmetricEncIntegrityPacket) encData).getVersion() != SymmetricEncIntegrityPacket.VERSION_2)
41+
{
42+
throw new PGPException("v6 PKESK packet MUST precede v2 SEIPD packet");
43+
}
44+
45+
SymmetricEncIntegrityPacket seipd2 = (SymmetricEncIntegrityPacket) encData;
46+
return Arrays.prepend(decryptedSessionData,
47+
(byte) (seipd2.getCipherAlgorithm() & 0xff));
48+
}
49+
// V3 PKESK does store the session key algorithm either encrypted or unencrypted, depending on the PK algorithm
50+
else
51+
{
52+
switch (pkesk.getAlgorithm())
53+
{
54+
case PublicKeyAlgorithmTags.X25519:
55+
// X25519 does not encrypt SK algorithm
56+
return Arrays.prepend(decryptedSessionData,
57+
pkesk.getEncSessionKey()[0][X25519PublicBCPGKey.LENGTH + 1]);
58+
case PublicKeyAlgorithmTags.X448:
59+
// X448 does not encrypt SK algorithm
60+
return Arrays.prepend(decryptedSessionData,
61+
pkesk.getEncSessionKey()[0][X448PublicBCPGKey.LENGTH + 1]);
62+
default:
63+
// others already prepended session key algorithm to session key
64+
return decryptedSessionData;
65+
}
66+
}
67+
}
68+
69+
protected boolean containsSKAlg(int pkeskVersion)
70+
{
71+
return pkeskVersion != PublicKeyEncSessionPacket.VERSION_6;
72+
}
73+
74+
protected static void checkRange(int pLen, byte[] enc)
75+
throws PGPException
76+
{
77+
if (pLen > enc.length)
78+
{
79+
throw new PGPException("encoded length out of range");
80+
}
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,54 @@
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
{
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)
21+
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+
* @return decrypted session info
33+
* @throws PGPException
34+
*/
835
byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
36+
throws PGPException;
37+
38+
/**
39+
* Recover the plain session info by decrypting the encrypted session key.
40+
* This method returns the decrypted session info as-is (without prefixing missing cipher algorithm),
41+
* so the return value is:
42+
* <pre>[sym-alg]?[session-key][checksum]?</pre>
43+
*
44+
* @deprecated use {@link #recoverSessionData(PublicKeyEncSessionPacket, InputStreamPacket)} instead.
45+
* @param keyAlgorithm public key algorithm
46+
* @param secKeyData encrypted session key data
47+
* @param pkeskVersion version of the PKESK packet
48+
* @return decrypted session info
49+
* @throws PGPException
50+
*/
51+
byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData, int pkeskVersion)
952
throws PGPException;
53+
1054
}

0 commit comments

Comments
 (0)