Skip to content

Commit 795ef5e

Browse files
Remove attributes checks of certificate extensions in CertificateVerification class and improve list of known standard extensions
It's wrong to perform the same checks of certificate extensions attributes for all certificates from the chain or keystore, such checks must depend on what certificate is currently inspected (e.g. root certificate, dig sig certificate, timestamp provider/crl client/ocsp responder certificate, etc). For example, KEY_USAGE extension attributes can be not only DIGITAL SIGNING or NONREPUDIATION for root certificates. Additionally we only had a very limited list of checks, which was also not correct in general case even for right certificates. Moreover those checks were inconsistent in Java: certificate extensions attributes check was only performed when there were some unsupported extensions according to '#hasUnsupportedCriticalExtension' method. While such checks should probably be performed, we should make a separate investigation on this matter. Also, list of known standard extensions is changed to correspond to RFC specifications. In Java some of the extensions were always allowed to be critical, regardless of '#hasUnsupportedCriticalExtension' method result. However one user recently experienced that this list was too restrictive and we extended it a bit. Such list extension was too case-specific and if we do this additional check after '#hasUnsupportedCriticalExtension', it makes sense to check for all standard extensions. For now in Java both '#hasUnsupportedCriticalExtension' method and standard extensions checks are performed. DEVSIX-2616
1 parent e78f20c commit 795ef5e

File tree

6 files changed

+236
-28
lines changed

6 files changed

+236
-28
lines changed

