Skip to content

Commit e3aab4f

Browse files
author
Andrei Stryhelski
committed
Fix crypt filter for AES-GSM
Fix tests DEVSIX-8588
1 parent 372ff5b commit e3aab4f

File tree

21 files changed

+333
-194
lines changed

21 files changed

+333
-194
lines changed

kernel/src/main/java/com/itextpdf/kernel/crypto/securityhandler/PubSecHandlerUsingAes256.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,16 @@ This file is part of the iText (R) project.
3333

3434
public class PubSecHandlerUsingAes256 extends PubSecHandlerUsingAes128 {
3535

36-
public PubSecHandlerUsingAes256(PdfDictionary encryptionDictionary, Certificate[] certs, int[] permissions, boolean encryptMetadata, boolean embeddedFilesOnly) {
36+
public PubSecHandlerUsingAes256(PdfDictionary encryptionDictionary, Certificate[] certs, int[] permissions,
37+
boolean encryptMetadata, boolean embeddedFilesOnly) {
3738
super(encryptionDictionary, certs, permissions, encryptMetadata, embeddedFilesOnly);
3839
}
3940

40-
public PubSecHandlerUsingAes256(PdfDictionary encryptionDictionary, Key certificateKey, Certificate certificate, String certificateKeyProvider, IExternalDecryptionProcess externalDecryptionProcess, boolean encryptMetadata) {
41-
super(encryptionDictionary, certificateKey, certificate, certificateKeyProvider, externalDecryptionProcess, encryptMetadata);
41+
public PubSecHandlerUsingAes256(PdfDictionary encryptionDictionary, Key certificateKey, Certificate certificate,
42+
String certificateKeyProvider, IExternalDecryptionProcess externalDecryptionProcess,
43+
boolean encryptMetadata) {
44+
super(encryptionDictionary, certificateKey, certificate, certificateKeyProvider, externalDecryptionProcess,
45+
encryptMetadata);
4246
}
4347

4448
@Override
@@ -57,19 +61,27 @@ protected void initKey(byte[] globalKey, int keyLength) {
5761
}
5862

5963
@Override
60-
protected void setPubSecSpecificHandlerDicEntries(PdfDictionary encryptionDictionary, boolean encryptMetadata, boolean embeddedFilesOnly) {
64+
protected void setPubSecSpecificHandlerDicEntries(PdfDictionary encryptionDictionary, boolean encryptMetadata,
65+
boolean embeddedFilesOnly) {
66+
int version = 5;
67+
PdfName filter = PdfName.AESV3;
68+
setEncryptionDictEntries(encryptionDictionary, encryptMetadata, embeddedFilesOnly, version, filter);
69+
}
70+
71+
void setEncryptionDictEntries(PdfDictionary encryptionDictionary, boolean encryptMetadata,
72+
boolean embeddedFilesOnly, int version, PdfName cryptFilter) {
6173
encryptionDictionary.put(PdfName.Filter, PdfName.Adobe_PubSec);
6274
encryptionDictionary.put(PdfName.SubFilter, PdfName.Adbe_pkcs7_s5);
6375

64-
encryptionDictionary.put(PdfName.V, new PdfNumber(5));
76+
encryptionDictionary.put(PdfName.V, new PdfNumber(version));
6577

6678
PdfArray recipients = createRecipientsArray();
6779
PdfDictionary stdcf = new PdfDictionary();
6880
stdcf.put(PdfName.Recipients, recipients);
6981
if (!encryptMetadata) {
7082
stdcf.put(PdfName.EncryptMetadata, PdfBoolean.FALSE);
7183
}
72-
stdcf.put(PdfName.CFM, PdfName.AESV3);
84+
stdcf.put(PdfName.CFM, cryptFilter);
7385
stdcf.put(PdfName.Length, new PdfNumber(256));
7486
PdfDictionary cf = new PdfDictionary();
7587
cf.put(PdfName.DefaultCryptFilter, stdcf);

kernel/src/main/java/com/itextpdf/kernel/crypto/securityhandler/PubSecHandlerUsingAesGcm.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ This file is part of the iText (R) project.
2828
import com.itextpdf.kernel.crypto.OutputStreamEncryption;
2929
import com.itextpdf.kernel.pdf.PdfDictionary;
3030
import com.itextpdf.kernel.pdf.PdfName;
31-
import com.itextpdf.kernel.pdf.PdfNumber;
3231
import com.itextpdf.kernel.security.IExternalDecryptionProcess;
3332

3433
import java.io.OutputStream;
@@ -77,6 +76,12 @@ public PubSecHandlerUsingAesGcm(PdfDictionary encryptionDictionary, Key certific
7776

7877
@Override
7978
public void setHashKeyForNextObject(int objNumber, int objGeneration) {
79+
// Make sure the same IV is never used twice in the same file. We do this by turning the objId/objGen into a
80+
// 5-byte nonce (with generation restricted to 1 byte instead of 2) plus an in-object 2-byte counter that
81+
// increments each time a new string is encrypted within the same object. The remaining 5 bytes will be
82+
// generated randomly using a strong PRNG.
83+
// This is very different from the situation with AES-CBC, where randomness is paramount.
84+
// GCM uses a variation of counter mode, so making sure the IV is unique is more important than randomness.
8085
this.inObjectNonceCounter = 0;
8186
this.noncePart = new byte[]{
8287
0, 0,
@@ -102,8 +107,10 @@ public IDecryptor getDecryptor() {
102107
}
103108

104109
@Override
105-
protected void setPubSecSpecificHandlerDicEntries(PdfDictionary encryptionDictionary, boolean encryptMetadata, boolean embeddedFilesOnly) {
106-
super.setPubSecSpecificHandlerDicEntries(encryptionDictionary, encryptMetadata, embeddedFilesOnly);
107-
encryptionDictionary.put(PdfName.V, new PdfNumber(7));
110+
protected void setPubSecSpecificHandlerDicEntries(PdfDictionary encryptionDictionary, boolean encryptMetadata,
111+
boolean embeddedFilesOnly) {
112+
int version = 6;
113+
PdfName filter = PdfName.AESV4;
114+
setEncryptionDictEntries(encryptionDictionary, encryptMetadata, embeddedFilesOnly, version, filter);
108115
}
109116
}

kernel/src/main/java/com/itextpdf/kernel/crypto/securityhandler/StandardHandlerUsingAes256.java

Lines changed: 65 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ public class StandardHandlerUsingAes256 extends StandardSecurityHandler {
5353
private static final int KEY_SALT_OFFSET = 40;
5454
private static final int SALT_LENGTH = 8;
5555

56-
private boolean isPdf2;
5756
protected boolean encryptMetadata;
58-
57+
private boolean isPdf2;
5958

6059
public StandardHandlerUsingAes256(PdfDictionary encryptionDictionary, byte[] userPassword, byte[] ownerPassword,
61-
int permissions, boolean encryptMetadata, boolean embeddedFilesOnly, PdfVersion version) {
60+
int permissions, boolean encryptMetadata, boolean embeddedFilesOnly,
61+
PdfVersion version) {
6262
isPdf2 = version != null && version.compareTo(PdfVersion.PDF_2_0) >= 0;
6363
initKeyAndFillDictionary(encryptionDictionary, userPassword, ownerPassword, permissions, encryptMetadata,
6464
embeddedFilesOnly);
@@ -68,6 +68,11 @@ public StandardHandlerUsingAes256(PdfDictionary encryptionDictionary, byte[] pas
6868
initKeyAndReadDictionary(encryptionDictionary, password);
6969
}
7070

71+
/**
72+
* Checks whether the document-level metadata stream will be encrypted.
73+
*
74+
* @return {@code true} if the document-level metadata stream shall be encrypted, {@code false} otherwise
75+
*/
7176
public boolean isEncryptMetadata() {
7277
return encryptMetadata;
7378
}
@@ -87,8 +92,52 @@ public IDecryptor getDecryptor() {
8792
return new AesDecryptor(nextObjectKey, 0, nextObjectKeySize);
8893
}
8994

95+
void setAES256DicEntries(PdfDictionary encryptionDictionary, byte[] oeKey, byte[] ueKey, byte[] aes256Perms,
96+
boolean encryptMetadata, boolean embeddedFilesOnly) {
97+
int version = 5;
98+
int rAes256 = 5;
99+
int rAes256Pdf2 = 6;
100+
int revision = isPdf2 ? rAes256Pdf2 : rAes256;
101+
PdfName cryptoFilter = PdfName.AESV3;
102+
setEncryptionDictionaryEntries(encryptionDictionary, oeKey, ueKey, aes256Perms, encryptMetadata,
103+
embeddedFilesOnly, version, revision, cryptoFilter);
104+
}
105+
106+
void setEncryptionDictionaryEntries(PdfDictionary encryptionDictionary, byte[] oeKey, byte[] ueKey,
107+
byte[] aes256Perms, boolean encryptMetadata, boolean embeddedFilesOnly,
108+
int version, int revision, PdfName cryptoFilter) {
109+
encryptionDictionary.put(PdfName.OE, new PdfLiteral(StreamUtil.createEscapedString(oeKey)));
110+
encryptionDictionary.put(PdfName.UE, new PdfLiteral(StreamUtil.createEscapedString(ueKey)));
111+
encryptionDictionary.put(PdfName.Perms, new PdfLiteral(StreamUtil.createEscapedString(aes256Perms)));
112+
encryptionDictionary.put(PdfName.R, new PdfNumber(revision));
113+
encryptionDictionary.put(PdfName.V, new PdfNumber(version));
114+
PdfDictionary stdcf = new PdfDictionary();
115+
stdcf.put(PdfName.Length, new PdfNumber(32));
116+
if (!encryptMetadata) {
117+
encryptionDictionary.put(PdfName.EncryptMetadata, PdfBoolean.FALSE);
118+
}
119+
if (embeddedFilesOnly) {
120+
stdcf.put(PdfName.AuthEvent, PdfName.EFOpen);
121+
encryptionDictionary.put(PdfName.EFF, PdfName.StdCF);
122+
encryptionDictionary.put(PdfName.StrF, PdfName.Identity);
123+
encryptionDictionary.put(PdfName.StmF, PdfName.Identity);
124+
} else {
125+
stdcf.put(PdfName.AuthEvent, PdfName.DocOpen);
126+
encryptionDictionary.put(PdfName.StrF, PdfName.StdCF);
127+
encryptionDictionary.put(PdfName.StmF, PdfName.StdCF);
128+
}
129+
stdcf.put(PdfName.CFM, cryptoFilter);
130+
PdfDictionary cf = new PdfDictionary();
131+
cf.put(PdfName.StdCF, stdcf);
132+
encryptionDictionary.put(PdfName.CF, cf);
133+
}
134+
135+
boolean isPdf2(PdfDictionary encryptionDictionary) {
136+
return encryptionDictionary.getAsNumber(PdfName.R).getValue() == 6;
137+
}
138+
90139
private void initKeyAndFillDictionary(PdfDictionary encryptionDictionary, byte[] userPassword, byte[] ownerPassword,
91-
int permissions, boolean encryptMetadata, boolean embeddedFilesOnly) {
140+
int permissions, boolean encryptMetadata, boolean embeddedFilesOnly) {
92141
ownerPassword = generateOwnerPasswordIfNullOrEmpty(ownerPassword);
93142
permissions |= PERMS_MASK_1_FOR_REVISION_3_OR_GREATER;
94143
permissions &= PERMS_MASK_2;
@@ -167,37 +216,6 @@ private void initKeyAndFillDictionary(PdfDictionary encryptionDictionary, byte[]
167216
}
168217
}
169218

170-
protected void setAES256DicEntries(PdfDictionary encryptionDictionary, byte[] oeKey, byte[] ueKey, byte[] aes256Perms,
171-
boolean encryptMetadata, boolean embeddedFilesOnly) {
172-
int vAes256 = 5;
173-
int rAes256 = 5;
174-
int rAes256Pdf2 = 6;
175-
encryptionDictionary.put(PdfName.OE, new PdfLiteral(StreamUtil.createEscapedString(oeKey)));
176-
encryptionDictionary.put(PdfName.UE, new PdfLiteral(StreamUtil.createEscapedString(ueKey)));
177-
encryptionDictionary.put(PdfName.Perms, new PdfLiteral(StreamUtil.createEscapedString(aes256Perms)));
178-
encryptionDictionary.put(PdfName.R, new PdfNumber(isPdf2 ? rAes256Pdf2 : rAes256));
179-
encryptionDictionary.put(PdfName.V, new PdfNumber(vAes256));
180-
PdfDictionary stdcf = new PdfDictionary();
181-
stdcf.put(PdfName.Length, new PdfNumber(32));
182-
if (!encryptMetadata) {
183-
encryptionDictionary.put(PdfName.EncryptMetadata, PdfBoolean.FALSE);
184-
}
185-
if (embeddedFilesOnly) {
186-
stdcf.put(PdfName.AuthEvent, PdfName.EFOpen);
187-
encryptionDictionary.put(PdfName.EFF, PdfName.StdCF);
188-
encryptionDictionary.put(PdfName.StrF, PdfName.Identity);
189-
encryptionDictionary.put(PdfName.StmF, PdfName.Identity);
190-
} else {
191-
stdcf.put(PdfName.AuthEvent, PdfName.DocOpen);
192-
encryptionDictionary.put(PdfName.StrF, PdfName.StdCF);
193-
encryptionDictionary.put(PdfName.StmF, PdfName.StdCF);
194-
}
195-
stdcf.put(PdfName.CFM, PdfName.AESV3);
196-
PdfDictionary cf = new PdfDictionary();
197-
cf.put(PdfName.StdCF, stdcf);
198-
encryptionDictionary.put(PdfName.CF, cf);
199-
}
200-
201219
private void initKeyAndReadDictionary(PdfDictionary encryptionDictionary, byte[] password) {
202220
try {
203221
if (password == null) {
@@ -206,11 +224,10 @@ private void initKeyAndReadDictionary(PdfDictionary encryptionDictionary, byte[]
206224
password = Arrays.copyOf(password, 127);
207225
}
208226

209-
isPdf2 = encryptionDictionary.getAsNumber(PdfName.R).getValue() == 6
210-
|| encryptionDictionary.getAsNumber(PdfName.R).getValue() == 7;
227+
isPdf2 = isPdf2(encryptionDictionary);
211228

212-
//truncate user and owner passwords to 48 bytes where the first 32 bytes
213-
//are a hash value, next 8 bytes are validation salt and final 8 bytes are the key salt
229+
// Truncate user and owner passwords to 48 bytes where the first 32 bytes
230+
// are a hash value, next 8 bytes are validation salt and final 8 bytes are the key salt
214231
byte[] oValue = truncateArray(getIsoBytes(encryptionDictionary.getAsString(PdfName.O)));
215232
byte[] uValue = truncateArray(getIsoBytes(encryptionDictionary.getAsString(PdfName.U)));
216233
byte[] oeValue = getIsoBytes(encryptionDictionary.getAsString(PdfName.OE));
@@ -250,7 +267,8 @@ private void initKeyAndReadDictionary(PdfDictionary encryptionDictionary, byte[]
250267
boolean encryptMetadata = decPerms[8] == (byte) 'T';
251268

252269
Boolean encryptMetadataEntry = encryptionDictionary.getAsBool(PdfName.EncryptMetadata);
253-
if (permissionsDecoded != permissions || encryptMetadataEntry != null && encryptMetadata != encryptMetadataEntry) {
270+
if (permissionsDecoded != permissions || encryptMetadataEntry != null &&
271+
encryptMetadata != encryptMetadataEntry) {
254272
Logger logger = LoggerFactory.getLogger(StandardHandlerUsingAes256.class);
255273
logger.error(IoLogMessageConstant.ENCRYPTION_ENTRIES_P_AND_ENCRYPT_METADATA_NOT_CORRESPOND_PERMS_ENTRY);
256274
}
@@ -263,11 +281,13 @@ private void initKeyAndReadDictionary(PdfDictionary encryptionDictionary, byte[]
263281
}
264282
}
265283

266-
private byte[] computeHash(byte[] password, byte[] salt, int saltOffset, int saltLen) throws NoSuchAlgorithmException {
284+
private byte[] computeHash(byte[] password, byte[] salt, int saltOffset, int saltLen)
285+
throws NoSuchAlgorithmException {
267286
return computeHash(password, salt, saltOffset, saltLen, null);
268287
}
269288

270-
private byte[] computeHash(byte[] password, byte[] salt, int saltOffset, int saltLen, byte[] userKey) throws NoSuchAlgorithmException {
289+
private byte[] computeHash(byte[] password, byte[] salt, int saltOffset, int saltLen, byte[] userKey)
290+
throws NoSuchAlgorithmException {
271291
MessageDigest mdSha256 = MessageDigest.getInstance("SHA-256");
272292

273293
mdSha256.update(password);
@@ -303,7 +323,8 @@ private byte[] computeHash(byte[] password, byte[] salt, int saltOffset, int sal
303323
}
304324

305325
// b)
306-
AESCipherCBCnoPad cipher = new AESCipherCBCnoPad(true, Arrays.copyOf(k, 16), Arrays.copyOfRange(k, 16, 32));
326+
AESCipherCBCnoPad cipher =
327+
new AESCipherCBCnoPad(true, Arrays.copyOf(k, 16), Arrays.copyOfRange(k, 16, 32));
307328
byte[] e = cipher.processBlock(k1, 0, k1.length);
308329

309330
// c)
@@ -323,6 +344,7 @@ private byte[] computeHash(byte[] password, byte[] salt, int saltOffset, int sal
323344
}
324345

325346
// d)
347+
assert md != null;
326348
k = md.digest(e);
327349

328350
++roundNum;

kernel/src/main/java/com/itextpdf/kernel/crypto/securityhandler/StandardHandlerUsingAesGcm.java

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ This file is part of the iText (R) project.
2828
import com.itextpdf.kernel.crypto.OutputStreamEncryption;
2929
import com.itextpdf.kernel.pdf.PdfDictionary;
3030
import com.itextpdf.kernel.pdf.PdfName;
31-
import com.itextpdf.kernel.pdf.PdfNumber;
3231
import com.itextpdf.kernel.pdf.PdfVersion;
3332

3433
import java.io.OutputStream;
@@ -66,25 +65,14 @@ public StandardHandlerUsingAesGcm(PdfDictionary encryptionDictionary, byte[] pas
6665
super(encryptionDictionary, password);
6766
}
6867

69-
/**
70-
* Checks whether the document-level metadata stream will be encrypted.
71-
*
72-
* @return {@code true} if the document-level metadata stream shall be encrypted, {@code false} otherwise
73-
*/
74-
public boolean isEncryptMetadata() {
75-
return encryptMetadata;
76-
}
77-
7868
@Override
7969
public void setHashKeyForNextObject(int objNumber, int objGeneration) {
80-
// make sure the same IV is never used twice in the same file
81-
// we do this by turning the objId/objGen into a 5-byte nonce (with generation restricted
82-
// to 1 byte instead of 2) plus an in-object 2-byte counter that increments each time
83-
// a new string is encrypted within the same object.
84-
// The remaining 5 bytes will be generated randomly using a strong PRNG.
85-
// This is *very different* from the situation with AES-CBC, where randomness is paramount.
86-
// GCM uses a variation of counter mode, so making sure the IV is unique is more important
87-
// than randomness.
70+
// Make sure the same IV is never used twice in the same file. We do this by turning the objId/objGen into a
71+
// 5-byte nonce (with generation restricted to 1 byte instead of 2) plus an in-object 2-byte counter that
72+
// increments each time a new string is encrypted within the same object. The remaining 5 bytes will be
73+
// generated randomly using a strong PRNG.
74+
// This is very different from the situation with AES-CBC, where randomness is paramount. GCM uses a variation
75+
// of counter mode, so making sure the IV is unique is more important than randomness.
8876
this.inObjectNonceCounter = 0;
8977
this.noncePart = new byte[]{
9078
0, 0,
@@ -110,10 +98,17 @@ public IDecryptor getDecryptor() {
11098
}
11199

112100
@Override
113-
protected void setAES256DicEntries(PdfDictionary encryptionDictionary, byte[] oeKey, byte[] ueKey, byte[] aes256Perms,
101+
void setAES256DicEntries(PdfDictionary encryptionDictionary, byte[] oeKey, byte[] ueKey, byte[] aes256Perms,
114102
boolean encryptMetadata, boolean embeddedFilesOnly) {
115-
super.setAES256DicEntries(encryptionDictionary, oeKey, ueKey, aes256Perms, encryptMetadata, embeddedFilesOnly);
116-
encryptionDictionary.put(PdfName.R, new PdfNumber(7));
117-
encryptionDictionary.put(PdfName.V, new PdfNumber(6));
103+
int version = 6;
104+
int revision = 7;
105+
PdfName cryptoFilter = PdfName.AESV4;
106+
setEncryptionDictionaryEntries(encryptionDictionary, oeKey, ueKey, aes256Perms, encryptMetadata, embeddedFilesOnly,
107+
version, revision, cryptoFilter);
108+
}
109+
110+
@Override
111+
boolean isPdf2(PdfDictionary encryptionDictionary) {
112+
return true;
118113
}
119114
}

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfEncryption.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,23 @@ private int readAndSetCryptoModeForStdHandler(PdfDictionary encDict) {
712712
}
713713
break;
714714
case 7:
715-
cryptoMode = EncryptionConstants.ENCRYPTION_AES_GCM;
715+
// (ISO/TS 32003) The security handler defines the use of encryption
716+
// and decryption in the same way as when the value of R is 6, and declares at least
717+
// one crypt filter using the AESV4 method.
718+
PdfDictionary cfDic = encDict.getAsDictionary(PdfName.CF);
719+
if (cfDic == null) {
720+
throw new PdfException(KernelExceptionMessageConstant.CF_NOT_FOUND_ENCRYPTION);
721+
}
722+
cfDic = (PdfDictionary) cfDic.get(PdfName.StdCF);
723+
if (cfDic == null) {
724+
throw new PdfException(KernelExceptionMessageConstant.STDCF_NOT_FOUND_ENCRYPTION);
725+
}
726+
if (PdfName.AESV4.equals(cfDic.get(PdfName.CFM))) {
727+
cryptoMode = EncryptionConstants.ENCRYPTION_AES_GCM;
728+
length = 256;
729+
} else {
730+
throw new PdfException(KernelExceptionMessageConstant.NO_COMPATIBLE_ENCRYPTION_FOUND);
731+
}
716732
PdfBoolean em7 = encDict.getAsBoolean(PdfName.EncryptMetadata);
717733
if (em7 != null && !em7.getValue()) {
718734
cryptoMode |= EncryptionConstants.DO_NOT_ENCRYPT_METADATA;

0 commit comments

Comments
 (0)