Skip to content

Commit ea09cbd

Browse files
committed
Improve logging for extension checkers
DEVSIX-8828
1 parent b3d9704 commit ea09cbd

File tree

9 files changed

+248
-57
lines changed

9 files changed

+248
-57
lines changed

sign/src/main/java/com/itextpdf/signatures/validation/CertificateChainValidator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public class CertificateChainValidator {
5656
"Certificate {0} is trusted, revocation data checks are not required.";
5757
static final String CERTIFICATE_TRUSTED_FOR_DIFFERENT_CONTEXT = "Certificate {0} is trusted for {1}, "
5858
+ "but it is not used in this context. Validation will continue as usual.";
59-
static final String EXTENSION_MISSING = "Required extension {0} is missing or incorrect.";
59+
static final String EXTENSION_MISSING = "Required extension validation failed: {0}";
6060
static final String ISSUER_MISSING = "Certificate {0} isn't trusted and issuer certificate isn't provided.";
6161
static final String EXPIRED_CERTIFICATE = "Certificate {0} is expired.";
6262
static final String NOT_YET_VALID_CERTIFICATE = "Certificate {0} is not yet valid.";
@@ -245,7 +245,7 @@ private void validateRequiredExtensions(ValidationReport result, ValidationConte
245245
}
246246
if (!requiredExtension.existsInCertificate(certificate)) {
247247
result.addReportItem(new CertificateReportItem(certificate, EXTENSIONS_CHECK,
248-
MessageFormatUtil.format(EXTENSION_MISSING, requiredExtension.getExtensionOid()),
248+
MessageFormatUtil.format(EXTENSION_MISSING, requiredExtension.getMessage()),
249249
ReportItemStatus.INVALID));
250250
}
251251
}

sign/src/main/java/com/itextpdf/signatures/validation/extensions/CertificateExtension.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ This file is part of the iText (R) project.
2323
package com.itextpdf.signatures.validation.extensions;
2424

2525
import com.itextpdf.commons.bouncycastle.asn1.IASN1Primitive;
26+
import com.itextpdf.commons.utils.MessageFormatUtil;
2627
import com.itextpdf.signatures.CertificateUtil;
2728

