Repository: AthenZ/athenz

Issue:
HTTP or TCP listener in sia container for GCP Cloud Run support
GCP Cloud Run services that use service identity typically require the SIA container to generate certificates first. If we make other containers depend on the SIA container, Cloud Run enforces a health check(or called start up probe) that must be TCP, HTTP, or gRPC based. 

Request to add HTTP or TCP interface to SIA that could be used as a health check listener.

Hints from issue comments:

Solution patch:
diff --git a/.gitignore b/.gitignore
index ff71ebef9..f075a6dbd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -119,6 +119,10 @@ utils/zts-rolecert/bin/
 utils/zts-rolecert/pkg/
 utils/zts-rolecert/src/
 utils/msd-agent/pkg/
+utils/zts-accesstoken/zts-accesstoken
+utils/zts-rolecert/zts-rolecert
+utils/zts-roletoken/zts-roletoken
+utils/zts-svccert/zts-svccert
 libs/go/ztsclientutil/bin/
 libs/go/ztsclientutil/pkg/
 libs/go/ztsclientutil/src/
diff --git a/libs/go/sia/Makefile b/libs/go/sia/Makefile
index 1b692cc52..06bbeea1d 100644
--- a/libs/go/sia/Makefile
+++ b/libs/go/sia/Makefile
@@ -13,9 +13,9 @@ ifneq ($(patsubst %$(SIA_DIR),,$(lastword $(ATHENZ_DIR))),)
 endif
 
 SUBDIRS = access/config access/tokens agent aws/attestation aws/doc aws/lambda aws/meta \
-	aws/options aws/stssession file futil gcp/attestation gcp/meta gcp/functions \
-	host/hostdoc host/ip host/provider host/signature host/utils logutil options pki/cert \
-	sds ssh/hostcert ssh/hostkey util verify
+	aws/options aws/stssession config file futil gcp/attestation gcp/functions gcp/meta \
+	host/hostdoc host/ip host/provider host/signature host/utils http logutil options \
+	otel pki/cert sds ssh/hostcert ssh/hostkey util verify
 OS = darwin linux windows
 
 # check to see if go utility is installed
