Blob Blame History Raw
/* -*- 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 "CTVerifyResult.h"
#include "mozilla/Casting.h"
#include "nsSSLStatus.h"
#include "nsIClassInfoImpl.h"
#include "nsIObjectOutputStream.h"
#include "nsIObjectInputStream.h"
#include "nsNSSCertificate.h"
#include "ssl.h"

NS_IMETHODIMP
nsSSLStatus::GetServerCert(nsIX509Cert** aServerCert) {
  NS_ENSURE_ARG_POINTER(aServerCert);

  nsCOMPtr<nsIX509Cert> cert = mServerCert;
  cert.forget(aServerCert);
  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetKeyLength(uint32_t* aKeyLength) {
  NS_ENSURE_ARG_POINTER(aKeyLength);
  if (!mHaveCipherSuiteAndProtocol) {
    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
nsSSLStatus::GetSecretKeyLength(uint32_t* aSecretKeyLength) {
  NS_ENSURE_ARG_POINTER(aSecretKeyLength);
  if (!mHaveCipherSuiteAndProtocol) {
    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
nsSSLStatus::GetCipherName(nsACString& aCipherName) {
  if (!mHaveCipherSuiteAndProtocol) {
    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
nsSSLStatus::GetKeaGroupName(nsACString& aKeaGroup) {
  if (!mHaveCipherSuiteAndProtocol) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  aKeaGroup.Assign(mKeaGroup);
  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetSignatureSchemeName(nsACString& aSignatureScheme) {
  if (!mHaveCipherSuiteAndProtocol) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  aSignatureScheme.Assign(mSignatureSchemeName);
  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetProtocolVersion(uint16_t* aProtocolVersion) {
  NS_ENSURE_ARG_POINTER(aProtocolVersion);
  if (!mHaveCipherSuiteAndProtocol) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  *aProtocolVersion = mProtocolVersion;
  return NS_OK;
}

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

  *aCertificateTransparencyStatus = mCertificateTransparencyStatus;
  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetIsDomainMismatch(bool* aIsDomainMismatch) {
  NS_ENSURE_ARG_POINTER(aIsDomainMismatch);

  *aIsDomainMismatch = mHaveCertErrorBits && mIsDomainMismatch;
  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetIsNotValidAtThisTime(bool* aIsNotValidAtThisTime) {
  NS_ENSURE_ARG_POINTER(aIsNotValidAtThisTime);

  *aIsNotValidAtThisTime = mHaveCertErrorBits && mIsNotValidAtThisTime;
  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetIsUntrusted(bool* aIsUntrusted) {
  NS_ENSURE_ARG_POINTER(aIsUntrusted);

  *aIsUntrusted = mHaveCertErrorBits && mIsUntrusted;
  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetIsExtendedValidation(bool* aIsEV) {
  NS_ENSURE_ARG_POINTER(aIsEV);
  *aIsEV = false;

  // Never allow bad certs for EV, regardless of overrides.
  if (mHaveCertErrorBits) {
    return NS_OK;
  }

  if (mHasIsEVStatus) {
    *aIsEV = mIsEV;
    return NS_OK;
  }

  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsSSLStatus::Read(nsIObjectInputStream* aStream) {
  nsCOMPtr<nsISupports> cert;
  nsresult rv = aStream->ReadObject(true, getter_AddRefs(cert));
  NS_ENSURE_SUCCESS(rv, rv);

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

  rv = aStream->Read16(&mCipherSuite);
  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);
  mProtocolVersion = protocolVersionAndStreamFormatVersion & 0xFF;
  const uint8_t streamFormatVersion =
      (protocolVersionAndStreamFormatVersion >> 8) & 0xFF;

  rv = aStream->ReadBoolean(&mIsDomainMismatch);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aStream->ReadBoolean(&mIsNotValidAtThisTime);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aStream->ReadBoolean(&mIsUntrusted);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aStream->ReadBoolean(&mIsEV);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aStream->ReadBoolean(&mHasIsEVStatus);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aStream->ReadBoolean(&mHaveCipherSuiteAndProtocol);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aStream->ReadBoolean(&mHaveCertErrorBits);
  NS_ENSURE_SUCCESS(rv, rv);

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

  // Added in version 2 (see bug 1304923).
  if (streamFormatVersion >= 2) {
    rv = aStream->ReadCString(mKeaGroup);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = aStream->ReadCString(mSignatureSchemeName);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  // Added in version 3 (see bug 1406856).
  if (streamFormatVersion >= 3) {
    nsCOMPtr<nsISupports> succeededCertChainSupports;
    rv = NS_ReadOptionalObject(aStream, true,
                               getter_AddRefs(succeededCertChainSupports));
    if (NS_FAILED(rv)) {
      return rv;
    }
    mSucceededCertChain = do_QueryInterface(succeededCertChainSupports);

    nsCOMPtr<nsISupports> failedCertChainSupports;
    rv = NS_ReadOptionalObject(aStream, true,
                               getter_AddRefs(failedCertChainSupports));
    if (NS_FAILED(rv)) {
      return rv;
    }
    mFailedCertChain = do_QueryInterface(failedCertChainSupports);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::Write(nsIObjectOutputStream* aStream) {
  // The current version of the binary stream format.
  const uint8_t STREAM_FORMAT_VERSION = 3;

  nsresult rv =
      aStream->WriteCompoundObject(mServerCert, NS_GET_IID(nsIX509Cert), true);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aStream->Write16(mCipherSuite);
  NS_ENSURE_SUCCESS(rv, rv);

  uint16_t protocolVersionAndStreamFormatVersion =
      mozilla::AssertedCast<uint8_t>(mProtocolVersion) |
      (STREAM_FORMAT_VERSION << 8);
  rv = aStream->Write16(protocolVersionAndStreamFormatVersion);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aStream->WriteBoolean(mIsDomainMismatch);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aStream->WriteBoolean(mIsNotValidAtThisTime);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aStream->WriteBoolean(mIsUntrusted);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aStream->WriteBoolean(mIsEV);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aStream->WriteBoolean(mHasIsEVStatus);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aStream->WriteBoolean(mHaveCipherSuiteAndProtocol);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = aStream->WriteBoolean(mHaveCertErrorBits);
  NS_ENSURE_SUCCESS(rv, rv);

  // Added in version 1.
  rv = aStream->Write16(mCertificateTransparencyStatus);
  NS_ENSURE_SUCCESS(rv, rv);

  // Added in version 2.
  rv = aStream->WriteStringZ(mKeaGroup.get());
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aStream->WriteStringZ(mSignatureSchemeName.get());
  NS_ENSURE_SUCCESS(rv, rv);

  // Added in version 3.
  rv = NS_WriteOptionalCompoundObject(aStream, mSucceededCertChain,
                                      NS_GET_IID(nsIX509CertList), true);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = NS_WriteOptionalCompoundObject(aStream, mFailedCertChain,
                                      NS_GET_IID(nsIX509CertList), true);
  if (NS_FAILED(rv)) {
    return rv;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetInterfaces(uint32_t* aCount, nsIID*** aArray) {
  *aCount = 0;
  *aArray = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetScriptableHelper(nsIXPCScriptable** aHelper) {
  *aHelper = nullptr;
  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetContractID(nsACString& aContractID) {
  aContractID.SetIsVoid(true);
  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetClassDescription(nsACString& aClassDescription) {
  aClassDescription.SetIsVoid(true);
  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetClassID(nsCID** aClassID) {
  *aClassID = (nsCID*)moz_xmalloc(sizeof(nsCID));
  if (!*aClassID) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return GetClassIDNoAlloc(*aClassID);
}

NS_IMETHODIMP
nsSSLStatus::GetFlags(uint32_t* aFlags) {
  *aFlags = 0;
  return NS_OK;
}

static NS_DEFINE_CID(kSSLStatusCID, NS_SSLSTATUS_CID);

NS_IMETHODIMP
nsSSLStatus::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
  *aClassIDNoAlloc = kSSLStatusCID;
  return NS_OK;
}

nsSSLStatus::nsSSLStatus()
    : mCipherSuite(0),
      mProtocolVersion(0),
      mCertificateTransparencyStatus(
          nsISSLStatus::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE),
      mKeaGroup(),
      mSignatureSchemeName(),
      mIsDomainMismatch(false),
      mIsNotValidAtThisTime(false),
      mIsUntrusted(false),
      mIsEV(false),
      mHasIsEVStatus(false),
      mHaveCipherSuiteAndProtocol(false),
      mHaveCertErrorBits(false) {}

NS_IMPL_ISUPPORTS(nsSSLStatus, nsISSLStatus, nsISerializable, nsIClassInfo)

void nsSSLStatus::SetServerCert(nsNSSCertificate* aServerCert,
                                EVStatus aEVStatus) {
  MOZ_ASSERT(aServerCert);

  mServerCert = aServerCert;
  mIsEV = (aEVStatus == EVStatus::EV);
  mHasIsEVStatus = true;
}

nsresult nsSSLStatus::SetSucceededCertChain(UniqueCERTCertList aCertList) {
  // nsNSSCertList takes ownership of certList
  mSucceededCertChain = new nsNSSCertList(Move(aCertList));

  return NS_OK;
}

void nsSSLStatus::SetFailedCertChain(nsIX509CertList* aX509CertList) {
  mFailedCertChain = aX509CertList;
}

NS_IMETHODIMP
nsSSLStatus::GetSucceededCertChain(nsIX509CertList** _result) {
  NS_ENSURE_ARG_POINTER(_result);

  nsCOMPtr<nsIX509CertList> tmpList = mSucceededCertChain;
  tmpList.forget(_result);

  return NS_OK;
}

NS_IMETHODIMP
nsSSLStatus::GetFailedCertChain(nsIX509CertList** _result) {
  NS_ENSURE_ARG_POINTER(_result);

  nsCOMPtr<nsIX509CertList> tmpList = mFailedCertChain;
  tmpList.forget(_result);

  return NS_OK;
}

void nsSSLStatus::SetCertificateTransparencyInfo(
    const mozilla::psm::CertificateTransparencyInfo& info) {
  using mozilla::ct::CTPolicyCompliance;

  mCertificateTransparencyStatus =
      nsISSLStatus::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;

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

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