/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * 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 "securemessage/public_key_proto_util.h"

#include <gtest/gtest.h>

using std::unique_ptr;

namespace securemessage {
namespace {

class PublicKeyProtoUtilTest : public testing::Test {
 public:
  unique_ptr<CryptoOps::PublicKey> public_key_;

  // A byte string representing the zero byte.
  std::string zero_byte_;

 protected:
  void SetEcP256PublicKey() {
    testing::Test::SetUp();

    unique_ptr<CryptoOps::KeyPair> key_pair =
        CryptoOps::GenerateEcP256KeyPair();
    public_key_ = unique_ptr<CryptoOps::PublicKey>(new CryptoOps::PublicKey(
        key_pair->public_key->data(), key_pair->public_key->algorithm()));
    zero_byte_ = std::string("", 1);
  }

  void SetRsa2048PublicKey() {
    unique_ptr<CryptoOps::KeyPair> key_pair =
        CryptoOps::GenerateRsa2048KeyPair();
    public_key_ = unique_ptr<CryptoOps::PublicKey>(new CryptoOps::PublicKey(
        key_pair->public_key->data(), key_pair->public_key->algorithm()));
    zero_byte_ = std::string("", 1);
  }

  void AssertKeyHasSimpleRsaStructure(const unique_ptr<GenericPublicKey>& key) {
    ASSERT_NE(nullptr, key);
    ASSERT_EQ(key->type(), PublicKeyType::RSA2048);
    ASSERT_FALSE(key->rsa2048_public_key().n().empty());
  }
};

}  // namespace
}  // namespace securemessage

namespace securemessage {
namespace unittesting {
namespace {

TEST_F(PublicKeyProtoUtilTest, EncodeAndParseEcP256) {
  SetEcP256PublicKey();

  unique_ptr<GenericPublicKey> generic_key =
      PublicKeyProtoUtil::EncodePublicKey(*public_key_);

  ASSERT_NE(nullptr, generic_key);
  ASSERT_EQ(generic_key->type(), PublicKeyType::EC_P256);

  ASSERT_FALSE(generic_key->ec_p256_public_key().x().empty());
  ASSERT_FALSE(generic_key->ec_p256_public_key().y().empty());

  // Ensure the highest bit is not set.
  char x_highest_byte = generic_key->ec_p256_public_key().x()[0];
  char y_highest_byte = generic_key->ec_p256_public_key().y()[0];
  ASSERT_FALSE(x_highest_byte & 0x80);
  ASSERT_FALSE(y_highest_byte & 0x80);

  unique_ptr<CryptoOps::PublicKey> restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*generic_key);

  ASSERT_NE(nullptr, restored_public_key);

  ASSERT_EQ(restored_public_key->algorithm(),
            CryptoOps::KeyAlgorithm::ECDSA_KEY);
  ASSERT_EQ(public_key_->data().String(), restored_public_key->data().String());
}

TEST_F(PublicKeyProtoUtilTest, InvalidEncodingEcP256) {
  SetEcP256PublicKey();

  unique_ptr<GenericPublicKey> invalid_proto;
  unique_ptr<CryptoOps::PublicKey> restored_public_key;

  unique_ptr<GenericPublicKey> valid_proto =
      PublicKeyProtoUtil::EncodePublicKey(*public_key_);
  ASSERT_NE(nullptr, valid_proto);

  // Mess up the X coordinate by repeating it twice
  invalid_proto = unique_ptr<GenericPublicKey>(
      new GenericPublicKey(*valid_proto));
  std::string new_x = valid_proto->ec_p256_public_key().x();
  new_x += new_x;
  invalid_proto->mutable_ec_p256_public_key()->set_x(new_x);
  restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*invalid_proto);
  ASSERT_EQ(nullptr, restored_public_key);

  // Mess up the Y coordinate by erasing it
  invalid_proto = unique_ptr<GenericPublicKey>(
      new GenericPublicKey(*valid_proto));
  invalid_proto->mutable_ec_p256_public_key()->set_y(zero_byte_);
  restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*invalid_proto);
  ASSERT_EQ(nullptr, restored_public_key);

  // Pick a point that is likely not on the curve by copying X over Y
  invalid_proto = unique_ptr<GenericPublicKey>(
      new GenericPublicKey(*valid_proto));
  invalid_proto->mutable_ec_p256_public_key()->set_y(
      valid_proto->ec_p256_public_key().x());
  restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*invalid_proto);
  ASSERT_EQ(nullptr, restored_public_key);

  // Try the point (0, 0)
  invalid_proto = unique_ptr<GenericPublicKey>(
      new GenericPublicKey(*valid_proto));
  invalid_proto->mutable_ec_p256_public_key()->set_x(zero_byte_);
  invalid_proto->mutable_ec_p256_public_key()->set_y(zero_byte_);
  restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*invalid_proto);
  ASSERT_EQ(nullptr, restored_public_key);
}

TEST_F(PublicKeyProtoUtilTest, EncodeAndParseRsa2048) {
  SetRsa2048PublicKey();

  unique_ptr<GenericPublicKey> generic_key =
      PublicKeyProtoUtil::EncodePublicKey(*public_key_);

  AssertKeyHasSimpleRsaStructure(generic_key);

  // Ensure the highest modulus bit is not set.
  char n_highest_byte = generic_key->rsa2048_public_key().n()[0];
  ASSERT_FALSE(n_highest_byte & 0x80);

  unique_ptr<CryptoOps::PublicKey> restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*generic_key);

  ASSERT_NE(nullptr, restored_public_key);

  ASSERT_EQ(restored_public_key->algorithm(),
            CryptoOps::KeyAlgorithm::RSA_KEY);
  ASSERT_EQ(public_key_->data().String(), restored_public_key->data().String());
}