2829
import java.io.IOException;
@@ -34,8 +35,14 @@ This file is part of the iText (R) project.
3435
*/
3536
public class CertificateExtension {
3637

38+
public static final String EXCEPTION_OCCURRED = " but an exception occurred {0}:{1}.";
39+
public static final String EXTENSION_NOT_FOUND = " but no extension with that id was found.";
40+
public static final String FOUND_VALUE = " but found value ";
41+
public static final String EXPECTED_EXTENSION_ID_AND_VALUE = "Expected extension with id {0} and value {1}"
42+
+ " {1} {2}";
3743
private final String extensionOid;
3844
private final IASN1Primitive extensionValue;
45+
private String errorMessage = "";
3946

4047
/**
4148
* Create new instance of {@link CertificateExtension} using provided extension OID and value.
@@ -66,6 +73,15 @@ public String getExtensionOid() {
6673
return extensionOid;
6774
}
6875

76+
/**
77+
* Returns a message with extra information about the check.
78+
* @return a message with extra information about the check.
79+
*/
80+
public String getMessage() {
81+
return MessageFormatUtil.format(EXPECTED_EXTENSION_ID_AND_VALUE,
82+
getExtensionOid(), getExtensionValue().toString(), errorMessage);
83+
}
84+
6985
/**
7086
* Check if this extension is present in the provided certificate.
7187
* <p>
@@ -81,9 +97,22 @@ public boolean existsInCertificate(X509Certificate certificate) {
8197
try {
8298
providedExtensionValue = CertificateUtil.getExtensionValue(certificate, extensionOid);
8399
} catch (IOException | RuntimeException e) {
100+
errorMessage = MessageFormatUtil.format(EXCEPTION_OCCURRED,
101+
e.getClass().getName(),e.getMessage());
84102
return false;
85103
}
86-
return Objects.equals(providedExtensionValue, extensionValue);
104+
if (providedExtensionValue == null) {
105+
if (extensionValue == null) {
106+
return true;
107+
}
108+
errorMessage = EXTENSION_NOT_FOUND;
109+
return false;
110+
}
111+
if (Objects.equals(providedExtensionValue, extensionValue)) {
112+
return true;
113+
}
114+
errorMessage = FOUND_VALUE + MessageFormatUtil.format(" but found value {0}.", extensionValue.toString());
115+
return false;
87116
}
88117

89118
@Override
@@ -102,4 +131,5 @@ public boolean equals(Object o) {
102131
public int hashCode() {
103132
return Objects.hash((Object) extensionOid, extensionValue);
104133
}
134+
105135
}

sign/src/main/java/com/itextpdf/signatures/validation/extensions/DynamicBasicConstraintsExtension.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@ This file is part of the iText (R) project.
2424

2525
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
2626
import com.itextpdf.commons.bouncycastle.IBouncyCastleFactory;
27+
import com.itextpdf.commons.utils.MessageFormatUtil;
2728
import com.itextpdf.kernel.crypto.OID;
28-
import com.itextpdf.signatures.CertificateUtil;
2929

30-
import java.io.IOException;
3130
import java.security.cert.X509Certificate;
3231

3332
/**
@@ -36,6 +35,9 @@ This file is part of the iText (R) project.
3635
*/
3736
public class DynamicBasicConstraintsExtension extends DynamicCertificateExtension {
3837
private static final IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.getFactory();
38+
public static final String ERROR_MESSAGE =
39+
"Expected extension 2.5.29.19 to have a value of at least {0} but found {1}";
40+
private String errorMessage;
3941

4042
/**
4143
* Create new instance of {@link DynamicBasicConstraintsExtension}.
@@ -55,6 +57,16 @@ public DynamicBasicConstraintsExtension() {
5557
*/
5658
@Override
5759
public boolean existsInCertificate(X509Certificate certificate) {
58-
return certificate.getBasicConstraints() >= getCertificateChainSize() - 1;
60+
if (certificate.getBasicConstraints() >= getCertificateChainSize() - 1) {
61+
return true;
62+
}
63+
errorMessage = MessageFormatUtil.format(ERROR_MESSAGE,
64+
getCertificateChainSize() - 1, certificate.getBasicConstraints());
65+
return false;
66+
}
67+
68+
@Override
69+
public String getMessage() {
70+
return errorMessage;
5971
}
6072
}

sign/src/main/java/com/itextpdf/signatures/validation/extensions/ExtendedKeyUsageExtension.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,13 @@ public class ExtendedKeyUsageExtension extends CertificateExtension {
4343
public static final String CLIENT_AUTH = "1.3.6.1.5.5.7.3.2";
4444

4545
private static final IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.getFactory();
46+
public static final String EXPECTED_KEY_USAGES = "Expected extended key usages:";
47+
public static final String ACTUAL = "But found :";
48+
public static final String NO_EXTENDED_KEY_USAGES_WERE_FOUND = " But no extended key usages were found.";
49+
public static final String ERROR_OCCURRED_DURING_RETRIEVAL = " But error occurred during retrieval ";
4650

4751
private final List<String> extendedKeyUsageOids;
52+
private String errorMessage = "";
4853

4954
/**
5055
* Create new {@link ExtendedKeyUsageExtension} instance.
@@ -71,14 +76,29 @@ public boolean existsInCertificate(X509Certificate certificate) {
7176
try {
7277
providedExtendedKeyUsage = (List<String>) certificate.getExtendedKeyUsage();
7378
} catch (CertificateParsingException | RuntimeException e) {
79+
errorMessage = ERROR_OCCURRED_DURING_RETRIEVAL + e.getClass().getName() + " " + e.getMessage();
7480
return false;
7581
}
7682

7783
if (providedExtendedKeyUsage == null) {
84+
errorMessage = NO_EXTENDED_KEY_USAGES_WERE_FOUND;
7885
return false;
7986
}
80-
return providedExtendedKeyUsage.contains(ANY_EXTENDED_KEY_USAGE_OID) ||
81-
new HashSet<>(providedExtendedKeyUsage).containsAll(extendedKeyUsageOids);
87+
88+
if (providedExtendedKeyUsage.contains(ANY_EXTENDED_KEY_USAGE_OID) ||
89+
new HashSet<>(providedExtendedKeyUsage).containsAll(extendedKeyUsageOids)) {
90+
return true;
91+
}
92+
93+
StringBuilder sb = new StringBuilder(ACTUAL);
94+
char sep = '(';
95+
for (String usage : providedExtendedKeyUsage) {
96+
sb.append(sep).append(usage);
97+
sep = ',';
98+
}
99+
sb.append(')');
100+
errorMessage = sb.toString();
101+
return false;
82102
}
83103

84104
private static IKeyPurposeId[] createKeyPurposeIds(List<String> extendedKeyUsageOids) {
@@ -89,4 +109,17 @@ private static IKeyPurposeId[] createKeyPurposeIds(List<String> extendedKeyUsage
89109
}
90110
return keyPurposeIds;
91111
}
112+
113+
@Override
114+
public String getMessage() {
115+
StringBuilder sb = new StringBuilder(EXPECTED_KEY_USAGES);
116+
char sep = '(';
117+
for (String usage : extendedKeyUsageOids) {
118+
sb.append(sep).append(usage);
119+
sep = ',';
120+
}
121+
sb.append(')');
122+
sb.append(errorMessage);
123+
return sb.toString();
124+
}
92125
}

sign/src/main/java/com/itextpdf/signatures/validation/extensions/KeyUsageExtension.java

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ This file is part of the iText (R) project.
2424

2525
import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
2626
import com.itextpdf.commons.bouncycastle.IBouncyCastleFactory;
27+
import com.itextpdf.commons.utils.MessageFormatUtil;
28+
import com.itextpdf.io.util.EnumUtil;
2729
import com.itextpdf.kernel.crypto.OID;
2830

2931
import java.security.cert.X509Certificate;
@@ -36,14 +38,24 @@ This file is part of the iText (R) project.
3638
public class KeyUsageExtension extends CertificateExtension {
3739

3840
private static final IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.getFactory();
41+
public static final String EXPECTED_VALUE =
42+
"Key usage expected: ({0})";
43+
public static final String ACTUAL_VALUE = "\nbut found {0}";
44+
public static final String MISSING_VALUE = "\nbut nothing found.";
3945

4046
private final int keyUsage;
4147
private final boolean resultOnMissingExtension;
48+
private String messagePreAmble;
49+
private String message;
4250

4351
/**
4452
* Create new {@link KeyUsageExtension} instance using provided {@code int} flag.
4553
*
4654
* @param keyUsage {@code int} flag which represents bit values for key usage value
55+
* bit strings are stored with the big-endian byte order and padding on the end,
56+
* the big endian notation causes a shift in actual integer values for
57+
* bits 1-8 becoming 0-7 and bit 1
58+
* the 7 bits padding makes for bit 0 to become bit 7 of the first byte
4759
*/
4860
public KeyUsageExtension(int keyUsage) {
4961
this(keyUsage, false);
@@ -52,14 +64,20 @@ public KeyUsageExtension(int keyUsage) {
5264
/**
5365
* Create new {@link KeyUsageExtension} instance using provided {@code int} flag.
5466
*
55-
* @param keyUsage {@code int} flag which represents bit values for key usage value
67+
* @param keyUsage {@code int} flag which represents bit values for key usage value
68+
* bit strings are stored with the big-endian byte order and padding on the end,
69+
* the big endian notation causes a shift in actual integer values for bits 1-8
70+
* becoming 0-7 and bit 1
71+
* the 7 bits padding makes for bit 0 to become bit 7 of the first byte
5672
* @param resultOnMissingExtension parameter which represents return value for
5773
* {@link #existsInCertificate(X509Certificate)} method in case of the extension not being present in a certificate
5874
*/
5975
public KeyUsageExtension(int keyUsage, boolean resultOnMissingExtension) {
6076
super(OID.X509Extensions.KEY_USAGE, FACTORY.createKeyUsage(keyUsage).toASN1Primitive());
6177
this.keyUsage = keyUsage;
6278
this.resultOnMissingExtension = resultOnMissingExtension;
79+
messagePreAmble =MessageFormatUtil.format(EXPECTED_VALUE , convertKeyUsageMaskToString(keyUsage));
80+
message = messagePreAmble;
6381
}
6482

6583
/**
@@ -74,7 +92,7 @@ public KeyUsageExtension(List<KeyUsage> keyUsages) {
7492
/**
7593
* Create new {@link KeyUsageExtension} instance using provided key usage enum list.
7694
*
77-
* @param keyUsages key usages {@link List} which represents key usage values
95+
* @param keyUsages key usages {@link List} which represents key usage values
7896
* @param resultOnMissingExtension parameter which represents return value for
7997
* {@link #existsInCertificate(X509Certificate)} method in case of the extension not being present in a certificate
8098
*/
@@ -115,44 +133,62 @@ public boolean existsInCertificate(X509Certificate certificate) {
115133
boolean[] providedKeyUsageFlags = certificate.getKeyUsage();
116134
if (providedKeyUsageFlags == null) {
117135
// By default, we want to return true if extension is not specified. Configurable.
136+
message = messagePreAmble + MISSING_VALUE;
118137
return resultOnMissingExtension;
119138
}
120-
for (int i = 0; i < providedKeyUsageFlags.length; ++i) {
121-
int power = providedKeyUsageFlags.length - i - 2;
122-
if (power < 0) {
123-
// Bits are encoded backwards, for the last bit power is -1 and in this case we need to go over byte
124-
power = 16 + power;
125-
}
126-
if ((keyUsage & (1 << power)) != 0 && !providedKeyUsageFlags[i]) {
127-
return false;
139+
int bitmap = 0;
140+
// bit strings are stored with the big-endian byte order and padding on the end,
141+
// the big endian notation causes a shift in actual integer values for bits 1-8 becoming 0-7 and bit 1
142+
// the 7 bits padding makes for bit 0 to become bit 7 of the first byte
143+
for (int i = 0; i < providedKeyUsageFlags.length - 1; ++i) {
144+
if (providedKeyUsageFlags[i]) {
145+
bitmap += 1 << (8-i-1);
128146
}
129147
}
148+
if (providedKeyUsageFlags[8]) {
149+
bitmap += 0x8000;
150+
}
151+
if ((bitmap & keyUsage) != keyUsage) {
152+
message = new StringBuilder(messagePreAmble).append(
153+
MessageFormatUtil.format(ACTUAL_VALUE, convertKeyUsageMaskToString(bitmap)))
154+
.toString();
155+
return false;
156+
}
130157
return true;
131158
}
159+
@Override
160+
public String getMessage() {
161+
return message;
162+
}
132163

133-
private static int convertKeyUsageSetToInt(List<KeyUsage> keyUsages) {
134-
KeyUsage[] possibleKeyUsage = new KeyUsage[] {
135-
KeyUsage.DIGITAL_SIGNATURE,
136-
KeyUsage.NON_REPUDIATION,
137-
KeyUsage.KEY_ENCIPHERMENT,
138-
KeyUsage.DATA_ENCIPHERMENT,
139-
KeyUsage.KEY_AGREEMENT,
140-
KeyUsage.KEY_CERT_SIGN,
141-
KeyUsage.CRL_SIGN,
142-
KeyUsage.ENCIPHER_ONLY,
143-
KeyUsage.DECIPHER_ONLY
144-
};
145-
int result = 0;
146-
for (int i = 0; i < possibleKeyUsage.length; ++i) {
147-
if (keyUsages.contains(possibleKeyUsage[i])) {
148-
int power = possibleKeyUsage.length - i - 2;
149-
if (power < 0) {
150-
// Bits are encoded backwards, for the last bit power is -1 and in this case we need to go over byte
151-
power = 16 + power;
152-
}
153-
result |= (1 << power);
164+
private static String convertKeyUsageMaskToString(int keyUsageMask) {
165+
StringBuilder result = new StringBuilder();
166+
String separator = "";
167+
// bit strings are stored with the big-endian byte order and padding on the end,
168+
// the big endian notation causes a shift in actual integer values for bits 1-8 becoming 0-7 and bit 1
169+
// the 7 bits padding makes for bit 0 to become bit 7 of the first byte
170+
for (KeyUsage usage: EnumUtil.getAllValuesOfEnum(KeyUsage.class)) {
171+
if (((1 << (8-usage.ordinal()-1)) & keyUsageMask) > 0 ||
172+
(usage == KeyUsage.DECIPHER_ONLY && (keyUsageMask & 0x8000) == 0x8000)) {
173+
result.append(separator)
174+
.append(usage);
175+
separator = ", ";
176+
}
177+
}
178+
return result.toString();
179+
}
180+
private static int convertKeyUsageSetToInt(Iterable<KeyUsage> keyUsages) {
181+
int keyUsageMask = 0;
182+
// bit strings are stored with the big-endian byte order and padding on the end,
183+
// the big endian notation causes a shift in actual integer values for bits 1-8 becoming 0-7 and bit 1
184+
// the 7 bits padding makes for bit 0 to become bit 7 of the first byte
185+
for (KeyUsage usage: keyUsages) {
186+
if (usage == KeyUsage.DECIPHER_ONLY) {
187+
keyUsageMask += 0x8000;
188+
continue;
154189
}
190+
keyUsageMask += 1 << ( 8 - usage.ordinal() - 1);
155191
}
156-
return result;
192+
return keyUsageMask;
157193
}
158194
}

0 commit comments

Comments
 (0)