sign/src/main/java/com/itextpdf/signatures/CertificateVerification.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public static String verifyCertificate(X509Certificate cert, Collection<CRL> crl
115115
* @param certs the certificate chain
116116
* @param keystore the <CODE>KeyStore</CODE>
117117
* @param crls the certificate revocation list or <CODE>null</CODE>
118-
* @return <CODE>null</CODE> if the certificate chain could be validated or a
118+
* @return empty list if the certificate chain could be validated or a
119119
* <CODE>Object[]{cert,error}</CODE> where <CODE>cert</CODE> is the
120120
* failed certificate and <CODE>error</CODE> is the error message
121121
*/
@@ -130,7 +130,7 @@ public static List<VerificationException> verifyCertificates(Certificate[] certs
130130
* @param keystore the <CODE>KeyStore</CODE>
131131
* @param crls the certificate revocation list or <CODE>null</CODE>
132132
* @param calendar the date, shall not be null
133-
* @return <CODE>null</CODE> if the certificate chain could be validated or a
133+
* @return empty list if the certificate chain could be validated or a
134134
* <CODE>Object[]{cert,error}</CODE> where <CODE>cert</CODE> is the
135135
* failed certificate and <CODE>error</CODE> is the error message
136136
*/

sign/src/main/java/com/itextpdf/signatures/OID.java

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ This file is part of the iText (R) project.
4242
*/
4343
package com.itextpdf.signatures;
4444

45+
import java.util.Arrays;
46+
import java.util.Collections;
47+
import java.util.LinkedHashSet;
48+
import java.util.Set;
49+
4550
/**
4651
* Class containing all the OID values used by iText.
4752
*/
@@ -55,9 +60,123 @@ private OID() {
5560
* Contains all OIDs used by iText in the context of Certificate Extensions.
5661
*/
5762
public static final class X509Extensions {
63+
/**
64+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
65+
* <p>
66+
* "Conforming CAs MUST mark this extension as non-critical."
67+
*/
68+
public static final String AUTHORITY_KEY_IDENTIFIER = "2.5.29.35";
69+
/**
70+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
71+
* <p>
72+
* "Conforming CAs MUST mark this extension as non-critical."
73+
*/
74+
public static final String SUBJECT_KEY_IDENTIFIER = "2.5.29.14";
75+
/**
76+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
77+
*/
78+
public static final String KEY_USAGE = "2.5.29.15";
79+
/**
80+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
81+
*/
82+
public static final String CERTIFICATE_POLICIES = "2.5.29.32";
83+
/**
84+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
85+
*/
86+
public static final String POLICY_MAPPINGS = "2.5.29.33";
87+
/**
88+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
89+
*/
90+
public static final String SUBJECT_ALTERNATIVE_NAME = "2.5.29.17";
91+
/**
92+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
93+
*/
94+
public static final String ISSUER_ALTERNATIVE_NAME = "2.5.29.18";
95+
/**
96+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
97+
* <p>
98+
* "Conforming CAs MUST mark this extension as non-critical."
99+
*/
100+
public static final String SUBJECT_DIRECTORY_ATTRIBUTES = "2.5.29.9";
101+
/**
102+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
103+
*/
58104
public static final String BASIC_CONSTRAINTS = "2.5.29.19";
105+
/**
106+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
107+
*/
108+
public static final String NAME_CONSTRAINTS = "2.5.29.30";
109+
/**
110+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
111+
*/
112+
public static final String POLICY_CONSTRAINTS = "2.5.29.36";
113+
/**
114+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
115+
*/
59116
public static final String EXTENDED_KEY_USAGE = "2.5.29.37";
117+
/**
118+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
119+
*/
120+
public static final String CRL_DISTRIBUTION_POINTS = "2.5.29.31";
121+
/**
122+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
123+
*/
124+
public static final String INHIBIT_ANY_POLICY = "2.5.29.54";
125+
/**
126+
* One of the standard extensions from https://tools.ietf.org/html/rfc5280
127+
* <p>
128+
* "The extension MUST be marked as non-critical by conforming CAs."
129+
*/
130+
public static final String FRESHEST_CRL = "2.5.29.46";
131+
132+
/**
133+
* One of the Internet Certificate Extensions also from https://tools.ietf.org/html/rfc5280
134+
* <p>
135+
* "The extension MUST be marked as non-critical by conforming CAs."
136+
*/
137+
public static final String AUTHORITY_INFO_ACCESS = "1.3.6.1.5.5.7.1.1";
138+
/**
139+
* One of the Internet Certificate Extensions also from https://tools.ietf.org/html/rfc5280
140+
* <p>
141+
* "Conforming CAs MUST mark this extension as non-critical."
142+
*/
143+
public static final String SUBJECT_INFO_ACCESS = "1.3.6.1.5.5.7.1.11";
144+
145+
146+
/**
147+
* One of the {@link #EXTENDED_KEY_USAGE} purposes from https://www.ietf.org/rfc/rfc2459.txt
148+
*/
60149
public static final String ID_KP_TIMESTAMPING = "1.3.6.1.5.5.7.3.8";
61-
public static final String KEY_USAGE = "2.5.29.15";
150+
151+
152+
/**
153+
* Extension for OCSP responder certificate
154+
* from https://www.ietf.org/rfc/rfc2560.txt.
155+
*/
156+
public static final String ID_PKIX_OCSP_NOCHECK = "1.3.6.1.5.5.7.48.1.5";
157+
158+
159+
/**
160+
* According to https://tools.ietf.org/html/rfc5280 4.2. "Certificate Extensions":
161+
* "A certificate-using system MUST reject the certificate if it encounters a critical extension it
162+
* does not recognize or a critical extension that contains information that it cannot process."
163+
* <p>
164+
* This set consists of standard extensions which are defined in RFC specifications and are not mentioned
165+
* as forbidden to be marked as critical.
166+
*/
167+
public static final Set<String> SUPPORTED_CRITICAL_EXTENSIONS = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(
168+
KEY_USAGE,
169+
CERTIFICATE_POLICIES,
170+
POLICY_MAPPINGS,
171+
SUBJECT_ALTERNATIVE_NAME,
172+
ISSUER_ALTERNATIVE_NAME,
173+
BASIC_CONSTRAINTS,
174+
NAME_CONSTRAINTS,
175+
POLICY_CONSTRAINTS,
176+
EXTENDED_KEY_USAGE,
177+
CRL_DISTRIBUTION_POINTS,
178+
INHIBIT_ANY_POLICY,
179+
ID_PKIX_OCSP_NOCHECK
180+
)));
62181
}
63182
}

