// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/base/ssl_config_service_mac.h"

#include <CoreFoundation/CoreFoundation.h>

#include "base/scoped_cftyperef.h"

using base::TimeDelta;
using base::TimeTicks;

namespace net {

namespace {

static const int kConfigUpdateInterval = 10;  // seconds

static const bool kSSL2EnabledDefaultValue = false;
static const bool kSSL3EnabledDefaultValue = true;
static const bool kTLS1EnabledDefaultValue = true;

static CFStringRef kRevocationPreferencesIdentifier =
    CFSTR("com.apple.security.revocation");
static CFStringRef kOCSPStyleKey = CFSTR("OCSPStyle");
static CFStringRef kCRLStyleKey = CFSTR("CRLStyle");
static CFStringRef kNoneRevocationValue = CFSTR("None");
static CFStringRef kBestAttemptRevocationValue = CFSTR("BestAttempt");
static CFStringRef kSSL2EnabledKey = CFSTR("org.chromium.ssl.ssl2");
static CFStringRef kSSL3EnabledKey = CFSTR("org.chromium.ssl.ssl3");
static CFStringRef kTLS1EnabledKey = CFSTR("org.chromium.ssl.tls1");

bool RevocationStyleIsEnabled(CFStringRef key) {
  CFPropertyListRef plist_ref = CFPreferencesCopyValue(key,
      kRevocationPreferencesIdentifier, kCFPreferencesCurrentUser,
      kCFPreferencesAnyHost);
  if (plist_ref) {
    scoped_cftyperef<CFPropertyListRef> scoped_plist_ref(plist_ref);
    if (CFGetTypeID(plist_ref) == CFStringGetTypeID()) {
      CFStringRef style = reinterpret_cast<CFStringRef>(plist_ref);
      if (CFStringCompare(kNoneRevocationValue, style,
                          kCFCompareCaseInsensitive))
        return true;
    }
  }
  return false;
}

inline bool SSLVersionIsEnabled(CFStringRef key, bool default_value) {
  Boolean exists_and_valid;
  Boolean rv = CFPreferencesGetAppBooleanValue(key,
                                               kCFPreferencesCurrentApplication,
                                               &exists_and_valid);
  if (!exists_and_valid)
    return default_value;
  return rv;
}

}  // namespace

SSLConfigServiceMac::SSLConfigServiceMac() : ever_updated_(false) {
  // We defer retrieving the settings until the first call to GetSSLConfig, to
  // avoid an expensive call on the UI thread, which could affect startup time.
}

SSLConfigServiceMac::SSLConfigServiceMac(TimeTicks now) : ever_updated_(false) {
  UpdateConfig(now);
}

void SSLConfigServiceMac::GetSSLConfigAt(SSLConfig* config, TimeTicks now) {
  if (!ever_updated_ ||
      now - config_time_ > TimeDelta::FromSeconds(kConfigUpdateInterval))
    UpdateConfig(now);
  *config = config_info_;
}

// static
bool SSLConfigServiceMac::GetSSLConfigNow(SSLConfig* config) {
  // Our own revocation checking flag is a binary value, but Mac OS X uses
  // several shades of revocation checking:
  //   - None (i.e., disabled, the default)
  //   - BestAttempt
  //   - RequireIfPresent
  //   - RequireForall
  // Mac OS X also breaks down revocation check for both CRLs and OCSP. We
  // set our revocation flag if the system-wide settings for either OCSP
  // or CRLs is anything other than None.
  config->rev_checking_enabled = (RevocationStyleIsEnabled(kOCSPStyleKey) ||
                                  RevocationStyleIsEnabled(kCRLStyleKey));

  config->ssl2_enabled = SSLVersionIsEnabled(kSSL2EnabledKey,
                                             kSSL2EnabledDefaultValue);
  config->ssl3_enabled = SSLVersionIsEnabled(kSSL3EnabledKey,
                                             kSSL3EnabledDefaultValue);
  config->tls1_enabled = SSLVersionIsEnabled(kTLS1EnabledKey,
                                             kTLS1EnabledDefaultValue);

  return true;
}

// static
void SSLConfigServiceMac::SetSSL2Enabled(bool enabled) {
  CFPreferencesSetAppValue(kSSL2EnabledKey,
                           enabled ? kCFBooleanTrue : kCFBooleanFalse,
                           kCFPreferencesCurrentApplication);
  CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
}

// static
void SSLConfigServiceMac::SetSSL3Enabled(bool enabled) {
  CFPreferencesSetAppValue(kSSL3EnabledKey,
                           enabled ? kCFBooleanTrue : kCFBooleanFalse,
                           kCFPreferencesCurrentApplication);
  CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
}

// static
void SSLConfigServiceMac::SetTLS1Enabled(bool enabled) {
  CFPreferencesSetAppValue(kTLS1EnabledKey,
                           enabled ? kCFBooleanTrue : kCFBooleanFalse,
                           kCFPreferencesCurrentApplication);
  CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
}

// static
void SSLConfigServiceMac::SetRevCheckingEnabled(bool enabled) {
  // This method is provided for use by the unit tests. These settings
  // are normally changed via the Keychain Access application's preferences
  // dialog.
  CFPreferencesSetValue(kOCSPStyleKey,
      enabled ? kBestAttemptRevocationValue : kNoneRevocationValue,
      kRevocationPreferencesIdentifier, kCFPreferencesCurrentUser,
      kCFPreferencesAnyHost);
  CFPreferencesSetValue(kCRLStyleKey,
      enabled ? kBestAttemptRevocationValue : kNoneRevocationValue,
      kRevocationPreferencesIdentifier, kCFPreferencesCurrentUser,
      kCFPreferencesAnyHost);
}

void SSLConfigServiceMac::UpdateConfig(TimeTicks now) {
  GetSSLConfigNow(&config_info_);
  config_time_ = now;
  ever_updated_ = true;
}

}  // namespace net