/*
 * 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/secure_message_builder.h"

#include "securemessage/secure_message_wrapper.h"
#include "securemessage/util.h"

using std::unique_ptr;

namespace securemessage {

SecureMessageBuilder::SecureMessageBuilder() {
  Reset();
}

SecureMessageBuilder::~SecureMessageBuilder() {}

SecureMessageBuilder* SecureMessageBuilder::Reset() {
  public_metadata_ = nullptr;
  decryption_key_id_ = nullptr;
  verification_key_id_ = nullptr;
  associated_data_ = nullptr;
  return this;
}

SecureMessageBuilder* SecureMessageBuilder::SetPublicMetadata(
    const string& public_metadata) {
  public_metadata_ = unique_ptr<ByteBuffer>(new ByteBuffer(public_metadata));
  return this;
}

SecureMessageBuilder* SecureMessageBuilder::SetVerificationKeyId(
    const string& verification_key_id) {
  verification_key_id_ =
      unique_ptr<ByteBuffer>(new ByteBuffer(verification_key_id));
  return this;
}

SecureMessageBuilder* SecureMessageBuilder::SetDecryptionKeyId(
    const string& decryption_key_id) {
  decryption_key_id_ =
      unique_ptr<ByteBuffer>(new ByteBuffer(decryption_key_id));
  return this;
}

SecureMessageBuilder* SecureMessageBuilder::SetAssociatedData(
    const string& associated_data) {
  if (associated_data.length() > 0) {
    associated_data_ = unique_ptr<ByteBuffer>(new ByteBuffer(associated_data));
  } else {
    associated_data_ = nullptr;
  }
  return this;
}

std::unique_ptr<SecureMessage>
SecureMessageBuilder::BuildSignedCleartextMessage(
    const CryptoOps::Key& signing_key, CryptoOps::SigType sig_type,
    const string& body) {

  if (body.length() == 0) {
    Util::LogError("Cannot build a secure message with an empty body");
    return nullptr;
  }

  if (decryption_key_id_ != nullptr) {
    Util::LogError("Cannot set decryption key id for a cleartext message");
    return nullptr;
  }

  ByteBuffer associated_data;
  if (associated_data_ != nullptr) {
    associated_data = *associated_data_;
  }

  unique_ptr<Header> header =
      BuildHeader(sig_type, CryptoOps::EncType::NONE, nullptr);

  ByteBuffer header_and_body =
      SerializeHeaderAndBody(header->SerializeAsString(), body);

  return CreateSignedResult(signing_key, sig_type, header_and_body,
                            associated_data);
}

std::unique_ptr<SecureMessage> SecureMessageBuilder::BuildSignCryptedMessage(
    const CryptoOps::Key& signing_key, CryptoOps::SigType sig_type,
    const CryptoOps::SecretKey& encryption_key, CryptoOps::EncType enc_type,
    const string& body) {

  if (body.length() == 0) {
    Util::LogError("Cannot build a secure message with an empty body");
    return nullptr;
  }

  if (enc_type == CryptoOps::EncType::NONE) {
    Util::LogError("NONE type not supported for encrypted messages");
    return nullptr;
  }

  if (CryptoOps::IsPublicKeyScheme(sig_type) &&
      verification_key_id_ == nullptr) {
    Util::LogError(
        "Must set a verification key id when using public key signature");
    return nullptr;
  }

  unique_ptr<string> iv = CryptoOps::GenerateIv(enc_type);
  if (iv == nullptr) {
    Util::LogError("Could not generate IV for given encryption type");
    return nullptr;
  }

  unique_ptr<Header> header = BuildHeader(sig_type, enc_type, iv);
  if (header == nullptr) {
    Util::LogError("Could not build header");
    return nullptr;
  }

  // We may or may not need an extra tag in front of the plaintext body
  ByteBuffer tagged_body;
  ByteBuffer associated_data_to_be_signed;
  string header_bytes = header->SerializeAsString();

  if (CryptoOps::TaggedPlaintextRequired(signing_key, sig_type,
                                         encryption_key)) {
    // Place a "tag" in front of the the plaintext message containing a
    // digest of the header
    string to_digest = header_bytes;
    if (associated_data_ != nullptr) {
      to_digest += associated_data_->String();
    }

    unique_ptr<string> digest = CryptoOps::Digest(to_digest);
    if (digest == nullptr) {
      Util::LogError("Error computing digest");
      return nullptr;
    }

    tagged_body = ByteBuffer::Concat(
        ByteBuffer(*digest),
        ByteBuffer(body));
  } else {
    tagged_body = ByteBuffer(body);

    if (associated_data_ != nullptr) {
      associated_data_to_be_signed = *associated_data_;
    }
  }

  // Compute the encrypted body, which binds the tag to the message inside the
  // ciphertext
  unique_ptr<string> encrypted_body =
      CryptoOps::Encrypt(encryption_key, enc_type, *iv, tagged_body.String());
  if (encrypted_body == nullptr) {
    Util::LogError("Could not encrypt body");
    return nullptr;
  }

  ByteBuffer header_and_body =
      SerializeHeaderAndBody(header_bytes, *encrypted_body);

  return CreateSignedResult(signing_key, sig_type, header_and_body,
                            associated_data_to_be_signed);
}

unique_ptr<Header> SecureMessageBuilder::BuildHeader(
    CryptoOps::SigType sig_type, CryptoOps::EncType enc_type,
    const unique_ptr<string>& iv) {
  int sig_scheme = SecureMessageWrapper::GetSigScheme(sig_type);
  int enc_scheme = SecureMessageWrapper::GetEncScheme(enc_type);
  if (!sig_scheme || !enc_scheme) {
    return NULL;
  }

  unique_ptr<Header> result(new Header());
  result->set_signature_scheme(static_cast<SigScheme>(sig_scheme));
  result->set_encryption_scheme(static_cast<EncScheme>(enc_scheme));

  // If we need to, set the rest
  if (verification_key_id_ != nullptr) {
    result->set_verification_key_id(verification_key_id_->ImmutableUInt8(),
                                    verification_key_id_->size());
  }
  if (decryption_key_id_ != nullptr) {
    result->set_decryption_key_id(decryption_key_id_->ImmutableUInt8(),
                                  decryption_key_id_->size());
  }
  if (public_metadata_ != nullptr) {
    result->set_public_metadata(public_metadata_->ImmutableUInt8(),
                                public_metadata_->size());
  }
  if (associated_data_ != nullptr) {
    result->set_associated_data_length(associated_data_->size());
  }
  if (iv != nullptr) {
    result->set_iv(*iv);
  }

  return result;
}

ByteBuffer SecureMessageBuilder::SerializeHeaderAndBody(const string& header,
                                                        const string& body) {
  HeaderAndBodyInternal header_and_body_internal;
  header_and_body_internal.set_header(header);
  header_and_body_internal.set_body(body);

  return ByteBuffer(header_and_body_internal.SerializeAsString());
}

std::unique_ptr<SecureMessage> SecureMessageBuilder::CreateSignedResult(
    const CryptoOps::Key& signing_key, const CryptoOps::SigType& sig_type,
    const ByteBuffer& header_and_body, const ByteBuffer& associated_data) {
  ByteBuffer header_and_body_with_associated_data =
      ByteBuffer::Concat(header_and_body, associated_data);

  unique_ptr<string> signature =
      CryptoOps::Sign(sig_type, signing_key,
                      header_and_body_with_associated_data.String());

  // Return nullptr if an error occurred
  if (signature == nullptr) {
    return nullptr;
  }

  unique_ptr<SecureMessage> result(new SecureMessage());
  result->set_header_and_body(header_and_body.String());
  result->set_signature(*signature);

  return result;
}

}  // namespace securemessage