TEST_F(PublicKeyProtoUtilTest, ParseInvalidEncodingRsa2048_EmptyModulus) {
  SetRsa2048PublicKey();

  unique_ptr<GenericPublicKey> generic_key =
      PublicKeyProtoUtil::EncodePublicKey(*public_key_);

  AssertKeyHasSimpleRsaStructure(generic_key);

  generic_key->mutable_rsa2048_public_key()->clear_n();

  unique_ptr<CryptoOps::PublicKey> restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*generic_key);

  ASSERT_EQ(nullptr, restored_public_key);
}

TEST_F(PublicKeyProtoUtilTest, ParseInvalidEncodingRsa2048_ModulusTooShort) {
  SetRsa2048PublicKey();

  unique_ptr<GenericPublicKey> generic_key =
      PublicKeyProtoUtil::EncodePublicKey(*public_key_);

  AssertKeyHasSimpleRsaStructure(generic_key);

  generic_key->mutable_rsa2048_public_key()->set_n("\x10\x00\x10");

  unique_ptr<CryptoOps::PublicKey> restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*generic_key);

  ASSERT_EQ(nullptr, restored_public_key);
}

TEST_F(PublicKeyProtoUtilTest,
       ParseInvalidEncodingRsa2048_ModulusEncodingTooLong) {
  SetRsa2048PublicKey();

  unique_ptr<GenericPublicKey> generic_key =
      PublicKeyProtoUtil::EncodePublicKey(*public_key_);

  AssertKeyHasSimpleRsaStructure(generic_key);

  generic_key->mutable_rsa2048_public_key()->set_n(
      std::string("\x00\x00\x80", 3).append(std::string(255, '0')));

  unique_ptr<CryptoOps::PublicKey> restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*generic_key);

  ASSERT_EQ(nullptr, restored_public_key);
}

TEST_F(PublicKeyProtoUtilTest, ParseInvalidEncodingRsa2048_ModulusNot2048bit) {
  SetRsa2048PublicKey();

  unique_ptr<GenericPublicKey> generic_key =
      PublicKeyProtoUtil::EncodePublicKey(*public_key_);

  AssertKeyHasSimpleRsaStructure(generic_key);

  generic_key->mutable_rsa2048_public_key()->set_n(
      std::string("\x7F").append(std::string(255, '0')));

  unique_ptr<CryptoOps::PublicKey> restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*generic_key);

  ASSERT_EQ(nullptr, restored_public_key);
}

TEST_F(PublicKeyProtoUtilTest, ParseInvalidEncodingRsa2048_DoubleModulusSize) {
  SetRsa2048PublicKey();

  unique_ptr<GenericPublicKey> generic_key =
      PublicKeyProtoUtil::EncodePublicKey(*public_key_);

  AssertKeyHasSimpleRsaStructure(generic_key);

  generic_key->mutable_rsa2048_public_key()->mutable_n()->append(
      generic_key->rsa2048_public_key().n());

  unique_ptr<CryptoOps::PublicKey> restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*generic_key);

  ASSERT_EQ(nullptr, restored_public_key);
}

TEST_F(PublicKeyProtoUtilTest, ParseInvalidKeyTypeForRsa2048) {
  SetRsa2048PublicKey();

  unique_ptr<GenericPublicKey> generic_key =
      PublicKeyProtoUtil::EncodePublicKey(*public_key_);

  AssertKeyHasSimpleRsaStructure(generic_key);
  generic_key->set_type(PublicKeyType::EC_P256);

  unique_ptr<CryptoOps::PublicKey> restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*generic_key);

  ASSERT_EQ(nullptr, restored_public_key);
}

TEST_F(PublicKeyProtoUtilTest, ParseInvalidKeyTypeForEcP256) {
  SetEcP256PublicKey();

  unique_ptr<GenericPublicKey> generic_key =
      PublicKeyProtoUtil::EncodePublicKey(*public_key_);

  ASSERT_NE(nullptr, generic_key);
  ASSERT_EQ(generic_key->type(), PublicKeyType::EC_P256);
  generic_key->set_type(PublicKeyType::RSA2048);

  unique_ptr<CryptoOps::PublicKey> restored_public_key =
      PublicKeyProtoUtil::ParsePublicKey(*generic_key);

  ASSERT_EQ(nullptr, restored_public_key);
}

TEST_F(PublicKeyProtoUtilTest, EncodeInvalidKeyTypeForRsa2048) {
  SetRsa2048PublicKey();

  unique_ptr<GenericPublicKey> generic_key =
      PublicKeyProtoUtil::EncodePublicKey(
          CryptoOps::PublicKey(public_key_->data(),
                               CryptoOps::ECDSA_KEY));

  ASSERT_EQ(nullptr, generic_key);
}

TEST_F(PublicKeyProtoUtilTest, EncodeInvalidKeyTypeForEcP256) {
  SetEcP256PublicKey();

  unique_ptr<GenericPublicKey> generic_key =
      PublicKeyProtoUtil::EncodePublicKey(
          CryptoOps::PublicKey(public_key_->data(),
                               CryptoOps::RSA_KEY));

  ASSERT_EQ(nullptr, generic_key);
}

}  // namespace
}  // namespace unittesting
}  // namespace securemessage
