Flip the sign on timezone offset calculation

A timezone offset of -0400 means that the local time is UTC-4. To go
from local time back to UTC, we have to negate the operation.

Update-Note: Invalid UTCTime structures with timezones will be parsed
differently. Previously we may have misinterpreted it by a day or so.

Change-Id: I4b90965eae96dc6bf215f351c0290cdd68e191f5
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/96607
Commit-Queue: Lily Chen <chlily@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Lily Chen <chlily@google.com>
diff --git a/crypto/asn1/asn1_test.cc b/crypto/asn1/asn1_test.cc
index e1b2993..087301d 100644
--- a/crypto/asn1/asn1_test.cc
+++ b/crypto/asn1/asn1_test.cc
@@ -1301,13 +1301,13 @@
   EXPECT_EQ("700101000000Z", ASN1StringToStringView(s.get()));
 
   // UTCTIME_set_string should not allow a timezone offset
-  EXPECT_FALSE(ASN1_UTCTIME_set_string(s.get(), "700101000000-0400"));
+  EXPECT_FALSE(ASN1_UTCTIME_set_string(s.get(), "700101000000+0400"));
 
   // Forcibly construct a utc time with a timezone offset.
-  ASSERT_TRUE(ASN1_STRING_set(s.get(), "700101000000-0400",
-                              strlen("700101000000-0400")));
+  ASSERT_TRUE(ASN1_STRING_set(s.get(), "700101000000+0400",
+                              strlen("700101000000+0400")));
   EXPECT_EQ(V_ASN1_UTCTIME, ASN1_STRING_type(s.get()));
-  EXPECT_EQ("700101000000-0400", ASN1StringToStringView(s.get()));
+  EXPECT_EQ("700101000000+0400", ASN1StringToStringView(s.get()));
 
   // check is expected to be valid with timezone offsets
   ASSERT_TRUE(ASN1_UTCTIME_check(s.get()));
@@ -1326,7 +1326,7 @@
   // This should be the correct value
   // EXPECT_EQ("19691231200000Z", ASN1StringToStringView(g.get()));
   // But this function currently generates invalid times.
-  EXPECT_EQ("19700101000000-0400", ASN1StringToStringView(g.get()));
+  EXPECT_EQ("19700101000000+0400", ASN1StringToStringView(g.get()));
   // Force this to be a generalized time the same as our utc time
   EXPECT_TRUE(ASN1_GENERALIZEDTIME_set_string(g.get(), "19691231200000Z"));
 
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index 443b086..32b6f6c 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -2077,6 +2077,60 @@
   }
 }
 
+TEST(CBSTest, ParseTime) {
+  tm expected = {};
+  expected.tm_year = 70; // 1970
+  expected.tm_mon = 0;   // January
+  expected.tm_mday = 1;
+  expected.tm_hour = 4;
+  expected.tm_min = 0;
+  expected.tm_sec = 0;
+
+  // 1970-01-01 00:00:00 -0400 should be 1970-01-01 04:00:00 UTC
+  CBS cbs;
+  tm tm;
+  cbs = StringAsBytes("700101000000-0400");
+  ASSERT_TRUE(CBS_parse_utc_time(&cbs, &tm, /*allow_timezone_offset=*/1));
+  EXPECT_EQ(tm.tm_year, expected.tm_year);
+  EXPECT_EQ(tm.tm_mon, expected.tm_mon);
+  EXPECT_EQ(tm.tm_mday, expected.tm_mday);
+  EXPECT_EQ(tm.tm_hour, expected.tm_hour);
+  EXPECT_EQ(tm.tm_min, expected.tm_min);
+  EXPECT_EQ(tm.tm_sec, expected.tm_sec);
+
+  cbs = StringAsBytes("19700101000000-0400");
+  ASSERT_TRUE(
+      CBS_parse_generalized_time(&cbs, &tm, /*allow_timezone_offset=*/1));
+  EXPECT_EQ(tm.tm_year, expected.tm_year);
+  EXPECT_EQ(tm.tm_mon, expected.tm_mon);
+  EXPECT_EQ(tm.tm_mday, expected.tm_mday);
+  EXPECT_EQ(tm.tm_hour, expected.tm_hour);
+  EXPECT_EQ(tm.tm_min, expected.tm_min);
+  EXPECT_EQ(tm.tm_sec, expected.tm_sec);
+
+  // 1970-01-01 08:00:00 +0400 should be 1970-01-01 04:00:00 UTC
+  expected.tm_hour = 4;
+  cbs = StringAsBytes("700101080000+0400");
+  ASSERT_TRUE(CBS_parse_utc_time(&cbs, &tm, /*allow_timezone_offset=*/1));
+  EXPECT_EQ(tm.tm_year, expected.tm_year);
+  EXPECT_EQ(tm.tm_mon, expected.tm_mon);
+  EXPECT_EQ(tm.tm_mday, expected.tm_mday);
+  EXPECT_EQ(tm.tm_hour, expected.tm_hour);
+  EXPECT_EQ(tm.tm_min, expected.tm_min);
+  EXPECT_EQ(tm.tm_sec, expected.tm_sec);
+
+  expected.tm_hour = 4;
+  cbs = StringAsBytes("19700101080000+0400");
+  ASSERT_TRUE(
+      CBS_parse_generalized_time(&cbs, &tm, /*allow_timezone_offset=*/1));
+  EXPECT_EQ(tm.tm_year, expected.tm_year);
+  EXPECT_EQ(tm.tm_mon, expected.tm_mon);
+  EXPECT_EQ(tm.tm_mday, expected.tm_mday);
+  EXPECT_EQ(tm.tm_hour, expected.tm_hour);
+  EXPECT_EQ(tm.tm_min, expected.tm_min);
+  EXPECT_EQ(tm.tm_sec, expected.tm_sec);
+}
+
 TEST(CBSTest, GetU64Decimal) {
   const struct {
     uint64_t val;
diff --git a/crypto/bytestring/cbs.cc b/crypto/bytestring/cbs.cc
index 2aef878..18a2c2e 100644
--- a/crypto/bytestring/cbs.cc
+++ b/crypto/bytestring/cbs.cc
@@ -920,10 +920,10 @@
     case 'Z':
       break;  // We correctly have 'Z' on the end as per spec.
     case '+':
-      offset_sign = 1;
+      offset_sign = -1;
       break;  // Should not be allowed per RFC 5280.
     case '-':
-      offset_sign = -1;
+      offset_sign = 1;
       break;  // Should not be allowed per RFC 5280.
     default:
       return 0;  // Reject anything else after the time.