sign/src/main/java/com/itextpdf/signatures/SignUtils.java

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ This file is part of the iText (R) project.
7373
import java.security.cert.CertificateEncodingException;
7474
import java.security.cert.CertificateException;
7575
import java.security.cert.CertificateFactory;
76-
import java.security.cert.CertificateParsingException;
7776
import java.security.cert.X509Certificate;
7877
import java.text.SimpleDateFormat;
7978
import java.util.ArrayList;
@@ -118,9 +117,6 @@ This file is part of the iText (R) project.
118117
final class SignUtils {
119118
static final Object UNDEFINED_TIMESTAMP_DATE = null;
120119

121-
private static final int KEY_USAGE_DIGITAL_SIGNATURE = 0;
122-
private static final int KEY_USAGE_NON_REPUDIATION = 1;
123-
124120
static String getPrivateKeyAlgorithm(PrivateKey pk) {
125121
String algorithm = pk.getAlgorithm();
126122

@@ -298,35 +294,26 @@ static TsaResponse getTsaResponseForUserRequest(String tsaUrl, byte[] requestByt
298294
*
299295
* @param cert X509Certificate instance to check
300296
* @return true if there are unsupported critical extensions, false if there are none
297+
* @deprecated this behavior is different in Java and .NET, because in Java we use this
298+
* two-step check: first via #hasUnsupportedCriticalExtension method, and then additionally allowing
299+
* standard critical extensions; in .NET there's only second step. However, removing
300+
* first step in Java can be a breaking change for some users and moreover we don't
301+
* have any means of providing customization for unsupported extensions check as of right now.
302+
* <p>
303+
* During major release I'd suggest changing java unsupported extensions check logic to the same as in .NET,
304+
* but only if it is possible to customize this logic.
301305
*/
306+
@Deprecated
302307
static boolean hasUnsupportedCriticalExtension(X509Certificate cert) {
303308
if ( cert == null ) {
304309
throw new IllegalArgumentException("X509Certificate can't be null.");
305310
}
306311

307312
if (cert.hasUnsupportedCriticalExtension()) {
308313
for (String oid : cert.getCriticalExtensionOIDs()) {
309-
// KEY USAGE and DIGITAL SIGNING or NONREPUDIATION is ALLOWED
310-
if (OID.X509Extensions.KEY_USAGE.equals(oid)) {
311-
if(cert.getKeyUsage()[KEY_USAGE_DIGITAL_SIGNATURE] || cert.getKeyUsage()[KEY_USAGE_NON_REPUDIATION]) { // allow digSig and nonRepudiation
312-
continue;
313-
}
314-
}
315-
316-
// BASIC CONSTRAINTS is ALLOWED
317-
if (OID.X509Extensions.BASIC_CONSTRAINTS.equals(oid)) { // allow basicConstraints, can be checked later
314+
if (OID.X509Extensions.SUPPORTED_CRITICAL_EXTENSIONS.contains(oid)) {
318315
continue;
319316
}
320-
321-
try {
322-
// EXTENDED KEY USAGE and TIMESTAMPING is ALLOWED
323-
if (OID.X509Extensions.EXTENDED_KEY_USAGE.equals(oid) && cert.getExtendedKeyUsage().contains(OID.X509Extensions.ID_KP_TIMESTAMPING)) {
324-
continue;
325-
}
326-
} catch (CertificateParsingException e) {
327-
// DO NOTHING;
328-
}
329-
330317
return true;
331318
}
332319
}
@@ -392,7 +379,7 @@ private void tryToGetNextCertificate() {
392379
while (keyStoreAliases.hasMoreElements()) {
393380
try {
394381
String alias = keyStoreAliases.nextElement();
395-
if (keyStore.isCertificateEntry(alias)) {
382+
if (keyStore.isCertificateEntry(alias) || keyStore.isKeyEntry(alias)) {
396383
nextCert = (X509Certificate) keyStore.getCertificate(alias);
397384
break;
398385
}

sign/src/test/java/com/itextpdf/signatures/CertificateSupportedCriticalExtensionsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public void extendedKeyUsageWithoutIdKpTimestampingTest() {
109109
.setExtendedKeyUsage("Not ID KP TIMESTAMPING.")
110110
.setKeyUsage(true, true);
111111

112-
Assert.assertTrue(SignUtils.hasUnsupportedCriticalExtension(cert));
112+
Assert.assertFalse(SignUtils.hasUnsupportedCriticalExtension(cert));
113113
}
114114

115115
@Test

sign/src/test/java/com/itextpdf/signatures/sign/PadesSigTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ public void padesRsaSigTest01() throws IOException, GeneralSecurityException, TS
9999
basicCheckSignedDoc(destinationFolder + "padesRsaSigTest01.pdf", "Signature1");
100100
}
101101

102+
@Test
103+
public void padesRsaSigTestWithChain01() throws IOException, GeneralSecurityException, TSPException, OperatorCreationException {
104+
signApproval(certsSrc + "signCertRsaWithChain.p12", destinationFolder + "padesRsaSigTestWithChain01.pdf");
105+
106+
basicCheckSignedDoc(destinationFolder + "padesRsaSigTestWithChain01.pdf", "Signature1");
107+
}
108+
102109
@Test
103110
@Ignore("DEVSIX-1620: For some reason signatures created with the given cert (either by iText or acrobat) are considered invalid")
104111
public void padesDsaSigTest01() throws IOException, GeneralSecurityException, TSPException, OperatorCreationException {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2019 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU Affero General Public License version 3
8+
as published by the Free Software Foundation with the addition of the
9+
following permission added to Section 15 as permitted in Section 7(a):
10+
FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
11+
ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
12+
OF THIRD PARTY RIGHTS
13+
14+
This program is distributed in the hope that it will be useful, but
15+
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16+
or FITNESS FOR A PARTICULAR PURPOSE.
17+
See the GNU Affero General Public License for more details.
18+
You should have received a copy of the GNU Affero General Public License
19+
along with this program; if not, see http://www.gnu.org/licenses or write to
20+
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21+
Boston, MA, 02110-1301 USA, or download the license from the following URL:
22+
http://itextpdf.com/terms-of-use/
23+
24+
The interactive user interfaces in modified source and object code versions
25+
of this program must display Appropriate Legal Notices, as required under
26+
Section 5 of the GNU Affero General Public License.
27+
28+
In accordance with Section 7(b) of the GNU Affero General Public License,
29+
a covered work must retain the producer line in every PDF that is created
30+
or manipulated using iText.
31+
32+
You can be released from the requirements of the license by purchasing
33+
a commercial license. Buying such a license is mandatory as soon as you
34+
develop commercial activities involving the iText software without
35+
disclosing the source code of your own applications.
36+
These activities include: offering paid services to customers as an ASP,
37+
serving PDFs on the fly in a web application, shipping iText with a closed
38+
source product.
39+
40+
For more information, please contact iText Software Corp. at this
41+
42+
*/
43+
package com.itextpdf.signatures.verify;
44+
45+
import com.itextpdf.signatures.CertificateVerification;
46+
import com.itextpdf.signatures.VerificationException;
47+
import com.itextpdf.signatures.testutils.Pkcs12FileHelper;
48+
import com.itextpdf.test.ExtendedITextTest;
49+
import com.itextpdf.test.ITextTest;
50+
import com.itextpdf.test.annotations.type.UnitTest;
51+
import java.io.IOException;
52+
import java.security.KeyStore;
53+
import java.security.KeyStoreException;
54+
import java.security.NoSuchAlgorithmException;
55+
import java.security.NoSuchProviderException;
56+
import java.security.Security;
57+
import java.security.UnrecoverableKeyException;
58+
import java.security.cert.Certificate;
59+
import java.security.cert.CertificateException;
60+
import java.util.List;
61+
import org.bouncycastle.jce.provider.BouncyCastleProvider;
62+
import org.junit.AfterClass;
63+
import org.junit.Assert;
64+
import org.junit.BeforeClass;
65+
import org.junit.Test;
66+
import org.junit.experimental.categories.Category;
67+
68+
@Category(UnitTest.class)
69+
public class CertificateVerificationClassTest extends ExtendedITextTest {
70+
private static final String certsSrc = "./src/test/resources/com/itextpdf/signatures/certs/";
71+
private static final char[] password = "testpass".toCharArray();
72+
73+
@BeforeClass
74+
public static void before() {
75+
Security.addProvider(new BouncyCastleProvider());
76+
ITextTest.removeCryptographyRestrictions();
77+
}
78+
79+
@AfterClass
80+
public static void after() {
81+
ITextTest.restoreCryptographyRestrictions();
82+
}
83+
84+
@Test
85+
public void validCertificateChain01() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException, NoSuchProviderException {
86+
Certificate[] certChain = Pkcs12FileHelper.readFirstChain(certsSrc + "signCertRsaWithChain.p12", password);
87+
88+
String caCertFileName = certsSrc + "rootRsa.p12";
89+
KeyStore caKeyStore = Pkcs12FileHelper.initStore(caCertFileName, password);
90+
91+
List<VerificationException> verificationExceptions = CertificateVerification.verifyCertificates(certChain, caKeyStore);
92+
93+
Assert.assertTrue(verificationExceptions.isEmpty());
94+
}
95+
}

0 commit comments

Comments
 (0)