diff --git a/libs/go/sia/agent/agent.go b/libs/go/sia/agent/agent.go
index 40cf5e6d8..d1a5e8091 100644
--- a/libs/go/sia/agent/agent.go
+++ b/libs/go/sia/agent/agent.go
@@ -38,6 +38,7 @@ import (
 	"github.com/AthenZ/athenz/libs/go/sia/access/tokens"
 	sc "github.com/AthenZ/athenz/libs/go/sia/config"
 	"github.com/AthenZ/athenz/libs/go/sia/host/provider"
+	"github.com/AthenZ/athenz/libs/go/sia/http"
 	"github.com/AthenZ/athenz/libs/go/sia/options"
 	"github.com/AthenZ/athenz/libs/go/sia/otel"
 	"github.com/AthenZ/athenz/libs/go/sia/sds"
@@ -110,6 +111,7 @@ func GetRoleCertificates(ztsUrl string, opts *sc.Options) (int, []string) {
 
 	//initialize our return state to success
 	failures := make([]string, 0)
+	tracker := http.GetStatusTracker()
 
 	for _, role := range opts.Roles {
 		var roleRequest = new(zts.RoleCertificateRequest)
@@ -176,6 +178,8 @@ func GetRoleCertificates(ztsUrl string, opts *sc.Options) (int, []string) {
 		roleCert, err := client.PostRoleCertificateRequestExt(roleRequest)
 		if err != nil {
 			log.Printf("PostRoleCertificateRequest failed for %s, err: %v\n", role.Name, err)
+			// Record failure
+			tracker.RecordRefreshFailure(role.Name, err)
 			failures = append(failures, role.Name)
 			continue
 		}
@@ -183,9 +187,13 @@ func GetRoleCertificates(ztsUrl string, opts *sc.Options) (int, []string) {
 		err = util.SaveRoleCertKey([]byte(roleKeyBytes), []byte(roleCert.X509Certificate), role.RoleKeyFilename, role.RoleCertFilename, svcKeyFile, role.Name, role.Uid, role.Gid, role.FileMode, opts.GenerateRoleKey, opts.RotateKey, opts.BackupDir, opts.FileDirectUpdate)
 		if err != nil {
 			log.Printf("Unable to save role cert key for role %s, err: %v\n", role.Name, err)
+			// Record failure
+			tracker.RecordRefreshFailure(role.Name, err)
 			failures = append(failures, role.Name)
 			continue
 		}
+		// Record success
+		tracker.RecordRefreshSuccess(role.Name, role.RoleCertFilename)
 	}
 	otel.RecordAgentCommandResult("GetRoleCertificates", len(failures) == 0)
 	log.Printf("SIA processed %d (failures %d) role certificate requests\n", len(opts.Roles), len(failures))
@@ -282,12 +290,13 @@ func registerSvc(svc sc.Service, ztsUrl string, opts *sc.Options) error {
 	if !opts.SanDnsHostname {
 		hostname = ""
 	}
+	serviceName := fmt.Sprintf("%s.%s", opts.Domain, svc.Name)
 	svcCertReqOptions := &util.SvcCertReqOptions{
 		Country:           opts.CertCountryName,
 		OrgName:           opts.CertOrgName,
 		Domain:            opts.Domain,
 		Service:           svc.Name,
-		CommonName:        opts.Domain + "." + svc.Name,
+		CommonName:        serviceName,
 		Account:           opts.Account,
 		InstanceId:        opts.InstanceId,
 		InstanceName:      opts.InstanceName,
@@ -339,22 +348,30 @@ func registerSvc(svc sc.Service, ztsUrl string, opts *sc.Options) error {
 		return err
 	}
 	client.AddCredentials("User-Agent", opts.Version)
+	tracker := http.GetStatusTracker()
+
 	ident, _, err := client.PostInstanceRegisterInformation(info)
 	if err != nil {
 		log.Printf("Unable to do PostInstanceRegisterInformation, err: %v\n", err)
+		tracker.RecordRefreshFailure(serviceName, err)
 		return err
 	}
 	svcKeyFile := util.GetSvcKeyFileName(opts.KeyDir, svc.KeyFilename, opts.Domain, svc.Name)
 	err = util.UpdateFile(svcKeyFile, []byte(util.PrivatePem(key)), svc.Uid, svc.Gid, 0440, opts.FileDirectUpdate, true)
 	if err != nil {
+		tracker.RecordRefreshFailure(serviceName, err)
 		return err
 	}
 	svcCertFile := util.GetSvcCertFileName(opts.CertDir, svc.CertFilename, opts.Domain, svc.Name)
 	err = util.UpdateFile(svcCertFile, []byte(ident.X509Certificate), svc.Uid, svc.Gid, 0444, opts.FileDirectUpdate, true)
 	if err != nil {
+		tracker.RecordRefreshFailure(serviceName, err)
 		return err
 	}
 
+	// Record success
+	tracker.RecordRefreshSuccess(serviceName, svcCertFile)
+
 	if opts.Services[0].Name == svc.Name {
 		err = util.UpdateFile(opts.AthenzCACertFile, []byte(ident.X509CertificateSigner), svc.Uid, svc.Gid, 0444, opts.FileDirectUpdate, true)
 		if err != nil {
@@ -482,10 +499,12 @@ func refreshSvc(svc sc.Service, ztsUrl string, opts *sc.Options) error {
 		return err
 	}
 	client.AddCredentials("User-Agent", opts.Version)
+	tracker := http.GetStatusTracker()
 
 	ident, err := client.PostInstanceRefreshInformation(zts.ServiceName(opts.Provider.GetName()), zts.DomainName(opts.Domain), zts.SimpleName(svc.Name), zts.PathElement(opts.InstanceId), info)
 	if err != nil {
 		log.Printf("Unable to refresh instance service certificate for %s, err: %v\n", opts.Name, err)
+		tracker.RecordRefreshFailure(serviceName, err)
 		return err
 	}
 
@@ -493,9 +512,13 @@ func refreshSvc(svc sc.Service, ztsUrl string, opts *sc.Options) error {
 	svcCertBytes := []byte(ident.X509Certificate)
 	err = util.SaveServiceCertKey([]byte(svcKeyBytes), svcCertBytes, keyFile, certFile, serviceName, svc.Uid, svc.Gid, svc.FileMode, opts.RotateKey, opts.BackupDir, opts.FileDirectUpdate)
 	if err != nil {
+		tracker.RecordRefreshFailure(serviceName, err)
 		return err
 	}
 
+	// Record success
+	tracker.RecordRefreshSuccess(serviceName, certFile)
+
 	if opts.Services[0].Name == svc.Name {
 		err = util.UpdateFile(opts.AthenzCACertFile, []byte(ident.X509CertificateSigner), svc.Uid, svc.Gid, 0444, opts.FileDirectUpdate, true)
 		if err != nil {
@@ -698,6 +721,10 @@ func runAgentCommand(siaCmd, ztsUrl string, opts *sc.Options) {
 
 	svcs := options.GetSvcNames(opts.Services)
 
+	// Initialize status tracker
+	tracker := http.GetStatusTracker()
+	tracker.Initialize(opts)
+
 	tokenOpts, err := tokenOptions(opts, ztsUrl)
 	if err != nil {
 		log.Printf("%s\n", err.Error())
@@ -813,7 +840,7 @@ func runAgentCommand(siaCmd, ztsUrl string, opts *sc.Options) {
 		util.NotifySystemdReadyForCommand(cmd, "systemd-notify")
 		log.Printf("Identity established for services: %s\n", svcs)
 
-		stop := make(chan bool, 1)
+		stop := make(chan struct{})
 		errors := make(chan error, 1)
 		certUpdates := make(chan bool, 1)
 
@@ -886,12 +913,23 @@ func runAgentCommand(siaCmd, ztsUrl string, opts *sc.Options) {
 			}
 		}()
 
+		go func() {
+			if opts.HttpPort != 0 {
+				log.Printf("Starting HTTP health check server on port %d\n", opts.HttpPort)
+				err := http.StartHttpServer(opts.HttpPort, stop)
+				if err != nil {
+					log.Printf("failed to start http server: %v\n", err)
+					return
+				}
+			}
+		}()
+
 		go func() {
 			if opts.SDSUdsPath != "" {
 				err := sds.StartGrpcServer(opts, certUpdates)
 				if err != nil {
 					log.Printf("failed to start grpc/uds server: %v\n", err)
-					stop <- true
+					close(stop)
 					return
 				}
 			}
@@ -902,7 +940,7 @@ func runAgentCommand(siaCmd, ztsUrl string, opts *sc.Options) {
 			signal.Notify(signals, os.Interrupt, syscall.SIGTERM)
 			sig := <-signals
 			log.Printf("Received signal %v, stopping rotation\n", sig)
-			stop <- true
+			close(stop)
 		}()
 
 		go func() {
@@ -916,7 +954,7 @@ func runAgentCommand(siaCmd, ztsUrl string, opts *sc.Options) {
 			for {
 				select {
 				case <-t2.C:
-					log.Printf("refreshing access-token..")
+					log.Printf("refreshing access-token...")
 					err := accessTokenRequest(tokenOpts)
 					if err != nil {
 						util.ExecuteScriptWithoutBlock(opts.RunAfterTokensErrParts, err.Error(), false)
diff --git a/libs/go/sia/aws/options/options.go b/libs/go/sia/aws/options/options.go
index 7aa31eba2..2f496455c 100644
--- a/libs/go/sia/aws/options/options.go
+++ b/libs/go/sia/aws/options/options.go
@@ -332,6 +332,9 @@ func InitEnvConfig(config *sc.Config) (*sc.Config, *sc.ConfigAccount, error) {
 	if config.OTel.ServiceInstanceID == "" {
 		config.OTel.ServiceInstanceID = os.Getenv("OTEL_SERVICE_INSTANCE_ID")
 	}
+	if config.HttpPort == 0 {
+		config.HttpPort = util.ParseEnvIntFlag("ATHENZ_SIA_HTTP_PORT", 0)
+	}
 
 	return config, &sc.ConfigAccount{
 		Account:      account,
@@ -394,6 +397,7 @@ func setOptions(config *sc.Config, account *sc.ConfigAccount, profileConfig *sc.
 	addlSanDNSEntries := make([]string, 0)
 	runAfterFailExit := false
 	roleCertsRequired := false
+	httpPort := 0
 
 	var storeTokenOption *int
 	if config != nil {
@@ -411,6 +415,7 @@ func setOptions(config *sc.Config, account *sc.ConfigAccount, profileConfig *sc.
 		storeTokenOption = config.StoreTokenOption
 		runAfterFailExit = config.RunAfterFailExit
 		roleCertsRequired = config.RoleCertsRequired
+		httpPort = config.HttpPort
 
 		if config.RefreshInterval > 0 {
 			refreshInterval = config.RefreshInterval
@@ -663,6 +668,7 @@ func setOptions(config *sc.Config, account *sc.ConfigAccount, profileConfig *sc.
 		RunAfterFailExit:       runAfterFailExit,
 		RoleCertsRequired:      roleCertsRequired,
 		OTel:                   oTelCfg,
+		HttpPort:               httpPort,
 	}, nil
 }
 
diff --git a/libs/go/sia/config/config.go b/libs/go/sia/config/config.go
index 09e1dff64..04de8b726 100644
--- a/libs/go/sia/config/config.go
+++ b/libs/go/sia/config/config.go
@@ -112,6 +112,7 @@ type Config struct {
 	RunAfterFailExit  bool                     `json:"run_after_fail_exit,omitempty"`        //exit process if run_after script fails
 	RoleCertsRequired bool                     `json:"role_certs_required,omitempty"`        //role certs are required for system operations, fail if not successful
 	OTel              OTel                     `json:"otel"`                                 //OpenTelemetry configuration
+	HttpPort          int                      `json:"http_port,omitempty"`                  //HTTP port for health check
 }
 
 type AccessProfileConfig struct {
@@ -223,6 +224,7 @@ type Options struct {
 	RunAfterFailExit       bool              //exit process if run_after script fails
 	RoleCertsRequired      bool              //role certs are required for system operations, fail if not successful
 	OTel                   OTel              //openTelemetry configuration
+	HttpPort               int               //HTTP port for health check
 }
 
 // OTel stores the configuration for OpenTelemetry.
diff --git a/libs/go/sia/http/http.go b/libs/go/sia/http/http.go
new file mode 100644
index 000000000..2dd159245
--- /dev/null
+++ b/libs/go/sia/http/http.go
@@ -0,0 +1,227 @@
+//
+// Copyright The Athenz Authors
+//
+// 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.
+//
+
+package http
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"log"
+	"net/http"
+	"sync"
+	"time"
+
+	sc "github.com/AthenZ/athenz/libs/go/sia/config"
+	"github.com/AthenZ/athenz/libs/go/sia/pki/cert"
+)
+
+// CertStatus represents the status of a certificate
+type CertStatus struct {
+	ExpiryTime       time.Time  `json:"expiry_time"`
+	LastRefreshTime  *time.Time `json:"last_refresh_time,omitempty"`
+	LastFailureTime  *time.Time `json:"last_failure_time,omitempty"`
+	LastFailureError string     `json:"last_failure_error,omitempty"`
+}
+
+// StatusTracker tracks certificate status information
+type StatusTracker struct {
+	mu           sync.RWMutex
+	certStatuses map[string]*CertStatus
+}
+
+var globalTracker *StatusTracker
+var trackerOnce sync.Once
+
+// GetStatusTracker returns the global status tracker instance
+func GetStatusTracker() *StatusTracker {
+	trackerOnce.Do(func() {
+		globalTracker = &StatusTracker{
+			certStatuses: make(map[string]*CertStatus),
+		}
+	})
+	return globalTracker
+}
+
+// Initialize initializes the status tracker with options
+func (st *StatusTracker) Initialize(opts *sc.Options) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	st.certStatuses = make(map[string]*CertStatus)
+}
+
+// RecordRefreshSuccess records a successful certificate refresh
+func (st *StatusTracker) RecordRefreshSuccess(certIdentity, certFile string) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+
+	now := time.Now()
+	status, exists := st.certStatuses[certIdentity]
+	if !exists {
+		status = &CertStatus{}
+		st.certStatuses[certIdentity] = status
+	}
+
+	status.LastRefreshTime = &now
+	status.LastFailureTime = nil
+	status.LastFailureError = ""
+
+	// Update expiry time from certificate file
+	if certificate, err := cert.FromFile(certFile); err == nil {
+		status.ExpiryTime = certificate.NotAfter
+	} else {
+		log.Printf("unable to read certificate from %s to update expiry: %v\n", certFile, err)
+	}
+}
+
+// RecordRefreshFailure records a failed certificate refresh
+func (st *StatusTracker) RecordRefreshFailure(certIdentity string, err error) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+
+	now := time.Now()
+	status, exists := st.certStatuses[certIdentity]
+	if !exists {
+		status = &CertStatus{}
+		st.certStatuses[certIdentity] = status
+	}
+
+	status.LastFailureTime = &now
+	errMsg := ""
+	if err != nil {
+		errMsg = err.Error()
+	}
+	status.LastFailureError = errMsg
+}
+
+// GetStatus returns the current status of all certificates
+func (st *StatusTracker) GetStatus() bool {
+	st.mu.RLock()
+	defer st.mu.RUnlock()
+
+	// if all the certificates in the status map are valid
+	// then return true, otherwise false. A certificate
+	// is considered valid if the expiry time is in the future
+	// regardless of when was the last refresh time or if there
+	// were any recent failures
+
+	for _, status := range st.certStatuses {
+		if time.Now().After(status.ExpiryTime) || status.ExpiryTime.IsZero() {
+			return false
+		}
+	}
+
+	return true
+}
+
+// GetCerts returns the current status of all certificates
+func (st *StatusTracker) GetCerts() map[string]*CertStatus {
+	st.mu.RLock()
+	defer st.mu.RUnlock()
+
+	// Create a copy of the status map
+	result := make(map[string]*CertStatus)
+	for certIdentity, status := range st.certStatuses {
+		// Create a copy of the status
+		statusCopy := &CertStatus{
+			ExpiryTime:       status.ExpiryTime,
+			LastFailureError: status.LastFailureError,
+		}
+		if status.LastFailureTime != nil {
+			t := *status.LastFailureTime
+			statusCopy.LastFailureTime = &t
+		}
+		if status.LastRefreshTime != nil {
+			t := *status.LastRefreshTime
+			statusCopy.LastRefreshTime = &t
+		}
+		result[certIdentity] = statusCopy
+	}
+
+	return result
+}
+
+// StartHttpServer starts the HTTP server with status endpoint
+func StartHttpServer(port int, stop <-chan struct{}) error {
+	mux := http.NewServeMux()
+
+	// Status endpoint
+	mux.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
+		if r.Method != http.MethodGet {
+			w.WriteHeader(http.StatusMethodNotAllowed)
+			return
+		}
+
+		tracker := GetStatusTracker()
+		status := tracker.GetStatus()
+
+		log.Printf("Status response: %t\n", status)
+		httpStatus := http.StatusOK
+		if !status {
+			httpStatus = http.StatusServiceUnavailable
+		}
+		w.WriteHeader(httpStatus)
+	})
+	mux.HandleFunc("/certs", func(w http.ResponseWriter, r *http.Request) {
+		if r.Method != http.MethodGet {
+			w.WriteHeader(http.StatusMethodNotAllowed)
+			return
+		}
+
+		tracker := GetStatusTracker()
+		certs := tracker.GetCerts()
+
+		// Convert to JSON
+		w.Header().Set("Content-Type", "application/json")
+		if err := json.NewEncoder(w).Encode(certs); err != nil {
+			log.Printf("Error encoding status response: %v\n", err)
+			w.WriteHeader(http.StatusInternalServerError)
+			return
+		}
+	})
+
+	server := &http.Server{
+		Addr:    fmt.Sprintf(":%d", port),
+		Handler: mux,
+	}
+
+	// Start server in a goroutine
+	errChan := make(chan error, 1)
+	go func() {
+		log.Printf("HTTP status server listening on port %d\n", port)
+		if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
+			log.Printf("Error starting HTTP server: %v\n", err)
+			errChan <- err
+		}
+	}()
+
+	// Wait for stop signal or error
+	select {
+	case err := <-errChan:
+		return err
+	case <-stop:
+		log.Println("Shutting down HTTP status server...")
+		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
+		defer cancel()
+		if err := server.Shutdown(ctx); err != nil {
+			log.Printf("Error shutting down HTTP status server: %v\n", err)
+		} else {
+			log.Println("HTTP Server successfully shutdown")
+		}
+		return nil
+	}
+}
diff --git a/libs/go/sia/options/options.go b/libs/go/sia/options/options.go
index 01e802931..3907b0036 100644
--- a/libs/go/sia/options/options.go
+++ b/libs/go/sia/options/options.go
@@ -406,6 +406,9 @@ func InitEnvConfig(config *sc.Config, provider provider.Provider) (*sc.Config, *
 	if config.OTel.ServiceInstanceID == "" {
 		config.OTel.ServiceInstanceID = os.Getenv("OTEL_SERVICE_INSTANCE_ID")
 	}
+	if config.HttpPort == 0 {
+		config.HttpPort = util.ParseEnvIntFlag("ATHENZ_SIA_HTTP_PORT", 0)
+	}
 
 	return config, configAccount, nil
 }
@@ -459,6 +462,7 @@ func setOptions(config *sc.Config, account *sc.ConfigAccount, profileConfig *sc.
 	addlSanDNSEntries := make([]string, 0)
 	runAfterFailExit := false
 	roleCertsRequired := false
+	httpPort := 0
 
 	var storeTokenOption *int
 	if config != nil {
@@ -476,6 +480,7 @@ func setOptions(config *sc.Config, account *sc.ConfigAccount, profileConfig *sc.
 		storeTokenOption = config.StoreTokenOption
 		runAfterFailExit = config.RunAfterFailExit
 		roleCertsRequired = config.RoleCertsRequired
+		httpPort = config.HttpPort
 
 		if config.RefreshInterval > 0 {
 			refreshInterval = config.RefreshInterval
@@ -736,6 +741,7 @@ func setOptions(config *sc.Config, account *sc.ConfigAccount, profileConfig *sc.
 		RunAfterFailExit:       runAfterFailExit,
 		RoleCertsRequired:      roleCertsRequired,
 		OTel:                   oTelCfg,
+		HttpPort:               httpPort,
 	}, nil
 }
 
diff --git a/libs/java/server_gcp_common/pom.xml b/libs/java/server_gcp_common/pom.xml
index bdccf2873..aed3210ca 100644
--- a/libs/java/server_gcp_common/pom.xml
+++ b/libs/java/server_gcp_common/pom.xml
@@ -45,6 +45,10 @@
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-parametermanager</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.google.cloud</groupId>
+      <artifactId>google-cloud-firestore</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
diff --git a/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStore.java b/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStore.java
new file mode 100644
index 000000000..1d95151ea
--- /dev/null
+++ b/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStore.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * 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.
+ */
+package io.athenz.server.gcp.common.cert.impl;
+
+import com.google.cloud.firestore.Firestore;
+import com.yahoo.athenz.auth.Principal;
+import com.yahoo.athenz.common.server.cert.CertRecordStore;
+import com.yahoo.athenz.common.server.cert.CertRecordStoreConnection;
+import com.yahoo.athenz.common.server.db.RolesProvider;
+import com.yahoo.athenz.common.server.notification.NotificationManager;
+import com.yahoo.athenz.common.utils.X509CertUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.cert.X509Certificate;
+
+public class FirestoreCertRecordStore implements CertRecordStore {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FirestoreCertRecordStore.class);
+    private static final Logger CERTLOGGER = LoggerFactory.getLogger("X509CertLogger");
+
+    private final Firestore firestore;
+    private final String collectionName;
+
+    public FirestoreCertRecordStore(Firestore firestore, final String collectionName) {
+        this.firestore = firestore;
+        this.collectionName = collectionName;
+    }
+
+    @Override
+    public CertRecordStoreConnection getConnection() {
+        return new FirestoreCertRecordStoreConnection(firestore, collectionName);
+    }
+
+    @Override
+    public void setOperationTimeout(int opTimeout) {
+        // Firestore client handles timeouts internally
+    }
+
+    @Override
+    public void clearConnections() {
+        // No connection pooling required for Firestore
+    }
+
+    @Override
+    public void log(final Principal principal, final String ip, final String provider,
+            final String instanceId, final X509Certificate x509Cert) {
+        X509CertUtils.logCert(CERTLOGGER, principal, ip, provider, instanceId, x509Cert);
+    }
+
+    @Override
+    public boolean enableNotifications(NotificationManager notificationManager, RolesProvider rolesProvider,
+            final String serverName) {
+        // Notification support can be added in the future if needed
+        LOGGER.info("Notification support not yet implemented for FirestoreCertRecordStore");
+        return false;
+    }
+}
diff --git a/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreConnection.java b/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreConnection.java
new file mode 100644
index 000000000..305728e29
--- /dev/null
+++ b/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreConnection.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * 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.
+ */
+package io.athenz.server.gcp.common.cert.impl;
+
+import com.google.api.core.ApiFuture;
+import com.google.cloud.Timestamp;
+import com.google.cloud.firestore.*;
+import com.yahoo.athenz.common.server.ServerResourceException;
+import com.yahoo.athenz.common.server.cert.CertRecordStoreConnection;
+import com.yahoo.athenz.common.server.cert.X509CertRecord;
+import org.eclipse.jetty.util.StringUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+public class FirestoreCertRecordStoreConnection implements CertRecordStoreConnection {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FirestoreCertRecordStoreConnection.class);
+
+    public static final String ZTS_PROP_CERT_FIRESTORE_ITEM_TTL_HOURS = "athenz.zts.cert_firestore_item_ttl_hours";
+    public static final String ZTS_PROP_NOTIFICATION_GRACE_PERIOD_HOURS = "athenz.zts.notification_cert_fail_grace_hours";
+
+    private static final String KEY_PRIMARY = "primaryKey";
+    private static final String KEY_PROVIDER = "provider";
+    private static final String KEY_INSTANCE_ID = "instanceId";
+    private static final String KEY_SERVICE = "service";
+    private static final String KEY_CURRENT_SERIAL = "currentSerial";
+    private static final String KEY_CURRENT_TIME = "currentTime";
+    private static final String KEY_CURRENT_IP = "currentIP";
+    private static final String KEY_PREV_SERIAL = "prevSerial";
+    private static final String KEY_PREV_TIME = "prevTime";
+    private static final String KEY_PREV_IP = "prevIP";
+    private static final String KEY_CLIENT_CERT = "clientCert";
+    private static final String KEY_LAST_NOTIFIED_TIME = "lastNotifiedTime";
+    private static final String KEY_LAST_NOTIFIED_SERVER = "lastNotifiedServer";
+    private static final String KEY_EXPIRY_TIME = "expiryTime";
+    private static final String KEY_HOSTNAME = "hostName";
+    private static final String KEY_TTL_TIMESTAMP = "ttlTimestamp";
+    private static final String KEY_REGISTER_TIME = "registerTime";
+    private static final String KEY_SVC_DATA_UPDATE_TIME = "svcDataUpdateTime";
+    private static final String KEY_SIA_PROVIDER = "siaProvider";
+
+    private static final String UNKNOWN_SIA_PROVIDER = "N/A";
+
+    // Configuration settings in hours
+    private static final Long EXPIRY_HOURS = Long.parseLong(
+            System.getProperty(ZTS_PROP_CERT_FIRESTORE_ITEM_TTL_HOURS, "720"));
+
+    // Default grace period - 2 weeks (336 hours)
+    private static final Long EXPIRY_HOURS_GRACE = Long.parseLong(
+            System.getProperty(ZTS_PROP_NOTIFICATION_GRACE_PERIOD_HOURS, "336"));
+
+    private final Firestore firestore;
+    private final String collectionName;
+
+    public FirestoreCertRecordStoreConnection(Firestore firestore, final String collectionName) {
+        this.firestore = firestore;
+        this.collectionName = collectionName;
+    }
+
+    @Override
+    public void setOperationTimeout(int queryTimeout) {
+        // Firestore client handles timeouts internally
+    }
+
+    @Override
+    public void close() {
+        // Firestore client is managed at the store level, not connection level
+    }
+
+    @Override
+    public X509CertRecord getX509CertRecord(String provider, String instanceId, String service) throws ServerResourceException {
+
+        final String primaryKey = getPrimaryKey(provider, instanceId, service);
+
+        try {
+            DocumentReference docRef = firestore.collection(collectionName).document(primaryKey);
+            ApiFuture<DocumentSnapshot> future = docRef.get();
+            DocumentSnapshot document = future.get();
+
+            if (!document.exists()) {
+                LOGGER.error("Firestore Get Error for {}: document not found", primaryKey);
+                return null;
+            }
+
+            return documentToX509CertRecord(document);
+        } catch (Exception ex) {
+            LOGGER.error("Firestore Get Error for {}: {}/{}", primaryKey, ex.getClass(), ex.getMessage());
+            if (ex instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+            return null;
+        }
+    }
+
+    private X509CertRecord documentToX509CertRecord(DocumentSnapshot document) {
+
+        X509CertRecord certRecord = new X509CertRecord();
+        certRecord.setProvider(document.getString(KEY_PROVIDER));
+        certRecord.setInstanceId(document.getString(KEY_INSTANCE_ID));
+        certRecord.setService(document.getString(KEY_SERVICE));
+        certRecord.setCurrentSerial(document.getString(KEY_CURRENT_SERIAL));
+        certRecord.setCurrentIP(document.getString(KEY_CURRENT_IP));
+        certRecord.setCurrentTime(getDateFromTimestamp(document.getTimestamp(KEY_CURRENT_TIME)));
+        certRecord.setPrevSerial(document.getString(KEY_PREV_SERIAL));
+        certRecord.setPrevIP(document.getString(KEY_PREV_IP));
+        certRecord.setPrevTime(getDateFromTimestamp(document.getTimestamp(KEY_PREV_TIME)));
+        certRecord.setClientCert(document.getBoolean(KEY_CLIENT_CERT));
+        certRecord.setLastNotifiedTime(getDateFromTimestamp(document.getTimestamp(KEY_LAST_NOTIFIED_TIME)));
+        certRecord.setLastNotifiedServer(document.getString(KEY_LAST_NOTIFIED_SERVER));
+        certRecord.setExpiryTime(getDateFromTimestamp(document.getTimestamp(KEY_EXPIRY_TIME)));
+        certRecord.setHostName(document.getString(KEY_HOSTNAME));
+        certRecord.setSvcDataUpdateTime(getDateFromTimestamp(document.getTimestamp(KEY_SVC_DATA_UPDATE_TIME)));
+        certRecord.setSiaProvider(document.getString(KEY_SIA_PROVIDER));
+        return certRecord;
+    }
+
+    private Date getDateFromTimestamp(Timestamp timestamp) {
+        if (timestamp == null) {
+            return null;
+        }
+        return timestamp.toDate();
+    }
+
+    private Timestamp getTimestampFromDate(Date date) {
+        if (date == null) {
+            return null;
+        }
+        return Timestamp.of(new java.sql.Timestamp(date.getTime()));
+    }
+
+    String getDefaultValueIfEmpty(final String value, final String defaultValue) {
+        return StringUtil.isEmpty(value) ? defaultValue : value;
+    }
+
+    @Override
+    public boolean updateX509CertRecord(X509CertRecord certRecord) {
+
+        final String primaryKey = getPrimaryKey(certRecord.getProvider(), certRecord.getInstanceId(),
+                certRecord.getService());
+
+        // if we don't have a svc update time we'll default to the current time
+        if (certRecord.getSvcDataUpdateTime() == null) {
+            certRecord.setSvcDataUpdateTime(new Date());
+        }
+
+        // Prevent inserting null values in hostName
+        final String hostName = getDefaultValueIfEmpty(certRecord.getHostName(), primaryKey);
+
+        // if we have null value for the SIA provider, we'll default to "N/A"
+        final String siaProvider = getDefaultValueIfEmpty(certRecord.getSiaProvider(), UNKNOWN_SIA_PROVIDER);
+
+        try {
+            DocumentReference docRef = firestore.collection(collectionName).document(primaryKey);
+
+            Map<String, Object> updates = new HashMap<>();
+            updates.put(KEY_INSTANCE_ID, certRecord.getInstanceId());
+            updates.put(KEY_PROVIDER, certRecord.getProvider());
+            updates.put(KEY_SERVICE, certRecord.getService());
+            updates.put(KEY_CURRENT_SERIAL, certRecord.getCurrentSerial());
+            updates.put(KEY_CURRENT_IP, certRecord.getCurrentIP());
+            updates.put(KEY_CURRENT_TIME, getTimestampFromDate(certRecord.getCurrentTime()));
+            updates.put(KEY_PREV_SERIAL, certRecord.getPrevSerial());
+            updates.put(KEY_PREV_IP, certRecord.getPrevIP());
+            updates.put(KEY_PREV_TIME, getTimestampFromDate(certRecord.getPrevTime()));
+            updates.put(KEY_CLIENT_CERT, certRecord.getClientCert());
+            updates.put(KEY_SVC_DATA_UPDATE_TIME, getTimestampFromDate(certRecord.getSvcDataUpdateTime()));
+            updates.put(KEY_EXPIRY_TIME, getTimestampFromDate(certRecord.getExpiryTime()));
+            updates.put(KEY_HOSTNAME, hostName);
+            updates.put(KEY_SIA_PROVIDER, siaProvider);
+
+            // Calculate TTL timestamp (current time + expiry hours)
+            if (certRecord.getCurrentTime() != null) {
+                long ttlMillis = certRecord.getCurrentTime().getTime() + TimeUnit.HOURS.toMillis(EXPIRY_HOURS);
+                updates.put(KEY_TTL_TIMESTAMP, Timestamp.of(new java.sql.Timestamp(ttlMillis)));
+            }
+
+            ApiFuture<WriteResult> future = docRef.update(updates);
+            future.get();
+            return true;
+        } catch (Exception ex) {
+            LOGGER.error("Firestore Update Error for {}: {}/{}", primaryKey, ex.getClass(), ex.getMessage());
+            if (ex instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public boolean insertX509CertRecord(X509CertRecord certRecord) {
+
+        final String primaryKey = getPrimaryKey(certRecord.getProvider(), certRecord.getInstanceId(),
+                certRecord.getService());
+
+        // Prevent inserting null values in hostName
+        final String hostName = getDefaultValueIfEmpty(certRecord.getHostName(), primaryKey);
+
+        // if we have null value for the SIA provider, we'll default to "N/A"
+        final String siaProvider = getDefaultValueIfEmpty(certRecord.getSiaProvider(), UNKNOWN_SIA_PROVIDER);
+
+        try {
+            DocumentReference docRef = firestore.collection(collectionName).document(primaryKey);
+
+            Map<String, Object> data = new HashMap<>();
+            data.put(KEY_PRIMARY, primaryKey);
+            data.put(KEY_INSTANCE_ID, certRecord.getInstanceId());
+            data.put(KEY_PROVIDER, certRecord.getProvider());
+            data.put(KEY_SERVICE, certRecord.getService());
+            data.put(KEY_CURRENT_SERIAL, certRecord.getCurrentSerial());
+            data.put(KEY_CURRENT_IP, certRecord.getCurrentIP());
+            data.put(KEY_CURRENT_TIME, getTimestampFromDate(certRecord.getCurrentTime()));
+            data.put(KEY_PREV_SERIAL, certRecord.getPrevSerial());
+            data.put(KEY_PREV_IP, certRecord.getPrevIP());
+            data.put(KEY_PREV_TIME, getTimestampFromDate(certRecord.getPrevTime()));
+            data.put(KEY_CLIENT_CERT, certRecord.getClientCert());
+            data.put(KEY_EXPIRY_TIME, getTimestampFromDate(certRecord.getExpiryTime()));
+            data.put(KEY_SVC_DATA_UPDATE_TIME, getTimestampFromDate(certRecord.getSvcDataUpdateTime()));
+            data.put(KEY_REGISTER_TIME, Timestamp.now());
+            data.put(KEY_HOSTNAME, hostName);
+            data.put(KEY_SIA_PROVIDER, siaProvider);
+
+            // Calculate TTL timestamp (current time + expiry hours)
+            if (certRecord.getCurrentTime() != null) {
+                long ttlMillis = certRecord.getCurrentTime().getTime() + TimeUnit.HOURS.toMillis(EXPIRY_HOURS);
+                data.put(KEY_TTL_TIMESTAMP, Timestamp.of(new java.sql.Timestamp(ttlMillis)));
+            }
+
+            ApiFuture<WriteResult> future = docRef.set(data);
+            future.get();
+            return true;
+        } catch (Exception ex) {
+            LOGGER.error("Firestore Insert Error for {}: {}/{}", primaryKey, ex.getClass(), ex.getMessage());
+            if (ex instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public boolean deleteX509CertRecord(String provider, String instanceId, String service) {
+
+        final String primaryKey = getPrimaryKey(provider, instanceId, service);
+
+        try {
+            DocumentReference docRef = firestore.collection(collectionName).document(primaryKey);
+            ApiFuture<WriteResult> future = docRef.delete();
+            future.get();
+            return true;
+        } catch (Exception ex) {
+            LOGGER.error("Firestore Delete Error for {}: {}/{}", primaryKey, ex.getClass(), ex.getMessage());
+            if (ex instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public int deleteExpiredX509CertRecords(int expiryTimeMins, int limit) {
+        // with Firestore there is no need to manually expunge expired
+        // record since we have the TTL option enabled for our table,
+        // and we just need to make sure the attribute is updated with
+        // the epoch time + timeout seconds when it should retire
+
+        return 0;
+    }
+
+    @Override
+    public List<X509CertRecord> updateUnrefreshedCertificatesNotificationTimestamp(final String lastNotifiedServer,
+            long lastNotifiedTime, final String provider) {
+
+        List<DocumentSnapshot> documents = getUnrefreshedCertsRecords(lastNotifiedTime, provider);
+        return updateLastNotified(lastNotifiedServer, lastNotifiedTime, documents);
+    }
+
+    private String getPrimaryKey(final String provider, final String instanceId, final String service) {
+        return provider + ":" + service + ":" + instanceId;
+    }
+
+    private List<X509CertRecord> updateLastNotified(String lastNotifiedServer, long lastNotifiedTime,
+            List<DocumentSnapshot> documents) {
+
+        Date yesterday = Date.from(
+                Instant.ofEpochMilli(lastNotifiedTime).minus(1, ChronoUnit.DAYS)
+        );
+
+        List<X509CertRecord> updatedRecords = new ArrayList<>();
+        WriteBatch batch = firestore.batch();
+        int updateCount = 0;
+
+        for (DocumentSnapshot document : documents) {
+            Date docLastNotifiedDate = getDateFromTimestamp(document.getTimestamp(KEY_LAST_NOTIFIED_TIME));
+
+            // Update if never notified or last notified before yesterday
+            if (docLastNotifiedDate == null || docLastNotifiedDate.before(yesterday)) {
+                Timestamp expectedTimestamp = Timestamp.of(new java.sql.Timestamp(lastNotifiedTime));
+
+                Map<String, Object> updates = new HashMap<>();
+                updates.put(KEY_LAST_NOTIFIED_TIME, expectedTimestamp);
+                updates.put(KEY_LAST_NOTIFIED_SERVER, lastNotifiedServer);
+
+                batch.update(document.getReference(), updates);
+                updateCount++;
+
+                // Build updated record from existing document + updates
+                X509CertRecord certRecord = documentToX509CertRecord(document);
+                certRecord.setLastNotifiedTime(new Date(lastNotifiedTime));
+                certRecord.setLastNotifiedServer(lastNotifiedServer);
+                updatedRecords.add(certRecord);
+            }
+        }
+
+        if (updateCount > 0) {
+            try {
+                batch.commit().get();
+            } catch (Exception ex) {
+                LOGGER.error("Firestore batch updateLastNotified failed: {}/{}", ex.getClass(), ex.getMessage());
+                if (ex instanceof InterruptedException) {
+                    Thread.currentThread().interrupt();
+                }
+                // If the batch fails, we should not return any records as updated.
+                return Collections.emptyList();
+            }
+        }
+
+        return updatedRecords;
+    }
+
+    private List<DocumentSnapshot> getUnrefreshedCertsRecords(long lastNotifiedTime, String provider) {
+
+        long yesterday = lastNotifiedTime - TimeUnit.DAYS.toMillis(1);
+        long unrefreshedCertsRangeBegin = lastNotifiedTime - TimeUnit.HOURS.toMillis(EXPIRY_HOURS);
+        long unrefreshedCertsRangeEnd = lastNotifiedTime - TimeUnit.HOURS.toMillis(EXPIRY_HOURS_GRACE);
+
+        Timestamp rangeBeginTs = Timestamp.of(new java.sql.Timestamp(unrefreshedCertsRangeBegin));
+        Timestamp rangeEndTs = Timestamp.of(new java.sql.Timestamp(unrefreshedCertsRangeEnd));
+        Timestamp yesterdayTs = Timestamp.of(new java.sql.Timestamp(yesterday));
+
+        try {
+            // Query for unrefreshed certificates
+            // Note: This query may require a composite index in Firestore
+            Query query = firestore.collection(collectionName)
+                    .whereEqualTo(KEY_PROVIDER, provider)
+                    .whereGreaterThanOrEqualTo(KEY_CURRENT_TIME, rangeEndTs)
+                    .whereLessThanOrEqualTo(KEY_CURRENT_TIME, rangeBeginTs);
+
+            ApiFuture<QuerySnapshot> future = query.get();
+            QuerySnapshot querySnapshot = future.get();
+
+            // Filter documents that need notification (never notified or notified before yesterday)
+            // and have hostName set
+            List<DocumentSnapshot> candidateDocuments = querySnapshot.getDocuments().stream()
+                    .filter(doc -> doc.contains(KEY_HOSTNAME) && !StringUtil.isEmpty(doc.getString(KEY_HOSTNAME)))
+                    .filter(doc -> {
+                        Timestamp lastNotifiedTs = doc.getTimestamp(KEY_LAST_NOTIFIED_TIME);
+                        return lastNotifiedTs == null || lastNotifiedTs.compareTo(yesterdayTs) < 0;
+                    })
+                    .collect(Collectors.toList());
+
+            // fetch all related records in bulk and filter in memory
+            return filterMostUpdatedHostRecords(candidateDocuments, provider);
+
+        } catch (Exception ex) {
+            LOGGER.error("Firestore getUnrefreshedCertsRecords Error: {}/{}", ex.getClass(), ex.getMessage());
+            if (ex instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Filters candidate documents to only include the most updated record for each (hostname, provider, service) combination.
+     * implementation note: currently not optimizing for N+1 query problem, since the usage of getUnrefreshedCertsRecords may not materialize in GCP.
+     */
+    private List<DocumentSnapshot> filterMostUpdatedHostRecords(List<DocumentSnapshot> candidateDocuments, String provider) {
+        if (candidateDocuments.isEmpty()) {
+            return candidateDocuments;
+        }
+
+        try {
+            // Group candidates by (hostname, provider, service) to identify unique combinations
+            Map<String, List<DocumentSnapshot>> groupedCandidates = candidateDocuments.stream()
+                    .filter(doc -> !StringUtil.isEmpty(doc.getString(KEY_HOSTNAME))
+                            && !StringUtil.isEmpty(doc.getString(KEY_SERVICE)))
+                    .collect(Collectors.groupingBy(doc ->
+                            doc.getString(KEY_HOSTNAME) + ":" +
+                            doc.getString(KEY_PROVIDER) + ":" +
+                            doc.getString(KEY_SERVICE)
+                    ));
+
+            // For each unique combination, query all records and find the most recent one
+            // Store most recent record IDs for quick lookup
+            Set<String> mostRecentRecordIds = new HashSet<>();
+
+            for (Map.Entry<String, List<DocumentSnapshot>> entry : groupedCandidates.entrySet()) {
+                List<DocumentSnapshot> groupDocs = entry.getValue();
+
+                // Extract hostname, provider, service from any document in the group
+                DocumentSnapshot sampleDoc = groupDocs.get(0);
+                String hostName = sampleDoc.getString(KEY_HOSTNAME);
+                String service = sampleDoc.getString(KEY_SERVICE);
+
+                // Query all records with same hostname, provider, and service
+                Query query = firestore.collection(collectionName)
+                        .whereEqualTo(KEY_HOSTNAME, hostName)
+                        .whereEqualTo(KEY_PROVIDER, provider)
+                        .whereEqualTo(KEY_SERVICE, service);
+
+                ApiFuture<QuerySnapshot> future = query.get();
+                QuerySnapshot querySnapshot = future.get();
+                List<QueryDocumentSnapshot> allRecords = querySnapshot.getDocuments();
+
+                // Find the most recent record (highest currentTime)
+                DocumentSnapshot mostRecentRecord = allRecords.stream()
+                        .filter(doc -> doc.getTimestamp(KEY_CURRENT_TIME) != null)
+                        .max(Comparator.comparing(doc -> doc.getTimestamp(KEY_CURRENT_TIME)))
+                        .orElse(null);
+
+                if (mostRecentRecord != null) {
+                    mostRecentRecordIds.add(mostRecentRecord.getId());
+                }
+            }
+
+            // Filter candidates to only include most recent records
+            return candidateDocuments.stream()
+                    .filter(doc -> mostRecentRecordIds.contains(doc.getId()))
+                    .collect(Collectors.toList());
+
+        } catch (Exception ex) {
+            LOGGER.error("Firestore filterMostUpdatedHostRecords Error: {}/{}", ex.getClass(), ex.getMessage());
+            if (ex instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+            // Return empty list on error to avoid potentially incorrect notifications
+            return Collections.emptyList();
+        }
+    }
+}
diff --git a/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreFactory.java b/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreFactory.java
new file mode 100644
index 000000000..2d3d0b9e1
--- /dev/null
+++ b/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * 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.
+ */
+package io.athenz.server.gcp.common.cert.impl;
+
+import com.google.cloud.firestore.Firestore;
+import com.google.cloud.firestore.FirestoreOptions;
+import com.yahoo.athenz.auth.PrivateKeyStore;
+import com.yahoo.athenz.common.server.cert.CertRecordStore;
+import com.yahoo.athenz.common.server.cert.CertRecordStoreFactory;
+import com.yahoo.athenz.common.server.ServerResourceException;
+import org.eclipse.jetty.util.StringUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FirestoreCertRecordStoreFactory implements CertRecordStoreFactory {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FirestoreCertRecordStoreFactory.class);
+
+    public static final String ZTS_PROP_CERT_FIRESTORE_PROJECT_ID = "athenz.zts.cert_firestore_project_id";
+    public static final String ZTS_PROP_CERT_FIRESTORE_COLLECTION_NAME = "athenz.zts.cert_firestore_collection_name";
+    public static final String ZTS_PROP_CERT_FIRESTORE_DATABASE_ID = "athenz.zts.cert_firestore_database_id";
+
+    @Override
+    public CertRecordStore create(PrivateKeyStore keyStore) throws ServerResourceException {
+
+        final String projectId = System.getProperty(ZTS_PROP_CERT_FIRESTORE_PROJECT_ID);
+        if (StringUtil.isEmpty(projectId)) {
+            LOGGER.error("Cert Store Firestore project ID not specified");
+            throw new ServerResourceException(ServerResourceException.SERVICE_UNAVAILABLE,
+                    "Firestore project ID not specified");
+        }
+
+        final String collectionName = System.getProperty(ZTS_PROP_CERT_FIRESTORE_COLLECTION_NAME);
+        if (StringUtil.isEmpty(collectionName)) {
+            LOGGER.error("Cert Store Firestore collection name not specified");
+            throw new ServerResourceException(ServerResourceException.SERVICE_UNAVAILABLE,
+                    "Firestore collection name not specified");
+        }
+
+        // Database ID is optional - defaults to "(default)"
+        final String databaseId = System.getProperty(ZTS_PROP_CERT_FIRESTORE_DATABASE_ID, "(default)");
+
+        Firestore firestore = getFirestoreClient(projectId, databaseId);
+        return new FirestoreCertRecordStore(firestore, collectionName);
+    }
+
+    Firestore getFirestoreClient(String projectId, String databaseId) {
+        try {
+            FirestoreOptions firestoreOptions = FirestoreOptions.newBuilder()
+                    .setProjectId(projectId)
+                    .setDatabaseId(databaseId)
+                    .build();
+            return firestoreOptions.getService();
+        } catch (Exception ex) {
+            LOGGER.error("Failed to create Firestore client: {}", ex.getMessage(), ex);
+            throw new RuntimeException("Failed to create Firestore client", ex);
+        }
+    }
+}
+
diff --git a/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStore.java b/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStore.java
new file mode 100644
index 000000000..ff8822419
--- /dev/null
+++ b/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStore.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * 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.
+ */
+package io.athenz.server.gcp.common.cert.impl;
+
+import com.google.cloud.firestore.Firestore;
+import com.yahoo.athenz.auth.Principal;
+import com.yahoo.athenz.common.server.ssh.SSHRecordStore;
+import com.yahoo.athenz.common.server.ssh.SSHRecordStoreConnection;
+import com.yahoo.athenz.common.server.db.RolesProvider;
+import com.yahoo.athenz.common.server.notification.NotificationManager;
+import com.yahoo.athenz.common.utils.X509CertUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FirestoreSSHRecordStore implements SSHRecordStore {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FirestoreSSHRecordStore.class);
+    private static final Logger SSHLOGGER = LoggerFactory.getLogger("SSHCertLogger");
+
+    private final Firestore firestore;
+    private final String collectionName;
+
+    public FirestoreSSHRecordStore(Firestore firestore, final String collectionName) {
+        this.firestore = firestore;
+        this.collectionName = collectionName;
+    }
+
+    @Override
+    public SSHRecordStoreConnection getConnection() {
+        return new FirestoreSSHRecordStoreConnection(firestore, collectionName);
+    }
+
+    @Override
+    public void setOperationTimeout(int opTimeout) {
+        // Firestore client handles timeouts internally
+    }
+
+    @Override
+    public void clearConnections() {
+        // No connection pooling required for Firestore
+    }
+
+    @Override
+    public void log(final Principal principal, final String ip, final String service,
+            final String instanceId) {
+        X509CertUtils.logSSH(SSHLOGGER, principal, ip, service, instanceId);
+    }
+
+    @Override
+    public boolean enableNotifications(NotificationManager notificationManager, RolesProvider rolesProvider,
+            final String serverName) {
+        // Notification support can be added in the future if needed
+        LOGGER.info("Notification support not yet implemented for FirestoreSSHRecordStore");
+        return false;
+    }
+}
diff --git a/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreConnection.java b/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreConnection.java
new file mode 100644
index 000000000..6339ad881
--- /dev/null
+++ b/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreConnection.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * 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.
+ */
+package io.athenz.server.gcp.common.cert.impl;
+
+import com.google.api.core.ApiFuture;
+import com.google.cloud.Timestamp;
+import com.google.cloud.firestore.DocumentReference;
+import com.google.cloud.firestore.DocumentSnapshot;
+import com.google.cloud.firestore.Firestore;
+import com.google.cloud.firestore.WriteResult;
+import com.yahoo.athenz.common.server.ssh.SSHCertRecord;
+import com.yahoo.athenz.common.server.ssh.SSHRecordStoreConnection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public class FirestoreSSHRecordStoreConnection implements SSHRecordStoreConnection {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FirestoreSSHRecordStoreConnection.class);
+
+    public static final String ZTS_PROP_SSH_FIRESTORE_ITEM_TTL_HOURS = "athenz.zts.ssh_firestore_item_ttl_hours";
+
+    private static final String KEY_PRIMARY = "primaryKey";
+    private static final String KEY_INSTANCE_ID = "instanceId";
+    private static final String KEY_SERVICE = "service";
+    private static final String KEY_PRINCIPALS = "principals";
+    private static final String KEY_CLIENT_IP = "clientIP";
+    private static final String KEY_PRIVATE_IP = "privateIP";
+    private static final String KEY_TTL_TIMESTAMP = "ttlTimestamp";
+
+    // Configuration setting in hours - defaults to 720 hours (30 days)
+    private static final Long EXPIRY_HOURS = Long.parseLong(
+            System.getProperty(ZTS_PROP_SSH_FIRESTORE_ITEM_TTL_HOURS, "720"));
+
+    private final Firestore firestore;
+    private final String collectionName;
+
+    public FirestoreSSHRecordStoreConnection(Firestore firestore, final String collectionName) {
+        this.firestore = firestore;
+        this.collectionName = collectionName;
+    }
+
+    @Override
+    public void setOperationTimeout(int queryTimeout) {
+        // Firestore client handles timeouts internally
+    }
+
+    @Override
+    public void close() {
+        // Firestore client is managed at the store level, not connection level
+    }
+
+    @Override
+    public SSHCertRecord getSSHCertRecord(String instanceId, String service) {
+
+        final String primaryKey = getPrimaryKey(instanceId, service);
+
+        try {
+            DocumentReference docRef = firestore.collection(collectionName).document(primaryKey);
+            ApiFuture<DocumentSnapshot> future = docRef.get();
+            DocumentSnapshot document = future.get();
+
+            if (!document.exists()) {
+                LOGGER.error("Firestore Get Error for {}: document not found", primaryKey);
+                return null;
+            }
+
+            SSHCertRecord certRecord = new SSHCertRecord();
+            certRecord.setInstanceId(document.getString(KEY_INSTANCE_ID));
+            certRecord.setService(document.getString(KEY_SERVICE));
+            certRecord.setPrincipals(document.getString(KEY_PRINCIPALS));
+            certRecord.setClientIP(document.getString(KEY_CLIENT_IP));
+            certRecord.setPrivateIP(document.getString(KEY_PRIVATE_IP));
+            return certRecord;
+
+        } catch (Exception ex) {
+            LOGGER.error("Firestore Get Error for {}: {}/{}", primaryKey, ex.getClass(), ex.getMessage());
+            if (ex instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public boolean updateSSHCertRecord(SSHCertRecord certRecord) {
+
+        final String primaryKey = getPrimaryKey(certRecord.getInstanceId(), certRecord.getService());
+
+        try {
+            DocumentReference docRef = firestore.collection(collectionName).document(primaryKey);
+
+            Map<String, Object> updates = new HashMap<>();
+            updates.put(KEY_INSTANCE_ID, certRecord.getInstanceId());
+            updates.put(KEY_SERVICE, certRecord.getService());
+            updates.put(KEY_CLIENT_IP, certRecord.getClientIP());
+            updates.put(KEY_PRINCIPALS, certRecord.getPrincipals());
+            updates.put(KEY_PRIVATE_IP, certRecord.getPrivateIP());
+
+            // Calculate TTL timestamp (current time + expiry hours)
+            long ttlMillis = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(EXPIRY_HOURS);
+            updates.put(KEY_TTL_TIMESTAMP, Timestamp.of(new java.sql.Timestamp(ttlMillis)));
+
+            ApiFuture<WriteResult> future = docRef.update(updates);
+            future.get();
+            return true;
+        } catch (Exception ex) {
+            LOGGER.error("Firestore Update Error for {}: {}/{}", primaryKey, ex.getClass(), ex.getMessage());
+            if (ex instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public boolean insertSSHCertRecord(SSHCertRecord certRecord) {
+
+        final String primaryKey = getPrimaryKey(certRecord.getInstanceId(), certRecord.getService());
+
+        try {
+            DocumentReference docRef = firestore.collection(collectionName).document(primaryKey);
+
+            Map<String, Object> data = new HashMap<>();
+            data.put(KEY_PRIMARY, primaryKey);
+            data.put(KEY_INSTANCE_ID, certRecord.getInstanceId());
+            data.put(KEY_SERVICE, certRecord.getService());
+            data.put(KEY_CLIENT_IP, certRecord.getClientIP());
+            data.put(KEY_PRINCIPALS, certRecord.getPrincipals());
+            data.put(KEY_PRIVATE_IP, certRecord.getPrivateIP());
+
+            // Calculate TTL timestamp (current time + expiry hours)
+            long ttlMillis = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(EXPIRY_HOURS);
+            data.put(KEY_TTL_TIMESTAMP, Timestamp.of(new java.sql.Timestamp(ttlMillis)));
+
+            ApiFuture<WriteResult> future = docRef.set(data);
+            future.get();
+            return true;
+        } catch (Exception ex) {
+            LOGGER.error("Firestore Insert Error for {}: {}/{}", primaryKey, ex.getClass(), ex.getMessage());
+            if (ex instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public boolean deleteSSHCertRecord(String instanceId, String service) {
+
+        final String primaryKey = getPrimaryKey(instanceId, service);
+
+        try {
+            DocumentReference docRef = firestore.collection(collectionName).document(primaryKey);
+            ApiFuture<WriteResult> future = docRef.delete();
+            future.get();
+            return true;
+        } catch (Exception ex) {
+            LOGGER.error("Firestore Delete Error for {}: {}/{}", primaryKey, ex.getClass(), ex.getMessage());
+            if (ex instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public int deleteExpiredSSHCertRecords(int expiryTimeMins, int limit) {
+        // with Firestore there is no need to manually expunge expired
+        // record since we have the TTL option enabled for our table,
+        // and we just need to make sure the attribute is updated with
+        // the epoch time + timeout seconds when it should retire
+
+        return 0;
+    }
+
+    private String getPrimaryKey(final String instanceId, final String service) {
+        return service + ":" + instanceId;
+    }
+}
diff --git a/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreFactory.java b/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreFactory.java
new file mode 100644
index 000000000..0de734176
--- /dev/null
+++ b/libs/java/server_gcp_common/src/main/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreFactory.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * 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.
+ */
+package io.athenz.server.gcp.common.cert.impl;
+
+import com.google.cloud.firestore.Firestore;
+import com.google.cloud.firestore.FirestoreOptions;
+import com.yahoo.athenz.auth.PrivateKeyStore;
+import com.yahoo.athenz.common.server.ssh.SSHRecordStore;
+import com.yahoo.athenz.common.server.ssh.SSHRecordStoreFactory;
+import com.yahoo.athenz.common.server.ServerResourceException;
+import org.eclipse.jetty.util.StringUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FirestoreSSHRecordStoreFactory implements SSHRecordStoreFactory {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(FirestoreSSHRecordStoreFactory.class);
+
+    public static final String ZTS_PROP_SSH_FIRESTORE_PROJECT_ID = "athenz.zts.ssh_firestore_project_id";
+    public static final String ZTS_PROP_SSH_FIRESTORE_COLLECTION_NAME = "athenz.zts.ssh_firestore_collection_name";
+    public static final String ZTS_PROP_SSH_FIRESTORE_DATABASE_ID = "athenz.zts.ssh_firestore_database_id";
+
+    @Override
+    public SSHRecordStore create(PrivateKeyStore keyStore) throws ServerResourceException {
+
+        final String projectId = System.getProperty(ZTS_PROP_SSH_FIRESTORE_PROJECT_ID);
+        if (StringUtil.isEmpty(projectId)) {
+            LOGGER.error("SSH Store Firestore project ID not specified");
+            throw new ServerResourceException(ServerResourceException.SERVICE_UNAVAILABLE,
+                    "Firestore project ID not specified");
+        }
+
+        final String collectionName = System.getProperty(ZTS_PROP_SSH_FIRESTORE_COLLECTION_NAME);
+        if (StringUtil.isEmpty(collectionName)) {
+            LOGGER.error("SSH Store Firestore collection name not specified");
+            throw new ServerResourceException(ServerResourceException.SERVICE_UNAVAILABLE,
+                    "Firestore collection name not specified");
+        }
+
+        // Database ID is optional - defaults to "(default)"
+        final String databaseId = System.getProperty(ZTS_PROP_SSH_FIRESTORE_DATABASE_ID, "(default)");
+
+        Firestore firestore = getFirestoreClient(projectId, databaseId);
+        return new FirestoreSSHRecordStore(firestore, collectionName);
+    }
+
+    Firestore getFirestoreClient(String projectId, String databaseId) {
+        try {
+            FirestoreOptions firestoreOptions = FirestoreOptions.newBuilder()
+                    .setProjectId(projectId)
+                    .setDatabaseId(databaseId)
+                    .build();
+            return firestoreOptions.getService();
+        } catch (Exception ex) {
+            LOGGER.error("Failed to create Firestore client: {}", ex.getMessage(), ex);
+            throw new RuntimeException("Failed to create Firestore client", ex);
+        }
+    }
+}



Test patch:
diff --git a/libs/go/sia/http/http_test.go b/libs/go/sia/http/http_test.go
new file mode 100644
index 000000000..10fc72e43
--- /dev/null
+++ b/libs/go/sia/http/http_test.go
@@ -0,0 +1,830 @@
+//
+// Copyright The Athenz Authors
+//
+// 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.
+//
+
+package http
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/json"
+	"encoding/pem"
+	"errors"
+	"fmt"
+	"math/big"
+	"net/http"
+	"os"
+	"sync"
+	"testing"
+	"time"
+
+	sc "github.com/AthenZ/athenz/libs/go/sia/config"
+)
+
+// Helper function to create a temporary certificate file
+func createTempCertFile(t *testing.T, expiryTime time.Time) string {
+	t.Helper()
+
+	// Generate a private key
+	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+	if err != nil {
+		t.Fatalf("Failed to generate private key: %v", err)
+	}
+
+	// Create certificate template
+	template := x509.Certificate{
+		SerialNumber:          big.NewInt(1),
+		Subject:               pkix.Name{CommonName: "test-cert"},
+		NotBefore:             time.Now().Add(-24 * time.Hour),
+		NotAfter:              expiryTime,
+		KeyUsage:              x509.KeyUsageDigitalSignature,
+		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+		BasicConstraintsValid: true,
+	}
+
+	// Create certificate
+	certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
+	if err != nil {
+		t.Fatalf("Failed to create certificate: %v", err)
+	}
+
+	// Create temp file
+	tmpFile, err := os.CreateTemp("", "test-cert-*.pem")
+	if err != nil {
+		t.Fatalf("Failed to create temp file: %v", err)
+	}
+	defer tmpFile.Close()
+
+	// Encode to PEM
+	err = pem.Encode(tmpFile, &pem.Block{Type: "CERTIFICATE", Bytes: certDER})
+	if err != nil {
+		t.Fatalf("Failed to encode certificate: %v", err)
+	}
+
+	return tmpFile.Name()
+}
+
+// Helper function to reset the global tracker for testing
+func resetGlobalTracker() {
+	globalTracker = nil
+	trackerOnce = sync.Once{}
+}
+
+func TestGetStatusTracker(t *testing.T) {
+	// Reset global tracker before each test
+	resetGlobalTracker()
+
+	// First call should create a new tracker
+	tracker1 := GetStatusTracker()
+	if tracker1 == nil {
+		t.Fatal("GetStatusTracker returned nil")
+	}
+
+	// Second call should return the same instance (singleton)
+	tracker2 := GetStatusTracker()
+	if tracker1 != tracker2 {
+		t.Error("GetStatusTracker should return the same instance (singleton pattern)")
+	}
+
+	// Verify it's initialized with empty map
+	tracker1.mu.RLock()
+	if tracker1.certStatuses == nil {
+		t.Error("certStatuses map should be initialized")
+	}
+	if len(tracker1.certStatuses) != 0 {
+		t.Error("certStatuses map should be empty initially")
+	}
+	tracker1.mu.RUnlock()
+}
+
+func TestInitialize(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	opts := &sc.Options{
+		Services: []sc.Service{
+			{Name: "service1"},
+			{Name: "service2"},
+		},
+		Roles: []sc.Role{
+			{Name: "role1"},
+			{Name: "role2"},
+		},
+	}
+
+	tracker.Initialize(opts)
+
+	tracker.mu.RLock()
+	defer tracker.mu.RUnlock()
+
+	// check we have an empty map after initialization
+	expectedCount := 0
+	if len(tracker.certStatuses) != expectedCount {
+		t.Errorf("Expected %d cert statuses, got %d", expectedCount, len(tracker.certStatuses))
+	}
+}
+
+func TestInitialize_EmptyOptions(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	opts := &sc.Options{
+		Services: []sc.Service{},
+		Roles:    []sc.Role{},
+	}
+
+	tracker.Initialize(opts)
+
+	tracker.mu.RLock()
+	defer tracker.mu.RUnlock()
+
+	if len(tracker.certStatuses) != 0 {
+		t.Errorf("Expected 0 cert statuses, got %d", len(tracker.certStatuses))
+	}
+}
+
+func TestRecordRefreshSuccess(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	certIdentity := "test-service"
+	expiryTime := time.Now().Add(24 * time.Hour)
+	certFile := createTempCertFile(t, expiryTime)
+	defer os.Remove(certFile)
+
+	beforeTime := time.Now()
+	tracker.RecordRefreshSuccess(certIdentity, certFile)
+	afterTime := time.Now()
+
+	tracker.mu.RLock()
+	defer tracker.mu.RUnlock()
+
+	status, exists := tracker.certStatuses[certIdentity]
+	if !exists {
+		t.Fatal("certIdentity should exist after RecordRefreshSuccess")
+	}
+
+	if status.LastRefreshTime == nil {
+		t.Error("LastRefreshTime should be set")
+	} else if status.LastRefreshTime.Before(beforeTime) || status.LastRefreshTime.After(afterTime) {
+		t.Error("LastRefreshTime should be set to current time")
+	}
+
+	if status.LastFailureTime != nil {
+		t.Error("LastFailureTime should be nil after successful refresh")
+	}
+
+	if status.LastFailureError != "" {
+		t.Error("LastFailureError should be empty after successful refresh")
+	}
+
+	// Check expiry time is set (with some tolerance for time differences)
+	if status.ExpiryTime.IsZero() {
+		t.Error("ExpiryTime should be set from certificate")
+	}
+	expectedExpiry := expiryTime
+	diff := status.ExpiryTime.Sub(expectedExpiry)
+	if diff < -time.Second || diff > time.Second {
+		t.Errorf("ExpiryTime should match certificate expiry. Expected ~%v, got %v", expectedExpiry, status.ExpiryTime)
+	}
+}
+
+func TestRecordRefreshSuccess_InvalidCertFile(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	certIdentity := "test-service"
+	invalidCertFile := "/nonexistent/file.pem"
+
+	tracker.RecordRefreshSuccess(certIdentity, invalidCertFile)
+
+	tracker.mu.RLock()
+	defer tracker.mu.RUnlock()
+
+	status, exists := tracker.certStatuses[certIdentity]
+	if !exists {
+		t.Fatal("certIdentity should exist even with invalid cert file")
+	}
+
+	if status.LastRefreshTime == nil {
+		t.Error("LastRefreshTime should still be set even if cert file is invalid")
+	}
+
+	// ExpiryTime should remain zero if cert file is invalid
+	if !status.ExpiryTime.IsZero() {
+		t.Error("ExpiryTime should remain zero when cert file is invalid")
+	}
+}
+
+func TestRecordRefreshSuccess_NewCertIdentity(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	certIdentity := "new-service"
+	expiryTime := time.Now().Add(48 * time.Hour)
+	certFile := createTempCertFile(t, expiryTime)
+	defer os.Remove(certFile)
+
+	tracker.RecordRefreshSuccess(certIdentity, certFile)
+
+	tracker.mu.RLock()
+	defer tracker.mu.RUnlock()
+
+	_, exists := tracker.certStatuses[certIdentity]
+	if !exists {
+		t.Fatal("New certIdentity should be created")
+	}
+}
+
+func TestRecordRefreshFailure(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	certIdentity := "test-service"
+	testError := errors.New("test error message")
+
+	beforeTime := time.Now()
+	tracker.RecordRefreshFailure(certIdentity, testError)
+	afterTime := time.Now()
+
+	tracker.mu.RLock()
+	defer tracker.mu.RUnlock()
+
+	status, exists := tracker.certStatuses[certIdentity]
+	if !exists {
+		t.Fatal("certIdentity should exist after RecordRefreshFailure")
+	}
+
+	if status.LastFailureTime == nil {
+		t.Error("LastFailureTime should be set")
+	} else if status.LastFailureTime.Before(beforeTime) || status.LastFailureTime.After(afterTime) {
+		t.Error("LastFailureTime should be set to current time")
+	}
+
+	if status.LastFailureError != testError.Error() {
+		t.Errorf("Expected LastFailureError '%s', got '%s'", testError.Error(), status.LastFailureError)
+	}
+}
+
+func TestRecordRefreshFailure_NilError(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	certIdentity := "test-service"
+
+	tracker.RecordRefreshFailure(certIdentity, nil)
+
+	tracker.mu.RLock()
+	defer tracker.mu.RUnlock()
+
+	status, exists := tracker.certStatuses[certIdentity]
+	if !exists {
+		t.Fatal("certIdentity should exist after RecordRefreshFailure")
+	}
+
+	if status.LastFailureTime == nil {
+		t.Error("LastFailureTime should be set even with nil error")
+	}
+
+	if status.LastFailureError != "" {
+		t.Error("LastFailureError should be empty string when error is nil")
+	}
+}
+
+func TestRecordRefreshFailure_NewCertIdentity(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	certIdentity := "new-service"
+	testError := errors.New("new error")
+
+	tracker.RecordRefreshFailure(certIdentity, testError)
+
+	tracker.mu.RLock()
+	defer tracker.mu.RUnlock()
+
+	_, exists := tracker.certStatuses[certIdentity]
+	if !exists {
+		t.Fatal("New certIdentity should be created")
+	}
+}
+
+func TestGetStatus_AllValid(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	// Add certificates with future expiry times
+	expiryTime1 := time.Now().Add(24 * time.Hour)
+	expiryTime2 := time.Now().Add(48 * time.Hour)
+	certFile1 := createTempCertFile(t, expiryTime1)
+	certFile2 := createTempCertFile(t, expiryTime2)
+	defer os.Remove(certFile1)
+	defer os.Remove(certFile2)
+
+	tracker.RecordRefreshSuccess("service1", certFile1)
+	tracker.RecordRefreshSuccess("service2", certFile2)
+
+	status := tracker.GetStatus()
+	if !status {
+		t.Error("GetStatus should return true when all certificates are valid")
+	}
+}
+
+func TestGetStatus_AllExpired(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	// Add certificates with past expiry times
+	expiryTime1 := time.Now().Add(-24 * time.Hour)
+	expiryTime2 := time.Now().Add(-48 * time.Hour)
+	certFile1 := createTempCertFile(t, expiryTime1)
+	certFile2 := createTempCertFile(t, expiryTime2)
+	defer os.Remove(certFile1)
+	defer os.Remove(certFile2)
+
+	tracker.RecordRefreshSuccess("service1", certFile1)
+	tracker.RecordRefreshSuccess("service2", certFile2)
+
+	status := tracker.GetStatus()
+	if status {
+		t.Error("GetStatus should return false when all certificates are expired")
+	}
+}
+
+func TestGetStatus_Mixed(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	// Add one valid and one expired certificate
+	expiryTime1 := time.Now().Add(24 * time.Hour)
+	expiryTime2 := time.Now().Add(-24 * time.Hour)
+	certFile1 := createTempCertFile(t, expiryTime1)
+	certFile2 := createTempCertFile(t, expiryTime2)
+	defer os.Remove(certFile1)
+	defer os.Remove(certFile2)
+
+	tracker.RecordRefreshSuccess("service1", certFile1)
+	tracker.RecordRefreshSuccess("service2", certFile2)
+
+	status := tracker.GetStatus()
+	if status {
+		t.Error("GetStatus should return false when at least one certificate is expired")
+	}
+}
+
+func TestGetStatus_EmptyMap(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	// Empty map should return true (all valid, since there are none)
+	status := tracker.GetStatus()
+	if !status {
+		t.Error("GetStatus should return true when certStatuses map is empty")
+	}
+}
+
+func TestGetStatus_ZeroExpiryTime(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	// Add a certificate with zero expiry time (not set)
+	tracker.RecordRefreshSuccess("service1", "/nonexistent/file.pem")
+
+	status := tracker.GetStatus()
+	if status {
+		t.Error("GetStatus should return false when expiry time is zero (not set)")
+	}
+}
+
+func TestGetCerts(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	expiryTime1 := time.Now().Add(24 * time.Hour)
+	expiryTime2 := time.Now().Add(48 * time.Hour)
+	certFile1 := createTempCertFile(t, expiryTime1)
+	certFile2 := createTempCertFile(t, expiryTime2)
+	defer os.Remove(certFile1)
+	defer os.Remove(certFile2)
+
+	testError := errors.New("test error")
+	now := time.Now()
+
+	tracker.RecordRefreshSuccess("service1", certFile1)
+	tracker.RecordRefreshFailure("service2", testError)
+
+	// Manually set times for service2 to test copying
+	tracker.mu.Lock()
+	if status, exists := tracker.certStatuses["service2"]; exists {
+		status.LastFailureTime = &now
+		status.LastRefreshTime = &now
+	}
+	tracker.mu.Unlock()
+
+	certs := tracker.GetCerts()
+
+	// Verify it's a copy (not the same map)
+	tracker.mu.RLock()
+	if &certs == &tracker.certStatuses {
+		t.Error("GetCerts should return a copy of the map, not the original")
+	}
+	tracker.mu.RUnlock()
+
+	// Verify contents
+	if len(certs) != 2 {
+		t.Errorf("Expected 2 certificates, got %d", len(certs))
+	}
+
+	// Verify service1
+	status1, exists := certs["service1"]
+	if !exists {
+		t.Fatal("service1 should exist in returned map")
+	}
+	if status1.LastRefreshTime == nil {
+		t.Error("service1 LastRefreshTime should be set")
+	}
+	if status1.LastFailureTime != nil {
+		t.Error("service1 LastFailureTime should be nil")
+	}
+
+	// Verify service2
+	status2, exists := certs["service2"]
+	if !exists {
+		t.Fatal("service2 should exist in returned map")
+	}
+	if status2.LastFailureError != testError.Error() {
+		t.Errorf("Expected LastFailureError '%s', got '%s'", testError.Error(), status2.LastFailureError)
+	}
+	if status2.LastFailureTime == nil {
+		t.Error("service2 LastFailureTime should be set")
+	}
+}
+
+func TestGetCerts_DeepCopy(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	expiryTime := time.Now().Add(24 * time.Hour)
+	certFile := createTempCertFile(t, expiryTime)
+	defer os.Remove(certFile)
+
+	tracker.RecordRefreshSuccess("service1", certFile)
+
+	certs := tracker.GetCerts()
+
+	newTime := time.Now().Add(72 * time.Hour)
+
+	// Modify the returned copy
+	certs["service1"].ExpiryTime = newTime
+
+	// Verify original is not modified
+	tracker.mu.RLock()
+	originalStatus := tracker.certStatuses["service1"]
+	tracker.mu.RUnlock()
+
+	if originalStatus.ExpiryTime == newTime {
+		t.Error("Modifying returned map should not affect original")
+	}
+}
+
+func TestGetCerts_EmptyMap(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	certs := tracker.GetCerts()
+
+	if len(certs) != 0 {
+		t.Errorf("Expected empty map, got %d entries", len(certs))
+	}
+}
+
+func TestStartHttpServer_StatusEndpoint(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	// Set up valid certificates
+	expiryTime := time.Now().Add(24 * time.Hour)
+	certFile := createTempCertFile(t, expiryTime)
+	defer os.Remove(certFile)
+	tracker.RecordRefreshSuccess("service1", certFile)
+
+	// Create a test server
+	port := 8888
+	stop := make(chan struct{})
+
+	// Start server in background
+	errChan := make(chan error, 1)
+	go func() {
+		errChan <- StartHttpServer(port, stop)
+	}()
+
+	// Give server time to start
+	time.Sleep(100 * time.Millisecond)
+
+	// Test GET /status with valid certificates
+	resp, err := http.Get(fmt.Sprintf("http://localhost:%d/status", port))
+	if err != nil {
+		t.Fatalf("Failed to make request: %v", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		t.Errorf("Expected status code %d, got %d", http.StatusOK, resp.StatusCode)
+	}
+
+	// Test POST /status (should return MethodNotAllowed)
+	resp2, err := http.Post(fmt.Sprintf("http://localhost:%d/status", port), "application/json", nil)
+	if err != nil {
+		t.Fatalf("Failed to make request: %v", err)
+	}
+	defer resp2.Body.Close()
+
+	if resp2.StatusCode != http.StatusMethodNotAllowed {
+		t.Errorf("Expected status code %d, got %d", http.StatusMethodNotAllowed, resp2.StatusCode)
+	}
+
+	// Stop server
+	close(stop)
+	time.Sleep(100 * time.Millisecond)
+}
+
+func TestStartHttpServer_StatusEndpoint_InvalidCerts(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	// Set up expired certificates
+	expiryTime := time.Now().Add(-24 * time.Hour)
+	certFile := createTempCertFile(t, expiryTime)
+	defer os.Remove(certFile)
+	tracker.RecordRefreshSuccess("service1", certFile)
+
+	port := 8889
+	stop := make(chan struct{})
+
+	errChan := make(chan error, 1)
+	go func() {
+		errChan <- StartHttpServer(port, stop)
+	}()
+
+	time.Sleep(100 * time.Millisecond)
+
+	resp, err := http.Get(fmt.Sprintf("http://localhost:%d/status", port))
+	if err != nil {
+		t.Fatalf("Failed to make request: %v", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusServiceUnavailable {
+		t.Errorf("Expected status code %d, got %d", http.StatusServiceUnavailable, resp.StatusCode)
+	}
+
+	close(stop)
+	time.Sleep(100 * time.Millisecond)
+}
+
+func TestStartHttpServer_CertsEndpoint(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	expiryTime := time.Now().Add(24 * time.Hour)
+	certFile := createTempCertFile(t, expiryTime)
+	defer os.Remove(certFile)
+	tracker.RecordRefreshSuccess("service1", certFile)
+	tracker.RecordRefreshFailure("service2", errors.New("test error"))
+
+	port := 8890
+	stop := make(chan struct{})
+
+	errChan := make(chan error, 1)
+	go func() {
+		errChan <- StartHttpServer(port, stop)
+	}()
+
+	time.Sleep(100 * time.Millisecond)
+
+	// Test GET /certs
+	resp, err := http.Get(fmt.Sprintf("http://localhost:%d/certs", port))
+	if err != nil {
+		t.Fatalf("Failed to make request: %v", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		t.Errorf("Expected status code %d, got %d", http.StatusOK, resp.StatusCode)
+	}
+
+	contentType := resp.Header.Get("Content-Type")
+	if contentType != "application/json" {
+		t.Errorf("Expected Content-Type 'application/json', got '%s'", contentType)
+	}
+
+	// Verify JSON response
+	var certs map[string]*CertStatus
+	decoder := json.NewDecoder(resp.Body)
+	if err := decoder.Decode(&certs); err != nil {
+		t.Fatalf("Failed to decode JSON response: %v", err)
+	}
+
+	if len(certs) != 2 {
+		t.Errorf("Expected 2 certificates in response, got %d", len(certs))
+	}
+
+	if _, exists := certs["service1"]; !exists {
+		t.Error("service1 should exist in response")
+	}
+
+	if _, exists := certs["service2"]; !exists {
+		t.Error("service2 should exist in response")
+	}
+
+	// Test POST /certs (should return MethodNotAllowed)
+	resp2, err := http.Post(fmt.Sprintf("http://localhost:%d/certs", port), "application/json", nil)
+	if err != nil {
+		t.Fatalf("Failed to make request: %v", err)
+	}
+	defer resp2.Body.Close()
+
+	if resp2.StatusCode != http.StatusMethodNotAllowed {
+		t.Errorf("Expected status code %d, got %d", http.StatusMethodNotAllowed, resp2.StatusCode)
+	}
+
+	close(stop)
+	time.Sleep(100 * time.Millisecond)
+}
+
+func TestStartHttpServer_Shutdown(t *testing.T) {
+	resetGlobalTracker()
+	port := 8891
+	stop := make(chan struct{})
+
+	errChan := make(chan error, 1)
+	go func() {
+		errChan <- StartHttpServer(port, stop)
+	}()
+
+	time.Sleep(100 * time.Millisecond)
+
+	// Verify server is running
+	resp, err := http.Get(fmt.Sprintf("http://localhost:%d/status", port))
+	if err != nil {
+		t.Fatalf("Server should be running: %v", err)
+	}
+	resp.Body.Close()
+
+	// Shutdown server
+	close(stop)
+
+	// Wait for shutdown
+	select {
+	case err := <-errChan:
+		if err != nil {
+			t.Errorf("Shutdown should not return error, got: %v", err)
+		}
+	case <-time.After(3 * time.Second):
+		t.Error("Shutdown should complete within timeout")
+	}
+
+	// Verify server is no longer accepting connections
+	time.Sleep(100 * time.Millisecond)
+	_, err = http.Get(fmt.Sprintf("http://localhost:%d/status", port))
+	if err == nil {
+		t.Error("Server should be shut down")
+	}
+}
+
+func TestStartHttpServer_ConcurrentAccess(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	expiryTime := time.Now().Add(24 * time.Hour)
+	certFile := createTempCertFile(t, expiryTime)
+	defer os.Remove(certFile)
+
+	// Initialize with multiple services
+	opts := &sc.Options{
+		Services: []sc.Service{
+			{Name: "service1"},
+			{Name: "service2"},
+			{Name: "service3"},
+		},
+	}
+	tracker.Initialize(opts)
+
+	// Concurrently record successes and failures
+	var wg sync.WaitGroup
+	numGoroutines := 10
+	for i := 0; i < numGoroutines; i++ {
+		wg.Add(1)
+		go func(id int) {
+			defer wg.Done()
+			serviceName := fmt.Sprintf("service%d", (id%3)+1)
+			if id%2 == 0 {
+				tracker.RecordRefreshSuccess(serviceName, certFile)
+			} else {
+				tracker.RecordRefreshFailure(serviceName, fmt.Errorf("error %d", id))
+			}
+		}(i)
+	}
+
+	wg.Wait()
+
+	// Verify all services have status
+	certs := tracker.GetCerts()
+	if len(certs) < 3 {
+		t.Errorf("Expected at least 3 certificates, got %d", len(certs))
+	}
+
+	// Concurrently call GetStatus and GetCerts
+	wg = sync.WaitGroup{}
+	for i := 0; i < numGoroutines; i++ {
+		wg.Add(2)
+		go func() {
+			defer wg.Done()
+			_ = tracker.GetStatus()
+		}()
+		go func() {
+			defer wg.Done()
+			_ = tracker.GetCerts()
+		}()
+	}
+
+	wg.Wait()
+}
+
+func TestRecordRefreshSuccess_OverwritesFailure(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	certIdentity := "test-service"
+	testError := errors.New("initial error")
+
+	// Record a failure first
+	tracker.RecordRefreshFailure(certIdentity, testError)
+
+	// Then record a success
+	expiryTime := time.Now().Add(24 * time.Hour)
+	certFile := createTempCertFile(t, expiryTime)
+	defer os.Remove(certFile)
+	tracker.RecordRefreshSuccess(certIdentity, certFile)
+
+	tracker.mu.RLock()
+	defer tracker.mu.RUnlock()
+
+	status := tracker.certStatuses[certIdentity]
+	if status.LastFailureTime != nil {
+		t.Error("LastFailureTime should be nil after successful refresh")
+	}
+	if status.LastFailureError != "" {
+		t.Error("LastFailureError should be cleared after successful refresh")
+	}
+	if status.LastRefreshTime == nil {
+		t.Error("LastRefreshTime should be set after successful refresh")
+	}
+}
+
+func TestRecordRefreshFailure_OverwritesSuccess(t *testing.T) {
+	resetGlobalTracker()
+	tracker := GetStatusTracker()
+
+	certIdentity := "test-service"
+	expiryTime := time.Now().Add(24 * time.Hour)
+	certFile := createTempCertFile(t, expiryTime)
+	defer os.Remove(certFile)
+
+	// Record a success first
+	tracker.RecordRefreshSuccess(certIdentity, certFile)
+
+	// Then record a failure
+	testError := errors.New("subsequent error")
+	tracker.RecordRefreshFailure(certIdentity, testError)
+
+	tracker.mu.RLock()
+	defer tracker.mu.RUnlock()
+
+	status := tracker.certStatuses[certIdentity]
+	// LastRefreshTime should still be set (not cleared by failure)
+	if status.LastRefreshTime == nil {
+		t.Error("LastRefreshTime should remain set even after failure")
+	}
+	// But failure information should be added
+	if status.LastFailureTime == nil {
+		t.Error("LastFailureTime should be set after failure")
+	}
+	if status.LastFailureError != testError.Error() {
+		t.Errorf("Expected LastFailureError '%s', got '%s'", testError.Error(), status.LastFailureError)
+	}
+}
diff --git a/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreConnectionTest.java b/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreConnectionTest.java
new file mode 100644
index 000000000..6cf6171b9
--- /dev/null
+++ b/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreConnectionTest.java
@@ -0,0 +1,1155 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * 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.
+ */
+package io.athenz.server.gcp.common.cert.impl;
+
+import com.google.api.core.ApiFuture;
+import com.google.api.core.ApiFutures;
+import com.google.cloud.Timestamp;
+import com.google.cloud.firestore.*;
+import com.yahoo.athenz.common.server.cert.X509CertRecord;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.*;
+import java.util.concurrent.ExecutionException;
+
+import static io.athenz.server.gcp.common.cert.impl.FirestoreCertRecordStoreConnection.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+import static org.testng.Assert.*;
+
+public class FirestoreCertRecordStoreConnectionTest {
+
+    private final String collectionName = "cert-collection";
+
+    @Mock private Firestore firestore;
+    @Mock private CollectionReference collectionReference;
+    @Mock private DocumentReference documentReference;
+    @Mock private ApiFuture<DocumentSnapshot> futureGet;
+    @Mock private ApiFuture<WriteResult> futureWrite;
+    @Mock private ApiFuture<List<WriteResult>> futureWriteBatch;
+    @Mock private ApiFuture<QuerySnapshot> futureQuery;
+    @Mock private DocumentSnapshot documentSnapshot;
+    @Mock private QuerySnapshot querySnapshot;
+    @Mock private WriteResult writeResult;
+    @Mock private WriteBatch writeBatch;
+
+    @BeforeMethod
+    public void setUp() {
+        MockitoAnnotations.openMocks(this);
+        when(firestore.collection(collectionName)).thenReturn(collectionReference);
+        when(collectionReference.document(any())).thenReturn(documentReference);
+    }
+
+    private FirestoreCertRecordStoreConnection getDBConnection() {
+        return new FirestoreCertRecordStoreConnection(firestore, collectionName);
+    }
+
+    @Test
+    public void testGetX509CertRecord() throws Exception {
+
+        when(documentReference.get()).thenReturn(futureGet);
+        when(futureGet.get()).thenReturn(documentSnapshot);
+        when(documentSnapshot.exists()).thenReturn(true);
+
+        Date now = new Date();
+        Timestamp timestamp = Timestamp.of(new java.sql.Timestamp(now.getTime()));
+
+        when(documentSnapshot.getString("provider")).thenReturn("athenz.provider");
+        when(documentSnapshot.getString("instanceId")).thenReturn("1234");
+        when(documentSnapshot.getString("service")).thenReturn("cn");
+        when(documentSnapshot.getString("currentSerial")).thenReturn("current-serial");
+        when(documentSnapshot.getString("currentIP")).thenReturn("current-ip");
+        when(documentSnapshot.getTimestamp("currentTime")).thenReturn(timestamp);
+        when(documentSnapshot.getString("prevSerial")).thenReturn("prev-serial");
+        when(documentSnapshot.getString("prevIP")).thenReturn("prev-ip");
+        when(documentSnapshot.getTimestamp("prevTime")).thenReturn(timestamp);
+        when(documentSnapshot.getBoolean("clientCert")).thenReturn(false);
+        when(documentSnapshot.getTimestamp("lastNotifiedTime")).thenReturn(timestamp);
+        when(documentSnapshot.getString("lastNotifiedServer")).thenReturn("last-notified-server");
+        when(documentSnapshot.getTimestamp("expiryTime")).thenReturn(timestamp);
+        when(documentSnapshot.getString("hostName")).thenReturn("hostname");
+        when(documentSnapshot.getTimestamp("svcDataUpdateTime")).thenReturn(timestamp);
+        when(documentSnapshot.getString("siaProvider")).thenReturn("sia-provider");
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+        dbConn.setOperationTimeout(10);
+        X509CertRecord certRecord = dbConn.getX509CertRecord("athenz.provider", "1234", "cn");
+
+        assertNotNull(certRecord);
+        assertEquals(certRecord.getProvider(), "athenz.provider");
+        assertEquals(certRecord.getInstanceId(), "1234");
+        assertEquals(certRecord.getService(), "cn");
+        assertEquals(certRecord.getCurrentSerial(), "current-serial");
+        assertEquals(certRecord.getCurrentIP(), "current-ip");
+        assertEquals(certRecord.getPrevSerial(), "prev-serial");
+        assertEquals(certRecord.getPrevIP(), "prev-ip");
+        assertFalse(certRecord.getClientCert());
+        assertEquals(certRecord.getLastNotifiedServer(), "last-notified-server");
+        assertEquals(certRecord.getHostName(), "hostname");
+        assertEquals(certRecord.getSiaProvider(), "sia-provider");
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testGetX509CertRecordNotFound() throws Exception {
+
+        when(documentReference.get()).thenReturn(futureGet);
+        when(futureGet.get()).thenReturn(documentSnapshot);
+        when(documentSnapshot.exists()).thenReturn(false);
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+        X509CertRecord certRecord = dbConn.getX509CertRecord("athenz.provider", "1234", "cn");
+        assertNull(certRecord);
+        dbConn.close();
+    }
+
+    @Test
+    public void testGetX509CertRecordExecutionException() throws Exception {
+
+        when(documentReference.get()).thenReturn(futureGet);
+        when(futureGet.get()).thenThrow(new ExecutionException("error", new Throwable("error")));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+        X509CertRecord certRecord = dbConn.getX509CertRecord("athenz.provider", "1234", "cn");
+        assertNull(certRecord);
+        dbConn.close();
+    }
+
+    @Test
+    public void testGetX509CertRecordException() throws Exception {
+        when(documentReference.get()).thenThrow(new RuntimeException("unknown exception"));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+        X509CertRecord certRecord = dbConn.getX509CertRecord("athenz.provider", "1234", "cn");
+        assertNull(certRecord);
+        dbConn.close();
+    }
+
+    @Test
+    public void testGetX509CertRecordInterruptedException() throws Exception {
+
+        when(documentReference.get()).thenReturn(futureGet);
+        when(futureGet.get()).thenThrow(new InterruptedException("interrupted"));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+        X509CertRecord certRecord = dbConn.getX509CertRecord("athenz.provider", "1234", "cn");
+        assertNull(certRecord);
+        dbConn.close();
+    }
+
+    @Test
+    public void testInsertX509CertRecord() throws Exception {
+
+        when(documentReference.set(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenReturn(writeResult);
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        X509CertRecord certRecord = new X509CertRecord();
+        certRecord.setProvider("athenz.provider");
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+        certRecord.setCurrentSerial("current-serial");
+        certRecord.setCurrentIP("current-ip");
+        certRecord.setCurrentTime(new Date());
+        certRecord.setPrevSerial("prev-serial");
+        certRecord.setPrevIP("prev-ip");
+        certRecord.setPrevTime(new Date());
+        certRecord.setClientCert(false);
+        certRecord.setExpiryTime(new Date());
+        certRecord.setSvcDataUpdateTime(new Date());
+        certRecord.setHostName("hostname");
+        certRecord.setSiaProvider("sia-provider");
+
+        boolean result = dbConn.insertX509CertRecord(certRecord);
+        assertTrue(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testInsertX509CertRecordNullHostName() throws Exception {
+
+        when(documentReference.set(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenReturn(writeResult);
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        X509CertRecord certRecord = new X509CertRecord();
+        certRecord.setProvider("athenz.provider");
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+        certRecord.setCurrentSerial("current-serial");
+        certRecord.setCurrentIP("current-ip");
+        certRecord.setCurrentTime(new Date());
+        certRecord.setHostName(null);
+
+        boolean result = dbConn.insertX509CertRecord(certRecord);
+        assertTrue(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testInsertX509CertRecordNullSiaProvider() throws Exception {
+
+        when(documentReference.set(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenReturn(writeResult);
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        X509CertRecord certRecord = new X509CertRecord();
+        certRecord.setProvider("athenz.provider");
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+        certRecord.setCurrentSerial("current-serial");
+        certRecord.setCurrentIP("current-ip");
+        certRecord.setCurrentTime(new Date());
+        certRecord.setSiaProvider(null);
+
+        boolean result = dbConn.insertX509CertRecord(certRecord);
+        assertTrue(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testInsertX509CertRecordException() throws Exception {
+
+        when(documentReference.set(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenThrow(new ExecutionException("error", new Throwable("error")));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        X509CertRecord certRecord = new X509CertRecord();
+        certRecord.setProvider("athenz.provider");
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+
+        boolean result = dbConn.insertX509CertRecord(certRecord);
+        assertFalse(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testInsertX509CertRecordInterruptedException() throws Exception {
+
+        when(documentReference.set(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenThrow(new InterruptedException("interrupted"));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        X509CertRecord certRecord = new X509CertRecord();
+        certRecord.setProvider("athenz.provider");
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+
+        boolean result = dbConn.insertX509CertRecord(certRecord);
+        assertFalse(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testUpdateX509CertRecord() throws Exception {
+
+        when(documentReference.update(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenReturn(writeResult);
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        X509CertRecord certRecord = new X509CertRecord();
+        certRecord.setProvider("athenz.provider");
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+        certRecord.setCurrentSerial("current-serial");
+        certRecord.setCurrentIP("current-ip");
+        certRecord.setCurrentTime(new Date());
+        certRecord.setPrevSerial("prev-serial");
+        certRecord.setPrevIP("prev-ip");
+        certRecord.setPrevTime(new Date());
+        certRecord.setClientCert(false);
+        certRecord.setExpiryTime(new Date());
+        certRecord.setHostName("hostname");
+        certRecord.setSiaProvider("sia-provider");
+
+        boolean result = dbConn.updateX509CertRecord(certRecord);
+        assertTrue(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testUpdateX509CertRecordNullSvcDataUpdateTime() throws Exception {
+
+        when(documentReference.update(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenReturn(writeResult);
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        X509CertRecord certRecord = new X509CertRecord();
+        certRecord.setProvider("athenz.provider");
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+        certRecord.setCurrentSerial("current-serial");
+        certRecord.setCurrentTime(new Date());
+        certRecord.setSvcDataUpdateTime(null);
+
+        boolean result = dbConn.updateX509CertRecord(certRecord);
+        assertTrue(result);
+        assertNotNull(certRecord.getSvcDataUpdateTime());
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testUpdateX509CertRecordException() throws Exception {
+
+        when(documentReference.update(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenThrow(new ExecutionException("error", new Throwable("error")));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        X509CertRecord certRecord = new X509CertRecord();
+        certRecord.setProvider("athenz.provider");
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+
+        boolean result = dbConn.updateX509CertRecord(certRecord);
+        assertFalse(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testUpdateX509CertRecordInterruptedException() throws Exception {
+
+        when(documentReference.update(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenThrow(new InterruptedException("interrupted"));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        X509CertRecord certRecord = new X509CertRecord();
+        certRecord.setProvider("athenz.provider");
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+
+        boolean result = dbConn.updateX509CertRecord(certRecord);
+        assertFalse(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testDeleteX509CertRecord() throws Exception {
+
+        when(documentReference.delete()).thenReturn(futureWrite);
+        when(futureWrite.get()).thenReturn(writeResult);
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        boolean result = dbConn.deleteX509CertRecord("athenz.provider", "1234", "cn");
+        assertTrue(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testDeleteX509CertRecordException() throws Exception {
+
+        when(documentReference.delete()).thenReturn(futureWrite);
+        when(futureWrite.get()).thenThrow(new ExecutionException("error", new Throwable("error")));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        boolean result = dbConn.deleteX509CertRecord("athenz.provider", "1234", "cn");
+        assertFalse(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testDeleteX509CertRecordInterruptedException() throws Exception {
+
+        when(documentReference.delete()).thenReturn(futureWrite);
+        when(futureWrite.get()).thenThrow(new InterruptedException("interrupted"));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        boolean result = dbConn.deleteX509CertRecord("athenz.provider", "1234", "cn");
+        assertFalse(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testDeleteExpiredX509CertRecords() {
+        // Firestore uses native TTL, so this method always returns 0
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        int deletedCount = dbConn.deleteExpiredX509CertRecords(100, 10);
+        assertEquals(deletedCount, 0);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testGetDefaultValueIfEmpty() {
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        assertEquals(dbConn.getDefaultValueIfEmpty("value", "default"), "value");
+        assertEquals(dbConn.getDefaultValueIfEmpty("", "default"), "default");
+        assertEquals(dbConn.getDefaultValueIfEmpty(null, "default"), "default");
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testUpdateUnrefreshedCertificatesNotificationTimestamp() throws Exception {
+
+        // Setup mocks for getUnrefreshedCertsRecords
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        DocumentReference ref = Mockito.mock(DocumentReference.class);
+
+        when(doc.getReference()).thenReturn(ref);
+        when(doc.contains("hostName")).thenReturn(true);
+        when(doc.getString("hostName")).thenReturn("hostname");
+        when(doc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+        when(doc.getString("provider")).thenReturn("athenz.provider");
+        when(doc.getString("service")).thenReturn("service");
+        when(doc.getTimestamp("currentTime")).thenReturn(Timestamp.now());
+        when(doc.getId()).thenReturn("doc-id");
+        // Add all fields required by documentToX509CertRecord
+        when(doc.getString("instanceId")).thenReturn("instance-id");
+        when(doc.getString("currentSerial")).thenReturn("current-serial");
+        when(doc.getString("currentIP")).thenReturn("10.0.0.1");
+        when(doc.getString("prevSerial")).thenReturn("prev-serial");
+        when(doc.getString("prevIP")).thenReturn("10.0.0.2");
+        when(doc.getTimestamp("prevTime")).thenReturn(null);
+        when(doc.getBoolean("clientCert")).thenReturn(false);
+        when(doc.getString("lastNotifiedServer")).thenReturn(null);
+        when(doc.getTimestamp("expiryTime")).thenReturn(null);
+        when(doc.getTimestamp("svcDataUpdateTime")).thenReturn(null);
+        when(doc.getString("siaProvider")).thenReturn(null);
+
+        List<QueryDocumentSnapshot> documents = Collections.singletonList(doc);
+        when(querySnapshot.getDocuments()).thenReturn(documents);
+
+        // Setup for mostUpdatedHostRecord check
+        Query hostQuery = Mockito.mock(Query.class);
+        QuerySnapshot hostQuerySnapshot = Mockito.mock(QuerySnapshot.class);
+        ApiFuture<QuerySnapshot> hostFutureQuery = Mockito.mock(ApiFuture.class);
+
+        when(collectionReference.whereEqualTo("hostName", "hostname")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("provider", "athenz.provider")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("service", "service")).thenReturn(hostQuery);
+        when(hostQuery.get()).thenReturn(hostFutureQuery);
+        when(hostFutureQuery.get()).thenReturn(hostQuerySnapshot);
+        when(hostQuerySnapshot.getDocuments()).thenReturn(documents);
+
+        // Setup batch operation
+        when(firestore.batch()).thenReturn(writeBatch);
+        when(writeBatch.update(any(DocumentReference.class), any(Map.class))).thenReturn(writeBatch);
+        when(writeBatch.commit()).thenReturn(futureWriteBatch);
+        when(futureWriteBatch.get()).thenReturn(Collections.emptyList());
+
+        long now = System.currentTimeMillis();
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", now, "athenz.provider");
+
+        assertNotNull(records);
+        assertEquals(records.size(), 1);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testUpdateUnrefreshedCertRecordsException() throws Exception {
+
+        // Setup mocks for getUnrefreshedCertsRecords
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenThrow(new InterruptedException("interrupted"));
+
+
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        DocumentReference ref = Mockito.mock(DocumentReference.class);
+
+        List<QueryDocumentSnapshot> documents = Collections.singletonList(doc);
+        when(querySnapshot.getDocuments()).thenReturn(documents);
+
+        long now = System.currentTimeMillis();
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", now, "athenz.provider");
+
+        assertNotNull(records);
+        assertEquals(records.size(), 0);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testUpdateUnrefreshedCertificatesException() throws Exception {
+
+        // Setup mocks for getUnrefreshedCertsRecords
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        DocumentReference ref = Mockito.mock(DocumentReference.class);
+
+        when(doc.getReference()).thenReturn(ref);
+        when(doc.contains("hostName")).thenReturn(true);
+        when(doc.getString("hostName")).thenReturn("hostname");
+        when(doc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+        when(doc.getString("provider")).thenReturn("athenz.provider");
+        when(doc.getString("service")).thenReturn("service");
+        when(doc.getTimestamp("currentTime")).thenReturn(Timestamp.now());
+        when(doc.getId()).thenReturn("doc-id");
+        // Add all fields required by documentToX509CertRecord
+        when(doc.getString("instanceId")).thenReturn("instance-id");
+        when(doc.getString("currentSerial")).thenReturn("current-serial");
+        when(doc.getString("currentIP")).thenReturn("10.0.0.1");
+        when(doc.getString("prevSerial")).thenReturn("prev-serial");
+        when(doc.getString("prevIP")).thenReturn("10.0.0.2");
+        when(doc.getTimestamp("prevTime")).thenReturn(null);
+        when(doc.getBoolean("clientCert")).thenReturn(false);
+        when(doc.getString("lastNotifiedServer")).thenReturn(null);
+        when(doc.getTimestamp("expiryTime")).thenReturn(null);
+        when(doc.getTimestamp("svcDataUpdateTime")).thenReturn(null);
+        when(doc.getString("siaProvider")).thenReturn(null);
+
+        List<QueryDocumentSnapshot> documents = Collections.singletonList(doc);
+        when(querySnapshot.getDocuments()).thenReturn(documents);
+
+        // Setup for mostUpdatedHostRecord check
+        Query hostQuery = Mockito.mock(Query.class);
+        QuerySnapshot hostQuerySnapshot = Mockito.mock(QuerySnapshot.class);
+        ApiFuture<QuerySnapshot> hostFutureQuery = Mockito.mock(ApiFuture.class);
+
+        when(collectionReference.whereEqualTo("hostName", "hostname")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("provider", "athenz.provider")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("service", "service")).thenReturn(hostQuery);
+        when(hostQuery.get()).thenReturn(hostFutureQuery);
+        when(hostFutureQuery.get()).thenReturn(hostQuerySnapshot);
+        when(hostQuerySnapshot.getDocuments()).thenReturn(documents);
+
+        // Setup batch operation to fail
+        when(firestore.batch()).thenReturn(writeBatch);
+        when(writeBatch.update(any(DocumentReference.class), any(Map.class))).thenReturn(writeBatch);
+        when(writeBatch.commit()).thenReturn(futureWriteBatch);
+        when(futureWriteBatch.get()).thenThrow(new RuntimeException("batch commit failed"));
+
+        long now = System.currentTimeMillis();
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", now, "athenz.provider");
+
+        assertNotNull(records);
+        assertEquals(records.size(), 0);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testUpdateUnrefreshedCertificatesInterruptedException() throws Exception {
+
+        // Setup mocks for getUnrefreshedCertsRecords
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        DocumentReference ref = Mockito.mock(DocumentReference.class);
+
+        when(doc.getReference()).thenReturn(ref);
+        when(doc.contains("hostName")).thenReturn(true);
+        when(doc.getString("hostName")).thenReturn("hostname");
+        when(doc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+        when(doc.getString("provider")).thenReturn("athenz.provider");
+        when(doc.getString("service")).thenReturn("service");
+        when(doc.getTimestamp("currentTime")).thenReturn(Timestamp.now());
+        when(doc.getId()).thenReturn("doc-id");
+        // Add all fields required by documentToX509CertRecord
+        when(doc.getString("instanceId")).thenReturn("instance-id");
+        when(doc.getString("currentSerial")).thenReturn("current-serial");
+        when(doc.getString("currentIP")).thenReturn("10.0.0.1");
+        when(doc.getString("prevSerial")).thenReturn("prev-serial");
+        when(doc.getString("prevIP")).thenReturn("10.0.0.2");
+        when(doc.getTimestamp("prevTime")).thenReturn(null);
+        when(doc.getBoolean("clientCert")).thenReturn(false);
+        when(doc.getString("lastNotifiedServer")).thenReturn(null);
+        when(doc.getTimestamp("expiryTime")).thenReturn(null);
+        when(doc.getTimestamp("svcDataUpdateTime")).thenReturn(null);
+        when(doc.getString("siaProvider")).thenReturn(null);
+
+        List<QueryDocumentSnapshot> documents = Collections.singletonList(doc);
+        when(querySnapshot.getDocuments()).thenReturn(documents);
+
+        // Setup for mostUpdatedHostRecord check
+        Query hostQuery = Mockito.mock(Query.class);
+        QuerySnapshot hostQuerySnapshot = Mockito.mock(QuerySnapshot.class);
+        ApiFuture<QuerySnapshot> hostFutureQuery = Mockito.mock(ApiFuture.class);
+
+        when(collectionReference.whereEqualTo("hostName", "hostname")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("provider", "athenz.provider")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("service", "service")).thenReturn(hostQuery);
+        when(hostQuery.get()).thenReturn(hostFutureQuery);
+        when(hostFutureQuery.get()).thenReturn(hostQuerySnapshot);
+        when(hostQuerySnapshot.getDocuments()).thenReturn(documents);
+
+        // Setup batch operation to throw InterruptedException
+        when(firestore.batch()).thenReturn(writeBatch);
+        when(writeBatch.update(any(DocumentReference.class), any(Map.class))).thenReturn(writeBatch);
+        when(writeBatch.commit()).thenReturn(futureWriteBatch);
+        when(futureWriteBatch.get()).thenThrow(new InterruptedException("interrupted"));
+
+        long now = System.currentTimeMillis();
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", now, "athenz.provider");
+
+        assertNotNull(records);
+        assertEquals(records.size(), 0);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testMostUpdatedHostRecordNullCurrentTime() throws Exception {
+        // Test case: recordCurrentTime is null, should be filtered out
+
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        DocumentReference ref = Mockito.mock(DocumentReference.class);
+
+        when(doc.getReference()).thenReturn(ref);
+        when(doc.contains("hostName")).thenReturn(true);
+        when(doc.getString("hostName")).thenReturn("hostname");
+        when(doc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+        when(doc.getString("provider")).thenReturn("athenz.provider");
+        when(doc.getString("service")).thenReturn("service");
+        when(doc.getTimestamp("currentTime")).thenReturn(null); // NULL current time
+        when(doc.getId()).thenReturn("doc-id");
+
+        List<QueryDocumentSnapshot> documents = Collections.singletonList(doc);
+        when(querySnapshot.getDocuments()).thenReturn(documents);
+
+        // Setup for mostUpdatedHostRecord check
+        Query hostQuery = Mockito.mock(Query.class);
+        QuerySnapshot hostQuerySnapshot = Mockito.mock(QuerySnapshot.class);
+        ApiFuture<QuerySnapshot> hostFutureQuery = Mockito.mock(ApiFuture.class);
+
+        when(collectionReference.whereEqualTo("hostName", "hostname")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("provider", "athenz.provider")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("service", "service")).thenReturn(hostQuery);
+        when(hostQuery.get()).thenReturn(hostFutureQuery);
+        when(hostFutureQuery.get()).thenReturn(hostQuerySnapshot);
+        when(hostQuerySnapshot.getDocuments()).thenReturn(documents);
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", System.currentTimeMillis(), "athenz.provider");
+
+        // Should return empty list because record has null currentTime
+        assertNotNull(records);
+        assertEquals(records.size(), 0);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testMostUpdatedHostRecordNewerRecordExists() throws Exception {
+        // Test case: Another record with same hostname has newer currentTime
+
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        QueryDocumentSnapshot oldDoc = Mockito.mock(QueryDocumentSnapshot.class);
+        DocumentReference ref = Mockito.mock(DocumentReference.class);
+
+        Timestamp oldTime = Timestamp.of(new java.sql.Timestamp(System.currentTimeMillis() - 1000000));
+        Timestamp newerTime = Timestamp.of(new java.sql.Timestamp(System.currentTimeMillis()));
+
+        when(oldDoc.getReference()).thenReturn(ref);
+        when(oldDoc.contains("hostName")).thenReturn(true);
+        when(oldDoc.getString("hostName")).thenReturn("hostname");
+        when(oldDoc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+        when(oldDoc.getString("provider")).thenReturn("athenz.provider");
+        when(oldDoc.getString("service")).thenReturn("service");
+        when(oldDoc.getTimestamp("currentTime")).thenReturn(oldTime);
+        when(oldDoc.getId()).thenReturn("old-doc-id");
+
+        List<QueryDocumentSnapshot> documents = Collections.singletonList(oldDoc);
+        when(querySnapshot.getDocuments()).thenReturn(documents);
+
+        // Setup for mostUpdatedHostRecord check - return TWO documents (old and new)
+        QueryDocumentSnapshot newDoc = Mockito.mock(QueryDocumentSnapshot.class);
+        when(newDoc.getTimestamp("currentTime")).thenReturn(newerTime);
+        when(newDoc.getId()).thenReturn("new-doc-id"); // Different ID
+
+        Query hostQuery = Mockito.mock(Query.class);
+        QuerySnapshot hostQuerySnapshot = Mockito.mock(QuerySnapshot.class);
+        ApiFuture<QuerySnapshot> hostFutureQuery = Mockito.mock(ApiFuture.class);
+
+        when(collectionReference.whereEqualTo("hostName", "hostname")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("provider", "athenz.provider")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("service", "service")).thenReturn(hostQuery);
+        when(hostQuery.get()).thenReturn(hostFutureQuery);
+        when(hostFutureQuery.get()).thenReturn(hostQuerySnapshot);
+        when(hostQuerySnapshot.getDocuments()).thenReturn(Arrays.asList(oldDoc, newDoc)); // Return both
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", System.currentTimeMillis(), "athenz.provider");
+
+        // Should return empty list because a newer record exists
+        assertNotNull(records);
+        assertEquals(records.size(), 0);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testMostUpdatedHostRecordEmptyHostname() throws Exception {
+        // Test case: hostname is empty, should be filtered out
+
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        DocumentReference ref = Mockito.mock(DocumentReference.class);
+
+        when(doc.getReference()).thenReturn(ref);
+        when(doc.contains("hostName")).thenReturn(true);
+        when(doc.getString("hostName")).thenReturn(""); // EMPTY hostname
+        when(doc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+        when(doc.getString("provider")).thenReturn("athenz.provider");
+        when(doc.getString("service")).thenReturn("service");
+        when(doc.getTimestamp("currentTime")).thenReturn(Timestamp.now());
+        when(doc.getId()).thenReturn("doc-id");
+
+        List<QueryDocumentSnapshot> documents = Collections.singletonList(doc);
+        when(querySnapshot.getDocuments()).thenReturn(documents);
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", System.currentTimeMillis(), "athenz.provider");
+
+        // Should return empty list because hostname is empty (filtered by getUnrefreshedCertsRecords)
+        assertNotNull(records);
+        assertEquals(records.size(), 0);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testMostUpdatedHostRecordEmptyProvider() throws Exception {
+        // Test case: provider is empty, should be filtered out by mostUpdatedHostRecord
+
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        DocumentReference ref = Mockito.mock(DocumentReference.class);
+
+        when(doc.getReference()).thenReturn(ref);
+        when(doc.contains("hostName")).thenReturn(true);
+        when(doc.getString("hostName")).thenReturn("hostname");
+        when(doc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+        when(doc.getString("provider")).thenReturn(""); // EMPTY provider
+        when(doc.getString("service")).thenReturn("service");
+        when(doc.getTimestamp("currentTime")).thenReturn(Timestamp.now());
+        when(doc.getId()).thenReturn("doc-id");
+
+        List<QueryDocumentSnapshot> documents = Collections.singletonList(doc);
+        when(querySnapshot.getDocuments()).thenReturn(documents);
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", System.currentTimeMillis(), "athenz.provider");
+
+        // Should return empty list because provider is empty (filtered by mostUpdatedHostRecord)
+        assertNotNull(records);
+        assertEquals(records.size(), 0);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testFilterMostUpdatedHostRecordsSuccess() throws Exception {
+        // Test successful filtering with a valid most recent record
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        Timestamp currentTime = Timestamp.now();
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        DocumentReference ref = Mockito.mock(DocumentReference.class);
+
+        when(doc.getReference()).thenReturn(ref);
+        when(doc.contains("hostName")).thenReturn(true);
+        when(doc.getString("hostName")).thenReturn("hostname");
+        when(doc.getString("provider")).thenReturn("athenz.provider");
+        when(doc.getString("service")).thenReturn("service");
+        when(doc.getTimestamp("currentTime")).thenReturn(currentTime);
+        when(doc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+        when(doc.getId()).thenReturn("doc-id-1");
+        when(doc.getString("instanceId")).thenReturn("instance-1");
+        when(doc.getString("currentSerial")).thenReturn("serial-1");
+        when(doc.getString("currentIP")).thenReturn("10.0.0.1");
+        when(doc.getString("prevSerial")).thenReturn(null);
+        when(doc.getString("prevIP")).thenReturn(null);
+        when(doc.getTimestamp("prevTime")).thenReturn(null);
+        when(doc.getBoolean("clientCert")).thenReturn(false);
+        when(doc.getString("lastNotifiedServer")).thenReturn(null);
+        when(doc.getTimestamp("expiryTime")).thenReturn(null);
+        when(doc.getTimestamp("svcDataUpdateTime")).thenReturn(null);
+        when(doc.getString("siaProvider")).thenReturn(null);
+
+        when(querySnapshot.getDocuments()).thenReturn(Collections.singletonList(doc));
+
+        // Setup bulk query
+        Query hostQuery = Mockito.mock(Query.class);
+        QuerySnapshot hostQuerySnapshot = Mockito.mock(QuerySnapshot.class);
+        ApiFuture<QuerySnapshot> hostFutureQuery = Mockito.mock(ApiFuture.class);
+
+        when(collectionReference.whereEqualTo("hostName", "hostname")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("provider", "athenz.provider")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("service", "service")).thenReturn(hostQuery);
+        when(hostQuery.get()).thenReturn(hostFutureQuery);
+        when(hostFutureQuery.get()).thenReturn(hostQuerySnapshot);
+        when(hostQuerySnapshot.getDocuments()).thenReturn(Collections.singletonList(doc));
+
+        // Setup batch operation
+        when(firestore.batch()).thenReturn(writeBatch);
+        when(writeBatch.update(any(DocumentReference.class), any(Map.class))).thenReturn(writeBatch);
+        when(writeBatch.commit()).thenReturn(futureWriteBatch);
+        when(futureWriteBatch.get()).thenReturn(Collections.emptyList());
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", System.currentTimeMillis(), "athenz.provider");
+
+        assertNotNull(records);
+        assertEquals(records.size(), 1);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testFilterMostUpdatedHostRecordsNullHostname() throws Exception {
+        // Test filtering documents with null hostname
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        when(doc.contains("hostName")).thenReturn(true);
+        when(doc.getString("hostName")).thenReturn(null); // NULL hostname
+        when(doc.getString("provider")).thenReturn("athenz.provider");
+        when(doc.getString("service")).thenReturn("service");
+        when(doc.getTimestamp("currentTime")).thenReturn(Timestamp.now());
+        when(doc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+
+        when(querySnapshot.getDocuments()).thenReturn(Collections.singletonList(doc));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", System.currentTimeMillis(), "athenz.provider");
+
+        assertNotNull(records);
+        assertEquals(records.size(), 0); // Should be filtered out
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testFilterMostUpdatedHostRecordsNullService() throws Exception {
+        // Test filtering documents with null service
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        when(doc.contains("hostName")).thenReturn(true);
+        when(doc.getString("hostName")).thenReturn("hostname");
+        when(doc.getString("provider")).thenReturn("athenz.provider");
+        when(doc.getString("service")).thenReturn(null); // NULL service
+        when(doc.getTimestamp("currentTime")).thenReturn(Timestamp.now());
+        when(doc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+
+        when(querySnapshot.getDocuments()).thenReturn(Collections.singletonList(doc));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", System.currentTimeMillis(), "athenz.provider");
+
+        assertNotNull(records);
+        assertEquals(records.size(), 0); // Should be filtered out
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testFilterMostUpdatedHostRecordsAllNullCurrentTime() throws Exception {
+        // Test when all records in a group have null currentTime - no record should be selected
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        when(doc.contains("hostName")).thenReturn(true);
+        when(doc.getString("hostName")).thenReturn("hostname");
+        when(doc.getString("provider")).thenReturn("athenz.provider");
+        when(doc.getString("service")).thenReturn("service");
+        when(doc.getTimestamp("currentTime")).thenReturn(null); // NULL currentTime
+        when(doc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+        when(doc.getId()).thenReturn("doc-id");
+
+        when(querySnapshot.getDocuments()).thenReturn(Collections.singletonList(doc));
+
+        // Setup bulk query - returns the same document with null currentTime
+        Query hostQuery = Mockito.mock(Query.class);
+        QuerySnapshot hostQuerySnapshot = Mockito.mock(QuerySnapshot.class);
+        ApiFuture<QuerySnapshot> hostFutureQuery = Mockito.mock(ApiFuture.class);
+
+        when(collectionReference.whereEqualTo("hostName", "hostname")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("provider", "athenz.provider")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("service", "service")).thenReturn(hostQuery);
+        when(hostQuery.get()).thenReturn(hostFutureQuery);
+        when(hostFutureQuery.get()).thenReturn(hostQuerySnapshot);
+        when(hostQuerySnapshot.getDocuments()).thenReturn(Collections.singletonList(doc));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", System.currentTimeMillis(), "athenz.provider");
+
+        assertNotNull(records);
+        assertEquals(records.size(), 0); // No record with valid currentTime
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testFilterMostUpdatedHostRecordsOlderRecordFiltered() throws Exception {
+        // Test that older records are filtered out when a newer record exists
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        Timestamp oldTime = Timestamp.of(new java.sql.Timestamp(System.currentTimeMillis() - 100000));
+        Timestamp newTime = Timestamp.of(new java.sql.Timestamp(System.currentTimeMillis()));
+
+        // Old document (candidate from initial query)
+        QueryDocumentSnapshot oldDoc = Mockito.mock(QueryDocumentSnapshot.class);
+        when(oldDoc.contains("hostName")).thenReturn(true);
+        when(oldDoc.getString("hostName")).thenReturn("hostname");
+        when(oldDoc.getString("provider")).thenReturn("athenz.provider");
+        when(oldDoc.getString("service")).thenReturn("service");
+        when(oldDoc.getTimestamp("currentTime")).thenReturn(oldTime);
+        when(oldDoc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+        when(oldDoc.getId()).thenReturn("old-doc-id");
+
+        when(querySnapshot.getDocuments()).thenReturn(Collections.singletonList(oldDoc));
+
+        // New document (returned by bulk query)
+        QueryDocumentSnapshot newDoc = Mockito.mock(QueryDocumentSnapshot.class);
+        when(newDoc.getTimestamp("currentTime")).thenReturn(newTime);
+        when(newDoc.getId()).thenReturn("new-doc-id");
+
+        // Setup bulk query - returns both old and new documents
+        Query hostQuery = Mockito.mock(Query.class);
+        QuerySnapshot hostQuerySnapshot = Mockito.mock(QuerySnapshot.class);
+        ApiFuture<QuerySnapshot> hostFutureQuery = Mockito.mock(ApiFuture.class);
+
+        when(collectionReference.whereEqualTo("hostName", "hostname")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("provider", "athenz.provider")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("service", "service")).thenReturn(hostQuery);
+        when(hostQuery.get()).thenReturn(hostFutureQuery);
+        when(hostFutureQuery.get()).thenReturn(hostQuerySnapshot);
+        when(hostQuerySnapshot.getDocuments()).thenReturn(Arrays.asList(oldDoc, newDoc));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", System.currentTimeMillis(), "athenz.provider");
+
+        assertNotNull(records);
+        assertEquals(records.size(), 0); // Old doc filtered out, new doc not in candidates
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testFilterMostUpdatedHostRecordsExecutionException() throws Exception {
+        // Test exception handling during bulk query
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        when(doc.contains("hostName")).thenReturn(true);
+        when(doc.getString("hostName")).thenReturn("hostname");
+        when(doc.getString("provider")).thenReturn("athenz.provider");
+        when(doc.getString("service")).thenReturn("service");
+        when(doc.getTimestamp("currentTime")).thenReturn(Timestamp.now());
+        when(doc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+
+        when(querySnapshot.getDocuments()).thenReturn(Collections.singletonList(doc));
+
+        // Setup bulk query to throw ExecutionException
+        Query hostQuery = Mockito.mock(Query.class);
+        ApiFuture<QuerySnapshot> hostFutureQuery = Mockito.mock(ApiFuture.class);
+
+        when(collectionReference.whereEqualTo("hostName", "hostname")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("provider", "athenz.provider")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("service", "service")).thenReturn(hostQuery);
+        when(hostQuery.get()).thenReturn(hostFutureQuery);
+        when(hostFutureQuery.get()).thenThrow(new ExecutionException("Query failed", new Throwable("error")));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", System.currentTimeMillis(), "athenz.provider");
+
+        assertNotNull(records);
+        assertEquals(records.size(), 0); // Exception returns empty list
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testFilterMostUpdatedHostRecordsInterruptedException() throws Exception {
+        // Test InterruptedException handling during bulk query
+        Query query = Mockito.mock(Query.class);
+        when(collectionReference.whereEqualTo(eq("provider"), anyString())).thenReturn(query);
+        when(query.whereGreaterThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.whereLessThanOrEqualTo(eq("currentTime"), any(Timestamp.class))).thenReturn(query);
+        when(query.get()).thenReturn(futureQuery);
+        when(futureQuery.get()).thenReturn(querySnapshot);
+
+        QueryDocumentSnapshot doc = Mockito.mock(QueryDocumentSnapshot.class);
+        when(doc.contains("hostName")).thenReturn(true);
+        when(doc.getString("hostName")).thenReturn("hostname");
+        when(doc.getString("provider")).thenReturn("athenz.provider");
+        when(doc.getString("service")).thenReturn("service");
+        when(doc.getTimestamp("currentTime")).thenReturn(Timestamp.now());
+        when(doc.getTimestamp("lastNotifiedTime")).thenReturn(null);
+
+        when(querySnapshot.getDocuments()).thenReturn(Collections.singletonList(doc));
+
+        // Setup bulk query to throw InterruptedException
+        Query hostQuery = Mockito.mock(Query.class);
+        ApiFuture<QuerySnapshot> hostFutureQuery = Mockito.mock(ApiFuture.class);
+
+        when(collectionReference.whereEqualTo("hostName", "hostname")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("provider", "athenz.provider")).thenReturn(hostQuery);
+        when(hostQuery.whereEqualTo("service", "service")).thenReturn(hostQuery);
+        when(hostQuery.get()).thenReturn(hostFutureQuery);
+        when(hostFutureQuery.get()).thenThrow(new InterruptedException("interrupted"));
+
+        FirestoreCertRecordStoreConnection dbConn = getDBConnection();
+
+        List<X509CertRecord> records = dbConn.updateUnrefreshedCertificatesNotificationTimestamp(
+                "server", System.currentTimeMillis(), "athenz.provider");
+
+        assertNotNull(records);
+        assertEquals(records.size(), 0); // InterruptedException returns empty list
+
+        dbConn.close();
+    }
+
+
+}
diff --git a/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreFactoryTest.java b/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreFactoryTest.java
new file mode 100644
index 000000000..96896d406
--- /dev/null
+++ b/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreFactoryTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * 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.
+ */
+package io.athenz.server.gcp.common.cert.impl;
+
+import com.google.cloud.firestore.Firestore;
+import com.google.cloud.firestore.FirestoreOptions;
+import com.yahoo.athenz.auth.PrivateKeyStore;
+import com.yahoo.athenz.common.server.cert.CertRecordStore;
+import com.yahoo.athenz.common.server.ServerResourceException;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+public class FirestoreCertRecordStoreFactoryTest {
+
+    @Mock private Firestore firestore;
+
+    class TestFirestoreCertRecordStoreFactory extends FirestoreCertRecordStoreFactory {
+        @Override
+        Firestore getFirestoreClient(String projectId, String databaseId) {
+            return firestore;
+        }
+    }
+
+    @BeforeMethod
+    public void setUp() {
+        MockitoAnnotations.openMocks(this);
+    }
+
+    @Test
+    public void testCreate() throws ServerResourceException {
+
+        System.setProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_PROJECT_ID, "test-project");
+        System.setProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_COLLECTION_NAME, "test-collection");
+
+        PrivateKeyStore keyStore = Mockito.mock(PrivateKeyStore.class);
+
+        TestFirestoreCertRecordStoreFactory factory = new TestFirestoreCertRecordStoreFactory();
+        CertRecordStore store = factory.create(keyStore);
+        assertNotNull(store);
+
+        System.clearProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_PROJECT_ID);
+        System.clearProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_COLLECTION_NAME);
+    }
+
+    @Test
+    public void testCreateWithDatabaseId() throws ServerResourceException {
+
+        System.setProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_PROJECT_ID, "test-project");
+        System.setProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_COLLECTION_NAME, "test-collection");
+        System.setProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_DATABASE_ID, "custom-db");
+
+        PrivateKeyStore keyStore = Mockito.mock(PrivateKeyStore.class);
+
+        TestFirestoreCertRecordStoreFactory factory = new TestFirestoreCertRecordStoreFactory();
+        CertRecordStore store = factory.create(keyStore);
+        assertNotNull(store);
+
+        System.clearProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_PROJECT_ID);
+        System.clearProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_COLLECTION_NAME);
+        System.clearProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_DATABASE_ID);
+    }
+
+    @Test
+    public void testCreateMissingProjectId() {
+
+        PrivateKeyStore keyStore = Mockito.mock(PrivateKeyStore.class);
+
+        System.clearProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_PROJECT_ID);
+        TestFirestoreCertRecordStoreFactory factory = new TestFirestoreCertRecordStoreFactory();
+        try {
+            factory.create(keyStore);
+            fail();
+        } catch (ServerResourceException ex) {
+            Assert.assertEquals(ex.getCode(), ServerResourceException.SERVICE_UNAVAILABLE);
+            Assert.assertTrue(ex.getMessage().contains("Firestore project ID not specified"));
+        }
+
+        System.setProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_PROJECT_ID, "");
+        try {
+            factory.create(keyStore);
+            fail();
+        } catch (ServerResourceException ex) {
+            Assert.assertEquals(ex.getCode(), ServerResourceException.SERVICE_UNAVAILABLE);
+            Assert.assertTrue(ex.getMessage().contains("Firestore project ID not specified"));
+        }
+
+        System.clearProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_PROJECT_ID);
+    }
+
+    @Test
+    public void testCreateMissingCollectionName() {
+
+        System.setProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_PROJECT_ID, "test-project");
+
+        PrivateKeyStore keyStore = Mockito.mock(PrivateKeyStore.class);
+
+        System.clearProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_COLLECTION_NAME);
+        TestFirestoreCertRecordStoreFactory factory = new TestFirestoreCertRecordStoreFactory();
+        try {
+            factory.create(keyStore);
+            fail();
+        } catch (ServerResourceException ex) {
+            Assert.assertEquals(ex.getCode(), ServerResourceException.SERVICE_UNAVAILABLE);
+            Assert.assertTrue(ex.getMessage().contains("Firestore collection name not specified"));
+        }
+
+        System.setProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_COLLECTION_NAME, "");
+        try {
+            factory.create(keyStore);
+            fail();
+        } catch (ServerResourceException ex) {
+            Assert.assertEquals(ex.getCode(), ServerResourceException.SERVICE_UNAVAILABLE);
+            Assert.assertTrue(ex.getMessage().contains("Firestore collection name not specified"));
+        }
+
+        System.clearProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_PROJECT_ID);
+        System.clearProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_COLLECTION_NAME);
+    }
+
+    @Test
+    public void testGetFirestoreClientException() {
+
+        System.setProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_PROJECT_ID, "test-project");
+        System.setProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_COLLECTION_NAME, "test-collection");
+
+        PrivateKeyStore keyStore = Mockito.mock(PrivateKeyStore.class);
+
+        FirestoreCertRecordStoreFactory factory = new FirestoreCertRecordStoreFactory() {
+            @Override
+            Firestore getFirestoreClient(String projectId, String databaseId) {
+                throw new RuntimeException("Failed to create Firestore client");
+            }
+        };
+
+        try {
+            factory.create(keyStore);
+            fail();
+        } catch (Exception ex) {
+            assertTrue(ex instanceof RuntimeException);
+            Assert.assertTrue(ex.getMessage().contains("Failed to create Firestore client"));
+        }
+
+        System.clearProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_PROJECT_ID);
+        System.clearProperty(FirestoreCertRecordStoreFactory.ZTS_PROP_CERT_FIRESTORE_COLLECTION_NAME);
+    }
+
+    @Test
+    public void testGetFirestoreClient() {
+        FirestoreCertRecordStoreFactory factory = new FirestoreCertRecordStoreFactory();
+        try {
+            // This will attempt to create a real Firestore client, which may fail
+            // without proper GCP credentials, but we're testing that the method exists
+            Firestore client = factory.getFirestoreClient("test-project", "(default)");
+            assertNotNull(client);
+        } catch (Exception ex) {
+            // Expected to fail without proper GCP setup
+            assertTrue(ex instanceof RuntimeException);
+        }
+    }
+
+    @Test
+    public void testGetFirestoreClientWithCustomDatabaseId() {
+        FirestoreCertRecordStoreFactory factory = new FirestoreCertRecordStoreFactory();
+        try (MockedStatic<FirestoreOptions> mocked = Mockito.mockStatic(FirestoreOptions.class)) {
+            mocked.when(FirestoreOptions::newBuilder)
+                    .thenThrow(new IllegalStateException("Simulated failure"));
+
+            try {
+                factory.getFirestoreClient("test-project", "(default)");
+                fail();
+            } catch (Exception ex) {
+                assertTrue(ex instanceof RuntimeException);
+                Assert.assertTrue(ex.getMessage().contains("Failed to create Firestore client"));
+            }
+        }
+    }
+}
diff --git a/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreTest.java b/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreTest.java
new file mode 100644
index 000000000..33cb9fca9
--- /dev/null
+++ b/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreCertRecordStoreTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * 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.
+ */
+package io.athenz.server.gcp.common.cert.impl;
+
+import com.google.cloud.firestore.Firestore;
+import com.yahoo.athenz.auth.Principal;
+import com.yahoo.athenz.auth.impl.SimplePrincipal;
+import com.yahoo.athenz.auth.util.Crypto;
+import com.yahoo.athenz.common.server.cert.CertRecordStoreConnection;
+import com.yahoo.athenz.common.server.db.RolesProvider;
+import com.yahoo.athenz.common.server.notification.NotificationManager;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.security.cert.X509Certificate;
+
+import static org.testng.Assert.*;
+
+public class FirestoreCertRecordStoreTest {
+
+    @Mock private Firestore firestore;
+
+    @BeforeMethod
+    public void setUp() {
+        MockitoAnnotations.openMocks(this);
+    }
+
+    @Test
+    public void testGetConnection() {
+
+        FirestoreCertRecordStore store = new FirestoreCertRecordStore(firestore, "test-collection");
+
+        CertRecordStoreConnection dbConn = store.getConnection();
+        assertNotNull(dbConn);
+
+        // empty methods
+        store.setOperationTimeout(10);
+        store.clearConnections();
+    }
+
+    @Test
+    public void testLog() {
+
+        FirestoreCertRecordStore store = new FirestoreCertRecordStore(firestore, "test-collection");
+
+        File file = new File("src/test/resources/cert_log.pem");
+        String pem = null;
+        try {
+            pem = new String(Files.readAllBytes(file.toPath()));
+        } catch (IOException ex) {
+            fail();
+        }
+        X509Certificate cert = Crypto.loadX509Certificate(pem);
+
+        Principal principal = SimplePrincipal.create("user", "joe", "creds");
+
+        // make sure no exceptions are thrown when processing log request
+
+        store.log(principal, "10.11.12.13", "athens.provider", "1234", cert);
+    }
+
+    @Test
+    public void testEnableNotifications() {
+        FirestoreCertRecordStore store = new FirestoreCertRecordStore(firestore, "test-collection");
+
+        NotificationManager notificationManager = Mockito.mock(NotificationManager.class);
+        RolesProvider rolesProvider = Mockito.mock(RolesProvider.class);
+        String serverName = "testServer";
+
+        boolean isEnabled = store.enableNotifications(notificationManager, rolesProvider, serverName);
+        assertFalse(isEnabled);
+    }
+}
\ No newline at end of file
diff --git a/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreConnectionTest.java b/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreConnectionTest.java
new file mode 100644
index 000000000..375b2f74a
--- /dev/null
+++ b/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreConnectionTest.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * 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.
+ */
+package io.athenz.server.gcp.common.cert.impl;
+
+import com.google.api.core.ApiFuture;
+import com.google.cloud.firestore.CollectionReference;
+import com.google.cloud.firestore.DocumentReference;
+import com.google.cloud.firestore.DocumentSnapshot;
+import com.google.cloud.firestore.Firestore;
+import com.google.cloud.firestore.WriteResult;
+import com.yahoo.athenz.common.server.ssh.SSHCertRecord;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+public class FirestoreSSHRecordStoreConnectionTest {
+
+    private final String collectionName = "ssh-collection";
+
+    @Mock private Firestore firestore;
+    @Mock private CollectionReference collectionReference;
+    @Mock private DocumentReference documentReference;
+    @Mock private ApiFuture<DocumentSnapshot> futureGet;
+    @Mock private ApiFuture<WriteResult> futureWrite;
+    @Mock private DocumentSnapshot documentSnapshot;
+    @Mock private WriteResult writeResult;
+
+    @BeforeMethod
+    public void setUp() {
+        MockitoAnnotations.openMocks(this);
+        when(firestore.collection(collectionName)).thenReturn(collectionReference);
+        when(collectionReference.document(any())).thenReturn(documentReference);
+    }
+
+    private FirestoreSSHRecordStoreConnection getDBConnection() {
+        return new FirestoreSSHRecordStoreConnection(firestore, collectionName);
+    }
+
+    @Test
+    public void testGetSSHCertRecord() throws Exception {
+
+        when(documentReference.get()).thenReturn(futureGet);
+        when(futureGet.get()).thenReturn(documentSnapshot);
+        when(documentSnapshot.exists()).thenReturn(true);
+
+        when(documentSnapshot.getString("instanceId")).thenReturn("1234");
+        when(documentSnapshot.getString("service")).thenReturn("cn");
+        when(documentSnapshot.getString("principals")).thenReturn("host1,host2");
+        when(documentSnapshot.getString("clientIP")).thenReturn("10.10.10.11");
+        when(documentSnapshot.getString("privateIP")).thenReturn("10.10.10.12");
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+        dbConn.setOperationTimeout(10);
+        SSHCertRecord certRecord = dbConn.getSSHCertRecord("1234", "cn");
+
+        assertNotNull(certRecord);
+        assertEquals(certRecord.getInstanceId(), "1234");
+        assertEquals(certRecord.getService(), "cn");
+        assertEquals(certRecord.getPrincipals(), "host1,host2");
+        assertEquals(certRecord.getClientIP(), "10.10.10.11");
+        assertEquals(certRecord.getPrivateIP(), "10.10.10.12");
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testGetSSHCertRecordNotFound() throws Exception {
+
+        when(documentReference.get()).thenReturn(futureGet);
+        when(futureGet.get()).thenReturn(documentSnapshot);
+        when(documentSnapshot.exists()).thenReturn(false);
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+        SSHCertRecord certRecord = dbConn.getSSHCertRecord("1234", "cn");
+        assertNull(certRecord);
+        dbConn.close();
+    }
+
+    @Test
+    public void testGetSSHCertRecordException() throws Exception {
+
+        when(documentReference.get()).thenReturn(futureGet);
+        when(futureGet.get()).thenThrow(new ExecutionException("error", new Throwable("error")));
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+        SSHCertRecord certRecord = dbConn.getSSHCertRecord("1234", "cn");
+        assertNull(certRecord);
+        dbConn.close();
+    }
+
+    @Test
+    public void testGetSSHCertRecordInterruptedException() throws Exception {
+
+        when(documentReference.get()).thenReturn(futureGet);
+        when(futureGet.get()).thenThrow(new InterruptedException("interrupted"));
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+        SSHCertRecord certRecord = dbConn.getSSHCertRecord("1234", "cn");
+        assertNull(certRecord);
+        dbConn.close();
+    }
+
+    @Test
+    public void testInsertSSHCertRecord() throws Exception {
+
+        when(documentReference.set(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenReturn(writeResult);
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+
+        SSHCertRecord certRecord = new SSHCertRecord();
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+        certRecord.setPrincipals("host1,host2");
+        certRecord.setClientIP("10.10.10.11");
+        certRecord.setPrivateIP("10.10.10.12");
+
+        boolean result = dbConn.insertSSHCertRecord(certRecord);
+        assertTrue(result);
+
+        // Verify that set was called with a map containing the expected fields
+        verify(documentReference).set(ArgumentMatchers.argThat(map -> {
+            Map<String, Object> m = (Map<String, Object>) map;
+            return m.containsKey("primaryKey") &&
+                   m.containsKey("instanceId") &&
+                   m.containsKey("service") &&
+                   m.containsKey("principals") &&
+                   m.containsKey("clientIP") &&
+                   m.containsKey("privateIP") &&
+                   m.containsKey("ttlTimestamp");
+        }));
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testInsertSSHCertRecordException() throws Exception {
+
+        when(documentReference.set(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenThrow(new ExecutionException("error", new Throwable("error")));
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+
+        SSHCertRecord certRecord = new SSHCertRecord();
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+
+        boolean result = dbConn.insertSSHCertRecord(certRecord);
+        assertFalse(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testInsertSSHCertRecordInterruptedException() throws Exception {
+
+        when(documentReference.set(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenThrow(new InterruptedException("interrupted"));
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+
+        SSHCertRecord certRecord = new SSHCertRecord();
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+
+        boolean result = dbConn.insertSSHCertRecord(certRecord);
+        assertFalse(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testUpdateSSHCertRecord() throws Exception {
+
+        when(documentReference.update(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenReturn(writeResult);
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+
+        SSHCertRecord certRecord = new SSHCertRecord();
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+        certRecord.setPrincipals("host1,host2");
+        certRecord.setClientIP("10.10.10.11");
+        certRecord.setPrivateIP("10.10.10.12");
+
+        boolean result = dbConn.updateSSHCertRecord(certRecord);
+        assertTrue(result);
+
+        // Verify that update was called with a map containing the expected fields
+        verify(documentReference).update(ArgumentMatchers.argThat(map -> {
+            Map<String, Object> m = (Map<String, Object>) map;
+            return m.containsKey("instanceId") &&
+                   m.containsKey("service") &&
+                   m.containsKey("principals") &&
+                   m.containsKey("clientIP") &&
+                   m.containsKey("privateIP") &&
+                   m.containsKey("ttlTimestamp");
+        }));
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testUpdateSSHCertRecordException() throws Exception {
+
+        when(documentReference.update(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenThrow(new ExecutionException("error", new Throwable("error")));
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+
+        SSHCertRecord certRecord = new SSHCertRecord();
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+
+        boolean result = dbConn.updateSSHCertRecord(certRecord);
+        assertFalse(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testUpdateSSHCertRecordInterruptedException() throws Exception {
+
+        when(documentReference.update(any(Map.class))).thenReturn(futureWrite);
+        when(futureWrite.get()).thenThrow(new InterruptedException("interrupted"));
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+
+        SSHCertRecord certRecord = new SSHCertRecord();
+        certRecord.setInstanceId("1234");
+        certRecord.setService("cn");
+
+        boolean result = dbConn.updateSSHCertRecord(certRecord);
+        assertFalse(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testDeleteSSHCertRecord() throws Exception {
+
+        when(documentReference.delete()).thenReturn(futureWrite);
+        when(futureWrite.get()).thenReturn(writeResult);
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+
+        boolean result = dbConn.deleteSSHCertRecord("1234", "cn");
+        assertTrue(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testDeleteSSHCertRecordException() throws Exception {
+
+        when(documentReference.delete()).thenReturn(futureWrite);
+        when(futureWrite.get()).thenThrow(new ExecutionException("error", new Throwable("error")));
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+
+        boolean result = dbConn.deleteSSHCertRecord("1234", "cn");
+        assertFalse(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testDeleteSSHCertRecordInterruptedException() throws Exception {
+
+        when(documentReference.delete()).thenReturn(futureWrite);
+        when(futureWrite.get()).thenThrow(new InterruptedException("interrupted"));
+
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+
+        boolean result = dbConn.deleteSSHCertRecord("1234", "cn");
+        assertFalse(result);
+
+        dbConn.close();
+    }
+
+    @Test
+    public void testDeleteExpiredSSHCertRecords() {
+        // Firestore uses native TTL, so this method always returns 0
+        FirestoreSSHRecordStoreConnection dbConn = getDBConnection();
+
+        int deletedCount = dbConn.deleteExpiredSSHCertRecords(100, 10);
+        assertEquals(deletedCount, 0);
+
+        dbConn.close();
+    }
+}
diff --git a/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreFactoryTest.java b/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreFactoryTest.java
new file mode 100644
index 000000000..33c53d2f9
--- /dev/null
+++ b/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreFactoryTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * 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.
+ */
+package io.athenz.server.gcp.common.cert.impl;
+
+import com.google.cloud.firestore.Firestore;
+import com.google.cloud.firestore.FirestoreOptions;
+import com.yahoo.athenz.auth.PrivateKeyStore;
+import com.yahoo.athenz.common.server.ssh.SSHRecordStore;
+import com.yahoo.athenz.common.server.ServerResourceException;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+public class FirestoreSSHRecordStoreFactoryTest {
+
+    @Mock private Firestore firestore;
+
+    class TestFirestoreSSHRecordStoreFactory extends FirestoreSSHRecordStoreFactory {
+        @Override
+        Firestore getFirestoreClient(String projectId, String databaseId) {
+            return firestore;
+        }
+    }
+
+    @BeforeMethod
+    public void setUp() {
+        MockitoAnnotations.openMocks(this);
+    }
+
+    @Test
+    public void testCreate() throws ServerResourceException {
+
+        System.setProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_PROJECT_ID, "test-project");
+        System.setProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_COLLECTION_NAME, "test-collection");
+
+        PrivateKeyStore keyStore = Mockito.mock(PrivateKeyStore.class);
+
+        TestFirestoreSSHRecordStoreFactory factory = new TestFirestoreSSHRecordStoreFactory();
+        SSHRecordStore store = factory.create(keyStore);
+        assertNotNull(store);
+
+        System.clearProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_PROJECT_ID);
+        System.clearProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_COLLECTION_NAME);
+    }
+
+    @Test
+    public void testCreateWithDatabaseId() throws ServerResourceException {
+
+        System.setProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_PROJECT_ID, "test-project");
+        System.setProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_COLLECTION_NAME, "test-collection");
+        System.setProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_DATABASE_ID, "custom-db");
+
+        PrivateKeyStore keyStore = Mockito.mock(PrivateKeyStore.class);
+
+        TestFirestoreSSHRecordStoreFactory factory = new TestFirestoreSSHRecordStoreFactory();
+        SSHRecordStore store = factory.create(keyStore);
+        assertNotNull(store);
+
+        System.clearProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_PROJECT_ID);
+        System.clearProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_COLLECTION_NAME);
+        System.clearProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_DATABASE_ID);
+    }
+
+    @Test
+    public void testCreateMissingProjectId() {
+
+        PrivateKeyStore keyStore = Mockito.mock(PrivateKeyStore.class);
+
+        System.clearProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_PROJECT_ID);
+        TestFirestoreSSHRecordStoreFactory factory = new TestFirestoreSSHRecordStoreFactory();
+        try {
+            factory.create(keyStore);
+            fail();
+        } catch (ServerResourceException ex) {
+            Assert.assertEquals(ex.getCode(), ServerResourceException.SERVICE_UNAVAILABLE);
+            Assert.assertTrue(ex.getMessage().contains("Firestore project ID not specified"));
+        }
+
+        System.setProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_PROJECT_ID, "");
+        try {
+            factory.create(keyStore);
+            fail();
+        } catch (ServerResourceException ex) {
+            Assert.assertEquals(ex.getCode(), ServerResourceException.SERVICE_UNAVAILABLE);
+            Assert.assertTrue(ex.getMessage().contains("Firestore project ID not specified"));
+        }
+
+        System.clearProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_PROJECT_ID);
+    }
+
+    @Test
+    public void testCreateMissingCollectionName() {
+
+        System.setProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_PROJECT_ID, "test-project");
+
+        PrivateKeyStore keyStore = Mockito.mock(PrivateKeyStore.class);
+
+        System.clearProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_COLLECTION_NAME);
+        TestFirestoreSSHRecordStoreFactory factory = new TestFirestoreSSHRecordStoreFactory();
+        try {
+            factory.create(keyStore);
+            fail();
+        } catch (ServerResourceException ex) {
+            Assert.assertEquals(ex.getCode(), ServerResourceException.SERVICE_UNAVAILABLE);
+            Assert.assertTrue(ex.getMessage().contains("Firestore collection name not specified"));
+        }
+
+        System.setProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_COLLECTION_NAME, "");
+        try {
+            factory.create(keyStore);
+            fail();
+        } catch (ServerResourceException ex) {
+            Assert.assertEquals(ex.getCode(), ServerResourceException.SERVICE_UNAVAILABLE);
+            Assert.assertTrue(ex.getMessage().contains("Firestore collection name not specified"));
+        }
+
+        System.clearProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_PROJECT_ID);
+        System.clearProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_COLLECTION_NAME);
+    }
+
+    @Test
+    public void testGetFirestoreClientException() {
+
+        System.setProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_PROJECT_ID, "test-project");
+        System.setProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_COLLECTION_NAME, "test-collection");
+
+        PrivateKeyStore keyStore = Mockito.mock(PrivateKeyStore.class);
+
+        // Override getFirestoreClient to throw an exception and trigger the catch block
+        FirestoreSSHRecordStoreFactory factory = new FirestoreSSHRecordStoreFactory() {
+            @Override
+            Firestore getFirestoreClient(String projectId, String databaseId) {
+                throw new RuntimeException("Failed to create Firestore client");
+            }
+        };
+
+        try {
+            factory.create(keyStore);
+            fail();
+        } catch (Exception ex) {
+            assertTrue(ex instanceof RuntimeException);
+            Assert.assertTrue(ex.getMessage().contains("Failed to create Firestore client"));
+        }
+
+        System.clearProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_PROJECT_ID);
+        System.clearProperty(FirestoreSSHRecordStoreFactory.ZTS_PROP_SSH_FIRESTORE_COLLECTION_NAME);
+    }
+
+    @Test
+    public void testGetFirestoreClient() {
+        FirestoreSSHRecordStoreFactory factory = new FirestoreSSHRecordStoreFactory();
+        try {
+            // This will attempt to create a real Firestore client, which may fail
+            // without proper GCP credentials, but we're testing that the method exists
+            Firestore client = factory.getFirestoreClient("test-project", "(default)");
+            assertNotNull(client);
+        } catch (Exception ex) {
+            // Expected to fail without proper GCP setup
+            assertTrue(ex instanceof RuntimeException);
+        }
+    }
+
+    @Test
+    public void testGetFirestoreClientWithCustomDatabaseId() {
+        FirestoreSSHRecordStoreFactory factory = new FirestoreSSHRecordStoreFactory();
+        try (MockedStatic<FirestoreOptions> mocked = Mockito.mockStatic(FirestoreOptions.class)) {
+            mocked.when(FirestoreOptions::newBuilder)
+                    .thenThrow(new IllegalStateException("Simulated failure"));
+
+            try {
+                factory.getFirestoreClient("test-project", "(default)");
+                fail();
+            } catch (Exception ex) {
+                assertTrue(ex instanceof RuntimeException);
+                Assert.assertTrue(ex.getMessage().contains("Failed to create Firestore client"));
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreTest.java b/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreTest.java
new file mode 100644
index 000000000..ddaeeab20
--- /dev/null
+++ b/libs/java/server_gcp_common/src/test/java/io/athenz/server/gcp/common/cert/impl/FirestoreSSHRecordStoreTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright The Athenz Authors
+ *
+ * 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.
+ */
+package io.athenz.server.gcp.common.cert.impl;
+
+import com.google.cloud.firestore.Firestore;
+import com.yahoo.athenz.auth.Principal;
+import com.yahoo.athenz.auth.impl.SimplePrincipal;
+import com.yahoo.athenz.common.server.ssh.SSHRecordStoreConnection;
+import com.yahoo.athenz.common.server.db.RolesProvider;
+import com.yahoo.athenz.common.server.notification.NotificationManager;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+public class FirestoreSSHRecordStoreTest {
+
+    @Mock private Firestore firestore;
+
+    @BeforeMethod
+    public void setUp() {
+        MockitoAnnotations.openMocks(this);
+    }
+
+    @Test
+    public void testGetConnection() {
+
+        FirestoreSSHRecordStore store = new FirestoreSSHRecordStore(firestore, "test-collection");
+
+        SSHRecordStoreConnection dbConn = store.getConnection();
+        assertNotNull(dbConn);
+
+        // empty methods
+        store.setOperationTimeout(10);
+        store.clearConnections();
+    }
+
+    @Test
+    public void testLog() {
+
+        FirestoreSSHRecordStore store = new FirestoreSSHRecordStore(firestore, "test-collection");
+
+        Principal principal = SimplePrincipal.create("user", "joe", "creds");
+
+        // make sure no exceptions are thrown when processing log request
+
+        store.log(principal, "10.11.12.13", "athenz.api", "1234");
+    }
+
+    @Test
+    public void testEnableNotifications() {
+        FirestoreSSHRecordStore store = new FirestoreSSHRecordStore(firestore, "test-collection");
+
+        NotificationManager notificationManager = Mockito.mock(NotificationManager.class);
+        RolesProvider rolesProvider = Mockito.mock(RolesProvider.class);
+        String serverName = "testServer";
+
+        boolean isEnabled = store.enableNotifications(notificationManager, rolesProvider, serverName);
+        assertFalse(isEnabled);
+    }
+}
diff --git a/libs/java/server_gcp_common/src/test/resources/cert_log.pem b/libs/java/server_gcp_common/src/test/resources/cert_log.pem
new file mode 100644
index 000000000..6a12d6a46
--- /dev/null
+++ b/libs/java/server_gcp_common/src/test/resources/cert_log.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAeoCCQCd8IyQD8/PfTANBgkqhkiG9w0BAQsFADBGMQswCQYDVQQGEwJV
+UzELMAkGA1UECAwCQ0ExEzARBgNVBAoMCkF0aGVuelRlc3QxFTATBgNVBAMMDEF0
+aGVuelRlc3RDQTAeFw0xODExMTkwNTI2MTdaFw0yMTA4MTUwNTI2MTdaMEAxCzAJ
+BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEPMA0GA1UECgwGQXRoZW56MRMwEQYDVQQD
+DAphdGhlbnouYXBpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA144b
+VnnII82XV+N+uVPhgcaSnvRY0cJ5N3b1ZUlTwhnKuHKxZQ13k7TpUXkxQekq6Ofw
+VhKmmZaVrEZuJ2ew5PYkmbEAyJJBReiBahuSaW9YSKF5JKTqhZKdqbJgLqUfdln9
+g6EAVEYEk7SLE69mpovQlk4+2wgWJG2e4TaCP3pupM3VkRlp2jkLiEwNXHeGzt8r
+/cWoQTnylyy9p9xjR5XL1E/+OZbdA50q4gP7G9E1YMleXJzkUs3CbRLtVKn7ZFO2
+diDoe7NIlogtvtfhsAE38kOnQxNRVH2pZ5BcB3Vu3Loa+oraPTiRCU9Zk6MQPqmI
+eO/P3s51yrBOncpF3wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAIkCiCfPK5I2Rx
+9sGJF+oIvENH8pKmESoGmTFOp/U56TuAtd+MhTWxFVjVpisEJ7+jp/fkljFVgtXi
+t99J05/O8UhI5V91gbGrsja1Eg4mbFrucAFEzA6n5LXj58c/GXhlxHt6Hha2MmfI
+IM0a7Tp2LbWOrSXEqa6JIeleGO3CsvcZqB/ioyEOZf3dRhljtozWwXbsb2M4Yf7l
+3D4Z03kttn0zUqDdfWK3XaKVoljHWRM65uX+WD0DKCg1lK6gt/hNb3UblCakDF3B
+68OV0FsEJgmD4fYd1idpzjvJCU20iFpbfYubPVLVx62XV0tchjIVpf0LF695kV9v
+Pudc7QUn
+-----END CERTIFICATE-----