-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Hi folk,
Bouncy Castle uses SimpleDateFormat in order to parse the time String it parses out of org.bouncycastle.asn1.ASN1UTCTime to a java.util.Date object, when performing a validity check for a Certificate.
bc-java/prov/src/main/java/org/bouncycastle/jce/provider/X509CertificateObject.java
Lines 138 to 151 in 119c510
| public void checkValidity( | |
| Date date) | |
| throws CertificateExpiredException, CertificateNotYetValidException | |
| { | |
| if (date.getTime() > this.getNotAfter().getTime()) // for other VM compatibility | |
| { | |
| throw new CertificateExpiredException("certificate expired on " + c.getEndDate().getTime()); | |
| } | |
| if (date.getTime() < this.getNotBefore().getTime()) | |
| { | |
| throw new CertificateNotYetValidException("certificate not valid till " + c.getStartDate().getTime()); | |
| } | |
| } |
bc-java/prov/src/main/java/org/bouncycastle/jce/provider/X509CertificateObject.java
Lines 219 to 222 in 119c510
| public Date getNotAfter() | |
| { | |
| return c.getEndDate().getDate(); | |
| } |
bc-java/core/src/main/java/org/bouncycastle/asn1/ASN1UTCTime.java
Lines 164 to 170 in a6c3de2
| public Date getDate() | |
| throws ParseException | |
| { | |
| SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmssz"); | |
| return dateF.parse(getTime()); | |
| } |
Since SimpleDateFormat is locale-sensitive, this leads to certificate validation errors for certain locales that follow the Gregorian calendar, but use different epochs , namely Thai and Japanese
The dates for the aforementioned locale calendars match day of the month and month (since they follow the Gregorian Calendar), but the year is off.
The following minimal example test replicates the issue:
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.util.Locale;
public class Main {
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
Locale list[] = DateFormat.getAvailableLocales();
for (Locale l : list){
InputStream in = new FileInputStream("certificate.pem");
Locale.setDefault(l);
CertificateFactory factory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) factory.generateCertificate(in);
try {
cert.checkValidity();
}catch (Exception cee){
System.out.println("Default failed for locale "+l);
System.out.println(cee.getMessage());
}
in = new FileInputStream("testclient.crt");
CertificateFactory BouncyCastleFactory = CertificateFactory.getInstance("X.509", "BC");
X509Certificate certB = (X509Certificate) BouncyCastleFactory.generateCertificate(in);
try {
certB.checkValidity();
}catch (Exception cee){
System.out.println("BouncyCastle failed for locale "+l);
System.out.println(cee.getMessage());
}
}
}
}with the following output
BouncyCastle failed for locale ja_JP_JP_#u-ca-japanese
certificate not valid till 20150923185255GMT+00:00
BouncyCastle failed for locale th_TH
certificate expired on 20190922185255GMT+00:00
BouncyCastle failed for locale th_TH_TH_#u-nu-thai
certificate expired on 20190922185255GMT+00:00
Note that the expiry dates are printed in a local unaware manner (as these were parsed from ASN1UTCTime )