/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "TransportSecurityInfo.h"

#include "ipc/IPCMessageUtils.h"
#include "mozilla/Base64.h"
#include "mozpkix/pkixtypes.h"
#include "nsBase64Encoder.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsIWebProgressListener.h"
#include "nsNSSCertHelper.h"
#include "nsNSSComponent.h"
#include "nsNSSHelper.h"
#include "nsReadableUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsStringStream.h"
#include "nsXULAppAPI.h"
#include "nsIX509Cert.h"
#include "secerr.h"
#include "ssl.h"

#include "mozilla/ipc/IPDLParamTraits.h"

// nsITransportSecurityInfo should not be created via do_CreateInstance. This
// stub prevents that.
template <>
already_AddRefed<nsISupports>
mozCreateComponent<mozilla::psm::TransportSecurityInfo>() {
  return nullptr;
}

namespace mozilla {
namespace psm {

TransportSecurityInfo::TransportSecurityInfo(
    uint32_t aSecurityState, PRErrorCode aErrorCode,
    nsTArray<RefPtr<nsIX509Cert>>&& aFailedCertChain,
    nsCOMPtr<nsIX509Cert>& aServerCert,
    nsTArray<RefPtr<nsIX509Cert>>&& aSucceededCertChain,
    Maybe<uint16_t> aCipherSuite, Maybe<nsCString> aKeaGroupName,
    Maybe<nsCString> aSignatureSchemeName, Maybe<uint16_t> aProtocolVersion,
    uint16_t aCertificateTransparencyStatus, Maybe<bool> aIsAcceptedEch,
    Maybe<bool> aIsDelegatedCredential,
    Maybe<OverridableErrorCategory> aOverridableErrorCategory,
    bool aMadeOCSPRequests, bool aUsedPrivateDNS, Maybe<bool> aIsEV,
    bool aNPNCompleted, const nsCString& aNegotiatedNPN, bool aResumed,
    bool aIsBuiltCertChainRootBuiltInRoot, const nsCString& aPeerId)
    : mSecurityState(aSecurityState),
      mErrorCode(aErrorCode),
      mFailedCertChain(std::move(aFailedCertChain)),
      mServerCert(aServerCert),
      mSucceededCertChain(std::move(aSucceededCertChain)),
      mCipherSuite(aCipherSuite),
      mKeaGroupName(aKeaGroupName),
      mSignatureSchemeName(aSignatureSchemeName),
      mProtocolVersion(aProtocolVersion),
      mCertificateTransparencyStatus(aCertificateTransparencyStatus),
      mIsAcceptedEch(aIsAcceptedEch),
      mIsDelegatedCredential(aIsDelegatedCredential),
      mOverridableErrorCategory(aOverridableErrorCategory),
      mMadeOCSPRequests(aMadeOCSPRequests),
      mUsedPrivateDNS(aUsedPrivateDNS),
      mIsEV(aIsEV),
      mNPNCompleted(aNPNCompleted),
      mNegotiatedNPN(aNegotiatedNPN),
      mResumed(aResumed),
      mIsBuiltCertChainRootBuiltInRoot(aIsBuiltCertChainRootBuiltInRoot),
      mPeerId(aPeerId) {}

NS_IMPL_ISUPPORTS(TransportSecurityInfo, nsITransportSecurityInfo)

NS_IMETHODIMP
TransportSecurityInfo::GetSecurityState(uint32_t* state) {
  *state = mSecurityState;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetErrorCode(int32_t* state) {
  *state = mErrorCode;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetErrorCodeString(nsAString& aErrorString) {
  const char* codeName = PR_ErrorToName(mErrorCode);
  aErrorString.Truncate();
  if (codeName) {
    aErrorString = NS_ConvertASCIItoUTF16(codeName);
  }

  return NS_OK;
}

// 16786594-0296-4471-8096-8f84497ca428
#define TRANSPORTSECURITYINFO_CID                    \
  {                                                  \
    0x16786594, 0x0296, 0x4471, {                    \
      0x80, 0x96, 0x8f, 0x84, 0x49, 0x7c, 0xa4, 0x28 \
    }                                                \
  }
static NS_DEFINE_CID(kTransportSecurityInfoCID, TRANSPORTSECURITYINFO_CID);

// This is a new magic value. However, it re-uses the first 4 bytes
// of the previous value. This is so when older versions attempt to
// read a newer serialized TransportSecurityInfo, they will actually
// fail and return NS_ERROR_FAILURE instead of silently failing.
#define TRANSPORTSECURITYINFOMAGIC                   \
  {                                                  \
    0xa9863a23, 0x1faa, 0x4169, {                    \
      0xb0, 0xd2, 0x81, 0x29, 0xec, 0x7c, 0xb1, 0xde \
    }                                                \
  }
static NS_DEFINE_CID(kTransportSecurityInfoMagic, TRANSPORTSECURITYINFOMAGIC);

NS_IMETHODIMP
TransportSecurityInfo::ToString(nsACString& aResult) {
  RefPtr<nsBase64Encoder> stream(new nsBase64Encoder());
  nsCOMPtr<nsIObjectOutputStream> objStream(NS_NewObjectOutputStream(stream));
  nsresult rv = objStream->WriteID(kTransportSecurityInfoCID);
  if (NS_FAILED(rv)) {
    return rv;
  }
  rv = objStream->WriteID(NS_ISUPPORTS_IID);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = objStream->WriteID(kTransportSecurityInfoMagic);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = objStream->Write32(mSecurityState);
  if (NS_FAILED(rv)) {
    return rv;
  }
  // mSubRequestsBrokenSecurity was removed in bug 748809
  rv = objStream->Write32(0);
  if (NS_FAILED(rv)) {
    return rv;
  }
  // mSubRequestsNoSecurity was removed in bug 748809
  rv = objStream->Write32(0);
  if (NS_FAILED(rv)) {
    return rv;
  }
  rv = objStream->Write32(static_cast<uint32_t>(mErrorCode));
  if (NS_FAILED(rv)) {
    return rv;
  }

  // Re-purpose mErrorMessageCached to represent serialization version
  // If string doesn't match exact version it will be treated as older
  // serialization.
  rv = objStream->WriteWStringZ(NS_ConvertUTF8toUTF16("9").get());
  if (NS_FAILED(rv)) {
    return rv;
  }

  // moved from nsISSLStatus
  rv = NS_WriteOptionalCompoundObject(objStream, mServerCert,
                                      NS_GET_IID(nsIX509Cert), true);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = objStream->Write16(mCipherSuite.isSome() ? *mCipherSuite : 0);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = objStream->Write16(mProtocolVersion.isSome() ? *mProtocolVersion : 0);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = objStream->Write32(mOverridableErrorCategory.isSome()
                              ? *mOverridableErrorCategory
                              : OverridableErrorCategory::ERROR_UNSET);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = objStream->WriteBoolean(mIsEV.isSome() ? *mIsEV : false);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = objStream->WriteBoolean(mIsEV.isSome());  // previously mHasIsEV
  NS_ENSURE_SUCCESS(rv, rv);
  rv = objStream->WriteBoolean(
      mCipherSuite.isSome());  // previously mHaveCipherSuiteAndProtocol
  NS_ENSURE_SUCCESS(rv, rv);
  rv = objStream->WriteBoolean(
      mOverridableErrorCategory.isSome());  // previously mHaveCertErrorBits
  NS_ENSURE_SUCCESS(rv, rv);

  rv = objStream->Write16(mCertificateTransparencyStatus);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = objStream->WriteStringZ(mKeaGroupName.isSome() ? (*mKeaGroupName).get()
                                                      : "");
  NS_ENSURE_SUCCESS(rv, rv);

  rv = objStream->WriteStringZ(
      mSignatureSchemeName.isSome() ? (*mSignatureSchemeName).get() : "");
  NS_ENSURE_SUCCESS(rv, rv);

  rv = objStream->Write16(mSucceededCertChain.Length());
  NS_ENSURE_SUCCESS(rv, rv);

  for (const auto& cert : mSucceededCertChain) {
    rv = objStream->WriteCompoundObject(cert, NS_GET_IID(nsIX509Cert), true);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  // END moved from nsISSLStatus
  rv = objStream->Write16(mFailedCertChain.Length());
  NS_ENSURE_SUCCESS(rv, rv);
  for (const auto& cert : mFailedCertChain) {
    rv = objStream->WriteCompoundObject(cert, NS_GET_IID(nsIX509Cert), true);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  rv = objStream->WriteBoolean(
      mIsDelegatedCredential.isSome() ? *mIsDelegatedCredential : false);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = objStream->WriteBoolean(mNPNCompleted);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = objStream->WriteStringZ(mNegotiatedNPN.get());
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = objStream->WriteBoolean(mResumed);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = objStream->WriteBoolean(mIsBuiltCertChainRootBuiltInRoot);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = objStream->WriteBoolean(mIsAcceptedEch.isSome() ? *mIsAcceptedEch
                                                       : false);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = objStream->WriteStringZ(mPeerId.get());
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = objStream->WriteBoolean(mMadeOCSPRequests);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = objStream->WriteBoolean(mUsedPrivateDNS);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = stream->Finish(aResult);
  if (NS_FAILED(rv)) {
    return rv;
  }

  return NS_OK;
}

nsresult TransportSecurityInfo::ReadOldOverridableErrorBits(
    nsIObjectInputStream* aStream,
    OverridableErrorCategory& aOverridableErrorCategory) {
  bool isDomainMismatch;
  nsresult rv = aStream->ReadBoolean(&isDomainMismatch);
  NS_ENSURE_SUCCESS(rv, rv);
  bool isNotValidAtThisTime;
  rv = aStream->ReadBoolean(&isNotValidAtThisTime);
  NS_ENSURE_SUCCESS(rv, rv);
  bool isUntrusted;
  rv = aStream->ReadBoolean(&isUntrusted);
  NS_ENSURE_SUCCESS(rv, rv);
  if (isUntrusted) {
    aOverridableErrorCategory =
        nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST;
  } else if (isDomainMismatch) {
    aOverridableErrorCategory =
        nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN;
  } else if (isNotValidAtThisTime) {
    aOverridableErrorCategory =
        nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME;
  } else {
    aOverridableErrorCategory =
        nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET;
  }

  return NS_OK;
}

// This is for backward compatibility to be able to read nsISSLStatus
// serialized object.
nsresult TransportSecurityInfo::ReadSSLStatus(
    nsIObjectInputStream* aStream, nsCOMPtr<nsIX509Cert>& aServerCert,
    Maybe<uint16_t>& aCipherSuite, Maybe<uint16_t>& aProtocolVersion,
    Maybe<OverridableErrorCategory>& aOverridableErrorCategory,
    Maybe<bool>& aIsEV, uint16_t& aCertificateTransparencyStatus,
    Maybe<nsCString>& aKeaGroupName, Maybe<nsCString>& aSignatureSchemeName,
    nsTArray<RefPtr<nsIX509Cert>>& aSucceededCertChain) {
  bool nsISSLStatusPresent;
  nsresult rv = aStream->ReadBoolean(&nsISSLStatusPresent);
  NS_ENSURE_SUCCESS(rv, rv);
  if (!nsISSLStatusPresent) {
    return NS_OK;
  }
  // nsISSLStatus present.  Prepare to read elements.
  // Throw away cid, validate iid
  nsCID cid;
  nsIID iid;
  rv = aStream->ReadID(&cid);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aStream->ReadID(&iid);
  NS_ENSURE_SUCCESS(rv, rv);

  static const nsIID nsSSLStatusIID = {
      0xfa9ba95b,
      0xca3b,
      0x498a,
      {0xb8, 0x89, 0x7c, 0x79, 0xcf, 0x28, 0xfe, 0xe8}};
  if (!iid.Equals(nsSSLStatusIID)) {
    return NS_ERROR_UNEXPECTED;
  }

  nsCOMPtr<nsISupports> cert;
  rv = aStream->ReadObject(true, getter_AddRefs(cert));
  NS_ENSURE_SUCCESS(rv, rv);

  if (cert) {
    aServerCert = do_QueryInterface(cert);
    if (!aServerCert) {
      return NS_NOINTERFACE;
    }
  }

  uint16_t cipherSuite;
  rv = aStream->Read16(&cipherSuite);
  NS_ENSURE_SUCCESS(rv, rv);

  // The code below is a workaround to allow serializing new fields
  // while preserving binary compatibility with older streams. For more details
  // on the binary compatibility requirement, refer to bug 1248628.
  // Here, we take advantage of the fact that mProtocolVersion was originally
  // stored as a 16 bits integer, but the highest 8 bits were never used.
  // These bits are now used for stream versioning.
  uint16_t protocolVersionAndStreamFormatVersion;
  rv = aStream->Read16(&protocolVersionAndStreamFormatVersion);
  NS_ENSURE_SUCCESS(rv, rv);
  const uint8_t streamFormatVersion =
      (protocolVersionAndStreamFormatVersion >> 8) & 0xFF;

  OverridableErrorCategory overridableErrorCategory;
  rv = ReadOldOverridableErrorBits(aStream, overridableErrorCategory);
  NS_ENSURE_SUCCESS(rv, rv);
  bool isEV;
  rv = aStream->ReadBoolean(&isEV);
  NS_ENSURE_SUCCESS(rv, rv);
  bool hasIsEVStatus;
  rv = aStream->ReadBoolean(&hasIsEVStatus);
  NS_ENSURE_SUCCESS(rv, rv);
  if (hasIsEVStatus) {
    aIsEV.emplace(isEV);
  }
  bool haveCipherSuiteAndProtocol;
  rv = aStream->ReadBoolean(&haveCipherSuiteAndProtocol);
  if (haveCipherSuiteAndProtocol) {
    aCipherSuite.emplace(cipherSuite);
    aProtocolVersion.emplace(protocolVersionAndStreamFormatVersion & 0xFF);
  }
  NS_ENSURE_SUCCESS(rv, rv);
  bool haveCertErrorBits;
  rv = aStream->ReadBoolean(&haveCertErrorBits);
  NS_ENSURE_SUCCESS(rv, rv);
  if (haveCertErrorBits) {
    aOverridableErrorCategory.emplace(overridableErrorCategory);
  }

  // Added in version 1 (see bug 1305289).
  if (streamFormatVersion >= 1) {
    rv = aStream->Read16(&aCertificateTransparencyStatus);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Added in version 2 (see bug 1304923).
  if (streamFormatVersion >= 2) {
    nsCString keaGroupName;
    rv = aStream->ReadCString(keaGroupName);
    NS_ENSURE_SUCCESS(rv, rv);
    if (haveCipherSuiteAndProtocol) {
      aKeaGroupName.emplace(keaGroupName);
    }

    nsCString signatureSchemeName;
    rv = aStream->ReadCString(signatureSchemeName);
    NS_ENSURE_SUCCESS(rv, rv);
    if (haveCipherSuiteAndProtocol) {
      aSignatureSchemeName.emplace(signatureSchemeName);
    }
  }

  // Added in version 3 (see bug 1406856).
  if (streamFormatVersion >= 3) {
    rv = ReadCertList(aStream, aSucceededCertChain);
    if (NS_FAILED(rv)) {
      return rv;
    }

    // Read only to consume bytes from the stream.
    nsTArray<RefPtr<nsIX509Cert>> failedCertChain;
    rv = ReadCertList(aStream, failedCertChain);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }
  return rv;
}

// This is for backward compatability to be able to read nsIX509CertList
// serialized object.
nsresult TransportSecurityInfo::ReadCertList(
    nsIObjectInputStream* aStream, nsTArray<RefPtr<nsIX509Cert>>& aCertList) {
  bool nsIX509CertListPresent;

  nsresult rv = aStream->ReadBoolean(&nsIX509CertListPresent);
  NS_ENSURE_SUCCESS(rv, rv);
  if (!nsIX509CertListPresent) {
    return NS_OK;
  }
  // nsIX509CertList present.  Prepare to read elements.
  // Throw away cid, validate iid
  nsCID cid;
  nsIID iid;
  rv = aStream->ReadID(&cid);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aStream->ReadID(&iid);
  NS_ENSURE_SUCCESS(rv, rv);

  static const nsIID nsIX509CertListIID = {
      0xae74cda5,
      0xcd2f,
      0x473f,
      {0x96, 0xf5, 0xf0, 0xb7, 0xff, 0xf6, 0x2c, 0x68}};

  if (!iid.Equals(nsIX509CertListIID)) {
    return NS_ERROR_UNEXPECTED;
  }

  uint32_t certListSize;
  rv = aStream->Read32(&certListSize);
  NS_ENSURE_SUCCESS(rv, rv);

  return ReadCertificatesFromStream(aStream, certListSize, aCertList);
}

nsresult TransportSecurityInfo::ReadCertificatesFromStream(
    nsIObjectInputStream* aStream, uint32_t aSize,
    nsTArray<RefPtr<nsIX509Cert>>& aCertList) {
  nsresult rv;
  for (uint32_t i = 0; i < aSize; ++i) {
    nsCOMPtr<nsISupports> support;
    rv = aStream->ReadObject(true, getter_AddRefs(support));
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr<nsIX509Cert> cert = do_QueryInterface(support);
    if (!cert) {
      return NS_ERROR_UNEXPECTED;
    }
    RefPtr<nsIX509Cert> castedCert(cert.get());
    aCertList.AppendElement(castedCert);
  }
  return NS_OK;
}

static nsITransportSecurityInfo::OverridableErrorCategory
IntToOverridableErrorCategory(uint32_t intVal) {
  switch (intVal) {
    case static_cast<uint32_t>(
        nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST):
      return nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TRUST;
    case static_cast<uint32_t>(
        nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN):
      return nsITransportSecurityInfo::OverridableErrorCategory::ERROR_DOMAIN;
    case static_cast<uint32_t>(
        nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME):
      return nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME;
    default:
      break;
  }
  return nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET;
}

nsresult TransportSecurityInfo::Read(const nsCString& aSerializedSecurityInfo,
                                     nsITransportSecurityInfo** aResult) {
  *aResult = nullptr;

  nsCString decodedSecurityInfo;
  nsresult rv = Base64Decode(aSerializedSecurityInfo, decodedSecurityInfo);
  if (NS_FAILED(rv)) {
    return rv;
  }
  nsCOMPtr<nsIInputStream> inputStream;
  rv = NS_NewCStringInputStream(getter_AddRefs(inputStream),
                                std::move(decodedSecurityInfo));
  if (NS_FAILED(rv)) {
    return rv;
  }
  nsCOMPtr<nsIObjectInputStream> objStream(
      NS_NewObjectInputStream(inputStream));
  if (!objStream) {
    return rv;
  }

  nsCID cid;
  rv = objStream->ReadID(&cid);
  if (NS_FAILED(rv)) {
    return rv;
  }
  if (!cid.Equals(kTransportSecurityInfoCID)) {
    return NS_ERROR_UNEXPECTED;
  }
  nsIID iid;
  rv = objStream->ReadID(&iid);
  if (NS_FAILED(rv)) {
    return rv;
  }
  if (!iid.Equals(NS_ISUPPORTS_IID)) {
    return rv;
  }

  nsID id;
  rv = objStream->ReadID(&id);
  if (NS_FAILED(rv)) {
    return rv;
  }
  if (!id.Equals(kTransportSecurityInfoMagic)) {
    return NS_ERROR_UNEXPECTED;
  }

  uint32_t aSecurityState = 0;
  PRErrorCode aErrorCode = 0;
  nsTArray<RefPtr<nsIX509Cert>> aFailedCertChain;
  nsCOMPtr<nsIX509Cert> aServerCert;
  nsTArray<RefPtr<nsIX509Cert>> aSucceededCertChain;
  Maybe<uint16_t> aCipherSuite;
  Maybe<nsCString> aKeaGroupName;
  Maybe<nsCString> aSignatureSchemeName;
  Maybe<uint16_t> aProtocolVersion;
  uint16_t aCertificateTransparencyStatus;
  Maybe<bool> aIsAcceptedEch;
  Maybe<bool> aIsDelegatedCredential;
  Maybe<OverridableErrorCategory> aOverridableErrorCategory;
  bool aMadeOCSPRequests = false;
  bool aUsedPrivateDNS = false;
  Maybe<bool> aIsEV;
  bool aNPNCompleted = false;
  nsCString aNegotiatedNPN;
  bool aResumed = false;
  bool aIsBuiltCertChainRootBuiltInRoot = false;
  nsCString aPeerId;
  rv = objStream->Read32(&aSecurityState);
  if (NS_FAILED(rv)) {
    return rv;
  }
  // mSubRequestsBrokenSecurity was removed in bug 748809
  uint32_t unusedSubRequestsBrokenSecurity;
  rv = objStream->Read32(&unusedSubRequestsBrokenSecurity);
  if (NS_FAILED(rv)) {
    return rv;
  }
  // mSubRequestsNoSecurity was removed in bug 748809
  uint32_t unusedSubRequestsNoSecurity;
  rv = objStream->Read32(&unusedSubRequestsNoSecurity);
  if (NS_FAILED(rv)) {
    return rv;
  }
  uint32_t errorCode;
  rv = objStream->Read32(&errorCode);
  if (NS_FAILED(rv)) {
    return rv;
  }
  // PRErrorCode will be a negative value
  aErrorCode = static_cast<PRErrorCode>(errorCode);

  // Re-purpose mErrorMessageCached to represent serialization version
  // If string doesn't match exact version it will be treated as older
  // serialization.
  nsAutoString serVersion;
  rv = objStream->ReadString(serVersion);
  if (NS_FAILED(rv)) {
    return rv;
  }

  int32_t serVersionParsedToInt = 0;

  if (!serVersion.IsEmpty()) {
    char first = serVersion.First();
    // Check whether the first character of serVersion is a number
    // since ToInteger() skipps some non integer values.
    if (first >= '0' && first <= '9') {
      nsresult error = NS_OK;
      serVersionParsedToInt = serVersion.ToInteger(&error);
      if (NS_FAILED(error)) {
        return error;
      }
    }
  }

  // moved from nsISSLStatus
  if (serVersionParsedToInt < 1) {
    // nsISSLStatus may be present
    rv = ReadSSLStatus(objStream, aServerCert, aCipherSuite, aProtocolVersion,
                       aOverridableErrorCategory, aIsEV,
                       aCertificateTransparencyStatus, aKeaGroupName,
                       aSignatureSchemeName, aSucceededCertChain);
    NS_ENSURE_SUCCESS(rv, rv);
  } else {
    nsCOMPtr<nsISupports> cert;
    rv = NS_ReadOptionalObject(objStream, true, getter_AddRefs(cert));
    NS_ENSURE_SUCCESS(rv, rv);

    if (cert) {
      aServerCert = do_QueryInterface(cert);
      if (!aServerCert) {
        return NS_NOINTERFACE;
      }
    }

    uint16_t cipherSuite;
    rv = objStream->Read16(&cipherSuite);
    NS_ENSURE_SUCCESS(rv, rv);

    uint16_t protocolVersion;
    rv = objStream->Read16(&protocolVersion);
    NS_ENSURE_SUCCESS(rv, rv);

    OverridableErrorCategory overridableErrorCategory;
    if (serVersionParsedToInt < 8) {
      rv = ReadOldOverridableErrorBits(objStream, overridableErrorCategory);
      NS_ENSURE_SUCCESS(rv, rv);
    } else {
      uint32_t overridableErrorCategoryInt;
      rv = objStream->Read32(&overridableErrorCategoryInt);
      NS_ENSURE_SUCCESS(rv, rv);
      overridableErrorCategory =
          IntToOverridableErrorCategory(overridableErrorCategoryInt);
    }
    bool isEV;
    rv = objStream->ReadBoolean(&isEV);
    NS_ENSURE_SUCCESS(rv, rv);
    bool hasIsEVStatus;
    rv = objStream->ReadBoolean(&hasIsEVStatus);
    NS_ENSURE_SUCCESS(rv, rv);
    if (hasIsEVStatus) {
      aIsEV.emplace(isEV);
    }
    bool haveCipherSuiteAndProtocol;
    rv = objStream->ReadBoolean(&haveCipherSuiteAndProtocol);
    NS_ENSURE_SUCCESS(rv, rv);
    if (haveCipherSuiteAndProtocol) {
      aCipherSuite.emplace(cipherSuite);
      aProtocolVersion.emplace(protocolVersion);
    }
    bool haveCertErrorBits;
    rv = objStream->ReadBoolean(&haveCertErrorBits);
    NS_ENSURE_SUCCESS(rv, rv);
    if (haveCertErrorBits) {
      aOverridableErrorCategory.emplace(overridableErrorCategory);
    }

    rv = objStream->Read16(&aCertificateTransparencyStatus);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCString keaGroupName;
    rv = objStream->ReadCString(keaGroupName);
    NS_ENSURE_SUCCESS(rv, rv);
    if (haveCipherSuiteAndProtocol) {
      aKeaGroupName.emplace(keaGroupName);
    }

    nsCString signatureSchemeName;
    rv = objStream->ReadCString(signatureSchemeName);
    NS_ENSURE_SUCCESS(rv, rv);
    if (haveCipherSuiteAndProtocol) {
      aSignatureSchemeName.emplace(signatureSchemeName);
    }

    if (serVersionParsedToInt < 3) {
      // The old data structure of certList(nsIX509CertList) presents
      rv = ReadCertList(objStream, aSucceededCertChain);
      NS_ENSURE_SUCCESS(rv, rv);
    } else {
      uint16_t certCount;
      rv = objStream->Read16(&certCount);
      NS_ENSURE_SUCCESS(rv, rv);

      rv =
          ReadCertificatesFromStream(objStream, certCount, aSucceededCertChain);
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }
  // END moved from nsISSLStatus
  if (serVersionParsedToInt < 3) {
    // The old data structure of certList(nsIX509CertList) presents
    rv = ReadCertList(objStream, aFailedCertChain);
    NS_ENSURE_SUCCESS(rv, rv);
  } else {
    uint16_t certCount;
    rv = objStream->Read16(&certCount);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = ReadCertificatesFromStream(objStream, certCount, aFailedCertChain);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // mIsDelegatedCredential added in bug 1562773
  if (serVersionParsedToInt >= 2) {
    bool isDelegatedCredential;
    rv = objStream->ReadBoolean(&isDelegatedCredential);
    if (NS_FAILED(rv)) {
      return rv;
    }
    // If aCipherSuite is Some, the serialized TransportSecurityinfo had its
    // cipher suite and protocol information, which means it has this
    // information.
    if (aCipherSuite.isSome()) {
      aIsDelegatedCredential.emplace(isDelegatedCredential);
    }
  }

  // mNPNCompleted, mNegotiatedNPN, mResumed added in bug 1584104
  if (serVersionParsedToInt >= 4) {
    rv = objStream->ReadBoolean(&aNPNCompleted);
    if (NS_FAILED(rv)) {
      return rv;
    }

    rv = objStream->ReadCString(aNegotiatedNPN);
    if (NS_FAILED(rv)) {
      return rv;
    }

    rv = objStream->ReadBoolean(&aResumed);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  // mIsBuiltCertChainRootBuiltInRoot added in bug 1485652
  if (serVersionParsedToInt >= 5) {
    rv = objStream->ReadBoolean(&aIsBuiltCertChainRootBuiltInRoot);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  // mIsAcceptedEch added in bug 1678079
  if (serVersionParsedToInt >= 6) {
    bool isAcceptedEch;
    rv = objStream->ReadBoolean(&isAcceptedEch);
    if (NS_FAILED(rv)) {
      return rv;
    }
    // If aCipherSuite is Some, the serialized TransportSecurityinfo had its
    // cipher suite and protocol information, which means it has this
    // information.
    if (aCipherSuite.isSome()) {
      aIsAcceptedEch.emplace(isAcceptedEch);
    }
  }

  // mPeerId added in bug 1738664
  if (serVersionParsedToInt >= 7) {
    rv = objStream->ReadCString(aPeerId);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  if (serVersionParsedToInt >= 9) {
    rv = objStream->ReadBoolean(&aMadeOCSPRequests);
    if (NS_FAILED(rv)) {
      return rv;
    }

    rv = objStream->ReadBoolean(&aUsedPrivateDNS);
    if (NS_FAILED(rv)) {
      return rv;
    };
  }

  RefPtr<nsITransportSecurityInfo> securityInfo(new TransportSecurityInfo(
      aSecurityState, aErrorCode, std::move(aFailedCertChain), aServerCert,
      std::move(aSucceededCertChain), aCipherSuite, aKeaGroupName,
      aSignatureSchemeName, aProtocolVersion, aCertificateTransparencyStatus,
      aIsAcceptedEch, aIsDelegatedCredential, aOverridableErrorCategory,
      aMadeOCSPRequests, aUsedPrivateDNS, aIsEV, aNPNCompleted, aNegotiatedNPN,
      aResumed, aIsBuiltCertChainRootBuiltInRoot, aPeerId));
  securityInfo.forget(aResult);
  return NS_OK;
}

void TransportSecurityInfo::SerializeToIPC(IPC::MessageWriter* aWriter) {
  WriteParam(aWriter, mSecurityState);
  WriteParam(aWriter, mErrorCode);
  WriteParam(aWriter, mFailedCertChain);
  WriteParam(aWriter, mServerCert);
  WriteParam(aWriter, mSucceededCertChain);
  WriteParam(aWriter, mCipherSuite);
  WriteParam(aWriter, mKeaGroupName);
  WriteParam(aWriter, mSignatureSchemeName);
  WriteParam(aWriter, mProtocolVersion);
  WriteParam(aWriter, mCertificateTransparencyStatus);
  WriteParam(aWriter, mIsAcceptedEch);
  WriteParam(aWriter, mIsDelegatedCredential);
  WriteParam(aWriter, mOverridableErrorCategory);
  WriteParam(aWriter, mMadeOCSPRequests);
  WriteParam(aWriter, mUsedPrivateDNS);
  WriteParam(aWriter, mIsEV);
  WriteParam(aWriter, mNPNCompleted);
  WriteParam(aWriter, mNegotiatedNPN);
  WriteParam(aWriter, mResumed);
  WriteParam(aWriter, mIsBuiltCertChainRootBuiltInRoot);
  WriteParam(aWriter, mPeerId);
}

bool TransportSecurityInfo::DeserializeFromIPC(
    IPC::MessageReader* aReader, RefPtr<nsITransportSecurityInfo>* aResult) {
  uint32_t aSecurityState;
  PRErrorCode aErrorCode;
  nsTArray<RefPtr<nsIX509Cert>> aFailedCertChain;
  nsCOMPtr<nsIX509Cert> aServerCert;
  nsTArray<RefPtr<nsIX509Cert>> aSucceededCertChain;
  Maybe<uint16_t> aCipherSuite;
  Maybe<nsCString> aKeaGroupName;
  Maybe<nsCString> aSignatureSchemeName;
  Maybe<uint16_t> aProtocolVersion;
  uint16_t aCertificateTransparencyStatus;
  Maybe<bool> aIsAcceptedEch;
  Maybe<bool> aIsDelegatedCredential;
  Maybe<OverridableErrorCategory> aOverridableErrorCategory;
  bool aMadeOCSPRequests;
  bool aUsedPrivateDNS;
  Maybe<bool> aIsEV;
  bool aNPNCompleted;
  nsCString aNegotiatedNPN;
  bool aResumed;
  bool aIsBuiltCertChainRootBuiltInRoot;
  nsCString aPeerId;

  if (!ReadParam(aReader, &aSecurityState) ||
      !ReadParam(aReader, &aErrorCode) ||
      !ReadParam(aReader, &aFailedCertChain) ||
      !ReadParam(aReader, &aServerCert) ||
      !ReadParam(aReader, &aSucceededCertChain) ||
      !ReadParam(aReader, &aCipherSuite) ||
      !ReadParam(aReader, &aKeaGroupName) ||
      !ReadParam(aReader, &aSignatureSchemeName) ||
      !ReadParam(aReader, &aProtocolVersion) ||
      !ReadParam(aReader, &aCertificateTransparencyStatus) ||
      !ReadParam(aReader, &aIsAcceptedEch) ||
      !ReadParam(aReader, &aIsDelegatedCredential) ||
      !ReadParam(aReader, &aOverridableErrorCategory) ||
      !ReadParam(aReader, &aMadeOCSPRequests) ||
      !ReadParam(aReader, &aUsedPrivateDNS) || !ReadParam(aReader, &aIsEV) ||
      !ReadParam(aReader, &aNPNCompleted) ||
      !ReadParam(aReader, &aNegotiatedNPN) || !ReadParam(aReader, &aResumed) ||
      !ReadParam(aReader, &aIsBuiltCertChainRootBuiltInRoot) ||
      !ReadParam(aReader, &aPeerId)) {
    return false;
  }

  RefPtr<nsITransportSecurityInfo> securityInfo(new TransportSecurityInfo(
      aSecurityState, aErrorCode, std::move(aFailedCertChain), aServerCert,
      std::move(aSucceededCertChain), aCipherSuite, aKeaGroupName,
      aSignatureSchemeName, aProtocolVersion, aCertificateTransparencyStatus,
      aIsAcceptedEch, aIsDelegatedCredential, aOverridableErrorCategory,
      aMadeOCSPRequests, aUsedPrivateDNS, aIsEV, aNPNCompleted, aNegotiatedNPN,
      aResumed, aIsBuiltCertChainRootBuiltInRoot, aPeerId));
  *aResult = std::move(securityInfo);
  return true;
}

NS_IMETHODIMP
TransportSecurityInfo::GetFailedCertChain(
    nsTArray<RefPtr<nsIX509Cert>>& aFailedCertChain) {
  MOZ_ASSERT(aFailedCertChain.IsEmpty());
  if (!aFailedCertChain.IsEmpty()) {
    return NS_ERROR_INVALID_ARG;
  }
  aFailedCertChain.AppendElements(mFailedCertChain);
  return NS_OK;
}

NS_IMETHODIMP TransportSecurityInfo::GetServerCert(nsIX509Cert** aServerCert) {
  NS_ENSURE_ARG_POINTER(aServerCert);
  nsCOMPtr<nsIX509Cert> cert = mServerCert;
  cert.forget(aServerCert);
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetSucceededCertChain(
    nsTArray<RefPtr<nsIX509Cert>>& aSucceededCertChain) {
  MOZ_ASSERT(aSucceededCertChain.IsEmpty());
  if (!aSucceededCertChain.IsEmpty()) {
    return NS_ERROR_INVALID_ARG;
  }
  aSucceededCertChain.AppendElements(mSucceededCertChain);
  return NS_OK;
}

NS_IMETHODIMP TransportSecurityInfo::GetIsBuiltCertChainRootBuiltInRoot(
    bool* aIsBuiltInRoot) {
  NS_ENSURE_ARG_POINTER(aIsBuiltInRoot);
  *aIsBuiltInRoot = mIsBuiltCertChainRootBuiltInRoot;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetCipherName(nsACString& aCipherName) {
  if (mCipherSuite.isNothing()) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  SSLCipherSuiteInfo cipherInfo;
  if (SSL_GetCipherSuiteInfo(*mCipherSuite, &cipherInfo, sizeof(cipherInfo)) !=
      SECSuccess) {
    return NS_ERROR_FAILURE;
  }

  aCipherName.Assign(cipherInfo.cipherSuiteName);
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetKeyLength(uint32_t* aKeyLength) {
  NS_ENSURE_ARG_POINTER(aKeyLength);

  if (mCipherSuite.isNothing()) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  SSLCipherSuiteInfo cipherInfo;
  if (SSL_GetCipherSuiteInfo(*mCipherSuite, &cipherInfo, sizeof(cipherInfo)) !=
      SECSuccess) {
    return NS_ERROR_FAILURE;
  }

  *aKeyLength = cipherInfo.symKeyBits;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetSecretKeyLength(uint32_t* aSecretKeyLength) {
  NS_ENSURE_ARG_POINTER(aSecretKeyLength);

  if (mCipherSuite.isNothing()) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  SSLCipherSuiteInfo cipherInfo;
  if (SSL_GetCipherSuiteInfo(*mCipherSuite, &cipherInfo, sizeof(cipherInfo)) !=
      SECSuccess) {
    return NS_ERROR_FAILURE;
  }

  *aSecretKeyLength = cipherInfo.effectiveKeyBits;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetKeaGroupName(nsACString& aKeaGroupName) {
  if (mKeaGroupName.isNothing()) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  aKeaGroupName.Assign(*mKeaGroupName);
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetSignatureSchemeName(nsACString& aSignatureScheme) {
  if (mSignatureSchemeName.isNothing()) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  aSignatureScheme.Assign(*mSignatureSchemeName);
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetProtocolVersion(uint16_t* aProtocolVersion) {
  if (mProtocolVersion.isNothing()) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  *aProtocolVersion = *mProtocolVersion;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetCertificateTransparencyStatus(
    uint16_t* aCertificateTransparencyStatus) {
  NS_ENSURE_ARG_POINTER(aCertificateTransparencyStatus);

  *aCertificateTransparencyStatus = mCertificateTransparencyStatus;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetMadeOCSPRequests(bool* aMadeOCSPRequests) {
  *aMadeOCSPRequests = mMadeOCSPRequests;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetUsedPrivateDNS(bool* aUsedPrivateDNS) {
  *aUsedPrivateDNS = mUsedPrivateDNS;
  return NS_OK;
}

// static
uint16_t TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
    const mozilla::psm::CertificateTransparencyInfo& info) {
  using mozilla::ct::CTPolicyCompliance;

  if (!info.enabled) {
    // CT disabled.
    return nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
  }

  switch (info.policyCompliance) {
    case CTPolicyCompliance::Compliant:
      return nsITransportSecurityInfo::
          CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT;
    case CTPolicyCompliance::NotEnoughScts:
      return nsITransportSecurityInfo ::
          CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS;
    case CTPolicyCompliance::NotDiverseScts:
      return nsITransportSecurityInfo ::
          CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS;
    case CTPolicyCompliance::Unknown:
    default:
      MOZ_ASSERT_UNREACHABLE("Unexpected CTPolicyCompliance type");
  }

  return nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
}

NS_IMETHODIMP
TransportSecurityInfo::GetOverridableErrorCategory(
    OverridableErrorCategory* aOverridableErrorCategory) {
  NS_ENSURE_ARG_POINTER(aOverridableErrorCategory);

  if (mOverridableErrorCategory.isSome()) {
    *aOverridableErrorCategory = *mOverridableErrorCategory;
  } else {
    *aOverridableErrorCategory = OverridableErrorCategory::ERROR_UNSET;
  }
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetIsExtendedValidation(bool* aIsEV) {
  NS_ENSURE_ARG_POINTER(aIsEV);

  *aIsEV = false;
  // Never allow bad certs for EV, regardless of overrides.
  if (mOverridableErrorCategory.isSome()) {
    return NS_OK;
  }

  if (!mIsEV.isSome()) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  *aIsEV = *mIsEV;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetIsAcceptedEch(bool* aIsAcceptedEch) {
  NS_ENSURE_ARG_POINTER(aIsAcceptedEch);

  if (mIsAcceptedEch.isNothing()) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  *aIsAcceptedEch = *mIsAcceptedEch;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetIsDelegatedCredential(bool* aIsDelegatedCredential) {
  NS_ENSURE_ARG_POINTER(aIsDelegatedCredential);

  if (mIsDelegatedCredential.isNothing()) {
    return NS_ERROR_NOT_AVAILABLE;
  }
  *aIsDelegatedCredential = *mIsDelegatedCredential;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetNegotiatedNPN(nsACString& aNegotiatedNPN) {
  if (!mNPNCompleted) {
    return NS_ERROR_NOT_CONNECTED;
  }

  aNegotiatedNPN = mNegotiatedNPN;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetResumed(bool* aResumed) {
  NS_ENSURE_ARG_POINTER(aResumed);
  *aResumed = mResumed;
  return NS_OK;
}

NS_IMETHODIMP
TransportSecurityInfo::GetPeerId(nsACString& aResult) {
  aResult.Assign(mPeerId);
  return NS_OK;
}

}  // namespace psm
}  // namespace mozilla