Skip to content

Certificate verification fails for certain Locales due to locale-sensitive date parsing #405

@jkakavas

Description

@jkakavas

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.

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());
}
}

public Date getNotAfter()
{
return c.getEndDate().getDate();
}

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 )

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions