am fc7eab96: am f4a6ab27: Merge "Add support for ECDSA signatures"

* commit 'fc7eab961f9dc85ee88e8c37ca1dc31a7f7b8331':
  Add support for ECDSA signatures
diff --git a/Android.mk b/Android.mk
index 645a835..1308066 100644
--- a/Android.mk
+++ b/Android.mk
@@ -24,6 +24,7 @@
     roots.cpp \
     ui.cpp \
     screen_ui.cpp \
+    asn1_decoder.cpp \
     verifier.cpp \
     adb_install.cpp
 
@@ -76,7 +77,13 @@
 
 include $(BUILD_EXECUTABLE)
 
-
+# All the APIs for testing
+include $(CLEAR_VARS)
+LOCAL_MODULE := libverifier
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := \
+    asn1_decoder.cpp
+include $(BUILD_STATIC_LIBRARY)
 
 include $(CLEAR_VARS)
 LOCAL_MODULE := verifier_test
@@ -85,6 +92,7 @@
 LOCAL_CFLAGS += -DNO_RECOVERY_MOUNT
 LOCAL_SRC_FILES := \
     verifier_test.cpp \
+    asn1_decoder.cpp \
     verifier.cpp \
     ui.cpp
 LOCAL_STATIC_LIBRARIES := \
@@ -101,6 +109,7 @@
     $(LOCAL_PATH)/minzip/Android.mk \
     $(LOCAL_PATH)/minadbd/Android.mk \
     $(LOCAL_PATH)/mtdutils/Android.mk \
+    $(LOCAL_PATH)/tests/Android.mk \
     $(LOCAL_PATH)/tools/Android.mk \
     $(LOCAL_PATH)/edify/Android.mk \
     $(LOCAL_PATH)/updater/Android.mk \
diff --git a/asn1_decoder.cpp b/asn1_decoder.cpp
new file mode 100644
index 0000000..7280f74
--- /dev/null
+++ b/asn1_decoder.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include "asn1_decoder.h"
+
+
+typedef struct asn1_context {
+    size_t length;
+    uint8_t* p;
+    int app_type;
+} asn1_context_t;
+
+
+static const int kMaskConstructed = 0xE0;
+static const int kMaskTag = 0x7F;
+static const int kMaskAppType = 0x1F;
+
+static const int kTagOctetString = 0x04;
+static const int kTagOid = 0x06;
+static const int kTagSequence = 0x30;
+static const int kTagSet = 0x31;
+static const int kTagConstructed = 0xA0;
+
+asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length) {
+    asn1_context_t* ctx = (asn1_context_t*) calloc(1, sizeof(asn1_context_t));
+    if (ctx == NULL) {
+        return NULL;
+    }
+    ctx->p = buffer;
+    ctx->length = length;
+    return ctx;
+}
+
+void asn1_context_free(asn1_context_t* ctx) {
+    free(ctx);
+}
+
+static inline int peek_byte(asn1_context_t* ctx) {
+    if (ctx->length <= 0) {
+        return -1;
+    }
+    return *ctx->p;
+}
+
+static inline int get_byte(asn1_context_t* ctx) {
+    if (ctx->length <= 0) {
+        return -1;
+    }
+    int byte = *ctx->p;
+    ctx->p++;
+    ctx->length--;
+    return byte;
+}
+
+static inline bool skip_bytes(asn1_context_t* ctx, size_t num_skip) {
+    if (ctx->length < num_skip) {
+        return false;
+    }
+    ctx->p += num_skip;
+    ctx->length -= num_skip;
+    return true;
+}
+
+static bool decode_length(asn1_context_t* ctx, size_t* out_len) {
+    int num_octets = get_byte(ctx);
+    if (num_octets == -1) {
+        return false;
+    }
+    if ((num_octets & 0x80) == 0x00) {
+        *out_len = num_octets;
+        return 1;
+    }
+    num_octets &= kMaskTag;
+    if ((size_t)num_octets >= sizeof(size_t)) {
+        return false;
+    }
+    size_t length = 0;
+    for (int i = 0; i < num_octets; ++i) {
+        int byte = get_byte(ctx);
+        if (byte == -1) {
+            return false;
+        }
+        length <<= 8;
+        length += byte;
+    }
+    *out_len = length;
+    return true;
+}
+
+/**
+ * Returns the constructed type and advances the pointer. E.g. A0 -> 0
+ */
+asn1_context_t* asn1_constructed_get(asn1_context_t* ctx) {
+    int type = get_byte(ctx);
+    if (type == -1 || (type & kMaskConstructed) != kTagConstructed) {
+        return NULL;
+    }
+    size_t length;
+    if (!decode_length(ctx, &length) || length > ctx->length) {
+        return NULL;
+    }
+    asn1_context_t* app_ctx = asn1_context_new(ctx->p, length);
+    app_ctx->app_type = type & kMaskAppType;
+    return app_ctx;
+}
+
+bool asn1_constructed_skip_all(asn1_context_t* ctx) {
+    int byte = peek_byte(ctx);
+    while (byte != -1 && (byte & kMaskConstructed) == kTagConstructed) {
+        skip_bytes(ctx, 1);
+        size_t length;
+        if (!decode_length(ctx, &length) || !skip_bytes(ctx, length)) {
+            return false;
+        }
+        byte = peek_byte(ctx);
+    }
+    return byte != -1;
+}
+
+int asn1_constructed_type(asn1_context_t* ctx) {
+    return ctx->app_type;
+}
+
+asn1_context_t* asn1_sequence_get(asn1_context_t* ctx) {
+    if ((get_byte(ctx) & kMaskTag) != kTagSequence) {
+        return NULL;
+    }
+    size_t length;
+    if (!decode_length(ctx, &length) || length > ctx->length) {
+        return NULL;
+    }
+    return asn1_context_new(ctx->p, length);
+}
+
+asn1_context_t* asn1_set_get(asn1_context_t* ctx) {
+    if ((get_byte(ctx) & kMaskTag) != kTagSet) {
+        return NULL;
+    }
+    size_t length;
+    if (!decode_length(ctx, &length) || length > ctx->length) {
+        return NULL;
+    }
+    return asn1_context_new(ctx->p, length);
+}
+
+bool asn1_sequence_next(asn1_context_t* ctx) {
+    size_t length;
+    if (get_byte(ctx) == -1 || !decode_length(ctx, &length) || !skip_bytes(ctx, length)) {
+        return false;
+    }
+    return true;
+}
+
+bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length) {
+    if (get_byte(ctx) != kTagOid) {
+        return false;
+    }
+    if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) {
+        return false;
+    }
+    *oid = ctx->p;
+    return true;
+}
+
+bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length) {
+    if (get_byte(ctx) != kTagOctetString) {
+        return false;
+    }
+    if (!decode_length(ctx, length) || *length == 0 || *length > ctx->length) {
+        return false;
+    }
+    *octet_string = ctx->p;
+    return true;
+}
diff --git a/asn1_decoder.h b/asn1_decoder.h
new file mode 100644
index 0000000..b17141c
--- /dev/null
+++ b/asn1_decoder.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ASN1_DECODER_H_
+#define ASN1_DECODER_H_
+
+#include <stdint.h>
+
+typedef struct asn1_context asn1_context_t;
+
+asn1_context_t* asn1_context_new(uint8_t* buffer, size_t length);
+void asn1_context_free(asn1_context_t* ctx);
+asn1_context_t* asn1_constructed_get(asn1_context_t* ctx);
+bool asn1_constructed_skip_all(asn1_context_t* ctx);
+int asn1_constructed_type(asn1_context_t* ctx);
+asn1_context_t* asn1_sequence_get(asn1_context_t* ctx);
+asn1_context_t* asn1_set_get(asn1_context_t* ctx);
+bool asn1_sequence_next(asn1_context_t* seq);
+bool asn1_oid_get(asn1_context_t* ctx, uint8_t** oid, size_t* length);
+bool asn1_octet_string_get(asn1_context_t* ctx, uint8_t** octet_string, size_t* length);
+
+#endif /* ASN1_DECODER_H_ */
diff --git a/testdata/otasigned_ecdsa_sha256.zip b/testdata/otasigned_ecdsa_sha256.zip
new file mode 100644
index 0000000..999fcdd
--- /dev/null
+++ b/testdata/otasigned_ecdsa_sha256.zip
Binary files differ
diff --git a/testdata/testkey_ecdsa.pk8 b/testdata/testkey_ecdsa.pk8
new file mode 100644
index 0000000..9a521c8
--- /dev/null
+++ b/testdata/testkey_ecdsa.pk8
Binary files differ
diff --git a/testdata/testkey_ecdsa.x509.pem b/testdata/testkey_ecdsa.x509.pem
new file mode 100644
index 0000000..b122836
--- /dev/null
+++ b/testdata/testkey_ecdsa.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBezCCASACCQC4g5wurPSmtzAKBggqhkjOPQQDAjBFMQswCQYDVQQGEwJBVTET
+MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMB4XDTEzMTAwODIxMTAxM1oXDTE0MTAwODIxMTAxM1owRTELMAkGA1UE
+BhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp
+ZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGcO1QDowF2E
+RboWVmAYI2oXTr5MHAJ4xpMUFsrWVvoktYSN2RhNuOl5jZGvSBsQII9p/4qfjLmS
+TBaCfQ0Xmt4wCgYIKoZIzj0EAwIDSQAwRgIhAIJjWmZAwngc2VcHUhYp2oSLoCQ+
+P+7AtbAn5242AqfOAiEAghO0t6jTKs0LUhLJrQwbOkHyZMVdZaG2vcwV9y9H5Qc=
+-----END CERTIFICATE-----
diff --git a/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..4d99d52
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,26 @@
+# Build the unit tests.
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# Build the unit tests.
+test_src_files := \
+    asn1_decoder_test.cpp
+
+shared_libraries := \
+    liblog \
+    libcutils
+
+static_libraries := \
+    libgtest \
+    libgtest_main \
+    libverifier
+
+$(foreach file,$(test_src_files), \
+    $(eval include $(CLEAR_VARS)) \
+    $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
+    $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
+    $(eval LOCAL_SRC_FILES := $(file)) \
+    $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+    $(eval LOCAL_C_INCLUDES := $(LOCAL_PATH)/..) \
+    $(eval include $(BUILD_NATIVE_TEST)) \
+)
\ No newline at end of file
diff --git a/tests/asn1_decoder_test.cpp b/tests/asn1_decoder_test.cpp
new file mode 100644
index 0000000..af96d87
--- /dev/null
+++ b/tests/asn1_decoder_test.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "asn1_decoder_test"
+
+#include <cutils/log.h>
+#include <gtest/gtest.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include "asn1_decoder.h"
+
+namespace android {
+
+class Asn1DecoderTest : public testing::Test {
+};
+
+TEST_F(Asn1DecoderTest, Empty_Failure) {
+    uint8_t empty[] = { };
+    asn1_context_t* ctx = asn1_context_new(empty, sizeof(empty));
+
+    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
+    EXPECT_FALSE(asn1_constructed_skip_all(ctx));
+    EXPECT_EQ(0, asn1_constructed_type(ctx));
+    EXPECT_EQ(NULL, asn1_sequence_get(ctx));
+    EXPECT_EQ(NULL, asn1_set_get(ctx));
+    EXPECT_FALSE(asn1_sequence_next(ctx));
+
+    uint8_t* junk;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ctx, &junk, &length));
+    EXPECT_FALSE(asn1_octet_string_get(ctx, &junk, &length));
+
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_TruncatedLength_Failure) {
+    uint8_t truncated[] = { 0xA0, 0x82, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_LengthTooBig_Failure) {
+    uint8_t truncated[] = { 0xA0, 0x8a, 0xA5, 0x5A, 0xA5, 0x5A,
+                            0xA5, 0x5A, 0xA5, 0x5A, 0xA5, 0x5A, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_EQ(NULL, asn1_constructed_get(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_TooSmallForChild_Failure) {
+    uint8_t data[] = { 0xA5, 0x02, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_constructed_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    EXPECT_EQ(5, asn1_constructed_type(ptr));
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedGet_Success) {
+    uint8_t data[] = { 0xA5, 0x03, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_constructed_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    EXPECT_EQ(5, asn1_constructed_type(ptr));
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0x01U, *oid);
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedSkipAll_TruncatedLength_Failure) {
+    uint8_t truncated[] = { 0xA2, 0x82, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_FALSE(asn1_constructed_skip_all(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, ConstructedSkipAll_Success) {
+    uint8_t data[] = { 0xA0, 0x03, 0x02, 0x01, 0x01,
+                            0xA1, 0x03, 0x02, 0x01, 0x01,
+                            0x06, 0x01, 0xA5, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    ASSERT_TRUE(asn1_constructed_skip_all(ctx));
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0xA5U, *oid);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SequenceGet_TruncatedLength_Failure) {
+    uint8_t truncated[] = { 0x30, 0x82, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_EQ(NULL, asn1_sequence_get(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SequenceGet_TooSmallForChild_Failure) {
+    uint8_t data[] = { 0x30, 0x02, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_sequence_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SequenceGet_Success) {
+    uint8_t data[] = { 0x30, 0x03, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_sequence_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0x01U, *oid);
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SetGet_TruncatedLength_Failure) {
+    uint8_t truncated[] = { 0x31, 0x82, };
+    asn1_context_t* ctx = asn1_context_new(truncated, sizeof(truncated));
+    EXPECT_EQ(NULL, asn1_set_get(ctx));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SetGet_TooSmallForChild_Failure) {
+    uint8_t data[] = { 0x31, 0x02, 0x06, 0x01, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_set_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ptr, &oid, &length));
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, SetGet_Success) {
+    uint8_t data[] = { 0x31, 0x03, 0x06, 0x01, 0xBA, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    asn1_context_t* ptr = asn1_set_get(ctx);
+    ASSERT_NE((asn1_context_t*)NULL, ptr);
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ptr, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0xBAU, *oid);
+    asn1_context_free(ptr);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OidGet_LengthZero_Failure) {
+    uint8_t data[] = { 0x06, 0x00, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OidGet_TooSmall_Failure) {
+    uint8_t data[] = { 0x06, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* oid;
+    size_t length;
+    EXPECT_FALSE(asn1_oid_get(ctx, &oid, &length));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OidGet_Success) {
+    uint8_t data[] = { 0x06, 0x01, 0x99, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* oid;
+    size_t length;
+    ASSERT_TRUE(asn1_oid_get(ctx, &oid, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0x99U, *oid);
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OctetStringGet_LengthZero_Failure) {
+    uint8_t data[] = { 0x04, 0x00, 0x55, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* string;
+    size_t length;
+    ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OctetStringGet_TooSmall_Failure) {
+    uint8_t data[] = { 0x04, 0x01, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* string;
+    size_t length;
+    ASSERT_FALSE(asn1_octet_string_get(ctx, &string, &length));
+    asn1_context_free(ctx);
+}
+
+TEST_F(Asn1DecoderTest, OctetStringGet_Success) {
+    uint8_t data[] = { 0x04, 0x01, 0xAA, };
+    asn1_context_t* ctx = asn1_context_new(data, sizeof(data));
+    uint8_t* string;
+    size_t length;
+    ASSERT_TRUE(asn1_octet_string_get(ctx, &string, &length));
+    EXPECT_EQ(1U, length);
+    EXPECT_EQ(0xAAU, *string);
+    asn1_context_free(ctx);
+}
+
+} // namespace android
diff --git a/verifier.cpp b/verifier.cpp
index 782a838..0930fbd 100644
--- a/verifier.cpp
+++ b/verifier.cpp
@@ -14,10 +14,14 @@
  * limitations under the License.
  */
 
+#include "asn1_decoder.h"
 #include "common.h"
-#include "verifier.h"
 #include "ui.h"
+#include "verifier.h"
 
+#include "mincrypt/dsa_sig.h"
+#include "mincrypt/p256.h"
+#include "mincrypt/p256_ecdsa.h"
 #include "mincrypt/rsa.h"
 #include "mincrypt/sha.h"
 #include "mincrypt/sha256.h"
@@ -28,6 +32,78 @@
 
 extern RecoveryUI* ui;
 
+/*
+ * Simple version of PKCS#7 SignedData extraction. This extracts the
+ * signature OCTET STRING to be used for signature verification.
+ *
+ * For full details, see http://www.ietf.org/rfc/rfc3852.txt
+ *
+ * The PKCS#7 structure looks like:
+ *
+ *   SEQUENCE (ContentInfo)
+ *     OID (ContentType)
+ *     [0] (content)
+ *       SEQUENCE (SignedData)
+ *         INTEGER (version CMSVersion)
+ *         SET (DigestAlgorithmIdentifiers)
+ *         SEQUENCE (EncapsulatedContentInfo)
+ *         [0] (CertificateSet OPTIONAL)
+ *         [1] (RevocationInfoChoices OPTIONAL)
+ *         SET (SignerInfos)
+ *           SEQUENCE (SignerInfo)
+ *             INTEGER (CMSVersion)
+ *             SEQUENCE (SignerIdentifier)
+ *             SEQUENCE (DigestAlgorithmIdentifier)
+ *             SEQUENCE (SignatureAlgorithmIdentifier)
+ *             OCTET STRING (SignatureValue)
+ */
+static bool read_pkcs7(uint8_t* pkcs7_der, size_t pkcs7_der_len, uint8_t** sig_der,
+        size_t* sig_der_length) {
+    asn1_context_t* ctx = asn1_context_new(pkcs7_der, pkcs7_der_len);
+    if (ctx == NULL) {
+        return false;
+    }
+
+    asn1_context_t* pkcs7_seq = asn1_sequence_get(ctx);
+    if (pkcs7_seq != NULL && asn1_sequence_next(pkcs7_seq)) {
+        asn1_context_t *signed_data_app = asn1_constructed_get(pkcs7_seq);
+        if (signed_data_app != NULL) {
+            asn1_context_t* signed_data_seq = asn1_sequence_get(signed_data_app);
+            if (signed_data_seq != NULL
+                    && asn1_sequence_next(signed_data_seq)
+                    && asn1_sequence_next(signed_data_seq)
+                    && asn1_sequence_next(signed_data_seq)
+                    && asn1_constructed_skip_all(signed_data_seq)) {
+                asn1_context_t *sig_set = asn1_set_get(signed_data_seq);
+                if (sig_set != NULL) {
+                    asn1_context_t* sig_seq = asn1_sequence_get(sig_set);
+                    if (sig_seq != NULL
+                            && asn1_sequence_next(sig_seq)
+                            && asn1_sequence_next(sig_seq)
+                            && asn1_sequence_next(sig_seq)
+                            && asn1_sequence_next(sig_seq)) {
+                        uint8_t* sig_der_ptr;
+                        if (asn1_octet_string_get(sig_seq, &sig_der_ptr, sig_der_length)) {
+                            *sig_der = (uint8_t*) malloc(*sig_der_length);
+                            if (*sig_der != NULL) {
+                                memcpy(*sig_der, sig_der_ptr, *sig_der_length);
+                            }
+                        }
+                        asn1_context_free(sig_seq);
+                    }
+                    asn1_context_free(sig_set);
+                }
+                asn1_context_free(signed_data_seq);
+            }
+            asn1_context_free(signed_data_app);
+        }
+        asn1_context_free(pkcs7_seq);
+    }
+    asn1_context_free(ctx);
+
+    return *sig_der != NULL;
+}
+
 // Look for an RSA signature embedded in the .ZIP file comment given
 // the path to the zip.  Verify it matches one of the given public
 // keys.
@@ -79,9 +155,8 @@
     LOGI("comment is %d bytes; signature %d bytes from end\n",
          comment_size, signature_start);
 
-    if (signature_start - FOOTER_SIZE < RSANUMBYTES) {
-        // "signature" block isn't big enough to contain an RSA block.
-        LOGE("signature is too short\n");
+    if (signature_start <= FOOTER_SIZE) {
+        LOGE("Signature start is in the footer");
         fclose(f);
         return VERIFY_FAILURE;
     }
@@ -187,6 +262,23 @@
     const uint8_t* sha1 = SHA_final(&sha1_ctx);
     const uint8_t* sha256 = SHA256_final(&sha256_ctx);
 
+    uint8_t* sig_der = NULL;
+    size_t sig_der_length = 0;
+
+    size_t signature_size = signature_start - FOOTER_SIZE;
+    if (!read_pkcs7(eocd + eocd_size - signature_start, signature_size, &sig_der,
+            &sig_der_length)) {
+        LOGE("Could not find signature DER block\n");
+        free(eocd);
+        return VERIFY_FAILURE;
+    }
+    free(eocd);
+
+    /*
+     * Check to make sure at least one of the keys matches the signature. Since
+     * any key can match, we need to try each before determining a verification
+     * failure has happened.
+     */
     for (i = 0; i < numKeys; ++i) {
         const uint8_t* hash;
         switch (pKeys[i].hash_len) {
@@ -197,16 +289,46 @@
 
         // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that
         // the signing tool appends after the signature itself.
-        if (RSA_verify(pKeys[i].public_key, eocd + eocd_size - 6 - RSANUMBYTES,
-                       RSANUMBYTES, hash, pKeys[i].hash_len)) {
-            LOGI("whole-file signature verified against key %d\n", i);
-            free(eocd);
+        if (pKeys[i].key_type == Certificate::RSA) {
+            if (sig_der_length < RSANUMBYTES) {
+                // "signature" block isn't big enough to contain an RSA block.
+                LOGI("signature is too short for RSA key %d\n", i);
+                continue;
+            }
+
+            if (!RSA_verify(pKeys[i].rsa, sig_der, RSANUMBYTES,
+                            hash, pKeys[i].hash_len)) {
+                LOGI("failed to verify against RSA key %d\n", i);
+                continue;
+            }
+
+            LOGI("whole-file signature verified against RSA key %d\n", i);
+            free(sig_der);
+            return VERIFY_SUCCESS;
+        } else if (pKeys[i].key_type == Certificate::EC
+                && pKeys[i].hash_len == SHA256_DIGEST_SIZE) {
+            p256_int r, s;
+            if (!dsa_sig_unpack(sig_der, sig_der_length, &r, &s)) {
+                LOGI("Not a DSA signature block for EC key %d\n", i);
+                continue;
+            }
+
+            p256_int p256_hash;
+            p256_from_bin(hash, &p256_hash);
+            if (!p256_ecdsa_verify(&(pKeys[i].ec->x), &(pKeys[i].ec->y),
+                                   &p256_hash, &r, &s)) {
+                LOGI("failed to verify against EC key %d\n", i);
+                continue;
+            }
+
+            LOGI("whole-file signature verified against EC key %d\n", i);
+            free(sig_der);
             return VERIFY_SUCCESS;
         } else {
-            LOGI("failed to verify against key %d\n", i);
+            LOGI("Unknown key type %d\n", pKeys[i].key_type);
         }
     }
-    free(eocd);
+    free(sig_der);
     LOGE("failed to verify whole-file signature\n");
     return VERIFY_FAILURE;
 }
@@ -238,6 +360,7 @@
 //       2: 2048-bit RSA key with e=65537 and SHA-1 hash
 //       3: 2048-bit RSA key with e=3 and SHA-256 hash
 //       4: 2048-bit RSA key with e=65537 and SHA-256 hash
+//       5: 256-bit EC key using the NIST P-256 curve parameters and SHA-256 hash
 //
 // Returns NULL if the file failed to parse, or if it contain zero keys.
 Certificate*
@@ -258,28 +381,41 @@
             ++*numKeys;
             out = (Certificate*)realloc(out, *numKeys * sizeof(Certificate));
             Certificate* cert = out + (*numKeys - 1);
-            cert->public_key = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+            memset(cert, '\0', sizeof(Certificate));
 
             char start_char;
             if (fscanf(f, " %c", &start_char) != 1) goto exit;
             if (start_char == '{') {
                 // a version 1 key has no version specifier.
-                cert->public_key->exponent = 3;
+                cert->key_type = Certificate::RSA;
+                cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+                cert->rsa->exponent = 3;
                 cert->hash_len = SHA_DIGEST_SIZE;
             } else if (start_char == 'v') {
                 int version;
                 if (fscanf(f, "%d {", &version) != 1) goto exit;
                 switch (version) {
                     case 2:
-                        cert->public_key->exponent = 65537;
+                        cert->key_type = Certificate::RSA;
+                        cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+                        cert->rsa->exponent = 65537;
                         cert->hash_len = SHA_DIGEST_SIZE;
                         break;
                     case 3:
-                        cert->public_key->exponent = 3;
+                        cert->key_type = Certificate::RSA;
+                        cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+                        cert->rsa->exponent = 3;
                         cert->hash_len = SHA256_DIGEST_SIZE;
                         break;
                     case 4:
-                        cert->public_key->exponent = 65537;
+                        cert->key_type = Certificate::RSA;
+                        cert->rsa = (RSAPublicKey*)malloc(sizeof(RSAPublicKey));
+                        cert->rsa->exponent = 65537;
+                        cert->hash_len = SHA256_DIGEST_SIZE;
+                        break;
+                    case 5:
+                        cert->key_type = Certificate::EC;
+                        cert->ec = (ECPublicKey*)calloc(1, sizeof(ECPublicKey));
                         cert->hash_len = SHA256_DIGEST_SIZE;
                         break;
                     default:
@@ -287,23 +423,55 @@
                 }
             }
 
-            RSAPublicKey* key = cert->public_key;
-            if (fscanf(f, " %i , 0x%x , { %u",
-                       &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
+            if (cert->key_type == Certificate::RSA) {
+                RSAPublicKey* key = cert->rsa;
+                if (fscanf(f, " %i , 0x%x , { %u",
+                           &(key->len), &(key->n0inv), &(key->n[0])) != 3) {
+                    goto exit;
+                }
+                if (key->len != RSANUMWORDS) {
+                    LOGE("key length (%d) does not match expected size\n", key->len);
+                    goto exit;
+                }
+                for (i = 1; i < key->len; ++i) {
+                    if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;
+                }
+                if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
+                for (i = 1; i < key->len; ++i) {
+                    if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
+                }
+                fscanf(f, " } } ");
+
+                LOGI("read key e=%d hash=%d\n", key->exponent, cert->hash_len);
+            } else if (cert->key_type == Certificate::EC) {
+                ECPublicKey* key = cert->ec;
+                int key_len;
+                unsigned int byte;
+                uint8_t x_bytes[P256_NBYTES];
+                uint8_t y_bytes[P256_NBYTES];
+                if (fscanf(f, " %i , { %u", &key_len, &byte) != 2) goto exit;
+                if (key_len != P256_NBYTES) {
+                    LOGE("Key length (%d) does not match expected size %d\n", key_len, P256_NBYTES);
+                    goto exit;
+                }
+                x_bytes[P256_NBYTES - 1] = byte;
+                for (i = P256_NBYTES - 2; i >= 0; --i) {
+                    if (fscanf(f, " , %u", &byte) != 1) goto exit;
+                    x_bytes[i] = byte;
+                }
+                if (fscanf(f, " } , { %u", &byte) != 1) goto exit;
+                y_bytes[P256_NBYTES - 1] = byte;
+                for (i = P256_NBYTES - 2; i >= 0; --i) {
+                    if (fscanf(f, " , %u", &byte) != 1) goto exit;
+                    y_bytes[i] = byte;
+                }
+                fscanf(f, " } } ");
+                p256_from_bin(x_bytes, &key->x);
+                p256_from_bin(y_bytes, &key->y);
+            } else {
+                LOGE("Unknown key type %d\n", cert->key_type);
                 goto exit;
             }
-            if (key->len != RSANUMWORDS) {
-                LOGE("key length (%d) does not match expected size\n", key->len);
-                goto exit;
-            }
-            for (i = 1; i < key->len; ++i) {
-                if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;
-            }
-            if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;
-            for (i = 1; i < key->len; ++i) {
-                if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;
-            }
-            fscanf(f, " } } ");
 
             // if the line ends in a comma, this file has more keys.
             switch (fgetc(f)) {
@@ -319,8 +487,6 @@
                 LOGE("unexpected character between keys\n");
                 goto exit;
             }
-
-            LOGI("read key e=%d hash=%d\n", key->exponent, cert->hash_len);
         }
     }
 
diff --git a/verifier.h b/verifier.h
index 6ce1b44..023d3bf 100644
--- a/verifier.h
+++ b/verifier.h
@@ -17,11 +17,24 @@
 #ifndef _RECOVERY_VERIFIER_H
 #define _RECOVERY_VERIFIER_H
 
+#include "mincrypt/p256.h"
 #include "mincrypt/rsa.h"
 
-typedef struct Certificate {
+typedef struct {
+    p256_int x;
+    p256_int y;
+} ECPublicKey;
+
+typedef struct {
+    typedef enum {
+        RSA,
+        EC,
+    } KeyType;
+
     int hash_len;  // SHA_DIGEST_SIZE (SHA-1) or SHA256_DIGEST_SIZE (SHA-256)
-    RSAPublicKey* public_key;
+    KeyType key_type;
+    RSAPublicKey* rsa;
+    ECPublicKey* ec;
 } Certificate;
 
 /* Look in the file for a signature footer, and verify that it
diff --git a/verifier_test.cpp b/verifier_test.cpp
index 1063cba..88fcad4 100644
--- a/verifier_test.cpp
+++ b/verifier_test.cpp
@@ -100,6 +100,18 @@
       65537
     };
 
+ECPublicKey test_ec_key =
+    {
+       {
+         {0xd656fa24u, 0x931416cau, 0x1c0278c6u, 0x174ebe4cu,
+          0x6018236au, 0x45ba1656u, 0xe8c05d84u, 0x670ed500u}
+      },
+      {
+        {0x0d179adeu, 0x4c16827du, 0x9f8cb992u, 0x8f69ff8au,
+         0x481b1020u, 0x798d91afu, 0x184db8e9u, 0xb5848dd9u}
+      }
+    };
+
 RecoveryUI* ui = NULL;
 
 // verifier expects to find a UI object; we provide one that does
@@ -136,34 +148,86 @@
     va_end(ap);
 }
 
+static Certificate* add_certificate(Certificate** certsp, int* num_keys,
+        Certificate::KeyType key_type) {
+    int i = *num_keys;
+    *num_keys = *num_keys + 1;
+    *certsp = (Certificate*) realloc(*certsp, *num_keys * sizeof(Certificate));
+    Certificate* certs = *certsp;
+    certs[i].rsa = NULL;
+    certs[i].ec = NULL;
+    certs[i].key_type = key_type;
+    certs[i].hash_len = SHA_DIGEST_SIZE;
+    return &certs[i];
+}
+
 int main(int argc, char **argv) {
-    if (argc < 2 || argc > 4) {
-        fprintf(stderr, "Usage: %s [-sha256] [-f4 | -file <keys>] <package>\n", argv[0]);
+    if (argc < 2) {
+        fprintf(stderr, "Usage: %s [-sha256] [-ec | -f4 | -file <keys>] <package>\n", argv[0]);
+        return 2;
+    }
+    Certificate* certs = NULL;
+    int num_keys = 0;
+
+    int argn = 1;
+    while (argn < argc) {
+        if (strcmp(argv[argn], "-sha256") == 0) {
+            if (num_keys == 0) {
+                fprintf(stderr, "May only specify -sha256 after key type\n");
+                return 2;
+            }
+            ++argn;
+            Certificate* cert = &certs[num_keys - 1];
+            cert->hash_len = SHA256_DIGEST_SIZE;
+        } else if (strcmp(argv[argn], "-ec") == 0) {
+            ++argn;
+            Certificate* cert = add_certificate(&certs, &num_keys, Certificate::EC);
+            cert->ec = &test_ec_key;
+        } else if (strcmp(argv[argn], "-e3") == 0) {
+            ++argn;
+            Certificate* cert = add_certificate(&certs, &num_keys, Certificate::RSA);
+            cert->rsa = &test_key;
+        } else if (strcmp(argv[argn], "-f4") == 0) {
+            ++argn;
+            Certificate* cert = add_certificate(&certs, &num_keys, Certificate::RSA);
+            cert->rsa = &test_f4_key;
+        } else if (strcmp(argv[argn], "-file") == 0) {
+            if (certs != NULL) {
+                fprintf(stderr, "Cannot specify -file with other certs specified\n");
+                return 2;
+            }
+            ++argn;
+            certs = load_keys(argv[argn], &num_keys);
+            ++argn;
+        } else if (argv[argn][0] == '-') {
+            fprintf(stderr, "Unknown argument %s\n", argv[argn]);
+            return 2;
+        } else {
+            break;
+        }
+    }
+
+    if (argn == argc) {
+        fprintf(stderr, "Must specify package to verify\n");
         return 2;
     }
 
-    Certificate default_cert;
-    Certificate* cert = &default_cert;
-    cert->public_key = &test_key;
-    cert->hash_len = SHA_DIGEST_SIZE;
-    int num_keys = 1;
-    ++argv;
-    if (strcmp(argv[0], "-sha256") == 0) {
-        ++argv;
-        cert->hash_len = SHA256_DIGEST_SIZE;
-    }
-    if (strcmp(argv[0], "-f4") == 0) {
-        ++argv;
-        cert->public_key = &test_f4_key;
-    } else if (strcmp(argv[0], "-file") == 0) {
-        ++argv;
-        cert = load_keys(argv[0], &num_keys);
-        ++argv;
+    if (num_keys == 0) {
+        certs = (Certificate*) calloc(1, sizeof(Certificate));
+        if (certs == NULL) {
+            fprintf(stderr, "Failure allocating memory for default certificate\n");
+            return 1;
+        }
+        certs->key_type = Certificate::RSA;
+        certs->rsa = &test_key;
+        certs->ec = NULL;
+        certs->hash_len = SHA_DIGEST_SIZE;
+        num_keys = 1;
     }
 
     ui = new FakeUI();
 
-    int result = verify_file(*argv, cert, num_keys);
+    int result = verify_file(argv[argn], certs, num_keys);
     if (result == VERIFY_SUCCESS) {
         printf("VERIFIED\n");
         return 0;
diff --git a/verifier_test.sh b/verifier_test.sh
index 65f77f4..4761cef 100755
--- a/verifier_test.sh
+++ b/verifier_test.sh
@@ -81,20 +81,30 @@
 expect_fail jarsigned.zip
 
 # success cases
-expect_succeed otasigned.zip
+expect_succeed otasigned.zip -e3
 expect_succeed otasigned_f4.zip -f4
-expect_succeed otasigned_sha256.zip -sha256
-expect_succeed otasigned_f4_sha256.zip -sha256 -f4
+expect_succeed otasigned_sha256.zip -e3 -sha256
+expect_succeed otasigned_f4_sha256.zip -f4 -sha256
+expect_succeed otasigned_ecdsa_sha256.zip -ec -sha256
+
+# success with multiple keys
+expect_succeed otasigned.zip -f4 -e3
+expect_succeed otasigned_f4.zip -ec -f4
+expect_succeed otasigned_sha256.zip -ec -e3 -e3 -sha256
+expect_succeed otasigned_f4_sha256.zip -ec -sha256 -e3 -f4 -sha256
+expect_succeed otasigned_ecdsa_sha256.zip -f4 -sha256 -e3 -ec -sha256
 
 # verified against different key
 expect_fail otasigned.zip -f4
-expect_fail otasigned_f4.zip
+expect_fail otasigned_f4.zip -e3
+expect_fail otasigned_ecdsa_sha256.zip -e3 -sha256
 
 # verified against right key but wrong hash algorithm
-expect_fail otasigned.zip -sha256
-expect_fail otasigned_f4.zip -sha256 -f4
+expect_fail otasigned.zip -e3 -sha256
+expect_fail otasigned_f4.zip -f4 -sha256
 expect_fail otasigned_sha256.zip
 expect_fail otasigned_f4_sha256.zip -f4
+expect_fail otasigned_ecdsa_sha256.zip
 
 # various other cases
 expect_fail random.zip