Need help setting up Google Cloud Directory Sync with AD using secure LDAP

Update Sept 23, 2020 Today I updated GCDS and the TLS stuff broke again. This time, the problem was with an inability to access our CRL file from an offline root CA. I found that Google has beefed up its help page for GCDS certificate problems here: https://support.google.com/a/answer/3075991 I found my solution there.

Update Jan 20, 2020: It is anticipated that with the March 2020 patches, Microsoft will disallow insecure binds. This answer is likely to gain some additional attention, as Google Cloud Directory Sync will likely then fail to connect to AD unless TLS is being used.

Original Answer

Google Cloud Directory Sync is a Java application. The GCDS installer installs a version of the Java run-time environment in a sub-folder. That installation has its own set of trusted root certificate authorities. It does not use the certs installed in Windows.

To get things working, you need to import the public certificate for the trusted Certificate Authority that issued the certificate being used by your domain controller. You could instead install the public certificate from your domain controller, but that certificate will likely expire much sooner than the issuing certificate authority's certificate.

Google provides instructions here: https://support.google.com/a/answer/3075991?hl=en However, their instructions use the DC's public certificate, not the CA's certificate.

Obtain the CA certificate. I'm going to call it the_cert.cer

If you're following Google's instructions, you're exporting the cert from the domain controller:

certutil -store My DomainController %TEMP%\the_cert.cer

But again, you're better off with the CA certificate.

Move the certificate to the GCDS host.

On the GCDS host

Change folders to the jre folder where GCDS is installed. For me it was:

cd "c:\Program Files\Google Cloud Directory Sync\jre"

Yours might be different depending on your environment.

Install the certificate into the Java keystore:

bin\keytool -keystore lib\security\cacerts -storepass changeit -import -file %TEMP%\the_cert.cer -alias pick_a_name_you_like

The keytool utility will prompt: Trust this certificate? [no]: Type yes and hit the Enter key.

Clean up:

del the_cert.cer

Now, going against my advice again and using the DC's cert, here's a complete script you could run via Task Scheduler to keep your certificate up-to-date on your domain controller, assuming you run GCDS on the same domain controller.

<#
  .SYNOPSIS
  Exports the bound AD LDAPS encryption certificate and imports it into
  Google Cloud Directory Sync's Java keystore, so that GCDS syncs will succeed.

  .DESCRIPTION
  Google Cloud Directory Sync runs on Java. Java maintains its own trusted keystore,
  separate from the host operating system. Often, this keystore grows stale when updates
  are neglected. Further, the keystore would never contain certificate information for
  self-signed or internally-distributed certificates.

  In order to make GCDS work with TLS using secure LDAPS binding, it is necessary to
  export your trusted certificate from the machine's certificate store and import it into
  the GCDS-bundled Java Runtime Environment's certificate store.

  This script assumes the DC being contacted resides on the same host as the GCDS installation.

  Given a ComputerName and Port, this script will connect to the named DC and determine the
  thumbprint of the certificate bound to the DC on the specific port.

  Using this thumbprint, the script then exports the certificate from the Local Computer's MY (Personal)
  certificate store. This does NOT include the private key, and therefore it's safe to do this.

  Next, the script deletes and re-imports the certificate into the JRE certificate store.

  .PARAMETER ComputerName
  Use the fully-qualified network name of the machine. We're assuming this is the same network name
  that will be used in GCDS to bind against the DC, and is also the CommonName represented in the certificate.

  .PARAMETER Port
  Usually this will be 636, but could be custom depending on your environment.

  .OUTPUTS
  Will list the thumbprint of the cert found and will show stderr and stdout of the keytool commands.
  Error handling could definitely be beefed up here.

  .EXAMPLE
  C:\PS> .\Update-JavaDomainControllerCertificate.ps1 -ComputerName my.domain.com -Port 636

#>

[CmdletBinding()]
param (
    [Parameter(Mandatory=$true)]
    [string]
    $ComputerName,

    [int]
    $Port = 636
)
$FilePath = "$($Env:TEMP)\adcert.crt"
$Certificate = $null
$TcpClient = New-Object -TypeName System.Net.Sockets.TcpClient
try {

    $TcpClient.Connect($ComputerName, $Port)
    $TcpStream = $TcpClient.GetStream()

    $Callback = { param($sender, $cert, $chain, $errors) return $true }

    $SslStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList @($TcpStream, $true, $Callback)
    try {

        $SslStream.AuthenticateAsClient('')
        $Certificate = $SslStream.RemoteCertificate

    } finally {
        $SslStream.Dispose()
    }

} finally {
    $TcpClient.Dispose()
}

if ($Certificate) {
    if ($Certificate -isnot [System.Security.Cryptography.X509Certificates.X509Certificate2]) {
        $Certificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $Certificate
    }
    Write-Output "Found Certificate:"
    Write-Output $Certificate
}

Export-Certificate -Cert $Certificate -Force -FilePath $FilePath | Out-Null

Set-Location -Path "C:\Program Files\Google Cloud Directory Sync\jre"

# Delete existing entry
& .\bin\keytool -keystore lib\security\cacerts -storepass changeit -delete -noprompt -alias $ComputerName 2>&1 | %{ "$_" }

# Add entry
& .\bin\keytool -keystore lib\security\cacerts -storepass changeit -importcert -noprompt -file $FilePath -alias $ComputerName 2>&1 | %{ "$_" }

Remove-Item -Path $FilePath -Force