Skip to content
This repository was archived by the owner on Jun 5, 2025. It is now read-only.

Commit fe41a9c

Browse files
Making the code to read the VaaS cert keystore secure against Zip Bomb Attacks
1 parent 2f8e1d7 commit fe41a9c

3 files changed

Lines changed: 105 additions & 6 deletions

File tree

src/main/java/com/venafi/vcert/sdk/connectors/ConnectorException.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,5 +447,49 @@ public ClientIdentifierException(Exception e) {
447447
super("It wasn't possible to determine the Client Identifier", e);
448448
}
449449
}
450+
451+
public static class KeyStoreZipEntriesExceeded extends ConnectorException {
452+
453+
private static final long serialVersionUID = 1L;
454+
455+
int maxEntriesExpected;
456+
String certificateId;
457+
458+
public KeyStoreZipEntriesExceeded(String certificateId, int maxEntriesExpected) {
459+
super(format("The Keystore Zip from certificate Id %s exceeded the maximum of %d expected compressed files.", certificateId, maxEntriesExpected));
460+
this.maxEntriesExpected = maxEntriesExpected;
461+
this.certificateId = certificateId;
462+
}
463+
}
464+
465+
public static class KeyStoreUnzipedFilesBytesSizeExceeded extends ConnectorException {
466+
467+
private static final long serialVersionUID = 1L;
468+
469+
int maxBytesSizeExpected;
470+
String certificateId;
471+
472+
public KeyStoreUnzipedFilesBytesSizeExceeded(String certificateId, int maxBytesSizeExpected) {
473+
super(format("The Keystore Zip from certificate Id %s exceeded the maximum of total %d expected bytes for uncompressed files.", certificateId, maxBytesSizeExpected));
474+
this.maxBytesSizeExpected = maxBytesSizeExpected;
475+
this.certificateId = certificateId;
476+
}
477+
}
478+
479+
public static class KeyStoreZipCompressionRatioExceeded extends ConnectorException {
480+
481+
private static final long serialVersionUID = 1L;
482+
483+
int maxCompressionRatioExpected;
484+
String certificateId;
485+
String fileName;
486+
487+
public KeyStoreZipCompressionRatioExceeded(String certificateId, String fileName, int maxCompressionRatioExpected) {
488+
super(format("The file %s in the Keystore Zip from certificate Id %s exceeded the maximum of %d expected compression ratio.", fileName, certificateId, maxCompressionRatioExpected));
489+
this.maxCompressionRatioExpected = maxCompressionRatioExpected;
490+
this.certificateId = certificateId;
491+
this.fileName = fileName;
492+
}
493+
}
450494

451495
}

src/main/java/com/venafi/vcert/sdk/connectors/cloud/CloudConnector.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ private PEMCollection retrieveCertificateAsPemCollectionFromCSRServiceGenerated(
432432
throw new VCertException(e);
433433
}
434434

435-
return CloudConnectorUtils.getPEMCollectionFromKeyStoreAsStream(keyStoreAsStream, request.chainOption(), request.keyPassword(), request.dataFormat());
435+
return CloudConnectorUtils.getPEMCollectionFromKeyStoreAsStream(keyStoreAsStream, request.certId(), request.chainOption(), request.keyPassword(), request.dataFormat());
436436
}
437437

