Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IllegalArgumentException (Unparseable date) during AndroidKeyStore key generation

I have faced the following problem in my application during RSA key generation using AndroidKeyStore, than understood that it could be easily reproduced in the BasicAndroidKeyStore sample app from the Android SDK. So, if your have Locale.getDefault() == Locale.US than this sample works well, but if you change locale to, for instance, "ar_EG", it would crash with exception:

java.lang.IllegalArgumentException: invalid date string: Unparseable date: "af`cadaaedcaGMT+00:00" (at offset 0) at com.android.org.bouncycastle.asn1.DERUTCTime.(DERUTCTime.java:98) at com.android.org.bouncycastle.asn1.x509.Time.(Time.java:62) at com.android.org.bouncycastle.x509.X509V3CertificateGenerator.setNotBefore(X509V3CertificateGenerator.java:112) at android.security.AndroidKeyPairGenerator.generateKeyPair(AndroidKeyPairGenerator.java:127) at java.security.KeyPairGenerator$KeyPairGeneratorImpl.generateKeyPair(KeyPairGenerator.java:276) at com.example.android.basicandroidkeystore.BasicAndroidKeyStoreFragment.createKeys(BasicAndroidKeyStoreFragment.java:237)

So, the problem is in the key validity time converting to String which is made with respect to a default locale. Here is code snippet from ASN1UTCTime class, which is used under the hood of KeyPairGenerator.generateKeyPair() following method call:

public ASN1UTCTime(
    String time)
{
    this.time = Strings.toByteArray(time);
    try
    {
        this.getDate();
    }
    catch (ParseException e)
    {
        throw new IllegalArgumentException("invalid date string: " + e.getMessage());
    }
}

Before calling to this method Date object is being passed to the following Time constructor, which uses default system locale:

public Time(
        Date    time)
    {
        SimpleTimeZone      tz = new SimpleTimeZone(0, "Z");
        SimpleDateFormat    dateF = new SimpleDateFormat("yyyyMMddHHmmss");
        dateF.setTimeZone(tz);
        String  d = dateF.format(time) + "Z";
        int     year = Integer.parseInt(d.substring(0, 4));
        if (year < 1950 || year > 2049)
        {
            this.time = new DERGeneralizedTime(d);
        }
        else
        {
            this.time = new DERUTCTime(d.substring(2));
        }
    } 

This is very strange, because ASN1UTCTime class has another constructor, which is seems to be more suitable for international work:

/**
     * Base constructor from a java.util.date and Locale - you may need to use this if the default locale
     * doesn't use a Gregorian calender so that the GeneralizedTime produced is compatible with other ASN.1 implementations.
     *
     * @param time a date object representing the time of interest.
     * @param locale an appropriate Locale for producing an ASN.1 UTCTime value.
     */
    public ASN1UTCTime(
        Date time,
        Locale locale)
    {
        SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmss'Z'", locale);
        dateF.setTimeZone(new SimpleTimeZone(0,"Z"));
        this.time = Strings.toByteArray(dateF.format(time));
    } 

So, what is the correct fix or advise how to solve this problem?

like image 781
texnedo Avatar asked Mar 14 '16 12:03

texnedo


1 Answers

This is a known issue with AndroidKeyStore.

Android KeyStore doesn't correctly take in the locale and it causes the failures for device locale with language from right to left. Sample stack trace:

Caused by: java.lang.IllegalArgumentException: invalid date string: Unparseable date: "aga``eaeeb`eGMT+00:00" (at offset 0)
    at com.android.org.bouncycastle.asn1.DERUTCTime.<init>(DERUTCTime.java:98)
    at com.android.org.bouncycastle.asn1.x509.Time.<init>(Time.java:62)
    at com.android.org.bouncycastle.x509.X509V3CertificateGenerator.setNotBefore(X509V3CertificateGenerator.java:112)
    at android.security.AndroidKeyPairGenerator.generateKeyPair(AndroidKeyPairGenerator.java:128)
    at java.security.KeyPairGenerator$KeyPairGeneratorImpl.generateKeyPair(KeyPairGenerator.java:275)

A workaround is setting English locale before generating a key pair and changing it back after all:

/**
 * Generates RSA keys.
 */
private void generateRsaKeys(Context context, String rsaAlias) {
    try {
        // Set English locale as default (workaround)
        Locale initialLocale = Locale.getDefault();
        setLocale(Locale.ENGLISH);
        // Generate the RSA key pairs
        Calendar start = Calendar.getInstance();
        Calendar end = Calendar.getInstance();
        end.add(Calendar.YEAR, 30); // 30 years
        KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
                .setAlias(rsaAlias)
                .setSubject(new X500Principal("CN=" + rsaAlias + ", O=Organization"))
                .setSerialNumber(BigInteger.TEN)
                .setStartDate(start.getTime())
                .setEndDate(end.getTime())
                .build();
        KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA, ANDROID_KEY_STORE);
        kpg.initialize(spec);
        kpg.generateKeyPair();
        // Reset default locale
        setLocale(initialLocale);
    } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
        Log.e(e, "generateRsaKeys: ");
    }
}

/**
 * Sets default locale.
 */
private void setLocale(Locale locale) {
    Locale.setDefault(locale);
    Resources resources = context.getResources();
    Configuration config = resources.getConfiguration();
    config.locale = locale;
    resources.updateConfiguration(config, resources.getDisplayMetrics());
}
like image 94
David Miguel Avatar answered Sep 21 '22 18:09

David Miguel