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?