438438
/**

src/main/java/com/venafi/vcert/sdk/connectors/cloud/CloudConnectorUtils.java

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import com.venafi.vcert.sdk.certificate.ChainOption;
66
import com.venafi.vcert.sdk.certificate.DataFormat;
77
import com.venafi.vcert.sdk.certificate.PEMCollection;
8+
import com.venafi.vcert.sdk.connectors.ConnectorException.KeyStoreUnzipedFilesBytesSizeExceeded;
9+
import com.venafi.vcert.sdk.connectors.ConnectorException.KeyStoreZipCompressionRatioExceeded;
10+
import com.venafi.vcert.sdk.connectors.ConnectorException.KeyStoreZipEntriesExceeded;
811
import com.venafi.vcert.sdk.connectors.ConnectorException.PolicyMatchException;
912
import com.venafi.vcert.sdk.connectors.cloud.CloudConnector.CsrAttributes;
1013
import com.venafi.vcert.sdk.connectors.cloud.CloudConnector.SubjectAlternativeNamesByType;
@@ -22,8 +25,9 @@
2225

2326
import static org.apache.commons.lang3.StringUtils.isNotBlank;
2427

28+
import java.io.IOException;
2529
import java.io.InputStream;
26-
import java.io.InputStreamReader;
30+
import java.io.StringReader;
2731
import java.security.PrivateKey;
2832
import java.util.*;
2933
import java.util.regex.Pattern;
@@ -390,7 +394,7 @@ public static String getVaaSChainOption(ChainOption chainOption) {
390394
}
391395
}
392396

393-
public static PEMCollection getPEMCollectionFromKeyStoreAsStream(InputStream keyStoreAsInputStream, ChainOption chainOption, String keyPassword, DataFormat dataFormat) throws VCertException {
397+
public static PEMCollection getPEMCollectionFromKeyStoreAsStream(InputStream keyStoreAsInputStream, String certId, ChainOption chainOption, String keyPassword, DataFormat dataFormat) throws VCertException {
394398
String certificateAsPem = null;
395399

396400
String pemFileSuffix = null;
@@ -402,19 +406,44 @@ public static PEMCollection getPEMCollectionFromKeyStoreAsStream(InputStream key
402406
PrivateKey privateKey = null;
403407

404408
try (ZipInputStream zis = new ZipInputStream(keyStoreAsInputStream)) {
405-
409+
//The next constants are in order to be on safe about of the zip bomb attacks
410+
final int MAX_ENTRIES = 6;//The expected number of files in the zip returned by the call to
411+
//the API "POST /outagedetection/v1/certificates/{id}/keystore"
412+
final int MAX_UNZIPED_FILES_SIZE = 1000000; //1 MB
413+
414+
int entriesCount = 0;
415+
int unzipedAcumulatedSize = 0;
416+
406417
ZipEntry zipEntry;
407418
while ((zipEntry = zis.getNextEntry())!= null) {
419+
420+
entriesCount++;
421+
422+
//ZIP Bomb Attack validation
423+
//If the number of entries is major that the expected max number of entries
424+
if(entriesCount > MAX_ENTRIES)
425+
throw new KeyStoreZipEntriesExceeded(certId, MAX_ENTRIES);
426+
427+
String zipEntryContent = readZipEntry(zipEntry, zis, certId);
428+
408429
String fileName = zipEntry.getName();
409430
if(fileName.endsWith(".key")) {
410431
//Getting the PrivateKey in PKCS8 and decrypting it
411-
PEMParser pemParser = new PEMParser(new InputStreamReader(zis));
432+
PEMParser pemParser = new PEMParser(new StringReader(zipEntryContent));
412433
privateKey = PEMCollection.decryptPKCS8PrivateKey(pemParser, keyPassword);
413434
} else {
414435
if(fileName.endsWith(pemFileSuffix)) {
415-
certificateAsPem = new String(zis.readAllBytes());
436+
certificateAsPem = zipEntryContent;
416437
}
417438
}
439+
440+
unzipedAcumulatedSize += zipEntryContent.getBytes().length;
441+
442+
//ZIP Bomb Attack validation
443+
//If the sum of the number of bytes of the unzipped files is major that the expected
444+
//maximum number of bytes.
445+
if (unzipedAcumulatedSize > MAX_UNZIPED_FILES_SIZE)
446+
throw new KeyStoreUnzipedFilesBytesSizeExceeded(certId, MAX_UNZIPED_FILES_SIZE);
418447
}
419448
} catch (Exception e) {
420449
throw new VCertException(e);
@@ -427,6 +456,32 @@ public static PEMCollection getPEMCollectionFromKeyStoreAsStream(InputStream key
427456
keyPassword,
428457
dataFormat);
429458
}
459+
460+
private static String readZipEntry(ZipEntry zipEntry, ZipInputStream zis, String certId) throws VCertException, IOException {
461+
462+
int totalSizeEntry = 0;
463+
464+
final int MAX_RATIO = 3;//It's expected that the compression ratio should't be more than 3
465+
466+
StringBuilder s = new StringBuilder();
467+
byte[] buffer = new byte[1024];
468+
int nBytes = 0;
469+
while ((nBytes = zis.read(buffer, 0, 1024)) >= 0) {
470+
s.append(new String(buffer, 0, nBytes));
471+
472+
//ZIP Bomb Attack validation
473+
//If the compression ratio of the current unzipped file is major that the expected
474+
// max ratio
475+
totalSizeEntry += nBytes;
476+
double compressionRatio = totalSizeEntry / zipEntry.getCompressedSize();
477+
if(compressionRatio > MAX_RATIO) {
478+
// ratio between compressed and uncompressed data is highly suspicious, looks like a Zip Bomb Attack
479+
throw new KeyStoreZipCompressionRatioExceeded(certId, zipEntry.getName(), MAX_RATIO);
480+
}
481+
}
482+
483+
return s.toString();
484+
}
430485

431486
@Data
432487
@AllArgsConstructor

0 commit comments

Comments
 (0)